NGA论坛自定义表情包(自用改版)

冲鸭

  1. // ==UserScript==
  2. // @name NGA论坛自定义表情包(自用改版)
  3. // @namespace https://github.com/biuuu
  4. // @version 0.0.5
  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. }
  50. .single_ttip2 .block_txt_big {
  51. padding: 0 0.5em;
  52. cursor: pointer;
  53. outline: 0;
  54. margin-left: -0.2em;
  55. margin-right: 0.6em;
  56. }
  57. .single_ttip2 .block_txt_big:hover {
  58. filter: brightness(0.95);
  59. }
  60. .single_ttip2 .block_txt_big:active {
  61. filter: brightness(0.9);
  62. }
  63. .sticker-toolbar-${randomNum} {
  64. position: absolute;
  65. right: 53px;
  66. top: 0;
  67. }
  68. .sticker-import-${randomNum} {
  69. opacity: 0;
  70. position: absolute;
  71. left: 0;
  72. top: 0;
  73. width: 100%;
  74. height: 100%;
  75. }
  76. `)
  77.  
  78.  
  79. let boxId = null
  80.  
  81. const sleep = function (time) {
  82. return new Promise(rev => {
  83. setTimeout(rev, time)
  84. })
  85. }
  86.  
  87. let stickerMap = new Map([
  88. ['猫猫龙', [
  89. './mon_202203/24/mhQ17n-ki6zKeT8S2s-2u.png',
  90. './mon_202203/24/mhQ17n-d3kwKeT8S2s-2v.png',
  91. './mon_202203/25/mhQ17n-fcvlKfT8S2s-2v.png',
  92. './mon_202203/25/mhQ17n-30l7KeT8S2s-2v.png',
  93. './mon_202203/26/mhQ7i87-6rqiKeT8S2s-2v.png',
  94. './mon_202203/26/mhQ7i87-ryrKfT8S2s-2v.png',
  95. './mon_202203/26/mhQ17n-i953ZeT1kS3s-3k.gif',//勾引
  96. './mon_202203/26/mhQ17n-8vp3K13ToS35-28.gif',//揉...
  97. './mon_202203/26/mhQ0-ahyeK1jToS4w-5k.png'
  98. ]]
  99. ])
  100.  
  101. let recentStickers = []
  102. try {
  103. let arr = JSON.parse(localStorage.getItem('custom-sticker'))
  104. if (Array.isArray(arr)) {
  105. stickerMap = new Map(arr)
  106. }
  107. } catch (e) {}
  108.  
  109. try {
  110. recentStickers = JSON.parse(localStorage.getItem('recent-sticker'))
  111. if (!Array.isArray(recentStickers)) {
  112. recentStickers = []
  113. }
  114. } catch (e) {}
  115.  
  116. const saveCustomSticker = (map = stickerMap) => {
  117. localStorage.setItem('custom-sticker', JSON.stringify([...map]))
  118. }
  119.  
  120. window.saveRecentSticker = (sticker) => {
  121. if (recentStickers.includes(sticker)) return
  122. recentStickers.push(sticker)
  123. recentStickers = recentStickers.slice(-10)
  124. localStorage.setItem('recent-sticker', JSON.stringify(recentStickers))
  125. }
  126.  
  127. const urlPrefix = 'https://img.nga.178.com/attachments'
  128.  
  129. const resolveUrl = (src) => {
  130. let url = src
  131. if (/^https?\:\/\//.test(src)) {
  132.  
  133. } else if (/^\.\//.test(src)) {
  134. url = `${urlPrefix}${src.replace(/^\./, '')}`
  135. } else if (/^[^\/]/.test(src)) {
  136. url = `${urlPrefix}/${src}`
  137. }
  138. return _.escape(url)
  139. }
  140.  
  141.  
  142. const insertStickers = async (stickerBox, list) => {
  143. let html = ''
  144. if (recentStickers.length) {
  145. recentStickers.forEach(sticker => {
  146. const src = resolveUrl(sticker)
  147. const safeSticker = _.escape(sticker)
  148. html += `
  149. <img onclick="window.saveRecentSticker('${safeSticker}');postfunc.addText('[img]${safeSticker}[/img]');postfunc.selectSmilesw._.hide()" src="${src}">
  150. `
  151. })
  152. html = `<div style="margin: 4px 0;
  153. border-bottom: 1px solid #dcc9b1;">${html}</div>`
  154. }
  155. for (let i = 0; i < list.length; i++) {
  156. let sticker = list[i]
  157. const src = resolveUrl(sticker)
  158. const safeSticker = _.escape(sticker)
  159. html += `
  160. <img onclick="window.saveRecentSticker('${safeSticker}');postfunc.addText('[img]${safeSticker}[/img]');postfunc.selectSmilesw._.hide()" src="${src}">
  161. `
  162. if (i && i % 60 === 0) {
  163. stickerBox.innerHTML = html
  164. await sleep(1000)
  165. }
  166. }
  167. stickerBox.innerHTML = html
  168. }
  169.  
  170. const stickerLoaded = new Set()
  171.  
  172. const changeBlock = (stickerBox, index) => {
  173. stickerBox.style.display = 'block'
  174. let blocks = stickerBox.parentNode.parentNode.querySelectorAll(`span>div:not(.sticker-${randomNum}-${index})`)
  175. blocks.forEach(item => item.style.display = 'none')
  176. }
  177.  
  178. window.setSticker = (index) => {
  179. const stickerBox = document.getElementById(`block-${randomNum}-sticker-${index}`)
  180. if (stickerBox) {
  181. if (stickerBox.style.display === 'block') {
  182. stickerBox.style.display = 'none'
  183. return
  184. } else if (stickerLoaded.has(index)) {
  185. changeBlock(stickerBox, index)
  186. return
  187. }
  188.  
  189. stickerLoaded.add(index)
  190. changeBlock(stickerBox, index)
  191. const list = ([...stickerMap])[index][1]
  192. insertStickers(stickerBox, list)
  193. }
  194. }
  195.  
  196. const getStickers = (text) => {
  197. const list = text.split(/\r?\n/)
  198. if (list[0] !== '==NGA CUSTOM STICKER==') {
  199. alert('文件格式错误: 第一行必须是“==NGA CUSTOM STICKER==”')
  200. return
  201. }
  202. const stickers = new Map()
  203. list.forEach(txt => {
  204. if (txt.startsWith('#')) {
  205. let name = txt.slice(1, txt.length)
  206. if (name) {
  207. stickers.set(name, [])
  208. }
  209. } else if (/^(https?:\/\/|\.\/)/.test(txt)) {
  210. const arr = Array.from(stickers.values()).pop()
  211. if (arr && Array.isArray(arr)) {
  212. arr.push(txt)
  213. }
  214. }
  215. })
  216. if (stickers.size) {
  217. saveCustomSticker(stickers)
  218. if (confirm('导入成功,刷新页面后生效。是否立即刷新')) {
  219. location.reload()
  220. }
  221. } else {
  222. alert('没有找到有效的表情地址')
  223. }
  224. }
  225.  
  226. const tryDownload = (content, filename) => {
  227. const eleLink = document.createElement('a')
  228. eleLink.download = filename
  229. eleLink.style.display = 'none'
  230. const blob = new Blob([content], { type: 'text/plain' })
  231. eleLink.href = URL.createObjectURL(blob)
  232. document.body.appendChild(eleLink)
  233. eleLink.click()
  234. document.body.removeChild(eleLink)
  235. }
  236.  
  237. window.importStickers = function (files) {
  238. if (!files.length) return
  239. const reader = new FileReader()
  240. reader.onload = e => {
  241. const text = e.target.result
  242. getStickers(text)
  243. }
  244. reader.readAsText(files[0])
  245. }
  246.  
  247. window.exportStickers = function () {
  248. let arr = ['==NGA CUSTOM STICKER==', '']
  249. for (let [name, list] of stickerMap) {
  250. arr.push(`#${name}`)
  251. for (let src of list) {
  252. arr.push(src)
  253. }
  254. arr.push('')
  255. }
  256. const text = arr.join('\r\n')
  257. tryDownload(text, 'custom-sticker.txt')
  258. }
  259.  
  260. let inserted = false
  261. const insertBtn = () => {
  262. if (inserted) return
  263. inserted = true
  264. let index = 0
  265. for (let [name] of stickerMap) {
  266. const safeName = _.escape(name)
  267. const btnBlock = document.querySelector(`#${boxId} .div3 .block_txt_big:last-child`)
  268. btnBlock.insertAdjacentHTML('afterend', `<button class="block_txt_big" onclick="window.setSticker(${index})">${safeName}</button>`)
  269. const stcBlock = document.querySelector(`#${boxId} .div3>span:last-child>div:last-child`)
  270. stcBlock.insertAdjacentHTML('afterend', `<div id="block-${randomNum}-sticker-${index}" class="sticker-${randomNum} sticker-${randomNum}-${index}"></div>`)
  271. index++
  272. }
  273. const box = document.querySelector(`#${boxId} .div1`)
  274. box.insertAdjacentHTML('afterend', `
  275. <div class="sticker-toolbar-${randomNum}">
  276. <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>
  277. <button class="block_txt_big" onclick="window.exportStickers()">导出</button>
  278. </div>
  279. `)
  280. }
  281.  
  282. const mutationCallback = (mutationsList) => {
  283. for (let mutation of mutationsList) {
  284. const type = mutation.type
  285. const addedNodes = mutation.addedNodes
  286. if (type === 'childList' && addedNodes.length && addedNodes.length < 2) {
  287. addedNodes.forEach(node => {
  288. if (/^commonwindow\d+$/.test(node.id) && node.querySelector('.tip_title .title').innerText === '插入表情') {
  289. boxId = node.id
  290. insertBtn()
  291. }
  292. })
  293. }
  294. }
  295. }
  296.  
  297. const obConfig = {
  298. subtree: true,
  299. childList: true
  300. }
  301.  
  302. const targetNode = document.body
  303. const observer = new MutationObserver(mutationCallback)
  304. observer.observe(targetNode, obConfig)
  305. })();

QingJ © 2025

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