NGA论坛自定义表情包

冲鸭

  1. // ==UserScript==
  2. // @name NGA论坛自定义表情包
  3. // @namespace https://github.com/biuuu
  4. // @version 0.0.4
  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/lodash@4.17.21/lodash.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. './mon_202103/01/i2Q16r-9dvkK6T8S1o-1o.png',
  91. './mon_202103/01/i2Q16r-j3x7KaT8S1o-1o.png',
  92. './mon_202103/01/i2Q16r-708aK9T8S1o-1o.png',
  93. './mon_202103/01/i2Q16r-g3c4K7T8S1o-1o.png',
  94. './mon_202103/01/i2Q16r-3s4iK7T8S1o-1o.png',
  95. './mon_202103/01/i2Q16r-ctnwK8T8S1o-1o.png',
  96. './mon_202103/01/i2Q16r-nikK9T8S1o-1o.png',
  97. './mon_202103/01/i2Q16r-9ii9K7T8S1o-1o.png',
  98. './mon_202103/01/i2Q16r-ilggK8T8S1o-1o.png',
  99. './mon_202103/01/i2Q16r-5v44K9T8S1o-1o.png',
  100. './mon_202103/01/i2Q16r-enk2K6T8S1o-1o.png',
  101. './mon_202103/01/i2Q16r-2dfkK8T8S1o-1o.png',
  102. './mon_202103/01/i2Q16r-dmbuK7T8S1o-1o.png',
  103. './mon_202103/01/i2Q16r-12xiK7T8S1o-1o.png',
  104. './mon_202103/01/i2Q16r-9tq0K6T8S1o-1o.png',
  105. './mon_202103/01/i2Q16r-it1bK8T8S1o-1o.png'
  106. ]],
  107. ['GBF',[
  108. './mon_201903/16/fkQ5-i23xK9T8S3c-3c.png',
  109. './mon_201903/16/fkQ5-1xyeKbT8S3c-3c.png'
  110. ]],
  111. ['闪耀色彩', [
  112. './mon_202102/27/-77rdlQj09-9h6dKcT8S2x-2g.png',
  113. './mon_202102/27/-77rdlQj09-brf3KhT8S2s-2g.png',
  114. './mon_202102/27/-77rdlQj09-ki6tKhT8S2s-2g.png',
  115. './mon_202102/27/-77rdlQj09-77piKeT8S2i-2g.png',
  116. './mon_202102/27/-77rdlQj09-fpreKiToS2x-2g.png',
  117. './mon_202102/27/-77rdlQj09-3cqxKiToS2x-2g.png',
  118. './mon_202102/27/-77rdlQj09-d9fpKmToS2x-2g.png',
  119. './mon_202102/27/-77rdlQj09-19pdKhToS2x-2g.png',
  120. './mon_202102/27/-77rdlQj09-ac3aKjToS2x-2g.png'
  121. ]]
  122. ])
  123.  
  124. let recentStickers = []
  125. try {
  126. let arr = JSON.parse(localStorage.getItem('custom-sticker'))
  127. if (Array.isArray(arr)) {
  128. stickerMap = new Map(arr)
  129. }
  130. } catch (e) {}
  131.  
  132. try {
  133. recentStickers = JSON.parse(localStorage.getItem('recent-sticker'))
  134. if (!Array.isArray(recentStickers)) {
  135. recentStickers = []
  136. }
  137. } catch (e) {}
  138.  
  139. const saveCustomSticker = (map = stickerMap) => {
  140. localStorage.setItem('custom-sticker', JSON.stringify([...map]))
  141. }
  142.  
  143. window.saveRecentSticker = (sticker) => {
  144. if (recentStickers.includes(sticker)) return
  145. recentStickers.push(sticker)
  146. recentStickers = recentStickers.slice(-10)
  147. localStorage.setItem('recent-sticker', JSON.stringify(recentStickers))
  148. }
  149.  
  150. const urlPrefix = 'https://img.nga.178.com/attachments'
  151.  
  152. const resolveUrl = (src) => {
  153. let url = src
  154. if (/^https?\:\/\//.test(src)) {
  155.  
  156. } else if (/^\.\//.test(src)) {
  157. url = `${urlPrefix}${src.replace(/^\./, '')}`
  158. } else if (/^[^\/]/.test(src)) {
  159. url = `${urlPrefix}/${src}`
  160. }
  161. return _.escape(url)
  162. }
  163.  
  164.  
  165. const insertStickers = async (stickerBox, list) => {
  166. let html = ''
  167. if (recentStickers.length) {
  168. recentStickers.forEach(sticker => {
  169. const src = resolveUrl(sticker)
  170. const safeSticker = _.escape(sticker)
  171. html += `
  172. <img onclick="window.saveRecentSticker('${safeSticker}');postfunc.addText('[img]${safeSticker}[/img]');postfunc.selectSmilesw._.hide()" src="${src}">
  173. `
  174. })
  175. html = `<div style="margin: 4px 0;
  176. border-bottom: 1px solid #dcc9b1;">${html}</div>`
  177. }
  178. for (let i = 0; i < list.length; i++) {
  179. let sticker = list[i]
  180. const src = resolveUrl(sticker)
  181. const safeSticker = _.escape(sticker)
  182. html += `
  183. <img onclick="window.saveRecentSticker('${safeSticker}');postfunc.addText('[img]${safeSticker}[/img]');postfunc.selectSmilesw._.hide()" src="${src}">
  184. `
  185. if (i && i % 60 === 0) {
  186. stickerBox.innerHTML = html
  187. await sleep(1000)
  188. }
  189. }
  190. stickerBox.innerHTML = html
  191. }
  192.  
  193. const stickerLoaded = new Set()
  194.  
  195. const changeBlock = (stickerBox, index) => {
  196. stickerBox.style.display = 'block'
  197. let blocks = stickerBox.parentNode.parentNode.querySelectorAll(`span>div:not(.sticker-${randomNum}-${index})`)
  198. blocks.forEach(item => item.style.display = 'none')
  199. }
  200.  
  201. window.setSticker = (index) => {
  202. const stickerBox = document.getElementById(`block-${randomNum}-sticker-${index}`)
  203. if (stickerBox) {
  204. if (stickerBox.style.display === 'block') {
  205. stickerBox.style.display = 'none'
  206. return
  207. } else if (stickerLoaded.has(index)) {
  208. changeBlock(stickerBox, index)
  209. return
  210. }
  211.  
  212. stickerLoaded.add(index)
  213. changeBlock(stickerBox, index)
  214. const list = ([...stickerMap])[index][1]
  215. insertStickers(stickerBox, list)
  216. }
  217. }
  218.  
  219. const getStickers = (text) => {
  220. const list = text.split(/\r?\n/)
  221. if (list[0] !== '==NGA CUSTOM STICKER==') {
  222. alert('文件格式错误: 第一行必须是“==NGA CUSTOM STICKER==”')
  223. return
  224. }
  225. const stickers = new Map()
  226. list.forEach(txt => {
  227. if (txt.startsWith('#')) {
  228. let name = txt.slice(1, txt.length)
  229. if (name) {
  230. stickers.set(name, [])
  231. }
  232. } else if (/^(https?:\/\/|\.\/)/.test(txt)) {
  233. const arr = Array.from(stickers.values()).pop()
  234. if (arr && Array.isArray(arr)) {
  235. arr.push(txt)
  236. }
  237. }
  238. })
  239. if (stickers.size) {
  240. saveCustomSticker(stickers)
  241. if (confirm('导入成功,刷新页面后生效。是否立即刷新')) {
  242. location.reload()
  243. }
  244. } else {
  245. alert('没有找到有效的表情地址')
  246. }
  247. }
  248.  
  249. const tryDownload = (content, filename) => {
  250. const eleLink = document.createElement('a')
  251. eleLink.download = filename
  252. eleLink.style.display = 'none'
  253. const blob = new Blob([content], { type: 'text/plain' })
  254. eleLink.href = URL.createObjectURL(blob)
  255. document.body.appendChild(eleLink)
  256. eleLink.click()
  257. document.body.removeChild(eleLink)
  258. }
  259.  
  260. window.importStickers = function (files) {
  261. if (!files.length) return
  262. const reader = new FileReader()
  263. reader.onload = e => {
  264. const text = e.target.result
  265. getStickers(text)
  266. }
  267. reader.readAsText(files[0])
  268. }
  269.  
  270. window.exportStickers = function () {
  271. let arr = ['==NGA CUSTOM STICKER==', '']
  272. for (let [name, list] of stickerMap) {
  273. arr.push(`#${name}`)
  274. for (let src of list) {
  275. arr.push(src)
  276. }
  277. arr.push('')
  278. }
  279. const text = arr.join('\r\n')
  280. tryDownload(text, 'custom-sticker.txt')
  281. }
  282.  
  283. let inserted = false
  284. const insertBtn = () => {
  285. if (inserted) return
  286. inserted = true
  287. let index = 0
  288. for (let [name] of stickerMap) {
  289. const safeName = _.escape(name)
  290. const btnBlock = document.querySelector(`#${boxId} .div3 .block_txt_big:last-child`)
  291. btnBlock.insertAdjacentHTML('afterend', `<button class="block_txt_big" onclick="window.setSticker(${index})">${safeName}</button>`)
  292. const stcBlock = document.querySelector(`#${boxId} .div3>span:last-child>div:last-child`)
  293. stcBlock.insertAdjacentHTML('afterend', `<div id="block-${randomNum}-sticker-${index}" class="sticker-${randomNum} sticker-${randomNum}-${index}"></div>`)
  294. index++
  295. }
  296. const box = document.querySelector(`#${boxId} .div1`)
  297. box.insertAdjacentHTML('afterend', `
  298. <div class="sticker-toolbar-${randomNum}">
  299. <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>
  300. <button class="block_txt_big" onclick="window.exportStickers()">导出</button>
  301. </div>
  302. `)
  303. }
  304.  
  305. const mutationCallback = (mutationsList) => {
  306. for (let mutation of mutationsList) {
  307. const type = mutation.type
  308. const addedNodes = mutation.addedNodes
  309. if (type === 'childList' && addedNodes.length && addedNodes.length < 2) {
  310. addedNodes.forEach(node => {
  311. if (/^commonwindow\d+$/.test(node.id) && node.querySelector('.tip_title .title').innerText === '插入表情') {
  312. boxId = node.id
  313. insertBtn()
  314. }
  315. })
  316. }
  317. }
  318. }
  319.  
  320. const obConfig = {
  321. subtree: true,
  322. childList: true
  323. }
  324.  
  325. const targetNode = document.body
  326. const observer = new MutationObserver(mutationCallback)
  327. observer.observe(targetNode, obConfig)
  328. })();

QingJ © 2025

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