GameMale
登陆 / 注册 搜索

USERCENTER

SEARCHSITE

搜索

查看: 6050|回复: 85
收起左侧

[扩展] GM论坛勋章博物馆属性复制助手

  [复制链接] |关注本帖

GM活动员

组件大师

法师 · I

金翼使猫咪合唱团(夏日)

     楼主| 咸鱼鱼 发表于 2025-4-30 10:53:56 | 显示全部楼层 |阅读模式 |取消关注该作者的回复

    目前大家往勋章博物馆贴属性,多采用截图的形式
    导致有两个缺点:
    1. 获取的图片不是gif而是静态图片,不能完整地展示勋章卡面
    2. 如果勋章描述是英文、日文、火星文、摩尔斯电码之类的内容,手打和文字识别起来有亿亿点困难
    3. 图片太长太大,不够美观
    现在既然我是勋章博物馆的站员了,那么我就应该负起责任
    于是基于黑达克之前的勋章属性格式,我写了一个脚本
    方便大家在勋章博物馆贴属性的时候,快速的获取勋章卡面和属性,同时也避免打错字之类的情况发生

    安装这个脚本时候,可以在我的勋章界面,或者帖子中任意一个勋章点击Ctrl+右键
    粘贴板里面暂存了对应勋章的勋章卡面图片链接 + 勋章描述 + 勋章属性
    其中勋章卡面是直接使用了勋章的图片链接,所以无需右键另存为图片再重新上传
    其中勋章属性因为不知道是手动升级还是自动升级,所以给出了两种格式供大家手动删除

    在新勋章上新的时间点推出这个脚本,希望大家都安装使用
    使用方法:在我的勋章界面和帖子内的勋章,
    ctrl+鼠标右键,复制好属性,然后找个地方粘贴即可
    在升级的时候右键保存一下属性和图片链接
    先暂存到本地的TXT和Word文档内,等勋章博物馆发了勋章贴之后再贴属性
    此外推荐大家使用纯文本编辑模式,这样能保证格式更不容易受到影响


    效果演示


    @Name @match
    1. // ==UserScript==
    2. // @name         GM论坛勋章博物馆属性复制助手
    3. // @namespace    http://tampermonkey.net/
    4. // @version      1.2
    5. // @description  使用方法,ctrl+鼠标右键,复制
    6. // @author       YourName
    7. // @match        https://www.gamemale.com/plugin.php?id=wodexunzhang:showxunzhang&action=my
    8. // @match        https://www.gamemale.com/wodexunzhang-showxunzhang.html?action=my
    9. // @match        https://www.gamemale.com/thread-*
    10. // @match        https://www.gamemale.com/forum.php?mod=viewthread&tid=*
    11. // @grant        none
    12. // @icon         https://www.gamemale.com/template/mwt2/extend/img/favicon.ico
    13. // ==/UserScript==

    14. (function () {
    15.     'use strict'

    16.     document.addEventListener('contextmenu', async (e) => {
    17.         if (!e.ctrlKey) return
    18.         e.preventDefault()

    19.         try {
    20.             const target = findValidTarget(e.target)
    21.             if (!target) return

    22.             // 统一数据采集
    23.             const rawData = collectRawData(target)
    24.             // 统一数据处理
    25.             const formattedData = processRawData(rawData)
    26.             console.log(formattedData)
    27.             const result = formatFinalData(formattedData)

    28.             await navigator.clipboard.writeText(result)
    29.             showFeedback('✅ 数据已复制')
    30.         } catch (err) {
    31.             console.error('处理失败:', err)
    32.             showFeedback('⚠ 数据提取失败')
    33.         }
    34.     })

    35.     // 统一数据采集
    36.     function collectRawData(target) {
    37.         const isCase1 = target.matches('img[tip]')
    38.         const isCase2 = target.matches('.mytip')

    39.         if (isCase1) {
    40.             return {
    41.                 type: 'case1',
    42.                 imgSrc: target.src,
    43.                 tipHtml: target.getAttribute('tip'),
    44.                 element: target
    45.             }
    46.         }

    47.         if (isCase2) {
    48.             const myblok = target.closest('.myblok')
    49.             return {
    50.                 type: 'case2',
    51.                 imgSrc: myblok?.querySelector('.myimg img')?.src,
    52.                 titleHtml: target.querySelector('.mingcheng')?.innerHTML,
    53.                 descriptionText: target.querySelector('.shuoming')?.textContent,
    54.                 attributes: Array.from(target.querySelectorAll('.jiage.shuxing'))
    55.                     .map(p => p.innerHTML),
    56.                 element: myblok
    57.             }
    58.         }

    59.         throw new Error('未知的数据类型')
    60.     }

    61.     // 统一数据处理
    62.     function processRawData(rawData) {
    63.         // 公共字段
    64.         const baseData = {
    65.             imgSrc: rawData.imgSrc || '无图片地址',
    66.             nextLevel: null
    67.         }

    68.         // 类型专属处理
    69.         if (rawData.type === 'case1') {
    70.             const doc = new DOMParser().parseFromString(rawData.tipHtml, 'text/html')
    71.             return {
    72.                 ...baseData,
    73.                 titleHtml: doc.querySelector('h4')?.innerHTML || '',
    74.                 descriptionText: doc.querySelector('p:not(.wode_shuxing p)')?.textContent || '',
    75.                 attributes: Array.from(doc.querySelectorAll('.wode_shuxing p'))
    76.                     .map(p => p.innerHTML),
    77.                 nextLevel: findNextLevel(doc)
    78.             }
    79.         }

    80.         if (rawData.type === 'case2') {
    81.             return {
    82.                 ...baseData,
    83.                 titleHtml: rawData.titleHtml || '',
    84.                 descriptionText: rawData.descriptionText || '',
    85.                 attributes: rawData.attributes,
    86.                 nextLevel: findNextLevel(rawData.element)
    87.             }
    88.         }
    89.     }

    90.     // 统一字段格式化
    91.     function formatFinalData(processedData) {
    92.         const formattedData = [
    93.             `[img]${processedData.imgSrc}[/img]`,
    94.             `[font=黑体][b]${formatTitle(processedData.titleHtml)}[/b][/font]`,
    95.             `[font=仿宋,仿宋_GB2312][b][color=Gray]${formatDescription(processedData.descriptionText)}[/color][/b][/font]`,
    96.             `[font=楷体,楷体_GB2312][b]${formatAttributes(processedData.attributes)}[/b][/font]` +
    97.             (processedData.nextLevel ? `[font=微软雅黑][b]▕▏[/b][/font][font=楷体, 楷体_GB2312][b]升级条件:${processedData.nextLevel}[/b][/font]` : ''),
    98.         ].filter(Boolean)

    99.         // 添加空行,避免影响后面的格式
    100.         // 看不出来,还是算了吧
    101.         // formattedData.push('')

    102.         return formattedData.join('\n')
    103.     }

    104.     // 格式化函数组
    105.     function formatTitle(html) {
    106.         return html
    107.             .replace(/<\/?h4>/g, '')
    108.             .replace(/<b>/g, '')
    109.             .replace(/<\/b>/g, ' ')
    110.     }

    111.     function formatDescription(text) {
    112.         return text.trim().replace(/\s+/g, ' ')
    113.     }

    114.     function formatAttributes(attrs = []) {
    115.         // 分类存储数据
    116.         const { trigger, reply, post } = attrs.reduce((acc, item) => {
    117.             const text = item.replace(/\s+|(&nbsp;)+/g, ' ').replace(/ (\+|-)/g, '$1').trim()

    118.             // 提取触发概率
    119.             if (text.includes('触发几率')) {
    120.                 acc.trigger = text.match(/\d+%/) || []
    121.                 return acc
    122.             }

    123.             // 分类回帖/发帖属性
    124.             const [type, ...values] = text.split(' ')
    125.             if (type === '回帖') acc.reply.push(...values)
    126.             if (type === '发帖') acc.post.push(...values)

    127.             return acc
    128.         }, { trigger: '', reply: [], post: [] })

    129.         // console.log(trigger, reply, post)

    130.         // 构建结果字符串
    131.         const parts = []
    132.         if (trigger) parts.push(trigger)
    133.         if (reply.length) parts.push(`回帖${reply.join(' ')}`)
    134.         if (post.length) parts.push(`发帖${post.join(' ')}`)

    135.         if (attrs.length === 0) {
    136.             return '无属性'
    137.         } else {
    138.             return parts.join('、').replace(/(%)、/, '$1 ')
    139.         }
    140.     }

    141.     // 辅助函数
    142.     function findValidTarget(startElement) {
    143.         if (startElement.matches('img[tip]')) return startElement

    144.         const myblok = startElement.closest('.myblok')
    145.         if (myblok) {
    146.             const mytip = myblok.querySelector('.mytip')
    147.             if (mytip) return mytip
    148.         }

    149.         throw new Error('未找到有效数据元素')
    150.     }

    151.     // 消耗xx属性,或者属性≥xx
    152.     function findNextLevel(element) {
    153.         const nextLevelDiv = Array.from(element.querySelectorAll('.wode_shuxing, .jiage'))
    154.             .find(el => el.textContent.includes('下一级'))

    155.         if (!nextLevelDiv) return null

    156.         // 清理文本
    157.         const processedText = nextLevelDiv.textContent
    158.             .replace(/下一级/g, '')
    159.             .replace(/&nbsp;/g, ' ')
    160.             .replace(/\s+/g, ' ')
    161.             .trim()

    162.         // 使用正则匹配【属性名 数字/数字】格式
    163.         const matches = processedText.match(/([\u4e00-\u9fa5]+)\s*(\d+)\/(-?\d+)/)


    164.         if (!matches) return null

    165.         const attribute = matches[1] // 属性名(如发帖数、金币等)
    166.         const requiredValue = matches[3] // 斜杠后的数字(如404)

    167.         return `消耗${requiredValue}${attribute} 或者 ${attribute}≥${requiredValue}`
    168.     }

    169.     function showFeedback(message) {
    170.         showPrompt(null, null, `<i>${message}</i>`, 2000)
    171.     }
    172. })()
    复制代码


    此外我最近也抽空把几乎所有的博物馆勋章都进行了整理
    主要内容是,如果你的帖子包含的所有的属性、卡面、描述的其中之一,且不是截图的形式,都会被我置顶方便查看
    如果一个勋章的属性、卡面、描述,由多个回帖人共同提供,我则会自己弄一份整合版方便置顶,
    目前还有以下勋章缺乏完整的属性、卡面、描述。放在折叠的部分,期待有缘人补充
    (过于古老的故事类和奖品类勋章未统计,因为发行量稀少且难以补充)




    最后希望大家多多支持我的工作,把勋章博物馆弄得整整齐齐漂漂亮亮的
    同时发现这是我的第100个主题帖,那就爆500金币庆祝一下吧

    本帖子中包含更多资源

    您需要 登录 才可以下载或查看,没有账号?立即注册

    x

    评分

    参与人数 27血液 +81 追随 +27 堕落 +15 收起 理由
    相见忧 + 1 赞一个!
    a0015100151 + 5 + 1 很给力!
    you9632587 + 5 + 1
    书の妖怪 + 3 + 1 很给力!
    sanweny + 3 + 1 + 1 很给力!
    zibatco2 + 3 + 1 + 1 赞一个!
    瓦尼 + 1 很给力!
    星之子 + 5 + 1 + 1 (*/ω\*)
    xvyi + 3 + 1
    wubingyonglei + 1 很给力!

    查看全部评分

    回复

    使用道具 举报

    游侠 · I

    【夏日限定】夏日的泰凯斯萨菲罗斯白骨大法师光之少女の魔法书邪恶圣杯结晶化法杖威猛尼特哀嚎恶灵猩红魔鹫

      回帖奖励 +10 枚金币

      很棒的脚本,一键复制用来填充博物馆属性很方便也很快捷,妈妈再也不用担心我发属性贴被抢先了(
      回复

      使用道具 举报

      内森·德雷克

        回帖奖励 +10 枚金币

        支持鱼宝喵~脚本对于整理勋章属性格式规范很有用噢~
        感谢鱼宝宝的大JB,很美味想再吃一点,吃干抹净捏
        回复

        使用道具 举报

        猫咪合唱团(夏日)黑铁战锤Mr.NeonYou Can Pet Blaidd業火死鬥传奇小镇的站台圣甲虫秘典

          回帖奖励 +10 枚金币

          回复

          使用道具 举报

          没有梦想的咸鱼月下之誓『大元素使』涅槃无光余烬葛莱分多猫咪合唱团(夏日)

            回复

            使用道具 举报

            四季如旧克里斯·埃文斯霉运小精灵[红]雾港捞月内森·德雷克猎魔人雄躯的昇格自定义男从Homunculus格拉迪欧拉斯莱因哈特·威尔海姆

              回复

              使用道具 举报

              法师I· 学识之章竹取物语巴比伦辞典鎏彩万幢雾港捞月

                回帖奖励 +10 枚金币

                和以往比起来确实要方便复制卡面和勋章描述多了,而且有了统一的工具也不用担心杂乱,非常棒的脚本,有机会我也要出一份力
                回复

                使用道具 举报

                蒂法·洛克哈特鎏彩万幢Dante最终幻想XVI荒野大镖客:救赎 II雪王的心脏不曾寄出的信件漂洋小船

                  回复

                  使用道具 举报

                  40x43 隐形➀死亡40x43 隐形➁传奇冬之歌

                    回帖奖励 +10 枚金币

                    勋章博物馆的大大们辛苦啦,这下降低了门槛之后感觉缺失的卡面广大的坛友也会慢慢补齐的
                    回复

                    使用道具 举报

                    永远的克叔萨菲罗斯克莱夫・罗兹菲尔德Dante康纳/Connor终归一人Doc杰西·麦克雷亚当‧简森肥皂

                      回帖奖励 +10 枚金币

                      回复

                      使用道具 举报

                      炉石与家猫咪合唱团(夏日)盈满心相元石法师I· 学识之章『召唤好运的角笛』雾港捞月鎏彩万幢男巫之歌女巫之路虚空之海的鲸

                        回复

                        使用道具 举报

                        城市的站台(夜)Amicus传说级训练家神奇宝贝大师球帅气的本・比格弗雷迪玩偶業火死鬥传奇perrito呆猫

                          回帖奖励 +10 枚金币

                          回复

                          使用道具 举报

                          『随时随地开启!』『随时随地开启!』小小舞台

                            回复

                            使用道具 举报

                            霉运小精灵[红]迷你蔓生灵树Joker克里斯‧雷德菲尔德克里斯·埃文斯猫咪合唱团(夏日)

                              回复

                              使用道具 举报

                              性感男神GM永远的守护者幽浮起司煲『草莓乳酪』图腾饼干『酒馆蛋煲』车厢特供 · 热巧 450ml『南瓜拿铁』男巫之歌缘起星空

                                回复

                                使用道具 举报

                                揄人者冠冕火玛瑙『领甜甜圈』吃饱金币的Doge炉石传说樱花树下的它『莲花宝灯』『落樱缤纷』迁徙之歌『召唤好运的角笛』

                                  回帖奖励 +10 枚金币

                                  这是只会提取当前等级的信息嘛
                                  回复

                                  使用道具 举报

                                  『住在GM村』雾港捞月霉运小精灵[红]猎个痛快!『炫目的铁塔』帅气的本・比格牛局长博戈传说中的黑龙幽浮起司煲You Can Pet Blaidd

                                    回帖奖励 +10 枚金币

                                    这个好,虽然之前点击进入勋章详情页那个也很好用,如果能复制的话更快更直观不用多开页面了
                                    回复

                                    使用道具 举报

                                    游侠I · 资深游侠裸体克里斯永远的克叔【新春限定】果体 隆成为蝙蝠侠史莱姆牧场虚空之海的鲸小镇的站台黑曜石赤螯蝎霉运小精灵[红]

                                      回帖奖励 +10 枚金币

                                      回复

                                      使用道具 举报

                                      岛田半藏岛田源氏Dante超人金钱马车『狄文卡德的残羽』近地夜航鎏彩万幢雾港捞月

                                        回复

                                        使用道具 举报

                                        『住在GM村』猫咪合唱团(夏日)【新春限定】果体 隆神人的编制发

                                          回复

                                          使用道具 举报

                                          您需要登录后才可以回帖 登录 | 立即注册

                                          本版积分规则

                                          关闭

                                          站长公告上一条 /1 下一条

                                          文字版|手机版|小黑屋|GameMale

                                          GMT+8, 2026-8-6 17:06 , Processed in 0.357673 second(s), 149 queries , Redis On.

                                          Copyright © 2013-2026 GameMale

                                          All Rights Reserved.

                                          快速回复 返回列表