Skip to content

Instantly share code, notes, and snippets.

@okayurisotto
Last active November 21, 2023 14:50
Show Gist options
  • Save okayurisotto/3832143506de81cd517f27c3df0fd108 to your computer and use it in GitHub Desktop.
Save okayurisotto/3832143506de81cd517f27c3df0fd108 to your computer and use it in GitHub Desktop.
ノートの投稿時刻をミリ秒単位で表示
// ==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