TwitterのHashflagsをMisskeyに表示するやつ
- // ==UserScript==
- // @name Misskey Hashflags
- // @namespace https://midra.me/
- // @version 1.0.5
- // @description TwitterのHashflagsをMisskeyに表示するやつ
- // @author Midra
- // @license MIT
- // @match https://*/*
- // @icon https://raw.githubusercontent.com/misskey-dev/assets/main/icon.png
- // @run-at document-body
- // @grant GM_getValue
- // @grant GM_setValue
- // ==/UserScript==
- // @ts-check
- /**
- * @typedef TwitterHashflag
- * @property {string} hashtag
- * @property {string} asset_url
- * @property {number} starting_timestamp_ms
- * @property {number} ending_timestamp_ms
- * @property {boolean} is_hashfetti_enabled
- */
- ;(async () => {
- 'use strict'
- const HASHFLAGS_UPDATE_INTERVAL = 2 * 60 * 60 * 1000
- const isTwitter =
- location.href.startsWith('https://twitter.com/') ||
- location.href.startsWith('https://x.com/')
- const isMisskey =
- document
- .querySelector('meta[name="application-name"]')
- ?.getAttribute('content') === 'Misskey'
- if (!isTwitter && !isMisskey) return
- /** @type {TwitterHashflag[]} */
- const hashflags = GM_getValue('hashflags', [])
- /** @type {TwitterHashflag[]} */
- const activeHashflags = hashflags
- .filter((v) => Date.now() < v.ending_timestamp_ms)
- .map((v) => ((v.hashtag = v.hashtag.toLowerCase()), v))
- const activeHashtags = activeHashflags.map((v) => v.hashtag)
- /**
- * @param {string} [hashtag]
- * @returns {TwitterHashflag | undefined}
- */
- const getHashflag = (hashtag) => {
- if (!hashtag) return
- const hashflag =
- activeHashflags[activeHashtags.indexOf(hashtag.toLowerCase())]
- if (
- hashflag &&
- hashflag.starting_timestamp_ms <= Date.now() &&
- Date.now() < hashflag.ending_timestamp_ms
- ) {
- return hashflag
- }
- }
- /**
- * @param {Element} target
- */
- const addHashflags = (target) => {
- if (activeHashflags.length === 0) return
- /** @type {NodeListOf<HTMLAnchorElement>} */
- const hashtags = target.querySelectorAll('a[href^="/tags/"]')
- for (const tag of hashtags) {
- if (tag.classList.contains('hasTwitterHashflag')) continue
- const text = tag.textContent
- if (!text?.startsWith('#')) continue
- const hashflag = getHashflag(text.substring(1))
- if (hashflag) {
- const img = document.createElement('img')
- img.classList.add('twitter_hashflag')
- img.src = hashflag.asset_url
- tag.appendChild(img)
- tag.classList.add('hasTwitterHashflag')
- }
- }
- }
- // /**
- // * @param {Element} target
- // */
- // const removeHashflags = (target) => {
- // for (const elm of target.getElementsByClassName('twitter_hashflag')) {
- // elm.remove()
- // }
- // for (const elm of target.getElementsByClassName('hasTwitterHashflag')) {
- // elm.classList.remove('hasTwitterHashflag')
- // }
- // }
- // Twitter (Hashflagsの取得・保存)
- if (isTwitter) {
- console.log('[Misskey Hashflags] Twitter')
- const lastUpdated = GM_getValue('hashflags_lastupdated', 0)
- if (HASHFLAGS_UPDATE_INTERVAL < Date.now() - lastUpdated) {
- try {
- const res = await fetch('https://twitter.com/i/api/1.1/hashflags.json')
- /** @type {TwitterHashflag[]} */
- const json = await res.json()
- if (json && 0 < json.length) {
- GM_setValue('hashflags', json)
- GM_setValue('hashflags_lastupdated', Date.now())
- console.log('[Misskey Hashflags] Hashflagsを保存しました')
- }
- } catch (e) {
- console.error('[Misskey Hashflags]', e)
- }
- }
- }
- // Misskey
- else if (isMisskey) {
- console.log('[Misskey Hashflags] Misskey')
- addHashflags(document.body)
- /** @type {MutationObserverInit} */
- const obs_options = {
- childList: true,
- subtree: true,
- }
- const obs = new MutationObserver((mutations) => {
- obs.disconnect()
- for (const mutation of mutations) {
- if (!(mutation.target instanceof HTMLElement)) continue
- if (0 < mutation.addedNodes.length) {
- addHashflags(mutation.target)
- }
- }
- obs.observe(document.body, obs_options)
- })
- obs.observe(document.body, obs_options)
- // style
- const style = document.createElement('style')
- style.textContent = `
- .twitter_hashflag {
- display: inline-block;
- height: 1.1em;
- margin: 0 2px -0.15em;
- }
- `
- document.body.appendChild(style)
- }
- })()