GreasyFork 通知助手

当你的脚本或你参与的讨论有新回复时,脚本会在网页上以模态窗口显示最新的讨论内容。

当前为 2024-09-03 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name GreasyFork Discussion Watcher
  3. // @description On GreasyFork, when there are new replies to your scripts or discussions you're involved in, the latest discussion content will be displayed on the webpage.
  4. // @name:zh-CN GreasyFork 通知助手
  5. // @description:zh-CN 当你的脚本或你参与的讨论有新回复时,脚本会在网页上以模态窗口显示最新的讨论内容。
  6. // @name:zh-TW GreasyFork 通知助手
  7. // @description:zh-TW 當你的腳本或你參與的討論有新回覆時,腳本會在網頁上以模態窗口顯示最新的討論內容。
  8. // @name:pt-BR GreasyFork Notificador
  9. // @description:pt-BR Quando seu script ou discussão em que você está participando tiver uma nova resposta, o script exibirá o conteúdo mais recente da discussão em uma janela modal na página.
  10. // @name:de GreasyFork Benachrichtigungsassistent
  11. // @description:de Wenn Ihr Skript oder eine Diskussion, an der Sie teilnehmen, eine neue Antwort erhält, wird das Skript den neuesten Diskussionsinhalt in einem Modalfenster auf der Seite anzeigen.
  12. // @name:fr GreasyFork Assistant de Notification
  13. // @description:fr Lorsque votre script ou une discussion à laquelle vous participez reçoit une nouvelle réponse, le script affichera le contenu le plus récent de la discussion dans une fenêtre modale sur la page.
  14. // @name:en GreasyFork Notification Assistant
  15. // @description:en When your script or a discussion you are participating in receives a new reply, the script will display the latest discussion content in a modal window on the page.
  16. // @name:es GreasyFork Asistente de Notificaciones
  17. // @description:es Cuando tu script o una discusión en la que participas recibe una nueva respuesta, el script mostrará el contenido más reciente de la discusión en una ventana modal en la página.
  18. // @name:ru GreasyFork Уведомления
  19. // @description:ru Когда ваш скрипт или обсуждение, в котором вы участвуете, получает новый ответ, скрипт отображает последнее содержимое обсуждения в модальном окне на странице.
  20. // @name:ko GreasyFork 알림 도우미
  21. // @description:ko 당신의 스크립트 또는 당신이 참여하고 있는 논의에 새 답글이 달리면, 스크립트가 최신 논의 내용을 모달 창으로 페이지에 표시합니다.
  22. // @name:ja GreasyFork 通知アシスタント
  23. // @description:ja あなたのスクリプトや参加しているディスカッションに新しい返信があると、スクリプトが最新のディスカッション内容をモーダルウィンドウでページに表示します。
  24. // @name:vi GreasyFork Trợ Lý Thông Báo
  25. // @description:vi Khi kịch bản của bạn hoặc một cuộc thảo luận mà bạn tham gia có phản hồi mới, kịch bản sẽ hiển thị nội dung thảo luận mới nhất trong một cửa sổ modal trên trang.
  26. // @name:ms GreasyFork Pembantu Pemberitahuan
  27. // @description:ms Apabila skrip anda atau perbincangan yang anda sertai menerima balasan baru, skrip akan memaparkan kandungan perbincangan terkini dalam tetingkap modal di halaman.
  28. // @name:cy GreasyFork Cymorth Rhybudd
  29. // @description:cy Pan fo'ch sgript neu drafodaeth rydych chi'n cymryd rhan ynddi'n derbyn ateb newydd, bydd y sgript yn dangos cynnwys y drafodaeth ddiweddaraf mewn ffenestr modal ar y dudalen.
  30. // @namespace https://github.com/ChinaGodMan/UserScripts
  31. // @version 1.1.0.1
  32. // @icon https://gf.qytechs.cn/vite/assets/blacklogo96-CxYTSM_T.png
  33. // @author 人民的勤务员 <toniaiwanowskiskr47@gmail.com>
  34. // @match https://gf.qytechs.cn/*
  35. // @grant GM_setValue
  36. // @grant GM_getValue
  37. // @grant GM_registerMenuCommand
  38. // @supportURL https://github.com/ChinaGodMan/UserScripts/issues
  39. // @homepageURL https://github.com/ChinaGodMan/UserScripts
  40. // ==/UserScript==
  41. (function () {
  42. 'use strict'
  43. const config = {
  44. isInstalled: GM_getValue('Installed', false),//第一次不加载~,
  45. lastUpdated: GM_getValue('lastUpdated', 0),//上次更新时间
  46. delay: GM_getValue('delay', "30m") // 格式如下: 1h1m1s, 1h1s, 1m, 1s, 1m1s
  47. }
  48. GM_registerMenuCommand("Set refresh time", function () {
  49. const currentDelay = config.delay
  50. const newDelay = prompt("New refresh time (example: 1h30m1s, 1s0m30s,1h1s, 1m, 1s):", currentDelay)
  51. if (newDelay !== null) {
  52. if (/^\d+(h|m|s)?(\d+(h|m|s)?)*$/.test(newDelay)) {
  53. GM_setValue('delay', newDelay)
  54. config.delay = newDelay
  55. } else {
  56. alert("The input format is incorrect, please re-enter!")
  57. }
  58. }
  59. })
  60. function timeToSeconds(timeStr) {
  61. let hours = 0, minutes = 0, seconds = 0
  62. const hoursMatch = timeStr.match(/(\d+)h/)
  63. const minutesMatch = timeStr.match(/(\d+)m/)
  64. const secondsMatch = timeStr.match(/(\d+)s/)
  65. if (hoursMatch) {
  66. hours = parseInt(hoursMatch[1], 10)
  67. }
  68. if (minutesMatch) {
  69. minutes = parseInt(minutesMatch[1], 10)
  70. }
  71. if (secondsMatch) {
  72. seconds = parseInt(secondsMatch[1], 10)
  73. }
  74. let totalSeconds = (hours * 3600) + (minutes * 60) + seconds
  75. return totalSeconds
  76. }
  77. function isUpdate() {
  78. const now = Math.floor(new Date().getTime() / 1000)
  79. const lastUpdated = config.lastUpdated
  80. const secondsDifference = now - lastUpdated
  81. if (secondsDifference > timeToSeconds(config.delay)) {
  82. GM_setValue('lastUpdated', now)
  83. console.log(`时间超过${config.delay} 进行更新`)
  84. return true
  85. }
  86. return false
  87. }
  88. function fetchAndDisplayDiscussions(urls) {
  89. // GM_setValue('discussions', [])
  90. if (!isUpdate()) {
  91. return
  92. }
  93. let discussions = GM_getValue('discussions', [])
  94. let fetchPromises = []
  95. let itemCount = 0
  96. var modalHTML = `
  97. <div id="discussion-modal" style="position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; visibility: hidden;align-items: center; justify-content: center; z-index: 1000;">
  98. <div id="modal-content" style="background: #fff; border-radius: 10px; box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); max-height: 80vh; overflow-y: auto; width: 80%; max-width: 600px; padding: 20px; font-family: Arial, sans-serif;">
  99. <ul id="discussion-list" style="list-style-type: none; padding: 0; margin: 0;"></ul>
  100. <button id="close-button" style="background-color: #ff5e5e; border: none; color: #fff; padding: 5px 10px; cursor: pointer; border-radius: 5px; font-family: Arial, sans-serif; font-size: 14px;">Close</button>
  101. </div>
  102. </div>
  103. `
  104. document.body.insertAdjacentHTML('beforeend', modalHTML)
  105. var discussionList = document.getElementById('discussion-list')
  106. var modalContent = document.getElementById('modal-content')
  107. urls.forEach(url => {
  108. fetchPromises.push(
  109. fetch(url)
  110. .then(response => response.text())
  111. .then(data => {
  112. var parser = new DOMParser()
  113. var doc = parser.parseFromString(data, 'text/html')
  114. var elements = doc.querySelectorAll('.discussion-list > div > div')
  115. elements.forEach(function (element) {
  116. var discussionTitle = element.querySelector('.discussion-title')
  117. var relativeTimes = element.querySelectorAll('relative-time')
  118. if (!discussionTitle || relativeTimes.length === 0) return
  119. var discussionTitleHref = discussionTitle.getAttribute('href')
  120. var latestRelativeTime = relativeTimes[relativeTimes.length - 1].getAttribute('datetime')
  121. var existingDiscussion = discussions.find(function (disc) {
  122. return disc.discussionTitleHref === discussionTitleHref
  123. })
  124. if (existingDiscussion && existingDiscussion.relativeTime === latestRelativeTime) return
  125. var discussionInfo = {
  126. discussionTitleHref: discussionTitleHref,
  127. relativeTime: latestRelativeTime,
  128. userName: element.querySelector('a.user-link') ? element.querySelector('a.user-link').textContent.trim() : null
  129. }
  130. if (existingDiscussion) {
  131. existingDiscussion.relativeTime = latestRelativeTime
  132. existingDiscussion.userName = discussionInfo.userName
  133. } else {
  134. discussions.push(discussionInfo)
  135. }
  136. var listItemHTML = '<li class="discussion-item">' + element.innerHTML + '<hr style="margin: 10px 0; border: none; border-top: 1px solid #ddd;"></li>'
  137. discussionList.innerHTML += listItemHTML
  138. itemCount++
  139. })
  140. })
  141. )
  142. })
  143. Promise.all(fetchPromises).then(() => {
  144. if (itemCount === 0) {
  145. return
  146. }
  147. // 将讨论信息保存到 GM_setValue
  148. GM_setValue('discussions', discussions)
  149. if (!config.isInstalled) {
  150. console.log('首次安装时,不弹出:')
  151. GM_setValue('Installed', true)
  152. return
  153. }
  154. // 计算关闭按钮的位置,并动态设置
  155. var closeButton = document.getElementById('close-button')
  156. closeButton.style.position = 'absolute'
  157. closeButton.style.top = `${discussionList.offsetTop - 50}px`
  158. closeButton.style.left = `${discussionList.right}px`
  159. document.getElementById('discussion-modal').style.visibility = 'visible'
  160. // 设置所有链接在新窗口中打开
  161. var links = discussionList.querySelectorAll('a')
  162. links.forEach(function (link) {
  163. link.setAttribute('target', '_blank')
  164. })
  165. // 添加关闭按钮事件
  166. closeButton.addEventListener('click', function () {
  167. document.body.removeChild(document.getElementById('discussion-modal'))
  168. })
  169. }).catch(error => {
  170. console.error('无法获取讨论列表:', error)
  171. })
  172. }
  173. function getUserId() {
  174. const profileLinkElement = document.querySelector("#nav-user-info > span.user-profile-link > a")
  175. if (profileLinkElement) {
  176. const href = profileLinkElement.getAttribute('href')
  177. const match = href.match(/\/users\/(\d+)-/)
  178. if (match) {
  179. const userId = match[1]
  180. console.log(userId)
  181. return userId
  182. } else {
  183. console.log('放弃操作,无法找到id')
  184. return null
  185. }
  186. } else {
  187. return null
  188. }
  189. }
  190. const userId = getUserId()
  191. if (userId) {
  192. fetchAndDisplayDiscussions([
  193. `https://gf.qytechs.cn/discussions?user=${userId}`,
  194. 'https://gf.qytechs.cn/discussions?me=script'
  195. ])
  196. } else {
  197. console.log("没有登录(不可用),放弃操作")
  198. }
  199. })()

QingJ © 2025

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