游戏社区(TapTap)列表页贴子预览

TapTap游戏社区列表页贴子卡片新增预览按钮,可在列表页直接预览贴子内容。

  1. // ==UserScript==
  2. // @name 游戏社区(TapTap)列表页贴子预览
  3. // @namespace 游戏社区(TapTap)列表页贴子预览
  4. // @license GPL-3.0 License
  5. // @version 1.3.3
  6. // @description TapTap游戏社区列表页贴子卡片新增预览按钮,可在列表页直接预览贴子内容。
  7. // @author QIAN
  8. // @match *://www.taptap.cn/app/*/topic*
  9. // @grant GM_addStyle
  10. // @grant GM_info
  11. // @require https://scriptcat.org/lib/513/2.0.1/ElementGetter.js?#sha256=V0EUYIfbOrr63nT8+W7BP1xEmWcumTLWu2PXFJHh5dg=
  12. // @supportURL https://github.com/QIUZAIYOU/Taptap-PostPreview
  13. // @homepageURL https://github.com/QIUZAIYOU/Taptap-PostPreview
  14. // @icon https://assets.tapimg.com/cupid-apps/web-app/favicon.2.ico
  15. // ==/UserScript==
  16. // ?#sha256=V0EUYIfbOrr63nT8+W7BP1xEmWcumTLWu2PXFJHh5dg=
  17. (function () {
  18. 'use strict';
  19. const selector = {
  20. tap: '#__nuxt',
  21. momentCardFooter: '.moment-card__footer',
  22. previewWrapper: '#previewWrapper',
  23. previewIframe: '#previewIframe',
  24. previewIframeMask: '#previewIframeMask',
  25. previewContentHeader: 'header',
  26. previewContentMain: 'main',
  27. previewButton: '.previewButton',
  28. voteButton: '.vote-button.moment-card__footer-button',
  29. }
  30. const styles = {
  31. PreviewWrapperStyle: '#previewWrapper{position:fixed;top:50%;left:50%;overflow:hidden;box-sizing:border-box;width:600px;height:97vh;border:2px solid #00d9c5;border-radius:10px;background:#191919;transform:translate(-50%,-50%)}#previewWrapper::backdrop{backdrop-filter:blur(3px)}#previewIframe{width:100%;height:100%;border:none;background:#191919}#previewIframeMask{position:absolute;display:flex;background:#191919;inset:0;align-items:center;justify-content:center}.previewButton{cursor: pointer}',
  32. PreviewIframeStyle: 'body{background:#191919!important}header{display:none!important}main{margin-left:0!important}'
  33. }
  34. const utils = {
  35. /**
  36. * 休眠
  37. * @param {Number} 时长
  38. * @returns
  39. */
  40. sleep(times) {
  41. return new Promise(resolve => setTimeout(resolve, times))
  42. },
  43. logger: {
  44. info(content) {
  45. console.info('%cTapTap预览', 'color:white;background:#006aff;padding:2px;border-radius:2px', content);
  46. },
  47. warn(content) {
  48. console.warn('%cTapTap贴子预览', 'color:white;background:#ff6d00;padding:2px;border-radius:2px', content);
  49. },
  50. error(content) {
  51. console.error('%cTapTap贴子预览', 'color:white;background:#f33;padding:2px;border-radius:2px', content);
  52. },
  53. debug(content) {
  54. console.info('%cTapTap贴子预览(调试)', 'color:white;background:#cc00ff;padding:2px;border-radius:2px', content);
  55. },
  56. },
  57. createElementAndInsert(HtmlString, target, method) {
  58. const element = elmGetter.create(HtmlString, target)
  59. target[method](element)
  60. return element
  61. },
  62. insertStyleToDocument(id, css) {
  63. const styleElement = GM_addStyle(css)
  64. styleElement.id = id
  65. },
  66. htmlStringToDom(htmlString) {
  67. const tempDiv = document.createElement('div');
  68. tempDiv.innerHTML = htmlString.trim();
  69. return tempDiv.firstChild;
  70. },
  71. isAsyncFunction(targetFunction) {
  72. return targetFunction.constructor.name === 'AsyncFunction'
  73. },
  74. reloadCurrentTab(...args) {
  75. if (args && args[0] === true) {
  76. location.reload()
  77. } else if (vals.auto_reload()) location.reload()
  78. },
  79. executeFunctionsSequentially(functionsArray) {
  80. if (functionsArray.length > 0) {
  81. const currentFunction = functionsArray.shift()
  82. if (utils.isAsyncFunction(currentFunction)) {
  83. currentFunction().then(result => {
  84. if (result) {
  85. const { message, callback } = result
  86. if (message) utils.logger.info(message)
  87. if (callback && Array.isArray(callback)) utils.executeFunctionsSequentially(callback)
  88. }
  89. utils.executeFunctionsSequentially(functionsArray)
  90. }).catch(error => {
  91. utils.logger.error(error)
  92. utils.reloadCurrentTab()
  93. })
  94. } else {
  95. const result = currentFunction()
  96. if (result) {
  97. const { message } = result
  98. if (message) utils.logger.info(message)
  99. }
  100. }
  101. }
  102. },
  103. checkElementExistence(elementsArray) {
  104. if (Array.isArray(elementsArray)) {
  105. return elementsArray.map(element => Boolean(element))
  106. } else {
  107. return [Boolean(elementsArray)]
  108. }
  109. },
  110. async getElementAndCheckExistence(selectors, ...args) {
  111. let delay = 7000, debug = false
  112. if (args.length === 1) {
  113. const type = typeof args[0]
  114. if (type === 'number') delay = args[0]
  115. if (type === 'boolean') debug = args[0]
  116. }
  117. if (args.length === 2) {
  118. delay = args[0]
  119. debug = args[1]
  120. }
  121. const result = await elmGetter.get(selectors, delay)
  122. if (debug) utils.logger.debug(utils.checkElementExistence(result))
  123. return result
  124. },
  125. }
  126. const modules = {
  127. async insertPreviewElementToDocument() {
  128. const existingPreviewWrapper = await utils.getElementAndCheckExistence(selector.previewWrapper);
  129. if (existingPreviewWrapper) existingPreviewWrapper.remove();
  130.  
  131. const previewElementHtml = `
  132. <div id="previewWrapper" popover>
  133. <div id="perViewFloat">
  134. <div id="previewClose"></div>
  135. <div id="previewEnterPost"><a href=""></a></div>
  136. </div>
  137. <div id="previewIframeMask">
  138. <div class="loading-dots__wrapper" type="dots" loading="true">
  139. <span class="loading-dots__dot" style="font-size: 6px;"></span>
  140. <span class="loading-dots__dot" style="font-size: 6px;"></span>
  141. <span class="loading-dots__dot" style="font-size: 6px;"></span>
  142. </div>
  143. </div>
  144. <iframe id="previewIframe" title="previewIframe"></iframe>
  145. </div>
  146. `;
  147. const previewWrapper = utils.createElementAndInsert(previewElementHtml, document.body, 'append');
  148. const previewIframe = previewWrapper.querySelector(selector.previewIframe);
  149. const previewIframeMask = previewWrapper.querySelector(selector.previewIframeMask);
  150. const previewIframeWindow = previewIframe.contentWindow;
  151.  
  152. previewWrapper.addEventListener('toggle', (event) => {
  153. if (event.newState === 'closed') {
  154. previewIframe.src = '';
  155. previewIframeMask.style.display = 'flex';
  156. document.querySelector(selector.tap).style.pointerEvents = 'auto';
  157. }
  158. });
  159.  
  160. previewIframe.addEventListener('load', () => {
  161. const previewContentHeader = previewIframeWindow.document.querySelector(selector.previewContentHeader);
  162. const previewContentMain = previewIframeWindow.document.querySelector(selector.previewContentMain);
  163. if (previewContentHeader && previewContentMain) {
  164. previewContentHeader.style.display = 'none';
  165. previewContentMain.style.marginLeft = '0';
  166. previewIframeMask.style.display = 'none';
  167. }
  168. });
  169. },
  170. async insertPreviewButtonToMomentCard() {
  171. const previewButtonHtml = `
  172. <div class="moment-card__footer-button">
  173. <span class="previewButton icon-button flex-center--y" data-booth-item="" data-track-prevent="click" data-booth-level="1">
  174. <svg xmlns="http://www.w3.org/2000/svg" width="26" height="26" class="icon" viewBox="0 0 1024 1024">
  175. <path fill="#d6d6d6" d="M935.253 494.933C849.067 294.827 686.933 170.667 512 170.667S174.933 294.827 88.747 494.933a42.667 42.667 0 0 0 0 34.134C174.933 729.173 337.067 853.333 512 853.333s337.067-124.16 423.253-324.266a42.667 42.667 0 0 0 0-34.134zM512 768c-135.253 0-263.253-97.707-337.067-256C248.747 353.707 376.747 256 512 256s263.253 97.707 337.067 256C775.253 670.293 647.253 768 512 768zm0-426.667A170.667 170.667 0 1 0 682.667 512 170.667 170.667 0 0 0 512 341.333zm0 256A85.333 85.333 0 1 1 597.333 512 85.333 85.333 0 0 1 512 597.333z"/>
  176. </svg>
  177. </span>
  178. </div>
  179. `;
  180.  
  181. const [previewWrapper, previewIframe, voteButton] = await utils.getElementAndCheckExistence([selector.previewWrapper, selector.previewIframe, selector.voteButton]);
  182. const versionData = voteButton.dataset;
  183. await elmGetter.each(selector.momentCardFooter, async (momentCardFooter) => {
  184. const existingPreviewButton = momentCardFooter.querySelector(selector.previewButton);
  185. if (existingPreviewButton) existingPreviewButton.remove();
  186. const previewButton = utils.createElementAndInsert(previewButtonHtml, momentCardFooter, 'append');
  187. for (let version in versionData) {
  188. if (versionData.hasOwnProperty(version)) {
  189. previewButton.setAttribute(`data-${version}`, versionData[version]);
  190. }
  191. }
  192. const tapElement = document.querySelector(selector.tap);
  193. const momentId = momentCardFooter.parentElement.dataset.eventObjKey.split(":")[1]
  194. const momentLink = `https://www.taptap.cn/moment/${momentId}`;
  195. previewButton.addEventListener('click', (event) => {
  196. event.preventDefault();
  197. event.stopPropagation();
  198. previewWrapper.showPopover();
  199. previewIframe.src = momentLink;
  200. tapElement.style.pointerEvents = 'none';
  201. });
  202. });
  203. },
  204. thePrepFunction() {
  205. utils.insertStyleToDocument('PreviewWrapperStyle', styles.PreviewWrapperStyle)
  206. },
  207. theMainFunction() {
  208. modules.thePrepFunction()
  209. utils.logger.info(`脚本版本|${GM_info.script.version}`)
  210. utils.logger.info('当前标签|已激活|开始应用配置')
  211. const functionsArray = [
  212. modules.insertPreviewElementToDocument,
  213. modules.insertPreviewButtonToMomentCard
  214. ]
  215. utils.executeFunctionsSequentially(functionsArray)
  216. }
  217. }
  218. modules.theMainFunction()
  219. })();

QingJ © 2025

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