Misskey Hashflags

TwitterのHashflagsをMisskeyに表示するやつ

目前为 2023-09-28 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Misskey Hashflags
  3. // @namespace https://midra.me/
  4. // @version 1.0.5
  5. // @description TwitterのHashflagsをMisskeyに表示するやつ
  6. // @author Midra
  7. // @license MIT
  8. // @match https://*/*
  9. // @icon https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png
  10. // @run-at document-body
  11. // @grant GM_getValue
  12. // @grant GM_setValue
  13. // ==/UserScript==
  14.  
  15. // @ts-check
  16.  
  17. /**
  18. * @typedef TwitterHashflag
  19. * @property {string} hashtag
  20. * @property {string} asset_url
  21. * @property {number} starting_timestamp_ms
  22. * @property {number} ending_timestamp_ms
  23. * @property {boolean} is_hashfetti_enabled
  24. */
  25.  
  26. ;(async () => {
  27. 'use strict'
  28.  
  29. const HASHFLAGS_UPDATE_INTERVAL = 2 * 60 * 60 * 1000
  30.  
  31. const isTwitter =
  32. location.href.startsWith('https://twitter.com/') ||
  33. location.href.startsWith('https://x.com/')
  34. const isMisskey =
  35. document
  36. .querySelector('meta[name="application-name"]')
  37. ?.getAttribute('content') === 'Misskey'
  38.  
  39. if (!isTwitter && !isMisskey) return
  40.  
  41. /** @type {TwitterHashflag[]} */
  42. const hashflags = GM_getValue('hashflags', [])
  43. /** @type {TwitterHashflag[]} */
  44. const activeHashflags = hashflags
  45. .filter((v) => Date.now() < v.ending_timestamp_ms)
  46. .map((v) => ((v.hashtag = v.hashtag.toLowerCase()), v))
  47. const activeHashtags = activeHashflags.map((v) => v.hashtag)
  48.  
  49. /**
  50. * @param {string} [hashtag]
  51. * @returns {TwitterHashflag | undefined}
  52. */
  53. const getHashflag = (hashtag) => {
  54. if (!hashtag) return
  55.  
  56. const hashflag =
  57. activeHashflags[activeHashtags.indexOf(hashtag.toLowerCase())]
  58.  
  59. if (
  60. hashflag &&
  61. hashflag.starting_timestamp_ms <= Date.now() &&
  62. Date.now() < hashflag.ending_timestamp_ms
  63. ) {
  64. return hashflag
  65. }
  66. }
  67.  
  68. /**
  69. * @param {Element} target
  70. */
  71. const addHashflags = (target) => {
  72. if (activeHashflags.length === 0) return
  73.  
  74. /** @type {NodeListOf<HTMLAnchorElement>} */
  75. const hashtags = target.querySelectorAll('a[href^="/tags/"]')
  76. for (const tag of hashtags) {
  77. if (tag.classList.contains('hasTwitterHashflag')) continue
  78.  
  79. const text = tag.textContent
  80. if (!text?.startsWith('#')) continue
  81.  
  82. const hashflag = getHashflag(text.substring(1))
  83. if (hashflag) {
  84. const img = document.createElement('img')
  85. img.classList.add('twitter_hashflag')
  86. img.src = hashflag.asset_url
  87. tag.appendChild(img)
  88. tag.classList.add('hasTwitterHashflag')
  89. }
  90. }
  91. }
  92.  
  93. // /**
  94. // * @param {Element} target
  95. // */
  96. // const removeHashflags = (target) => {
  97. // for (const elm of target.getElementsByClassName('twitter_hashflag')) {
  98. // elm.remove()
  99. // }
  100. // for (const elm of target.getElementsByClassName('hasTwitterHashflag')) {
  101. // elm.classList.remove('hasTwitterHashflag')
  102. // }
  103. // }
  104.  
  105. // Twitter (Hashflagsの取得・保存)
  106. if (isTwitter) {
  107. console.log('[Misskey Hashflags] Twitter')
  108.  
  109. const lastUpdated = GM_getValue('hashflags_lastupdated', 0)
  110. if (HASHFLAGS_UPDATE_INTERVAL < Date.now() - lastUpdated) {
  111. try {
  112. const res = await fetch('https://twitter.com/i/api/1.1/hashflags.json')
  113. /** @type {TwitterHashflag[]} */
  114. const json = await res.json()
  115.  
  116. if (json && 0 < json.length) {
  117. GM_setValue('hashflags', json)
  118. GM_setValue('hashflags_lastupdated', Date.now())
  119.  
  120. console.log('[Misskey Hashflags] Hashflagsを保存しました')
  121. }
  122. } catch (e) {
  123. console.error('[Misskey Hashflags]', e)
  124. }
  125. }
  126. }
  127. // Misskey
  128. else if (isMisskey) {
  129. console.log('[Misskey Hashflags] Misskey')
  130.  
  131. addHashflags(document.body)
  132.  
  133. /** @type {MutationObserverInit} */
  134. const obs_options = {
  135. childList: true,
  136. subtree: true,
  137. }
  138. const obs = new MutationObserver((mutations) => {
  139. obs.disconnect()
  140.  
  141. for (const mutation of mutations) {
  142. if (!(mutation.target instanceof HTMLElement)) continue
  143.  
  144. if (0 < mutation.addedNodes.length) {
  145. addHashflags(mutation.target)
  146. }
  147. }
  148.  
  149. obs.observe(document.body, obs_options)
  150. })
  151.  
  152. obs.observe(document.body, obs_options)
  153.  
  154. // style
  155. const style = document.createElement('style')
  156. style.textContent = `
  157. .twitter_hashflag {
  158. display: inline-block;
  159. height: 1.1em;
  160. margin: 0 2px -0.15em;
  161. }
  162. `
  163. document.body.appendChild(style)
  164. }
  165. })()

QingJ © 2025

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