Greasy Fork is available in English.

Show ctime of retweets

Also shows Twitter Blue badges and tweet source labels.

// ==UserScript==
// @name                 Show ctime of retweets
// @name:ja              リツイート自体の日時を表示
// @name:ko              리트윗 날짜와 시간을 표시합니다.
// @name:zh-CN           显示转发的日期和时间。
// @name:zh-TW           顯示轉發的日期和時間。
// @namespace            https://greasyfork.org/en/scripts/462070-show-ctime-of-retweets
// @version              1.11.0
// @description          Also shows Twitter Blue badges and tweet source labels.
// @description:ja       Twitter Blue バッジ、ツイートソースラベルも表示できます。
// @description:ko       Twitter Blue 배지, 트윗 소스 라벨도 표시할 수 있습니다.
// @description:zh-CN    您还可以显示推特蓝徽章、推文来源标签。
// @description:zh-TW    您還可以顯示推特藍徽章、推文來源標籤。
// @author               AeamaN
// @contributionURL      bitcoin:1DC6uWJWzzwU3iRJDXhUquv6QAYaRvtfFJ
// @match                https://twitter.com/*
// @match                https://mobile.twitter.com/*
// @match                https://twitter3e4tixl4xyajtrzo62zg5vztmjuricljdp2c5kshju4avyoid.onion/*
// @match                https://mobile.twitter3e4tixl4xyajtrzo62zg5vztmjuricljdp2c5kshju4avyoid.onion/*
// @match                https://x.com/*
// @match                https://mobile.x.com/*
// @require              https://update.greasyfork.org/scripts/467232/1197541/Legacy%20verified%2012.js
// @require              https://update.greasyfork.org/scripts/467238/1197542/Legacy%20verified%2015.js
// @require              https://update.greasyfork.org/scripts/467255/1197539/Legacy%20verified%2019.js
// @require              https://update.greasyfork.org/scripts/467256/1197544/Legacy%20verified%2024.js
// @require              https://update.greasyfork.org/scripts/467257/1197545/Legacy%20verified%2029.js
// @require              https://update.greasyfork.org/scripts/467258/1197546/Legacy%20verified%203.js
// @require              https://update.greasyfork.org/scripts/467259/1197547/Legacy%20verified%204.js
// @require              https://update.greasyfork.org/scripts/467260/1197548/Legacy%20verified%205.js
// @require              https://update.greasyfork.org/scripts/467262/1197549/Legacy%20verified%206.js
// @require              https://update.greasyfork.org/scripts/467263/1197550/Legacy%20verified%207.js
// @require              https://update.greasyfork.org/scripts/467264/1197553/Legacy%20verified%208.js
// @require              https://update.greasyfork.org/scripts/467265/1197554/Legacy%20verified%209.js
// @grant                GM.getValue
// @grant                GM.registerMenuCommand
// @grant                GM.setValue
// @run-at               document-body
// ==/UserScript==

// ES2017(ES8) or later.
// ES2017(ES8) 以降が必要です。


