Twitter X Title

Change Twitter X Title

当前为 2023-08-23 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Twitter X Title
  3. // @namespace TwitterX
  4. // @match https://twitter.com/*
  5. // @grant none
  6. // @version 0.2.3
  7. // @author CY Fung
  8. // @description Change Twitter X Title
  9. // @run-at document-start
  10. // @license MIT
  11. // @unwrap
  12. // @inject-into page
  13. // ==/UserScript==
  14.  
  15.  
  16. (function () {
  17. 'use strict';
  18.  
  19. let i18nXs = null;
  20.  
  21. const i18nCache = new Set();
  22.  
  23. function customTitle(p) {
  24. if (typeof p !== 'string' || !p.includes('X')) return p;
  25.  
  26. if (!i18nXs && window.webpackChunk_twitter_responsive_web) {
  27. i18nXs = generateI18nXs();
  28. console.log('i18nXs', i18nXs)
  29. }
  30.  
  31. let q = p.replace(/[\xA0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000]/g, ' ');
  32. const mxLen = q.length;
  33. let skipI18N = false;
  34. if (q.replace(/[\(\d\)]/g, '').trim() === 'X') {
  35. q = q.replace('X', 'Twitter');
  36. skipI18N = true;
  37. } else if (q.endsWith(' / X')) {
  38. q = q.substring(0, q.length - ' / X'.length) + ' / Twitter';
  39. }
  40.  
  41.  
  42. if (!skipI18N) {
  43. let uv = null;
  44. for (const i18nX of i18nXs) {
  45. const { s, l, y, m } = i18nX;
  46. if (uv !== null && uv !== m) break;
  47. if (mxLen >= l && q.includes(s)) {
  48. const xc = s;
  49. const idx = q.indexOf(xc);
  50. if (idx >= 0 && idx >= y) {
  51. const tc = xc.replace(/\bX\b/g, 'Twitter');
  52. q = q.substring(0, idx) + tc + q.substring(idx + xc.length);
  53. uv = m;
  54. }
  55. }
  56. }
  57. if (typeof uv === 'string' && !i18nCache.has(uv)) {
  58. if (i18nCache.size > 12) i18nCache.clear();
  59. i18nCache.add(uv);
  60. moveToFront(i18nXs, uv);
  61. }
  62. }
  63. return q;
  64. }
  65.  
  66.  
  67.  
  68. function generateI18nXs() {
  69. let i18nXs = [];
  70. let i18nFunction = null;
  71. for (const s of window.webpackChunk_twitter_responsive_web) {
  72. if (s && s[0] && s[0][0]) {
  73. const tag = s[0][0]
  74. if (typeof tag === 'string' && tag.startsWith('i18n/')) {
  75. if (s[1] && typeof s[1] === 'object') {
  76. let entries = Object.entries(s[1]);
  77. if (entries.length === 1 && typeof entries[0][1] === 'function') {
  78. i18nFunction = entries[0][1];
  79. }
  80. }
  81. break;
  82. }
  83. }
  84. }
  85.  
  86. let i18nFunctionString = i18nFunction + "";
  87. let mFuncs = [...i18nFunctionString.matchAll(/function\([,a-zA-Z0-9$_]*\)\{return([^\{\}]+\bX\b[^\{\}]+)\}/g)].map(c => c[1]);
  88. for (const mfString of mFuncs) {
  89.  
  90. let rk1 = mfString.includes('"');
  91. let rk2 = mfString.includes("'");
  92. let rt1 = rk1 && !rk2 ? '"' : !rk1 && rk2 ? "'" : '';
  93. let rt2 = rk1 && !rk2 ? "'" : !rk1 && rk2 ? '"' : '';
  94. if (!rt1 || !rt2) continue;
  95. if (mfString.includes(rt2) || mfString.includes("\\") || mfString.includes("&#")) continue;
  96. const p = mfString.split(rt1)
  97. if ((p.length % 2) !== 1) continue;
  98.  
  99. let uLen = 0;
  100. let jLen = 0;
  101.  
  102. const qm = [];
  103.  
  104. for (let i = 1; i < p.length; i += 2) {
  105. p[i] = p[i].length > 0 ? p[i].replace(/[\xA0\u1680\u180e\u2000-\u200a\u202f\u205f\u3000]/g, ' ') : '';
  106. qm[i] = p[i].replace(/\bX\b/g, '_').replace(/\bTwitter\b/g, '_').length;
  107. uLen += qm[i];
  108. }
  109.  
  110. for (let i = 1; i < p.length; i += 2) {
  111. if (/\bX\b/.test(p[i]) && !/\bTwitter\b/.test(p[i])) {
  112.  
  113. i18nXs.push({
  114. s: p[i],
  115. y: jLen,
  116. l: uLen,
  117. m: mfString
  118. })
  119. }
  120.  
  121. jLen += qm[i];
  122. }
  123.  
  124. }
  125.  
  126. return i18nXs;
  127.  
  128. }
  129.  
  130. function moveToFront(arr, value) {
  131. let insertAt = 0;
  132.  
  133. for (let i = 0; i < arr.length;) {
  134. if (arr[i].m === value) {
  135. let start = i;
  136. do {
  137. i++;
  138. } while (i < arr.length && arr[i].m === value)
  139. let end = i;
  140.  
  141. // Remove the segment from the array
  142. const matchedItems = arr.splice(start, end - start);
  143.  
  144. // Insert the segment to the front at the offset
  145. arr.splice(insertAt, 0, ...matchedItems);
  146.  
  147. insertAt += matchedItems.length;
  148. } else {
  149. i++;
  150. }
  151. }
  152.  
  153. return arr;
  154. }
  155.  
  156. const map = new Map();
  157.  
  158. function fixTitle() {
  159.  
  160. let p = document.title;
  161. if (!p) return;
  162.  
  163. let q = map.get(p)
  164. if (q) {
  165. if (p !== q) {
  166. document.title = q;
  167. }
  168. return;
  169. }
  170.  
  171. q = customTitle(p);
  172.  
  173. if (map.size > 24 && p !== q) map.clear();
  174. map.set(p, q)
  175.  
  176. if (p !== q) {
  177. map.set(q, q)
  178. document.title = q;
  179. }
  180.  
  181. }
  182.  
  183.  
  184. function handleTitleChange(mutationsList) {
  185. console.log(document.title)
  186.  
  187. let b = false;
  188. for (const mutation of mutationsList) {
  189. if (mutation.type === 'childList') {
  190. // Title has changed, do something here
  191. b = true;
  192. break;
  193. }
  194. }
  195.  
  196. if (b) {
  197. // console.log('Title changed:', document.title);
  198. fixTitle();
  199. }
  200. }
  201.  
  202. let observer = null;
  203.  
  204. function doActionFn() {
  205.  
  206. // Check if the title element has appeared
  207. const titleElement = document.querySelector('title');
  208. if (titleElement) {
  209. // Title element found, stop observing
  210. if (observer) observer.disconnect();
  211.  
  212. // Now, create a new observer to monitor the title changes
  213. const titleObserver = new MutationObserver(handleTitleChange);
  214.  
  215. // Configure the observer to monitor the title text changes
  216. const config = { childList: true, subtree: true };
  217.  
  218. // Start observing the title element
  219. titleObserver.observe(titleElement, config);
  220.  
  221. // Log the initial title
  222. // console.log('Initial Title:', titleElement.textContent);
  223. fixTitle()
  224. }
  225.  
  226. }
  227.  
  228. // Function to handle the title changes
  229. function mutCallback(mutationsList, observer) {
  230. let doAction = false;
  231. for (const mutation of mutationsList) {
  232. if (mutation.type === 'childList') {
  233. doAction = true;
  234. break;
  235. }
  236. }
  237. if (doAction) doActionFn();
  238. }
  239.  
  240. if (document.querySelector('title')) {
  241.  
  242. doActionFn();
  243.  
  244. } else {
  245.  
  246.  
  247. // Create a new MutationObserver to monitor the document for the title element
  248. observer = new MutationObserver(mutCallback);
  249.  
  250. // Configure the observer to monitor childList changes (new elements)
  251. const config = { childList: true, subtree: true };
  252.  
  253. // Start observing the document
  254. observer.observe(document, config);
  255.  
  256. }
  257.  
  258. })();