NGA论坛自定义表情包

冲鸭

目前为 2021-02-27 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name NGA论坛自定义表情包
  3. // @namespace https://github.com/biuuu
  4. // @version 0.0.1
  5. // @description 冲鸭
  6. // @author 芭芭拉
  7. // @match *://bbs.ngacn.cc/*.php*
  8. // @match *://ngabbs.com/*.php*
  9. // @match *://nga.178.com/*.php*
  10. // @match *://bbs.nga.cn/*.php*
  11. // @grant none
  12. // ==/UserScript==
  13. (async function() {
  14. 'use strict';
  15. const addStyle = (css) => {
  16. const style = document.createElement('style')
  17. style.innerText = css
  18. document.head.appendChild(style)
  19. }
  20.  
  21. const loadScript = async () => {
  22. const script = document.createElement('script')
  23. script.src = 'https://cdn.jsdelivr.net/npm/dompurify@2.2.6/dist/purify.min.js'
  24. document.head.appendChild(script)
  25. return new Promise((rev, rej) => {
  26. script.onload = rev
  27. script.onerror = rej
  28. })
  29. }
  30.  
  31. await loadScript()
  32.  
  33. const randomNum = Math.floor(Math.random() * 1e5)
  34.  
  35. addStyle(`
  36. .single_ttip2 .div3 > div {
  37. padding: 4px 4px 0 4px;
  38. }
  39. .single_ttip2 .div3 > div:empty {
  40. display: inline-block;
  41. padding: 0;
  42. }
  43. .sticker-${randomNum} img {
  44. max-height: 70px;
  45. cursor: pointer;
  46. }
  47. .sticker-${randomNum} {
  48. margin: 0.2em;
  49. width: 962px;
  50. }
  51. .single_ttip2 .block_txt_big {
  52. padding: 0 0.5em;
  53. cursor: pointer;
  54. outline: 0;
  55. margin-left: -0.2em;
  56. margin-right: 0.6em;
  57. }
  58. .single_ttip2 .block_txt_big:hover {
  59. filter: brightness(0.95);
  60. }
  61. .single_ttip2 .block_txt_big:active {
  62. filter: brightness(0.9);
  63. }
  64. .sticker-toolbar-${randomNum} {
  65. position: absolute;
  66. right: 53px;
  67. top: 0;
  68. }
  69. .sticker-import-${randomNum} {
  70. opacity: 0;
  71. position: absolute;
  72. left: 0;
  73. top: 0;
  74. width: 100%;
  75. height: 100%;
  76. }
  77. `)
  78.  
  79.  
  80. let boxId = null
  81.  
  82. const sleep = function (time) {
  83. return new Promise(rev => {
  84. setTimeout(rev, time)
  85. })
  86. }
  87.  
  88. let stickerMap = new Map([
  89. ['原神', [
  90. 'https://img-static.mihoyo.com/communityweb/upload/84f36618f15f905398a7a15fda3b1241.png',
  91. 'https://img-static.mihoyo.com/communityweb/upload/393e46eed37fe293e68121afe07c1f37.png',
  92. 'https://img-static.mihoyo.com/communityweb/upload/efc2b5f2804b63cbaf377d5cacd5eff6.png',
  93. 'https://img-static.mihoyo.com/communityweb/upload/fe198bb1523528c2b852d28626379a2a.png'
  94. ]],
  95. ['GBF',[
  96. './mon_201903/16/fkQ5-i23xK9T8S3c-3c.png',
  97. './mon_201903/16/fkQ5-1xyeKbT8S3c-3c.png'
  98. ]],
  99. ['闪耀色彩', [
  100. './mon_202102/27/-77rdlQj09-9h6dKcT8S2x-2g.png',
  101. './mon_202102/27/-77rdlQj09-brf3KhT8S2s-2g.png',
  102. './mon_202102/27/-77rdlQj09-ki6tKhT8S2s-2g.png',
  103. './mon_202102/27/-77rdlQj09-77piKeT8S2i-2g.png',
  104. './mon_202102/27/-77rdlQj09-fpreKiToS2x-2g.png',
  105. './mon_202102/27/-77rdlQj09-3cqxKiToS2x-2g.png',
  106. './mon_202102/27/-77rdlQj09-d9fpKmToS2x-2g.png',
  107. './mon_202102/27/-77rdlQj09-19pdKhToS2x-2g.png',
  108. './mon_202102/27/-77rdlQj09-ac3aKjToS2x-2g.png'
  109. ]]
  110. ])
  111.  
  112. let recentStickers = []
  113. try {
  114. let arr = JSON.parse(localStorage.getItem('custom-sticker'))
  115. if (Array.isArray(arr)) {
  116. stickerMap = new Map(arr)
  117. }
  118. } catch (e) {}
  119.  
  120. try {
  121. recentStickers = JSON.parse(localStorage.getItem('recent-sticker'))
  122. if (!Array.isArray(recentStickers)) {
  123. recentStickers = []
  124. }
  125. } catch (e) {}
  126.  
  127. const saveCustomSticker = (map = stickerMap) => {
  128. localStorage.setItem('custom-sticker', JSON.stringify([...map]))
  129. }
  130.  
  131. window.saveRecentSticker = (sticker) => {
  132. if (recentStickers.includes(sticker)) return
  133. recentStickers.push(sticker)
  134. recentStickers = recentStickers.slice(-10)
  135. localStorage.setItem('recent-sticker', JSON.stringify(recentStickers))
  136. }
  137.  
  138. const urlPrefix = 'https://img.nga.178.com/attachments'
  139.  
  140. const resolveUrl = (src) => {
  141. let url = src
  142. if (/^https?\:\/\//.test(src)) {
  143.  
  144. } else if (/^\.\//.test(src)) {
  145. url = `${urlPrefix}${src.replace(/^\./, '')}`
  146. } else if (/^[^\/]/.test(src)) {
  147. url = `${urlPrefix}/${src}`
  148. }
  149. return DOMPurify.sanitize(url)
  150. }
  151.  
  152.  
  153. const insertStickers = async (stickerBox, list) => {
  154. let html = ''
  155. if (recentStickers.length) {
  156. recentStickers.forEach(sticker => {
  157. const src = resolveUrl(sticker)
  158. const safeSticker = DOMPurify.sanitize(sticker)
  159. html += `
  160. <img onclick="window.saveRecentSticker('${safeSticker}');postfunc.addText('[img]${safeSticker}[/img]');postfunc.selectSmilesw._.hide()" src="${src}">
  161. `
  162. })
  163. html = `<div style="margin: 4px 0;
  164. border-bottom: 1px solid #dcc9b1;">${html}</div>`
  165. }
  166. for (let i = 0; i < list.length; i++) {
  167. let sticker = list[i]
  168. const src = resolveUrl(sticker)
  169. const safeSticker = DOMPurify.sanitize(sticker)
  170. html += `
  171. <img onclick="window.saveRecentSticker('${safeSticker}');postfunc.addText('[img]${safeSticker}[/img]');postfunc.selectSmilesw._.hide()" src="${src}">
  172. `
  173. if (i && i % 60 === 0) {
  174. stickerBox.innerHTML = html
  175. await sleep(1000)
  176. }
  177. }
  178. stickerBox.innerHTML = html
  179. }
  180.  
  181. const stickerLoaded = new Set()
  182.  
  183. const changeBlock = (stickerBox, index) => {
  184. stickerBox.style.display = 'block'
  185. let blocks = stickerBox.parentNode.parentNode.querySelectorAll(`span>div:not(.sticker-${randomNum}-${index})`)
  186. blocks.forEach(item => item.style.display = 'none')
  187. }
  188.  
  189. window.setSticker = (index) => {
  190. const stickerBox = document.getElementById(`block-${randomNum}-sticker-${index}`)
  191. if (stickerBox) {
  192. if (stickerBox.style.display === 'block') {
  193. stickerBox.style.display = 'none'
  194. return
  195. } else if (stickerLoaded.has(index)) {
  196. changeBlock(stickerBox, index)
  197. return
  198. }
  199.  
  200. stickerLoaded.add(index)
  201. changeBlock(stickerBox, index)
  202. const list = ([...stickerMap])[index][1]
  203. insertStickers(stickerBox, list)
  204. }
  205. }
  206.  
  207. const getStickers = (text) => {
  208. const list = text.split(/\r?\n/)
  209. if (list[0] !== '==NGA CUSTOM STICKER==') {
  210. alert('文件格式错误: 第一行必须是“==NGA CUSTOM STICKER==”')
  211. return
  212. }
  213. const stickers = new Map()
  214. list.forEach(txt => {
  215. if (txt.startsWith('#')) {
  216. let name = txt.slice(1, txt.length)
  217. if (name) {
  218. stickers.set(name, [])
  219. }
  220. } else if (/^(https?:\/\/|\.\/)/.test(txt)) {
  221. const arr = Array.from(stickers.values()).pop()
  222. if (arr && Array.isArray(arr)) {
  223. arr.push(txt)
  224. }
  225. }
  226. })
  227. if (stickers.size) {
  228. saveCustomSticker(stickers)
  229. if (confirm('导入成功,刷新页面后生效。是否立即刷新')) {
  230. location.reload()
  231. }
  232. } else {
  233. alert('没有找到有效的表情地址')
  234. }
  235. }
  236.  
  237. const tryDownload = (content, filename) => {
  238. const eleLink = document.createElement('a')
  239. eleLink.download = filename
  240. eleLink.style.display = 'none'
  241. const blob = new Blob([content], { type: 'text/plain' })
  242. eleLink.href = URL.createObjectURL(blob)
  243. document.body.appendChild(eleLink)
  244. eleLink.click()
  245. document.body.removeChild(eleLink)
  246. }
  247.  
  248. window.importStickers = function (files) {
  249. if (!files.length) return
  250. const reader = new FileReader()
  251. reader.onload = e => {
  252. const text = e.target.result
  253. getStickers(text)
  254. }
  255. reader.readAsText(files[0])
  256. }
  257.  
  258. window.exportStickers = function () {
  259. let arr = ['==NGA CUSTOM STICKER==', '']
  260. for (let [name, list] of stickerMap) {
  261. arr.push(`#${name}`)
  262. for (let src of list) {
  263. arr.push(src)
  264. }
  265. arr.push('')
  266. }
  267. const text = arr.join('\r\n')
  268. tryDownload(text, 'custom-sticker.txt')
  269. }
  270.  
  271. let inserted = false
  272. const insertBtn = () => {
  273. if (inserted) return
  274. inserted = true
  275. let index = 0
  276. for (let [name] of stickerMap) {
  277. const safeName = DOMPurify.sanitize(name)
  278. const btnBlock = document.querySelector(`#${boxId} .div3 .block_txt_big:last-child`)
  279. btnBlock.insertAdjacentHTML('afterend', `<button class="block_txt_big" onclick="window.setSticker(${index})">${safeName}</button>`)
  280. const stcBlock = document.querySelector(`#${boxId} .div3>span:last-child>div:last-child`)
  281. stcBlock.insertAdjacentHTML('afterend', `<div id="block-${randomNum}-sticker-${index}" class="sticker-${randomNum} sticker-${randomNum}-${index}"></div>`)
  282. index++
  283. }
  284. const box = document.querySelector(`#${boxId} .div1`)
  285. box.insertAdjacentHTML('afterend', `
  286. <div class="sticker-toolbar-${randomNum}">
  287. <button class="block_txt_big" style="position:relative;overflow:hidden">导入<input type="file" class="sticker-import-${randomNum}" onchange="window.importStickers(this.files)" accept=".txt"></button>
  288. <button class="block_txt_big" onclick="window.exportStickers()">导出</button>
  289. </div>
  290. `)
  291. }
  292.  
  293. const mutationCallback = (mutationsList) => {
  294. for (let mutation of mutationsList) {
  295. const type = mutation.type
  296. const addedNodes = mutation.addedNodes
  297. if (type === 'childList' && addedNodes.length && addedNodes.length < 2) {
  298. addedNodes.forEach(node => {
  299. if (/^commonwindow\d+$/.test(node.id) && node.querySelector('.tip_title .title').innerText === '插入表情') {
  300. boxId = node.id
  301. insertBtn()
  302. }
  303. })
  304. }
  305. }
  306. }
  307.  
  308. const obConfig = {
  309. subtree: true,
  310. childList: true
  311. }
  312.  
  313. const targetNode = document.body
  314. const observer = new MutationObserver(mutationCallback)
  315. observer.observe(targetNode, obConfig)
  316. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址