Last active
November 21, 2023 14:50
-
-
Save okayurisotto/3832143506de81cd517f27c3df0fd108 to your computer and use it in GitHub Desktop.
ノートの投稿時刻をミリ秒単位で表示
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ==UserScript== | |
// @name msec_override for misskey.io | |
// @namespace https://misskey.okayurisotto.net/@okayurisotto | |
// @version 0.5 | |
// @description ノートの投稿時刻をミリ秒単位で表示 | |
// @author @[email protected] | |
// @match https://misskey.io/* | |
// @grant none | |
// @license MIT | |
// ==/UserScript== | |
// Original Author @[email protected] | |
// Original Link https://greasyfork.org/ja/scripts/459345 | |
(() => { | |
const SCRIPT_LABEL = "msec_override for misskey.io (v0.5)\n"; | |
/** | |
* @param {string} aid | |
* @returns {Date} | |
*/ | |
const getTimestampFromAid = (aid) => { | |
const timestampOffset = 1000 * 60 * 60 * 24 * 10957; // 10957 days | |
const timestampPart = aid.substring(0, 8); | |
const unixtime = parseInt(timestampPart, 36) + timestampOffset; | |
const timestamp = new Date(unixtime); | |
return timestamp; | |
}; | |
/** | |
* aidxの場合、途中で途切れてしまう。 | |
* ただaidxとaidにはある程度の互換性があり、タイムスタンプ部分の取得に関しては途切れても問題にならない。 | |
* (なのでこの関数は、aidの取得とaidxの部分的な取得が可能なものになっている。) | |
* | |
* @param {string} noteUrl | |
* @returns {string} | |
*/ | |
const getAidFromNoteUrl = (noteUrl) => { | |
/** `/notes/:aid` */ | |
const path = new URL(noteUrl).pathname; | |
const aidLength = 10; | |
const aid = path.substring(7, 7 + aidLength); | |
return aid; | |
}; | |
/** | |
* @param {Date} date | |
* @param {boolean} withMilliseconds | |
* @returns {string} | |
*/ | |
const formatDateToString = (date, withMilliseconds = true) => { | |
return [ | |
[ | |
date.getFullYear().toString(), | |
(date.getMonth() + 1).toString().padStart(2, "0"), | |
date.getDate().toString().padStart(2, "0"), | |
].join("/"), | |
[ | |
date.getHours().toString().padStart(2, "0"), | |
date.getMinutes().toString().padStart(2, "0"), | |
date.getSeconds().toString().padStart(2, "0"), | |
...(withMilliseconds | |
? [date.getMilliseconds().toString().padStart(3, "0")] | |
: []), | |
].join(":"), | |
].join(" "); | |
}; | |
/** | |
* @param {HTMLElement} element | |
* @param {string} limitSelector | |
* @returns {HTMLElement[]} | |
*/ | |
const getAncestors = (element, limitSelector) => { | |
if (element.matches(limitSelector)) return [element]; | |
const parent = element.parentElement; | |
if (parent === null) return [element]; | |
return [...getAncestors(parent, limitSelector), element]; | |
}; | |
/** ノート追加監視 */ | |
const observer = new MutationObserver((records) => { | |
const nodes = records.map(({ addedNodes }) => [...addedNodes]).flat(); | |
for (const node of nodes) { | |
if (!(node instanceof HTMLElement)) continue; | |
for (const timeEl of node.querySelectorAll("a>time")) { | |
const parent = timeEl.parentElement; | |
if (!(parent instanceof HTMLAnchorElement)) continue; | |
const aid = getAidFromNoteUrl(parent.href); | |
const timestamp = getTimestampFromAid(aid); | |
const timeStr = formatDateToString(timestamp); | |
timeEl.textContent = timeStr; | |
} | |
// Renoteにも対応(ただしミリ秒は取得できないため表示に含まれない) | |
for (const timeEl of node.querySelectorAll("button>time[title]")) { | |
if (!(timeEl instanceof HTMLElement)) continue; | |
const title = timeEl.getAttribute("title"); | |
if (title === null) continue; | |
/** このtimestampにはミリ秒が含まれていない */ | |
const timestamp = new Date(title); | |
const timeStr = formatDateToString(timestamp, false); | |
timeEl.textContent = timeStr; | |
} | |
} | |
}); | |
// UIが検出できなかった場合、検出できるまで繰り返す(検出できた場合は`clearInterval`する) | |
const timer = setInterval(() => { | |
/** クラシックUIでのobserve対象 */ | |
const targetClassic = document.querySelector("main"); | |
/** デッキUIでのobserve対象 */ | |
const targetDeck = document.querySelectorAll( | |
"#misskey_app>div>div>div>section" | |
); | |
/** デフォルトUIを検知する用 */ | |
const targetDefault = document.querySelector("article"); | |
if (targetClassic !== null) { | |
console.log(SCRIPT_LABEL + "Detected UI Mode: Classic"); | |
clearInterval(timer); | |
observer.observe(targetClassic, { | |
childList: true, | |
subtree: true, | |
characterData: true, | |
}); | |
} else if (targetDeck.length > 0) { | |
console.log(SCRIPT_LABEL + "Detected UI Mode: Deck"); | |
clearInterval(timer); | |
for (const target of targetDeck) { | |
observer.observe(target, { | |
childList: true, | |
subtree: true, | |
characterData: true, | |
}); | |
} | |
} else if (targetDefault !== null) { | |
console.log(SCRIPT_LABEL + "Detected UI Mode: Default"); | |
clearInterval(timer); | |
const ancestors = getAncestors(targetDefault, "#misskey_app"); | |
const root = ancestors[3]; | |
for (const timeEl of root.querySelectorAll("a>time")) { | |
const parent = timeEl.parentElement; | |
if (!(parent instanceof HTMLAnchorElement)) continue; | |
const aid = getAidFromNoteUrl(parent.href); | |
const time = getTimestampFromAid(aid); | |
const timeStr = formatDateToString(time); | |
timeEl.textContent = timeStr; | |
} | |
observer.observe(root, { | |
childList: true, | |
subtree: true, | |
characterData: true, | |
}); | |
} | |
}, 100); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment