咸鱼鱼 发表于 2025-4-30 10:53:56

GM论坛勋章博物馆属性复制助手

本帖最后由 咸鱼鱼 于 2025-5-2 20:24 编辑

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

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

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

效果演示


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

(function () {
    'use strict'

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

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

            // 统一数据采集
            const rawData = collectRawData(target)
            // 统一数据处理
            const formattedData = processRawData(rawData)
            console.log(formattedData)
            const result = formatFinalData(formattedData)

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

    // 统一数据采集
    function collectRawData(target) {
      const isCase1 = target.matches('img')
      const isCase2 = target.matches('.mytip')

      if (isCase1) {
            return {
                type: 'case1',
                imgSrc: target.src,
                tipHtml: target.getAttribute('tip'),
                element: target
            }
      }

      if (isCase2) {
            const myblok = target.closest('.myblok')
            return {
                type: 'case2',
                imgSrc: myblok?.querySelector('.myimg img')?.src,
                titleHtml: target.querySelector('.mingcheng')?.innerHTML,
                descriptionText: target.querySelector('.shuoming')?.textContent,
                attributes: Array.from(target.querySelectorAll('.jiage.shuxing'))
                  .map(p => p.innerHTML),
                element: myblok
            }
      }

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

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

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

      if (rawData.type === 'case2') {
            return {
                ...baseData,
                titleHtml: rawData.titleHtml || '',
                descriptionText: rawData.descriptionText || '',
                attributes: rawData.attributes,
                nextLevel: findNextLevel(rawData.element)
            }
      }
    }

    // 统一字段格式化
    function formatFinalData(processedData) {
      const formattedData = [
            `${processedData.imgSrc}`,
            `${formatTitle(processedData.titleHtml)}`,
            `${formatDescription(processedData.descriptionText)}`,
            `${formatAttributes(processedData.attributes)}` +
            (processedData.nextLevel ? `▕▏升级条件:${processedData.nextLevel}` : ''),
      ].filter(Boolean)

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

      return formattedData.join('\n')
    }

    // 格式化函数组
    function formatTitle(html) {
      return html
            .replace(/<\/?h4>/g, '')
            .replace(/<b>/g, '')
            .replace(/<\/b>/g, ' ')
    }

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

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

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

            // 分类回帖/发帖属性
            const = text.split(' ')
            if (type === '回帖') acc.reply.push(...values)
            if (type === '发帖') acc.post.push(...values)

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

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

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

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

    // 辅助函数
    function findValidTarget(startElement) {
      if (startElement.matches('img')) return startElement

      const myblok = startElement.closest('.myblok')
      if (myblok) {
            const mytip = myblok.querySelector('.mytip')
            if (mytip) return mytip
      }

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

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

      if (!nextLevelDiv) return null

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

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


      if (!matches) return null

      const attribute = matches // 属性名(如发帖数、金币等)
      const requiredValue = matches // 斜杠后的数字(如404)

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

    function showFeedback(message) {
      showPrompt(null, null, `<i>${message}</i>`, 2000)
    }
})()

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


以下的勋章大部分都是属性完整但是缺卡面,期待有缘人补充卡面
或者属性多是截图的静态图片,没有完整的动态gif图

No.0079 魔术师奥斯卡(Magician Oscar)
https://www.gamemale.com/thread-12124-1-1.html

No.0120 艾德尔(已补完,感谢娱乐法师火布偶
https://www.gamemale.com/thread-12376-1-1.html

No.0122 史莱姆养殖证书/史莱姆牧场
https://www.gamemale.com/thread-12378-1-1.html

No.0126 戴尔‧芭芭拉
https://www.gamemale.com/thread-12645-1-1.html

No.0127 婴儿泪之瓶
https://www.gamemale.com/thread-12646-1-1.html

No.0128 雪王的心脏(已补完,感谢深暗幽狼
https://www.gamemale.com/thread-12647-1-1.html

No.0138 阿尔萨斯·米奈希尔(Arthas Menethil)
https://www.gamemale.com/thread-13286-1-1.html

No.0151 岛田半藏(Shimada Hanzo)
https://www.gamemale.com/thread-17946-1-1.html

No.0155 亚瑟·库瑞(Arthur Curry)
https://www.gamemale.com/thread-68059-1-1.html

No.0243 莫瑞甘
https://www.gamemale.com/thread-72921-1-1.html

No.0238 老旧的怀表(已补完,感谢深暗幽狼
https://www.gamemale.com/thread-72225-1-1.html

No.0229 莱因哈特·威尔海姆
https://www.gamemale.com/thread-72137-1-1.html

No.0253 熔岩蛋(已补完,感谢深暗幽狼
https://www.gamemale.com/thread-73631-1-1.html

No.0269 灵鹫蛋(目前的太乱了,希望有人有空重新发一个比较整洁的
https://www.gamemale.com/thread-74203-1-1.html

No.0301 汉克/Hank
https://www.gamemale.com/thread-77158-1-1.html

No.0355 血红色的蛋(已补完,感谢找乐子企鹅仔
https://www.gamemale.com/thread-85524-1-1.html

No.0385 大黄蜂(ChevroletCamaro)(已补完,感谢我自己
https://www.gamemale.com/thread-94948-1-1.html

No.0422 艾利克斯
https://www.gamemale.com/thread-111280-1-1.html

No.0559 梅琳娜Melina
https://www.gamemale.com/thread-153407-1-1.html


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

亚洛斯 发表于 2025-4-30 11:05:30

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

SweetUncle 发表于 2025-4-30 11:05:57

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

找乐子企鹅仔 发表于 2025-4-30 11:06:39

用过的都说好,麻麻再也不用担心咱打不出来蟑螂蛋的满级描述了

万俟 发表于 2025-4-30 11:07:50

这个看起来不错哎,比截图什么的方便太多了

死水 发表于 2025-4-30 11:08:18

原来还有神秘语言的勋章吗?没有见过捏

柏芸 发表于 2025-4-30 11:09:08

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

Daddy控 发表于 2025-4-30 11:09:08

看样子效果还可以惹,学习中{:6_200:}

莲一 发表于 2025-4-30 11:09:26

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

泥潭蘑菇 发表于 2025-4-30 11:10:46

{:6_170:}辛苦了,这样的脚本设计出来真的是很服务坛友了,傻瓜也能用实在是太强了

娱乐法师火布偶 发表于 2025-4-30 11:10:47

上次试用了一下,确实非常方便,回去再更新一下脚本

Magicskull 发表于 2025-4-30 11:17:22

看起来就是超大工程,一些卡面如果是gif的就更好欣赏了(小强除外)

凯诺斯 发表于 2025-4-30 11:17:42

好方便的脚本,这样子复制感觉方便多了:loveliness:

你好再见 发表于 2025-4-30 11:24:29

很方便,方便新勋章出时在勋章馆里回复属性

一只随行 发表于 2025-4-30 11:25:32

有了这个脚本,好像这个大工程的麻烦程度大大降低了诶{:6_184:}

notunicorna 发表于 2025-4-30 11:26:14

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

臭熊猫 发表于 2025-4-30 11:35:58

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

cinder 发表于 2025-4-30 11:41:46

確實要推薦徽章都要打字很久挺麻煩,
能一鍵複製輕鬆多了.=w=

kkiss 发表于 2025-4-30 11:46:39

紧挨着补货就释出了,以后博物馆魅力upup

Inari 发表于 2025-4-30 12:02:25

很方便呢,原来能想到的也就只能截图识别了
页: [1] 2 3 4 5
查看完整版本: GM论坛勋章博物馆属性复制助手