(async function () { /* START */


'use strict';


// //////////// Settings //////////// //
// No GUI Settings
// Default values are used
const NOGUI = false;
// ////////////////////////////////// //

// ///////// Default valuse ///////// //
// Substitute TB badge
// when TB without legacy verification
const TB = false;

// Show source labels
// 1. Tweet only
// 2. Tweet and (Retweet)
// 0. Not shown
const SSL = 2;

// Date formats
//  1. 31.12.70 23:59
//  2. 31.12.70 23:59:59
//  3. 31.12.70(Th) 23:59
//  4. 31.12.70(Th) 23:59:59
//
//  5. 70/12/31 23:59
//  6. 70/12/31 23:59:59
//  7. 70/12/31(Th) 23:59
//  8. 70/12/31(Th) 23:59:59 [ye/mo/da(we) ho:mi:se]
//
//  9. 70-12/31 23:59
// 10. 70-12/31 23:59'59
// 11. 70-12/31(Th) 23:59
// 12. 70-12/31(Th) 23:59'59
//
// 13. M59-12-31 23:59
// 14. M59-12-31 23:59:59
// 15. M59-12-31(Th) 23:59
// 16. M59-12-31(Th) 23:59:59
//
//  0. Not Shown
const FMT = 7;

// Loop interval(ms)
const INTL = 800;
// ////////////////////////////////// //


const MYNAME = 'sctrt1110';
const BTKN =
	'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs=' +
	'1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
const EBTKN = encodeURIComponent(BTKN);

let cntr_i = [0, 0];
let dtbs = {};
let time_r = {};
let s_mus = true;
let observer = new MutationObserver(function (mutations) {
	s_mus = mutations;
});
let tb, ssl, fmt, intl;
let originalXHROPEN;


async function load_dtbs(key) {
	let str = await GM.getValue(key);
	let array = [];
	if (str === undefined) { // 何も無い時
		await GM.setValue(key, JSON.stringify(array));
	} else { // ある時
		array = JSON.parse(str);
	}
	
	return array;
}


function getcv(name) {
	let cookie = document.cookie;
	if (!cookie) return [];
	let obj = {};
	
	cookie.split(';').forEach(function (rec) {
		let kav = rec.split('=');
		let key, val;
		[key, val] =
			rec.includes('=') ?
				 [unescape(kav[0].trim()), unescape(kav[1].trim())] :
					['', unescape(kav[0].trim())];
		if (!obj[key]) obj[key] = [val];
		else obj[key].push(val);
	});
	
	return obj[name] ?? [];
}


function pathexists(obj, path) {
	for (let key of path) {
		if (!(obj = obj[key])) return false;
	}
	return true;
}


function makeDialog() {
	let dalg = document.createElement('div');
	
	dalg.className = `us-${MYNAME}`;
	
	dalg.style.all = 'initial';
	dalg.style.backgroundColor = 'rgb(235, 235, 235)';
	dalg.style.border = '3px outset';
	dalg.style.borderRadius = '1%';
	dalg.style.display = 'none';
	dalg.style.fontFamily = 'monospace';
	dalg.style.fontSize = '12px';
	dalg.style.height = '360px';
	dalg.style.width = '420px';
	dalg.style.paddingLeft = '2px';
	dalg.style.paddingRight = '2px';
	dalg.style.position = 'fixed';
	dalg.style.right = '8px';
	dalg.style.top = '8px';
	dalg.style.zIndex = '2147483647';
	dalg.style.overflow = 'auto';
	
	let html =
		'<span style="all: initial; font-size: 120%; line-height: 210%">' +
		`${GM.info.script.name} ${GM.info.script.version} Settings` +
		'</span><br />\n' +
		
		'<input type="radio" name="fmt" value="1" class="top_r" />31.12.70 23:59' +
		'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' +
		'<input type="radio" name="fmt" value="2" class="top_r" />31.12.70 23:59:59<br />\n' +
		'<input type="radio" name="fmt" value="3" class="mid_r" />31.12.70(Th) 23:59' +
		'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' +
		'<input type="radio" name="fmt" value="4" class="mid_r" />31.12.70(Th) 23:59:59<br />\n' +
		'<input type="radio" name="fmt" value="5" class="mid_r" />70/12/31 23:59' +
		'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' +
		'<input type="radio" name="fmt" value="6" class="mid_r" />70/12/31 23:59:59<br />\n' +
		'<input type="radio" name="fmt" value="7" class="mid_r" />70/12/31(Th) 23:59' +
		'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' +
		'<input type="radio" name="fmt" value="8" class="mid_r" />70/12/31(Th) 23:59:59<br />\n' +
		'<input type="radio" name="fmt" value="9" class="mid_r" />70-12/31 23:59' +
		'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' +
		'<input type="radio" name="fmt" value="10" class="mid_r" />70-12/31 23:59\'59<br />\n' +
		'<input type="radio" name="fmt" value="11" class="mid_r" />70-12/31(Th) 23:59' +
		'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' +
		'<input type="radio" name="fmt" value="12" class="mid_r" />70-12/31(Th) 23:59\'59<br />\n' +
		'<input type="radio" name="fmt" value="13" class="mid_r" />M59-12-31 23:59' +
		'&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;' +
		'<input type="radio" name="fmt" value="14" class="mid_r" />M59-12-31 23:59:59<br />\n' +
		'<input type="radio" name="fmt" value="15" class="mid_r" />M59-12-31(Th) 23:59' +
		'&nbsp;&nbsp;&nbsp;&nbsp;' +
		'<input type="radio" name="fmt" value="16" class="mid_r" />M59-12-31(Th) 23:59:59<br />\n' +
		'<input type="radio" name="fmt" value="0" class="btm_r" />Not Shown<br />\n' +
		
		'<input type="checkbox" name="tb" class="top_c" />Substitute TB badge<br />\n' +
		
		'<input type="radio" name="ssl" value="1" class="top_r" />Tweet source labels only' +
		'&nbsp;&nbsp;&nbsp;&nbsp;' +
		'<input type="radio" name="ssl" value="2" class="top_r" />Tweet and (Retweet)<br />\n' +
		'<input type="radio" name="ssl" value="0" class="btm_r" />Not shown<br />\n' +
		
		'<span style="all: initial; font-size: 100%">' +
		'Loop interval(ms)&nbsp;' +
		'</span><input type="text" name="intl" size="10" class="top_t" /><br />\n' +
		
		'<input type="button" class="top_b" value="Cancel" />\n' +
		'<input type="button" class="top_b" value="Set default" />\n' +
		'<input type="button" class="top_b" value="Save & Close" />\n';
	
	dalg.innerHTML = html;
	
	for (let e of dalg.querySelectorAll('input.top_r')) {
		e.style.all = 'initial';
		e.style.appearance = 'auto';
		e.style.marginRight = '1px';
		e.style.marginTop = '1px';
	}
	for (let e of dalg.querySelectorAll('input.mid_r, input.btm_r')) {
		e.style.all = 'initial';
		e.style.appearance = 'auto';
		e.style.marginRight = '1px';
		e.style.marginTop = '1px';
	}
	for (let e of dalg.querySelectorAll('input.top_c')) {
		e.style.all = 'initial';
		e.style.appearance = 'auto';
		e.style.marginRight = '1px';
		e.style.marginTop = '1px';
	}
	for (let e of dalg.querySelectorAll('input.top_t')) {
		e.style.all = 'initial';
		e.style.backgroundColor = 'rgb(255, 255, 255)';
		e.style.fontFamily = 'monospace';
		e.style.fontSize = '100%';
		e.style.marginLeft = '1px';
		e.style.marginRight = '1px';
		e.style.marginTop = '8px';
		e.style.marginBottom = '0px';
		e.style.paddingLeft = '1px';
		e.style.paddingRight = '1px';
		e.style.paddingTop = '1px';
		e.style.paddingBottom = '1px';
	}
	for (let e of dalg.querySelectorAll('input.top_b')) {
		e.style.all = 'initial';
		e.style.backgroundColor = 'rgb(190, 190, 190)';
		e.style.borderRadius = '10%';
		e.style.cursor = 'default';
		e.style.fontSize = '110%';
		e.style.marginTop = '10px';
		e.style.marginBottom = '0px';
		e.style.paddingTop = '6px';
		e.style.paddingBottom = '6px';
		e.style.textAlign = 'center';
		e.style.width = '90px';
	}
	
	return dalg;
}


function makeFunc(dalg) {
	dalg.addEventListener(
		'click',
		function (event) { event.stopPropagation(); },
		false
	);
	
	dalg.querySelector('input[value="Cancel"]').addEventListener(
		'click',
		function () { dalg.style.display = 'none'; },
		false
	);
	dalg.querySelector('input[value="Cancel"]').addEventListener(
		'mouseenter',
		function (event) { event.target.style.backgroundColor = 'rgb(170, 170, 170)'; },
		false
	);
	dalg.querySelector('input[value="Cancel"]').addEventListener(
		'mouseleave',
		function (event) { event.target.style.backgroundColor = 'rgb(190, 190, 190)'; },
		false
	);
	
	dalg.querySelector('input[value="Set default"]').addEventListener(
		'click',
		function () {
			dalg.querySelector(`input[name="fmt"][value="${FMT}"]`).checked = true;
			dalg.querySelector('input[name="tb"]').checked = TB;
			dalg.querySelector(`input[name="ssl"][value="${SSL}"]`).checked = true;
			dalg.querySelector('input[name="intl"]').value = INTL;
		},
		false
	);
	dalg.querySelector('input[value="Set default"]').addEventListener(
		'mouseenter',
		function (event) { event.target.style.backgroundColor = 'rgb(170, 170, 170)'; },
		false
	);
	dalg.querySelector('input[value="Set default"]').addEventListener(
		'mouseleave',
		function (event) { event.target.style.backgroundColor = 'rgb(190, 190, 190)'; },
		false
	);
	
	dalg.querySelector('input[value="Save & Close"]').addEventListener(
		'click',
		async function () {
			for (let e of dalg.querySelectorAll('input[name="fmt"]')) {
				if (e.checked) {
					fmt = +e.value;
					break;
				}
			}
			tb = dalg.querySelector('input[name="tb"]').checked;
			for (let e of dalg.querySelectorAll('input[name="ssl"]')) {
				if (e.checked) {
					ssl = +e.value;
					break;
				}
			}
			intl = +dalg.querySelector('input[name="intl"]').value;
			
			await GM.setValue('fmt', fmt);
			await GM.setValue('tb', tb);
			await GM.setValue('ssl', ssl);
			await GM.setValue('intl', intl);
			
			dalg.style.display = 'none';
		},
		false
	);
	dalg.querySelector('input[value="Save & Close"]').addEventListener(
		'mouseenter',
		function (event) { event.target.style.backgroundColor = 'rgb(170, 170, 170)'; },
		false
	);
	dalg.querySelector('input[value="Save & Close"]').addEventListener(
		'mouseleave',
		function (event) { event.target.style.backgroundColor = 'rgb(190, 190, 190)'; },
		false
	);
}


async function initgui() {
	if ((await GM.getValue('fmt')) === undefined) {
		await GM.setValue('fmt', FMT);
	} else {
		fmt = await GM.getValue('fmt');
	}
	if ((await GM.getValue('tb')) === undefined) {
		await GM.setValue('tb', TB);
	} else {
		tb = await GM.getValue('tb');
	}
	if ((await GM.getValue('ssl')) === undefined) {
		await GM.setValue('ssl', SSL);
	} else {
		ssl = await GM.getValue('ssl');
	}
	if ((await GM.getValue('intl')) === undefined) {
		await GM.setValue('intl', INTL);
	} else {
		intl = await GM.getValue('intl');
	}
	
	let dalg = makeDialog();
	makeFunc(dalg);
	document.body.appendChild(dalg);
	
	GM.registerMenuCommand('Settings', function () {
		if (dalg.style.display == 'none') {
			dalg.querySelector(`input[name="fmt"][value="${fmt}"]`).checked = true;
			dalg.querySelector('input[name="tb"]').checked = tb;
			dalg.querySelector(`input[name="ssl"][value="${ssl}"]`).checked = true;
			dalg.querySelector('input[name="intl"]').value = intl;
			
			dalg.style.display = 'block';
		}
	});
}


function datef(date, f) {
	const LANG = document.documentElement.getAttribute('lang');
	
	const WEEK = {
		'ja': ['日', '月', '火', '水', '木', '金', '土'],
		'ko': ['일', '월', '화', '수', '목', '금', '토'],
		'zh-Hant': ['日', '一', '二', '三', '四', '五', '六'],
		'zh': ['日', '一', '二', '三', '四', '五', '六'],
		'ru': ['ВС', 'ПН', 'ВТ', 'СР', 'ЧТ', 'ПТ', 'СБ'],
		'de': ['Son', 'Mon', 'Die', 'Mit', 'Don', 'Fre', 'Sam'],
		'it': ['Dom', 'Lun', 'Mar', 'Mer', 'Gio', 'Ven', 'Sab'],
		'fr': ['Dim', 'Lun', 'Mar', 'Mer', 'Jeu', 'Ven', 'Sam'],
		'pt': ['Dom', 'Seg', 'Ter', 'Qua', 'Qui', 'Sex', 'Sáb'],
		'en': ['Su', 'Mo', 'Tu', 'We', 'Th', 'Fr', 'Sa'] // Add your language
	};
	const WEEK_L = WEEK[LANG] ?? WEEK['en'];
	
	const YE = date.getFullYear().toString().slice(-2);
	const YM = date.getFullYear() - 1911;
	const MO = ('0' + (date.getMonth() + 1)).slice(-2);
	const DA = ('0' + date.getDate()).slice(-2);
	const WE = WEEK_L[date.getDay()];
	const HO = ('0' + date.getHours()).slice(-2);
	const MI = ('0' + date.getMinutes()).slice(-2);
	const SE = ('0' + date.getSeconds()).slice(-2);
	
	const F = [
		`${DA}.${MO}.${YE} ${HO}:${MI}`, // 0=1
		`${DA}.${MO}.${YE} ${HO}:${MI}:${SE}`,
		`${DA}.${MO}.${YE}(${WE}) ${HO}:${MI}`,
		`${DA}.${MO}.${YE}(${WE}) ${HO}:${MI}:${SE}`,
		`${YE}/${MO}/${DA} ${HO}:${MI}`,
		`${YE}/${MO}/${DA} ${HO}:${MI}:${SE}`,
		`${YE}/${MO}/${DA}(${WE}) ${HO}:${MI}`,
		`${YE}/${MO}/${DA}(${WE}) ${HO}:${MI}:${SE}`,
		`${YE}-${MO}/${DA} ${HO}:${MI}`,
		`${YE}-${MO}/${DA} ${HO}:${MI}'${SE}`,
		`${YE}-${MO}/${DA}(${WE}) ${HO}:${MI}`,
		`${YE}-${MO}/${DA}(${WE}) ${HO}:${MI}'${SE}`,
		`M${YM}-${MO}-${DA} ${HO}:${MI}`,
		`M${YM}-${MO}-${DA} ${HO}:${MI}:${SE}`,
		`M${YM}-${MO}-${DA}(${WE}) ${HO}:${MI}`,
		`M${YM}-${MO}-${DA}(${WE}) ${HO}:${MI}:${SE}`,
		`${YE}/${MO}/${DA}(${WE}) ${HO}:${MI}:${SE}`
	];
	
	return F[f - 1] ?? F[16];
}


function mkreq(url, eq, ebt, controller) {
	let req;
	
	if (getcv('gt').length && !getcv('twid').length) {
		req = new Request(`${url}${eq}`, {
			headers: {
				'authorization': `Bearer ${ebt}`,
				'x-csrf-token': getcv('ct0')[0],
				'x-guest-token': getcv('gt')[0]
			},
			cache: 'force-cache',
			redirect: 'follow',
			signal: controller.signal
		});
	} else {
		req = new Request(`${url}${eq}`, {
			headers: {
				'authorization': `Bearer ${ebt}`,
				'x-csrf-token': getcv('ct0')[0],
				'x-twitter-auth-type': 'OAuth2Session'
			},
			cache: 'force-cache',
			redirect: 'follow',
			mode: 'cors',
			credentials: 'include',
			signal: controller.signal
		});
	}
	
	return req;
}


async function getuid(s_name) {
	let url = 'https://api.x.com/graphql/rePnxwe9LZ51nQ7Sn_xN_A/UserByScreenName';
	if (/^[^:]+:\/\/[^/]+\.onion\//i.test(document.URL)) {
		url =
			'https://api.twitter3e4tixl4xyajtrzo62zg5vztmjuricljdp2c5kshju4avyoid.onion/' +
			'graphql/rePnxwe9LZ51nQ7Sn_xN_A/UserByScreenName';
	}
	let q =
		'?variables={' +
		`"screen_name":"${s_name}",` +
		'"withSafetyModeUserFields":true,' +
		'"withSuperFollowsUserFields":true' +
		'}' +
		'&features={' +
		'"responsive_web_twitter_blue_verified_badge_is_enabled":true,' +
		'"responsive_web_graphql_exclude_directive_enabled":false,' + // false
		'"verified_phone_label_enabled":true,' + // false
		'"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,' +
		'"responsive_web_graphql_timeline_navigation_enabled":true' +
		'}';
	let eq = encodeURI(q);
	let controller = new AbortController();
	let req = mkreq(url, eq, EBTKN, controller);
	let res = {};
	
	try {
		setTimeout(function () { controller.abort(); }, 60000);
		res = await fetch(req);
		if (!res.ok) {
			console.log(`${MYNAME}: getuid:notok:${res.ok}.`);
			return null;
		} // 失敗なら空で終わり
	} catch (err) {
		console.log(`${MYNAME}: getuid:error:${err}.`);
		return null; // 失敗なら空で終わり
	}
	
	let json = JSON.parse(await res.text());
	
	let id, bl, ca, sn, vf;
	id = json.data.user.result.rest_id;
	bl = json.data.user.result.is_blue_verified;
	ca = json.data.user.result.legacy.created_at;
	sn = json.data.user.result.legacy.screen_name;
	vf = json.data.user.result.legacy.verified;
	
	return [id, bl, ca, sn, vf];
}


async function unique(key) { // 要${MYNAME}_idl、${MYNAME}_twts
	let n = key == 'idl' ? 0 : 1;
	
	if (!cntr_i[n]) return;
	
	if (Date.now() - time_r[key] > 8000) {
		let i = cntr_i[n];
		cntr_i[n] = 0;
		time_r[key] = Date.now();
		
		let str = await GM.getValue('cntr');
		if (str === undefined) str = JSON.stringify([0, 0]); // 何も無い時
		
		let cntr = JSON.parse(str);
		let limit;
		
		if (key == 'idl') {
			cntr = [cntr[0] + i, cntr[1]];
			limit = 100;
		} else {
			cntr = [cntr[0], cntr[1] + i];
			limit = 50;
		}
		
		if (cntr[n] > limit) {
			dtbs[key] = [...new Set(dtbs[key].map(JSON.stringify))].map(JSON.parse);
			
			cntr = n == 0 ? [0, cntr[1]] : [cntr[0], 0];
		}
		
		await GM.setValue(key, JSON.stringify(dtbs[key]));
		await GM.setValue('cntr', JSON.stringify(cntr));
	}
}


async function touid(sn) {
	for (let e of dtbs.idl) {
		if (e[3].toLowerCase() == sn.toLowerCase()) return e;
	}
	
	let r = await getuid(sn);
	// 無い時
	// screen name -> id, blue, created_at, screen_name, verified
	if (r) {
		dtbs.idl = dtbs.idl.concat([r]);
		cntr_i[0]++;
		
		return r.includes(sn) ? r : null;
	}
	console.log(`${MYNAME}: touid:error.`);
	return null; // エラー
}


function get10xn(path_result, tl) {
	let path_t = path_result.rest_id ? path_result : path_result.tweet; // ある前提
	let path_t_1 = null;
	let path_t_2 = null;
	
	let path_qt = null;
	
	if (pathexists(path_t, ['legacy', 'retweeted_status_result', 'result', 'legacy'])) {
		path_t_1 = path_t.legacy.retweeted_status_result.result;
	} else if(pathexists(path_t, ['legacy', 'retweeted_status_result', 'result', 'tweet'])) {
		path_t_1 = path_t.legacy.retweeted_status_result.result.tweet;
	}
	
	if (path_t_1) {
		if (pathexists(path_t_1, ['edit_control', 'edit_tweet_ids'])) {
			path_t_2 = path_t_1.edit_control;
		} else { // 他も必ずある?
			path_t_2 = path_t_1.edit_control.edit_control_initial;
		}
	}
	
	if (pathexists(path_result, ['quoted_status_result', 'result', 'rest_id'])) {
		path_qt = path_result.quoted_status_result.result;
	} else if(pathexists(path_result, ['quoted_status_result', 'result', 'tweet', 'rest_id'])) {
		path_qt = path_result.quoted_status_result.result.tweet;
	}
	
	let tuid, tsn, tid, tca, ts, rtuid, rtsn, rtid, rtca, rts;
	let ret = [];
	
	tid = path_t.rest_id;
	ts = path_t.source;
	
	if (!path_t.legacy) { // 何も無い
		tuid = 'none';
		tsn = 'none'; // 未確認
		tca = 'none';
		rtuid = 'none';
		rtsn = 'none';
		rtid = 'none';
		rtca = 'none';
		rts = 'none';
	} else if (!path_t_1) { // リツイートでは無い
		tuid = path_t.legacy.user_id_str;
		tsn = path_t.core.user_results.result.legacy.screen_name;
		tca = path_t.legacy.created_at;
		rtuid = 'none';
		rtsn = 'none'; // 未確認
		rtid = 'none';
		rtca = 'none';
		rts = 'none';
	} else {
		tuid = path_t.legacy.user_id_str;
		tsn = path_t.core.user_results.result.legacy.screen_name;
		tca = path_t.legacy.created_at;
		rtuid = path_t_1.legacy.user_id_str;
		rtsn = path_t_1.core.user_results.result.legacy.screen_name;
		rtid =
			tl ? path_t_2.edit_tweet_ids[5 - path_t_2.edits_remaining] :
				path_t_1.rest_id;
		rtca = path_t_1.legacy.created_at;
		rts = path_t_1.source;
	}
	
	ret.push([tuid, tsn, tid, tca, ts, rtuid, rtsn, rtid, rtca, rts]);
	
	if (!ret.length) {
		console.log(`${MYNAME}: get10xn:error:t.`);
		return null; // エラー
	}
	
	// tuid, tsn, tid, tca, ts, rtuid, rtsn, rtid, rtca, rts再利用
	if (path_qt) {
		tid = path_qt.rest_id;
		ts = path_qt.source;
		
		if (!path_qt.legacy) { // 何も無い
			tuid = 'none';
			tsn = 'none';
			tca = 'none';
			rtuid = 'none';
			rtsn = 'none';
			rtid = 'none';
			rtca = 'none';
			rts = 'none';
		} else {
			tuid = path_qt.legacy.user_id_str;
			tsn = path_qt.core.user_results.result.legacy.screen_name;
			tca = path_qt.legacy.created_at;
			rtuid = 'none';
			rtsn = 'none';
			rtid = 'none';
			rtca = 'none';
			rts = 'none';
		}
		
		if (!tid || !ts) {
			console.log(`${MYNAME}: get10xn:error:qt.`);
			return null; // エラー
		}
		
		ret.push([tuid, tsn, tid, tca, ts, rtuid, rtsn, rtid, rtca, rts]);
	}
	
	return ret; // 最大2
}


async function getdetl(twtid) {
	let url = 'https://x.com/i/api/graphql/3HC_X_wzxnMmUBRIn3MWpQ/TweetResultByRestId';
	
	let q =
		'?variables={' +
		`"tweetId":"${twtid}",` +
		'"withCommunity":false,' +
		'"includePromotedContent":false,' +
		'"withVoice":false' +
		'}' +
		'&features={' +
		'"creator_subscriptions_tweet_preview_api_enabled":true,' +
		'"tweetypie_unmention_optimization_enabled":true,' +
		'"responsive_web_edit_tweet_api_enabled":true,' +
		'"graphql_is_translatable_rweb_tweet_is_translatable_enabled":true,' +
		'"view_counts_everywhere_api_enabled":true,' +
		'"longform_notetweets_consumption_enabled":true,' +
		'"responsive_web_twitter_article_tweet_consumption_enabled":false,' +
		'"tweet_awards_web_tipping_enabled":false,' +
		'"freedom_of_speech_not_reach_fetch_enabled":true,' +
		'"standardized_nudges_misinfo":true,' +
		'"tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled":true,' +
		'"longform_notetweets_rich_text_read_enabled":true,' +
		'"longform_notetweets_inline_media_enabled":true,' +
		'"responsive_web_graphql_exclude_directive_enabled":true,' +
		'"verified_phone_label_enabled":false,' +
		'"responsive_web_media_download_video_enabled":false,' +
		'"responsive_web_graphql_skip_user_profile_image_extensions_enabled":false,' +
		'"responsive_web_graphql_timeline_navigation_enabled":true,' +
		'"responsive_web_enhance_cards_enabled":false' +
		'}' +
		'&fieldToggles={' +
		'"withArticleRichContentState":false,' +
		'"withAuxiliaryUserLabels":false' +
		'}';
	let eq = encodeURI(q);
	
	let controller = new AbortController();
	let req = mkreq(url, eq, EBTKN, controller);
	let res = {};
	
	try {
		setTimeout(function () { controller.abort(); }, 60000);
		res = await fetch(req);
		if(!res.ok) {
			console.log(`${MYNAME}: getdtl:notok:${res.ok}.`);
			return null;
		} // 失敗なら空で終わり
	} catch (err) {
		console.log(`${MYNAME}: getdtl:error:${err}.`);
		return null; // 失敗なら空で終わり
	}
	
	let json = JSON.parse(await res.text());
	let path_result = json.data.tweetResult.result;
	let ret = null;
	
	if (path_result) ret = get10xn(path_result, false);
	
	if (ret) {
		dtbs.twts = dtbs.twts.concat(ret);
		cntr_i[1]++;
	}
	
	return (
		ret ? [
			ret[0][0], ret[0][1], ret[0][2], ret[0][3], ret[0][4],
			ret[0][5], ret[0][6], ret[0][7], ret[0][8], ret[0][9]
		] :
			ret
	);
}


function gettwts_r(stat, text) {
	if (stat < 200 || stat > 299) {
		console.log(`${MYNAME}: gettwts_r:error:${stat}.`);
		return null; // エラー
	}
	
	let json = JSON.parse(text);
	let path_result = json.data.tweetResult.result;
	let ret = null;
	
	if (path_result) ret = get10xn(path_result, false);
	
	if (ret) {
		dtbs.twts = dtbs.twts.concat(ret);
		cntr_i[1]++;
	}
}


function gettwts_d(stat, text) {
	if (stat < 200 || stat > 299) {
		console.log(`${MYNAME}: gettwts_d:error:${stat}.`);
		return null; // エラー
	}
	
	let json = JSON.parse(text);
	let ret = [];
	
	if (json.errors) {
		console.log(`${MYNAME}: gettwts_d:error:errors.`);
		return null; // エラー
	}
	
	let path_instructions =
		json.data.threaded_conversation_with_injections_v2.instructions;
	
	for (let n in path_instructions) {
		if (path_instructions[+n].type == 'TimelineAddEntries') {
			let path_entries = path_instructions[+n].entries;
			
			for (let n in path_entries) {
				let path_result = null;
				if (/^tweet-/i.test(path_entries[+n].entryId)) {
					if (
						pathexists(path_entries[+n], [
							'content',
							'itemContent',
							'tweet_results',
							'result',
							'rest_id'
						])
					) {
						path_result =
							path_entries[+n].content.itemContent.tweet_results.result;
					} else if(
						pathexists(path_entries[+n], [
							'content',
							'itemContent',
							'tweet_results',
							'result',
							'tweet'
						])
					) {
						path_result =
							path_entries[+n].content.itemContent.tweet_results.result.tweet;
					}
				} else if (/^conversationthread-/i.test(path_entries[+n].entryId)) {
					if (
						pathexists(path_entries[+n], [
							'content',
							 'items',
							 0,
							 'item',
							'itemContent',
							'tweet_results',
							'result',
							'rest_id'
						])
					) {
						path_result =
							path_entries[+n].content.items[0].item.itemContent.tweet_results.result;
						// 0だけ
					} else if (
						pathexists(path_entries[+n], [
							'content',
							'items',
							0,
							'item',
							'itemContent',
							'tweet_results',
							'result',
							'tweet'
						])
					) {
						path_result =
							path_entries[+n].content.items[0].item.itemContent.tweet_results.result.tweet;
					}
				} else {
					continue;
				}
				
				if(path_result) ret = ret.concat(get10xn(path_result, false));
			}
		} else if (path_instructions[+n].type == 'TimelineAddToModule') {
			let path_moduleitems = path_instructions[+n].moduleItems;
			
			for (let n in path_moduleitems) {
				let path_result = null;
				if (/^conversationthread-/i.test(path_moduleitems[+n].entryId)) {
					if (
						pathexists(path_moduleitems[+n], [
							'item',
							'itemContent',
							'tweet_results',
							'result',
							'rest_id'
						])
					) {
						path_result =
							path_moduleitems[+n].item.itemContent.tweet_results.result;
					} else if (
						pathexists(path_moduleitems[+n], [
							'item',
							'itemContent',
							'tweet_results',
							'result',
							'tweet'
						])
					) {
						path_result =
							path_moduleitems[+n].item.itemContent.tweet_results.result.tweet;
					}
				} else {
					continue;
				}
				
				if (path_result) ret = ret.concat(get10xn(path_result, false));
			}
		}
	}
	
	if (ret.length) dtbs.twts = dtbs.twts.concat(ret);
	
	cntr_i[1]++;
}


function gettwts(stat, text, path) {
	if (stat < 200 || stat > 299) {
		console.log(`${MYNAME}: gettwts:error:${stat}.`);
		return null; // エラー
	}
	
	let json = JSON.parse(text);
	let path_instructions =
		path.length == 4 ? json['data'][path[0]][path[1]][path[2]][path[3]]['instructions'] :
		path.length == 3 ? json['data'][path[0]][path[1]][path[2]]['instructions'] :
			json['data'][path[0]][path[1]]['instructions'];
	let n = 0;
	let ret = [];
	
	while (path_instructions[n].type != 'TimelineAddEntries')
		if (typeof path_instructions[++n] === 'undefined') return null;
	
	let path_entries = path_instructions[n].entries;
	let num = Object.keys(path_entries).length;
	
	for (let n in path_entries) {
		let path_result = null;
		if (/^tweet-/i.test(path_entries[+n].entryId)) {
			if (pathexists(
				path_entries[+n],
				['content', 'itemContent', 'tweet_results', 'result', 'rest_id']
			)) {
				path_result =
					path_entries[+n].content.itemContent.tweet_results.result;
			} else if (pathexists(
				path_entries[+n],
				['content', 'itemContent', 'tweet_results', 'result', 'tweet']
			)) {
				path_result =
					path_entries[+n].content.itemContent.tweet_results.result.tweet;
			}
		} else if (/^conversationthread-/i.test(path_entries[+n].entryId)) {
			if (pathexists(
				path_entries[+n],
				['content', 'items', 0, 'item','itemContent', 'tweet_results', 'result', 'rest_id']
			)) {
				path_result =
					path_entries[+n].content.items[0].item.itemContent.tweet_results.result;
				// 0だけ
			} else if (pathexists(
				path_entries[+n],
				['content', 'items', 0, 'item','itemContent', 'tweet_results', 'result', 'tweet']
			)) {
				path_result =
					path_entries[+n].content.items[0].item.itemContent.tweet_results.result.tweet;
			}
		} else {
			continue;
		}
		
		if (path_result) ret = ret.concat(get10xn(path_result, false));
	}
	
	if (ret.length) dtbs.twts = dtbs.twts.concat(ret);
	
	cntr_i[1]++;
}


function statssn(sn) {
	let snl = sn.toLowerCase();
	
	for (let e of dtbs.twts) {
		let t = e[1].toLowerCase() == snl;
		let rt;
		if (!t) rt = e[6].toLowerCase() == snl;
		
		if (t || rt) return t ? [e[0], e[1]] : [e[5], e[6]];
	}
	
	return null;
}


function chklv(id) {
	const UD = +id.substring(0, 2);
	
	const L =
		UD < 13 ? LVL_12 :
		UD < 16 ? LVL_15 :
		UD < 20 ? LVL_19 :
		UD < 25 ? LVL_24 :
		UD < 30 ? LVL_29 :
		UD < 40 ? LVL_3 :
		UD < 50 ? LVL_4 :
		UD < 60 ? LVL_5 :
		UD < 70 ? LVL_6 :
		UD < 80 ? LVL_7 :
		UD < 90 ? LVL_8 :
			LVL_9;
	
	return L.includes(id);
}


function statsrt(uid, rtid) {
	for (let e of dtbs.twts) {
		if (e[0] == uid && e[7] == rtid) return [e[2], e[3]]; // あれば終わり
	}
	
	console.log(`${MYNAME}: statsrt:end.`);
	return ['0000000000000000000', 'Thu Jan 01 00:00:00 +0000 1970'];
	// エラー
}


function twXHRStateHandler({ target: xhr }, res) {
	if (xhr.readyState === 4) {
		const STAT = xhr.status;
		const rawTEXT = xhr.responseText;
		if (res == 1)
			gettwts_r(STAT, rawTEXT);
		else if (res == 2)
			gettwts_d(STAT, rawTEXT);
		else if (res == 3)
			gettwts(STAT, rawTEXT, ['user', 'result', 'timeline_v2', 'timeline']);
		else if (res == 4)
			gettwts(STAT, rawTEXT, ['home', 'home_timeline_urt']);
		else if (res == 5)
			gettwts(STAT, rawTEXT, ['home', 'home_timeline_urt']);
		else if (res == 6)
			gettwts(STAT, rawTEXT, ['list', 'tweets_timeline', 'timeline']);
		else if (res == 7)
			gettwts(STAT, rawTEXT, ['search_by_raw_query', 'search_timeline', 'timeline']);
	}
}


function isTwtsURL(url) {
	if (!url.includes('/graphql/')) return 0;
	else if (url.includes('/TweetResultByRestId?')) return 1;
	else if (url.includes('/TweetDetail?')) return 2;
	else if (url.includes('/UserTweets?')) return 3;
	else if (url.includes('/HomeTimeline')) return 4;
	else if (url.includes('/HomeLatestTimeline')) return 5;
	else if (url.includes('/ListLatestTweetsTimeline?')) return 6;
	else if (url.includes('/SearchTimeline?')) return 7;
	else return 0;
}


function overrideXHROpen() {
	originalXHROPEN = XMLHttpRequest.prototype.open;
	XMLHttpRequest.prototype.open = function () {
		const RES = isTwtsURL(arguments[1]);
		if (RES) {
			this.addEventListener(
				'readystatechange',
				function (evt) { twXHRStateHandler(evt, RES); }
			);
		}
		return originalXHROPEN.apply(this, arguments);
	};
	
	console.log(`${MYNAME}: XMLHttpRequest.open overriden.`);
}


async function subsbadge() {
	const SEL_V = 'path[d^="M20.396 11c-.018-.646-.215-1.275-.57-1"]';
	const SEL_B = 'path[d^="M16.5 3H2v18h15c3.038 0 5.5-2.46 5.5-5"]';
	
	let elms = document.querySelectorAll(`${SEL_V}, ${SEL_B}`);
	
	for (let e of elms) {
		const SEL_E =
			'svg.r-4qtqp9.r-yyyyoo.r-lwhw9o.r-dnmrzs.r-bnwqim.' +
			'r-1plcrui.r-lrvibr.r-cnnz9e';
		// def-ja, def-en, ble-ja, ble-en
		const SEL_E2 =
			'svg.r-4qtqp9.r-yyyyoo.r-1xvli5t.r-dnmrzs.r-bnwqim.' +
			'r-1plcrui.r-f5ekn1.r-17h40o6.r-lrvibr';
		// def-ja, def-en, ble-ja, ble-en
		const D_V =
			'M20.396 11c-.018-.646-.215-1.275-.57-1.816-.354-.54-.852-.972-1.438-1.246.223-.6' +
			'07.27-1.264.14-1.897-.131-.634-.437-1.218-.882-1.687-.47-.445-1.053-.75-1.687-.8' +
			'82-.633-.13-1.29-.083-1.897.14-.273-.587-.704-1.086-1.245-1.44S11.647 1.62 11 1.' +
			'604c-.646.017-1.273.213-1.813.568s-.969.854-1.24 1.44c-.608-.223-1.267-.272-1.90' +
			'2-.14-.635.13-1.22.436-1.69.882-.445.47-.749 1.055-.878 1.688-.13.633-.08 1.29.1' +
			'44 1.896-.587.274-1.087.705-1.443 1.245-.356.54-.555 1.17-.574 1.817.02.647.218 ' +
			'1.276.574 1.817.356.54.856.972 1.443 1.245-.224.606-.274 1.263-.144 1.896.13.634' +
			'.433 1.218.877 1.688.47.443 1.054.747 1.687.878.633.132 1.29.084 1.897-.136.274.' +
			'586.705 1.084 1.246 1.439.54.354 1.17.551 1.816.569.647-.016 1.276-.213 1.817-.5' +
			'67s.972-.854 1.245-1.44c.604.239 1.266.296 1.903.164.636-.132 1.22-.447 1.68-.90' +
			'7.46-.46.776-1.044.908-1.681s.075-1.299-.165-1.903c.586-.274 1.084-.705 1.439-1.' +
			'246.354-.54.551-1.17.569-1.816zM9.662 14.85l-3.429-3.428 1.293-1.302 2.072 2.072' +
			' 4.4-4.794 1.347 1.246z';
		const D_B =
			'M16.5 3H2v18h15c3.038 0 5.5-2.46 5.5-5.5 0-1.4-.524-2.68-1.385-3.65-.08-.09-.089' +
			'-.22-.023-.32.574-.87.908-1.91.908-3.03C22 5.46 19.538 3 16.5 3zm-.796 5.99c.457' +
			'-.05.892-.17 1.296-.35-.302.45-.684.84-1.125 1.15.004.1.006.19.006.29 0 2.94-2.2' +
			'69 6.32-6.421 6.32-1.274 0-2.46-.37-3.459-1 .177.02.357.03.539.03 1.057 0 2.03-.' +
			'35 2.803-.95-.988-.02-1.821-.66-2.109-1.54.138.03.28.04.425.04.206 0 .405-.03.59' +
			'5-.08-1.033-.2-1.811-1.1-1.811-2.18v-.03c.305.17.652.27 1.023.28-.606-.4-1.004-1' +
			'.08-1.004-1.85 0-.4.111-.78.305-1.11 1.113 1.34 2.775 2.22 4.652 2.32-.038-.17-.' +
			'058-.33-.058-.51 0-1.23 1.01-2.22 2.256-2.22.649 0 1.235.27 1.647.7.514-.1.997-.' +
			'28 1.433-.54-.168.52-.526.96-.992 1.23z';
		const SEL_P_KS = 'form button[data-testid="TypeaheadUser"]'; // キーワード検索
		const SEL_P_KSR = 'form div[data-testid="typeaheadRecentSearchesItem"]';
		// キーワード検索(最新)
		const SEL_P_DS =
			'div[aria-labelledby="modal-header"] div.css-175oi2r.r-1wbh5a2.r-dnmrzs.r-1ny4l3l>' +
			'div[data-testid^="User-Name"]'; // 表示をカスタマイズする、ノート
		const SEL_P_VRRT = 'main section article div[data-testid="card.layoutLarge.detail"]'; // ビデオ引用RT
		const SEL_P_T2 = 'main div[data-testid="primaryColumn"] div[data-testid="UserName"]'; // トップ2
		const SEL_P_RRT = 'main section article div[data-testid^="User-Name"]'; // 引用RT
		const SEL_KS = 'div.css-175oi2r.r-1awozwy.r-18u37iz.r-1wbh5a2 div.css-146c3p1.r-1wvb978 span';
		// バグ?対策 def-ja, def-en
		const SEL = 'div.css-175oi2r.r-18u37iz.r-1wbh5a2.r-1ez5h0i span.css-1jxf684.r-bcqeeo.r-qvutc0';
		// def-ja, def-en, ble-ja, ble-en
		const SEL_2 = 'div.css-175oi2r.r-1awozwy.r-18u37iz.r-1wbh5a2 span.css-1jxf684.r-bcqeeo.r-qvutc0';
		// def-ja, def-en, ble-ja, ble-en
		
		let spe = e.parentNode.parentNode;
		let tpe = e.parentNode.parentNode.parentNode;
		let sn;
		let ss; // Temp.
		
		if (tpe.querySelector(`${SEL_E}, ${SEL_E2}`)) continue;
		
		if (spe.style.color == 'rgb(232, 134, 143)') {
			ss = s_mus;
			spe.style.color = 'rgb(29, 155, 240)';
			s_mus = ss;
		}
		
		let xpe = e.closest('main div[data-testid="primaryColumn"] h2[role="heading"]'); // トップ
		if (xpe) {
			sn = document.URL.split('/')[3];
		}
		
		if (!sn && spe.getAttribute('data-testid') == 'verificationBadge')
			sn = document.URL.split('/')[3];
		// 認証済アカウントポップアップ
		
		if (!sn) {
			let xpe = e.closest(SEL_P_KS);
			let sne;
			if (xpe) sne = xpe.querySelector(SEL_KS);
			if (sne) sn = sne.textContent.split('@')[1];
		}
		if (!sn) {
			let xpe = e.closest(SEL_P_KSR);
			let sne;
			if (xpe) sne = xpe.querySelector(SEL_KS);
			if (sne) sn = sne.textContent.split('@')[1];
		}
		if (!sn) {
			let xpe = e.closest(SEL_P_DS);
			let sne;
			if (xpe) sne = xpe.querySelector(SEL);
			if (sne) sn = sne.textContent.split('@')[1];
		}
		if (!sn) {
			let xpe = e.closest(SEL_P_VRRT);
			let sne;
			if (xpe) sne = xpe.querySelector(SEL);
			if (sne) sn = sne.textContent.split('@')[1];
		}
		if (!sn) {
			let xpe = e.closest(SEL_P_T2);
			let sne;
			if (xpe) sne = xpe.querySelector(SEL_2);
			if (sne) sn = sne.textContent.split('@')[1];
		}
		if (!sn) {
			let xpe = e.closest(SEL_P_RRT);
			let sne;
			if (xpe) sne = xpe.querySelector(SEL);
			if (sne) sn = sne.textContent.split('@')[1];
		}
		
		if (!sn) {
			let xpe = e.closest('a'); // ビデオ引用TW、TL等
			if (xpe) {
				let a = xpe.getAttribute('href').split('/')[1];
				let b = xpe.getAttribute('href').split('/')[2];
				let c = xpe.getAttribute('href').split('/')[3];
				
				if (a == 'i' && b == 'status') {
					for (let e of dtbs.twts) {
						if (e[2] == c) {
							sn = e[1];
							break;
						} else if (e[7] == c) {
							sn = e[6];
							break;
						}
					}
					
					if (!sn) {
						let detl = await getdetl(c);
						if (detl) sn = detl[1];
					}
				} else {
					sn = a;
				}
			}
		}
		
		if (!sn) {
			ss = s_mus;
			spe.style.color = 'rgb(232, 134, 143)';
			s_mus = ss;
			continue;
		}
		
		let a = [
			'',      'compose',  'explore',       'home',   'i',
			'login', 'messages', 'notifications', 'search', 'search-advanced',
			'settings'
		];
		
		if (a.includes(sn.toLowerCase())) {
			ss = s_mus;
			spe.style.color = 'rgb(232, 134, 143)';
			s_mus = ss;
			continue;
		}
		
		let pr = statssn(sn); // screen name -> id, screen_name
		if (!pr) pr = await touid(sn); // screen name -> id, blue, created_at, screen_name, verified
		let r = pr ? chklv(pr[0]) : pr;
		
		ss = s_mus;
		if (r === true) {
			e.setAttribute('d', D_V);
		} else if (r === false) {
			e.setAttribute('d', D_B);
		} else {
			spe.style.color = 'rgb(232, 134, 143)';
		}
		s_mus = ss;
	}
}


async function addsl() {
	const SEL_END =
		'main div[data-testid="primaryColumn"] section article ' +
		'div.css-175oi2r.r-1d09ksm.r-1471scf.r-18u37iz.r-1wbh5a2';
	const SEL_END_RT =
		'main div[data-testid="primaryColumn"] section article ' +
		'span[id].r-8akbws.r-krxsd3.r-n6v787.r-1cwl3u0.r-b88u0q';
	const SEL_ADD = `span.us-${MYNAME}`;
	
	let uid, tid, tsl, rtsl, thref, rthref, detl;
	let ca, span, span2, a, a2;
	
	let elm_end = document.querySelector(SEL_END);
	let elm_end_rt = document.querySelector(SEL_END_RT);
	if (!elm_end) return;
	
	let old = elm_end.querySelectorAll(SEL_ADD);
	if (old.length) return;
	
	tsl = '?';
	rtsl = '(?)';
	thref = 'https://help.twitter.com/using-twitter/how-to-tweet#source-labels';
	if (/^[^:]+:\/\/[^/]+\.onion\//i.test(document.URL)) {
		thref =
			'https://help.twitter3e4tixl4xyajtrzo62zg5vztmjuricljdp2c5kshju4avyoid.onion' +
			'/using-twitter/how-to-tweet#source-labels';
	}
	rthref = thref;
	
	ca = elm_end.querySelector('a');
	tid = ca.getAttribute('href').split('/')[3];
	
	if (elm_end_rt) {
		let pe = elm_end_rt.parentNode;
		let sn = pe.getAttribute('href').slice(1);
		
		uid = statssn(sn); // screen name -> id, screen_name
		if (!uid) uid = await touid(sn);
		// screen name -> id, blue, created_at, screen_name, verified
		// リツイートした人のuid
	}
	
	let i = 2;
	if (document.URL.split('/')[5] != tid) i = 7;
	
	for (let e of dtbs.twts) {
		if (e[i] == tid) {
			detl = e;
			break;
		}
	}
	
	if (!detl) detl = await getdetl(tid); // ツイートはtid、rtidどちらにもなり得る
	
	if (detl) {
		tsl = detl[4].split('<')[1].split('>')[1];
		thref = detl[4].split('"')[1];
	} else {
		console.log(`${MYNAME}: addsl:error.`); // エラー
	}
	
	if (ssl == 1 || !elm_end_rt) {
		;
	} else if (uid) {
		for (let e of dtbs.twts) {
			if (e[0] == uid[0] && e[7] == tid) { // リツイートは一つしか無い
				rtsl = `(${e[4].split('<')[1].split('>')[1]})`;
				rthref = e[4].split('"')[1];
				break;
			}
		}
	}
	
	span = document.createElement('span');
	span.className = `us-${MYNAME}`;
	span.style.margin = '0px 3px 0px 3px';
	span.textContent = '·';
	span.style.color = getComputedStyle(ca, null).color;
	span.style.font = getComputedStyle(ca, null).font;
	span.style.lineHeight = getComputedStyle(elm_end, null).lineHeight;
	
	span2 = document.createElement('span');
	span2.className = `us-${MYNAME}`;
	span2.style.lineHeight = getComputedStyle(elm_end, null).lineHeight;
	span2.style.display = 'inline-block';
	
	a = document.createElement('a');
	a.className = `us-${MYNAME}`;
	a.setAttribute('role', 'link');
	a.setAttribute('href', thref);
	a.setAttribute('target', '_blank');
	a.setAttribute('rel', 'nofollow noopener noreferrer');
	a.textContent = tsl;
	a.style.color = getComputedStyle(ca, null).color;
	a.style.font = getComputedStyle(ca, null).font;
	a.style.textDecoration = getComputedStyle(ca, null).textDecoration;
	
	a2 = document.createElement('a');
	a2.className = `us-${MYNAME}`;
	a2.setAttribute('role', 'link');
	a2.setAttribute('href', rthref);
	a2.setAttribute('target', '_blank');
	a2.setAttribute('rel', 'nofollow noopener noreferrer');
	a2.textContent = rtsl;
	a2.style.color = getComputedStyle(ca, null).color;
	a2.style.font = getComputedStyle(ca, null).font;
	a2.style.textDecoration = getComputedStyle(ca, null).textDecoration;
	
	let ss = s_mus;
	
	elm_end.appendChild(span);
	elm_end.appendChild(span2);
	span2.appendChild(a);
	if (ssl == 2 && elm_end_rt) span2.appendChild(a2);
	
	s_mus = ss;
}


async function main_track() {
	const SEL_END_RT =
		'main div[data-testid="primaryColumn"] section article ' +
		'span[id].r-8akbws.r-krxsd3.r-n6v787.r-1cwl3u0.r-b88u0q';
	
	let elms = document.querySelectorAll(SEL_END_RT);
	
	for (let elm of elms) {
		const SEL_RTTO = `div[data-testid^="User-Name"] a.css-146c3p1.r-qvutc0:not(.us-${MYNAME})`;
		// UTL, HTL
		const SEL_RTTO_2 =
			'main div[data-testid="primaryColumn"] section article ' +
			`div.css-175oi2r.r-1d09ksm.r-1471scf.r-18u37iz.r-1wbh5a2 a.css-1jxf684.r-qvutc0:not(.us-${MYNAME})`;
		// Retweet def-ja, def-en, ble-ja, ble-en
		const SEL_ADD = `a.us-${MYNAME}`;
		
		let pe = elm.parentNode;
		let fpe = elm.parentNode.parentNode.parentNode.parentNode;
		let xpe = elm.closest('article');
		let elm2 = xpe.querySelector(SEL_RTTO);
		if (!elm2) elm2 = xpe.querySelector(SEL_RTTO_2);
		let old = fpe.querySelector(SEL_ADD);
		
		let sn = pe.getAttribute('href').slice(1);
		if (sn.includes('/')) continue;
		
		let rtid = elm2.getAttribute('href').split('/')[3];
		
		let id = statssn(sn); // screen name -> id, screen_name
		if (!id) id = await touid(sn); // screen name -> id, blue, created_at, screen_name, verified
		if (!id) continue;
		
		let stats = statsrt(id[0], rtid); // id, rtid -> tid, tca
		
		let span, span2, a;
		let date;
		let ss; // Temp.
		
		if (!fmt) continue;
		
		if (!old) {
			span = document.createElement('span');
			span.className = `us-${MYNAME}`;
			span.style.margin = '0px 3px 0px 3px';
			span.textContent = '·';
			span.style.color = getComputedStyle(elm, null).color;
			span.style.font = getComputedStyle(elm, null).font;
			span.style.setProperty(
				'line-height',
				getComputedStyle(elm, null).lineHeight,
				'important'
			);
			
			span2 = document.createElement('span');
			span2.className = `us-${MYNAME}`;
			span2.style.display = 'inline-block';
			
			a = document.createElement('a');
			a.className = `us-${MYNAME}`;
			a.setAttribute('dir', 'ltr');
			a.setAttribute('role', 'link');
			a.setAttribute('href', `/${sn}/status/${stats[0]}`);
			a.setAttribute('target', '_blank');
			a.setAttribute('rel', 'noopener noreferrer');
			date = datef(new Date(stats[1]), fmt);
			a.textContent = date;
			a.style.color = getComputedStyle(elm, null).color;
			a.style.font = getComputedStyle(elm, null).font;
			a.style.textDecoration = getComputedStyle(elm, null).textDecoration;
			
			ss = s_mus;
			
			fpe.appendChild(span);
			fpe.appendChild(a);
			
			s_mus = ss;
		} else {
			ss = s_mus;
			
			date = datef(new Date(stats[1]), fmt);
			if (old.textContent != date) old.textContent = date; // TZ change
			
			s_mus = ss;
		}
	}
}


console.log(`${MYNAME}: start.`);

dtbs.idl = await load_dtbs('idl');
dtbs.twts = await load_dtbs('twts');
time_r.idl = time_r.twts = Date.now();

tb = TB;
ssl = SSL;
fmt = FMT;
intl = INTL;

if (!NOGUI) await initgui();
overrideXHROpen();
observer.observe(document.documentElement, { childList: true, subtree: true });

while (true) {
	if (s_mus) {
		s_mus = null; // 初期値がtrue、変更もしない
		if (tb) await subsbadge();
		if (ssl) await addsl();
		if (fmt || ssl == 2) await main_track();
	}
	
	await unique('idl');
	await unique('twts');
	
	await new Promise((resolve) => setTimeout(resolve, intl));
	// intl は外から非同期に変更する
}


})(); /*  END  */