恢復 YouTube 評論名稱

此腳本將 YouTube 評論部分中的“handle”替換為用戶名

  1. // ==UserScript==
  2. // @match https://www.youtube.com/*
  3. // @version 0.6.1
  4. // @run-at document-start
  5. // @name Return YouTube Comment Username
  6. // @name:ja YouTubeコメント欄の名前を元に戻す
  7. // @description This script replaces the "handle" in the YouTube comments section to user name
  8. // @description:ja YouTubeのコメント欄の名前をハンドル(@...)からユーザー名に書き換えます。
  9. // @name:zh-CN 恢復 YouTube 评论用户名
  10. // @name:zh-TW 恢復 YouTube 評論名稱
  11. // @description:zh-TW 此腳本將 YouTube 評論部分中的“handle”替換為用戶名
  12. // @description:zh-CN 此脚本将 YouTube 评论部分中的“handle”替换为用户名
  13. // @author yakisova41
  14. // @namespace https://yt-returnname-api.pages.dev/extension/
  15. // @grant unsafeWindow
  16. // @license MIT
  17. // @icon data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAYAAABXAvmHAAAACXBIWXMAAAWJAAAFiQFtaJ36AAAAGXRFWHRTb2Z0d2FyZQB3d3cuaW5rc2NhcGUub3Jnm+48GgAABI5JREFUaIHVml1ok1cYx39v4pT6Ebdo0baoWCd+VsEPNqfthigOi0IZnRc1XgxFELwQBGE474eIV1UoOqsw9MILSzsv1G4wRQe1zG9RWdGWVBgSMGldrab/XTy2Sc2bj2aZSf5wyMk5zzn5P5/nzUkcSYzAcT4GAsDXwGpgHIWBIaAT+AU4idQ/MiPJGnwpeC5Qgbeg4LNh3vHkXxUAuUzbP4L1knAEfuARMD0/0ZE1eoElHmA3xUceoBz4zgN8k28m/wGbHUEfMCnfTLLE344gCnjyzSRLyEOhkV+2DM6dg5s34exZqKpKLV8AJTHWNm2SBgY0CgMD0saNydYMOQIl1a6iAmpqYOZMCIehsxNu3cqxyd9h3Djo6oJZsxLnnj2DefMgGn1/Ru4eqKyULlyQhoaUgBs3pMWLc2/95csTPyseVVWuHkhUYMUK6cULW9TWJgUCUk2NtGWLdOqUFI1KoZC0cGFuFVi6NLUC7kZ7TwGfT+ruNpINDZLjSAsWSFOmxGR27LANr13LjNj69bZPOjmPR3ryxJ38o0c2n1aBgwdtwZEj0rRpRlIyj1y/bv1AQLp6NZVbR7eWFqm11YyTTnbdOikSGU0+HJbWrk2axKMVuH/f4r6iQjp50jZ48EA6fTq2YXW1dPiw9XftSk+qtdVk79yR5s5NL19ZKR07Jl2+LDU2plszFHveHz8eFi2C7m4IBqG+3sa3b4fbt6GhAbxeePoUXr60uUnvDvCSEvD73atLSYm9VlVBRwds2wbt7cmrUTgM589DeTn09kIkkrp6jWjj95ul7t6VJk6MWdznk5Yssf7goOT1Ss3N9r6uztYGAqkTMB5v3kgHDiRas7zc9nU7B5qbpbIyVw/EzgHHgYEB6OuD0lLTfsYMOHMGVq0y70SjsHcvHD1qsnPmmMWmTnWv3wCzZ8PkySYfDMbG792D16+tv3o1tLRAWVlyS/f2wtatdhbF23+URu3tpvWGDVZtBgft/aVL0r59MauEQlJtbW7KZ2mpFAxm5r2eHmn69BRJXFdngl1dVj79fmn+/Nj8ypVSfb1VqFyQB6mpKfPwk6Tjx5OE0DAaG2HPHnN5W5u5GixcNm+GpiY4dCh1YmUKn89CY9IYnuYjEUvwvj6XEBpuO3dKDx8mav/4sbR7d+6sX109NusPY80alzIajxMnrFVUWHL291sChkI5MfwIvF64cmXs6yZMGO6leRotfLwqrC8zY0ePB7v1KlbcdQRvAW++mWSJWkcwCHyUbyZZ4A/gCw/FmcR/Ad8iqRgVuAh8jtQDdn2eiQIXgf60UrnHZOziLQTcA35Duh8vkMn9/+9Itf8DuZwgXRkVsP8DcckK6XLgZ6SOD0UmGziCCBZr8RBWXhcgPfvwtDJHMg84wJFCJw/mgZeAL25MwAvgU6RwfmhlDrckdoAfioE8mAdCwCdxYw+BZUhv88RpTHDzwP5iIQ+JP278inQxL0yyRHwVGqLADy03xIfQT0h/5pNMNnAEz4EpwHyk5/kmNFYM58CPxUgezAMdwFfE/wOkiOABvi9W8gD/AtVVmDkJXLSNAAAAAElFTkSuQmCC
  18. // ==/UserScript==
  19.  
  20. // src/index.ts
  21. function c3JjL2luZGV4LnRz() {
  22. "use strict";
  23. (() => {
  24. // node_modules/crx-monkey/dist/client/main.js
  25. function getRunningRuntime() {
  26. if (typeof window.__CRX_CONTENT_BUILD_ID === "undefined") {
  27. return "Userscript";
  28. } else {
  29. return "Extension";
  30. }
  31. }
  32. async function bypassSendMessage(message, options, callback) {
  33. const actionId = crypto.randomUUID();
  34. window.postMessage(
  35. {
  36. type: "send-message",
  37. crxContentBuildId: window.__CRX_CONTENT_BUILD_ID,
  38. detail: { message, options },
  39. actionId,
  40. },
  41. "*",
  42. );
  43. const data = await waitResultOnce("send-message", actionId);
  44. if (callback !== void 0) {
  45. callback(data.response);
  46. }
  47. }
  48. async function waitResultOnce(type, actionId) {
  49. return new Promise((resolve) => {
  50. const onResult = (e) => {
  51. if (e.detail.type === type && e.detail.actionId === actionId) {
  52. window.removeEventListener(
  53. "crx-isolate-connector-result",
  54. onResult,
  55. );
  56. resolve(e.detail.data);
  57. }
  58. };
  59. window.addEventListener("crx-isolate-connector-result", onResult);
  60. });
  61. }
  62.  
  63. // src/utils/isCommentRenderer.ts
  64. function isCommentRenderer(continuationItems) {
  65. if (continuationItems.length > 0) {
  66. if ("commentThreadRenderer" in continuationItems[0]) {
  67. return false;
  68. }
  69. if ("commentRenderer" in continuationItems[0]) {
  70. return true;
  71. }
  72. }
  73. return false;
  74. }
  75. function isCommentRendererV2(continuationItems) {
  76. if (continuationItems.length > 0) {
  77. if ("commentThreadRenderer" in continuationItems[0]) {
  78. return false;
  79. }
  80. if ("commentViewModel" in continuationItems[0]) {
  81. return true;
  82. }
  83. }
  84. return false;
  85. }
  86.  
  87. // package.json
  88. var package_default = {
  89. name: "return-youtube-comment-username",
  90. version: "0.6.1",
  91. devDependencies: {
  92. "@types/chrome": "^0.0.263",
  93. "@types/encoding-japanese": "^2.0.5",
  94. "@types/markdown-it": "^13.0.8",
  95. eslint: "^8.57.0",
  96. prettier: "^3.3.1",
  97. "ts-extension-builder": "^0.2.8",
  98. },
  99. license: "MIT",
  100. scripts: {
  101. "esbuild-register": "node --require esbuild-register",
  102. build: "npx crx-monkey build",
  103. dev: "npx crx-monkey dev",
  104. lint: "npx eslint --fix src/**/*.ts",
  105. },
  106. type: "module",
  107. dependencies: {
  108. "@mdit-vue/plugin-title": "^2.1.3",
  109. "@typescript-eslint/eslint-plugin": "^6.21.0",
  110. "@typescript-eslint/parser": "^6.21.0",
  111. "crx-monkey": "0.11.2",
  112. "encoding-japanese": "^2.2.0",
  113. "eslint-config-prettier": "^9.1.0",
  114. "markdown-it": "^14.1.0",
  115. typescript: "^5.4.5",
  116. },
  117. };
  118.  
  119. // src/utils/debugLog.ts
  120. function debugLog(message, value = "") {
  121. if (getRunningRuntime() === "Extension") {
  122. bypassSendMessage({
  123. type: "log",
  124. value: [`[rycu] ${message} %c${value}`, "color:cyan;"],
  125. });
  126. } else {
  127. console.log(`[rycu] ${message} %c${value}`, "color:cyan;");
  128. }
  129. }
  130. function debugErr(message) {
  131. console.error(`[rycu] ${message}`);
  132. if (getRunningRuntime() === "Extension") {
  133. bypassSendMessage({
  134. type: "err",
  135. value: [`[rycu] ${message}`],
  136. });
  137. }
  138. }
  139. function outputDebugInfo() {
  140. const logs = [""];
  141. const ytConf = window.yt.config_;
  142. if (ytConf !== void 0) {
  143. logs.push(
  144. "PAGE_BUILD_LABEL: " +
  145. (ytConf.PAGE_BUILD_LABEL !== void 0
  146. ? ytConf.PAGE_BUILD_LABEL
  147. : " undefined"),
  148. );
  149. logs.push(
  150. "INNERTUBE_CLIENT_VERSION: " +
  151. (ytConf.INNERTUBE_CLIENT_VERSION !== void 0
  152. ? ytConf.INNERTUBE_CLIENT_VERSION
  153. : " undefined"),
  154. );
  155. logs.push(
  156. "INNERTUBE_CONTEXT_CLIENT_VERSION: " +
  157. (ytConf.INNERTUBE_CONTEXT_CLIENT_VERSION !== void 0
  158. ? ytConf.INNERTUBE_CONTEXT_CLIENT_VERSION
  159. : " undefined"),
  160. );
  161. logs.push(
  162. "INNERTUBE_CONTEXT_GL: " +
  163. (ytConf.INNERTUBE_CONTEXT_GL !== void 0
  164. ? ytConf.INNERTUBE_CONTEXT_GL
  165. : " undefined"),
  166. );
  167. logs.push(
  168. "Browser: " +
  169. (ytConf.INNERTUBE_CONTEXT.client.browserName !== void 0
  170. ? ytConf.INNERTUBE_CONTEXT.client.browserName
  171. : " undefined"),
  172. );
  173. logs.push(
  174. "Is login: " +
  175. (ytConf.LOGGED_IN !== void 0
  176. ? `${ytConf.LOGGED_IN}`
  177. : " undefined"),
  178. );
  179. }
  180. logs.push(`Href: ${location.href}`);
  181. debugLog(
  182. `Return Youtube comment Username v${package_default.version}`,
  183. logs.join("\n"),
  184. );
  185. }
  186.  
  187. // src/utils/findElementByTrackingParams.ts
  188. function findElementByTrackingParams(trackingParams, elementSelector) {
  189. let returnElement = null;
  190. let errorAlerted = false;
  191. const elems = document.querySelectorAll(elementSelector);
  192. for (let i = 0; i < elems.length; i++) {
  193. if (
  194. elems[i]?.trackedParams === void 0 &&
  195. elems[i]?.polymerController?.trackedParams === void 0
  196. ) {
  197. debugErr(new Error("TrackedParams not found in element property."));
  198. }
  199. if (elems[i].trackedParams === trackingParams) {
  200. returnElement = elems[i];
  201. break;
  202. } else if (
  203. elems[i]?.polymerController?.trackedParams === trackingParams
  204. ) {
  205. returnElement = elems[i];
  206. break;
  207. } else {
  208. if (!errorAlerted) {
  209. void searchTrackedParamsByObject(trackingParams, elems[i]);
  210. errorAlerted = true;
  211. }
  212. }
  213. }
  214. return returnElement;
  215. }
  216. async function reSearchElement(trackingParams, selector) {
  217. return await new Promise((resolve) => {
  218. let isFinding = true;
  219. const search = () => {
  220. const el = findElementByTrackingParams(trackingParams, selector);
  221. if (el !== null) {
  222. resolve(el);
  223. isFinding = false;
  224. }
  225. if (isFinding) {
  226. setTimeout(() => {
  227. search();
  228. }, 100);
  229. }
  230. };
  231. search();
  232. });
  233. }
  234. function findElementAllByCommentId(commnetId, elementSelector) {
  235. const returnElements = [];
  236. const elems = document.querySelectorAll(elementSelector);
  237. for (let i = 0; i < elems.length; i++) {
  238. if (elems[i] !== void 0) {
  239. if (
  240. elems[i]?.__data?.data?.commentId === void 0 &&
  241. elems[i]?.polymerController?.__data?.data?.commentId === void 0
  242. ) {
  243. debugErr(new Error("Reply CommentId not found."));
  244. } else if (
  245. elems[i]?.__data?.data?.commentId !== void 0 &&
  246. elems[i].__data.data.commentId === commnetId
  247. ) {
  248. returnElements.push(elems[i]);
  249. } else if (
  250. elems[i]?.polymerController?.__data?.data?.commentId !== void 0 &&
  251. elems[i].polymerController.__data.data.commentId === commnetId
  252. ) {
  253. returnElements.push(elems[i]);
  254. }
  255. }
  256. }
  257. return returnElements;
  258. }
  259. async function reSearchElementAllByCommentId(commnetId, selector) {
  260. return await new Promise((resolve) => {
  261. let isFinding = true;
  262. const search = () => {
  263. const el = findElementAllByCommentId(commnetId, selector);
  264. if (el !== null) {
  265. resolve(el);
  266. isFinding = false;
  267. }
  268. if (isFinding) {
  269. setTimeout(() => {
  270. search();
  271. }, 100);
  272. }
  273. };
  274. search();
  275. });
  276. }
  277. async function searchTrackedParamsByObject(param, elem) {
  278. const elemObj = Object(elem);
  279. const search = (obj, history) => {
  280. Object.keys(obj).forEach((k) => {
  281. if (typeof obj[k] === "object") {
  282. search(obj[k], [...history, k]);
  283. } else if (obj[k] === param) {
  284. history.push(k);
  285. throw debugErr(
  286. new Error(`Unknown Object format!
  287. "${history.join(" > ")}"`),
  288. );
  289. }
  290. });
  291. };
  292. search(elemObj, []);
  293. }
  294.  
  295. // src/types/AppendContinuationItemsAction.ts
  296. function isReplyContinuationItemsV1(obj) {
  297. return Object.hasOwn(obj[0], "commentRenderer");
  298. }
  299. function isReplyContinuationItemsV2(obj) {
  300. return Object.hasOwn(obj[0], "commentViewModel");
  301. }
  302. function isConfinuationItemV2(obj) {
  303. return Object.hasOwn(obj, "commentViewModel");
  304. }
  305. function isConfinuationItemV1(obj) {
  306. return Object.hasOwn(obj, "comment");
  307. }
  308.  
  309. // src/utils/escapeString.ts
  310. function escapeString(text) {
  311. return text
  312. .replace(/</g, "&lt;")
  313. .replace(/>/g, "&gt;")
  314. .replace(/"/g, `&quot;`)
  315. .replace(/'/g, `&#39;`)
  316. .replace(/&/g, `&amp;`);
  317. }
  318. function decodeString(text) {
  319. return text
  320. .replace(/&lt;/g, "<")
  321. .replace(/&gt;/g, ">")
  322. .replace(/&quot;/g, `"`)
  323. .replace(/&#39;/g, `'`)
  324. .replace(/&amp;/g, `&`);
  325. }
  326.  
  327. // src/utils/getUserName.ts
  328. var isUseFeed = true;
  329. async function getUserName(id) {
  330. return new Promise((resolve) => {
  331. if (isUseFeed) {
  332. fetchFeed(id)
  333. .then((name) => {
  334. resolve(name);
  335. })
  336. .catch(() => {
  337. isUseFeed = false;
  338. debugErr(
  339. new Error("Catch Feed API Error, so change to Browse mode."),
  340. );
  341. fetchBrowse(id).then((name) => {
  342. resolve(name);
  343. });
  344. });
  345. } else {
  346. fetchBrowse(id).then((name) => {
  347. resolve(name);
  348. });
  349. }
  350. });
  351. }
  352. async function fetchFeed(id) {
  353. return await fetch(
  354. `https://www.youtube.com/feeds/videos.xml?channel_id=${id}`,
  355. {
  356. method: "GET",
  357. cache: "default",
  358. keepalive: true,
  359. },
  360. )
  361. .then(async (res) => {
  362. if (res.status !== 200)
  363. throw debugErr(
  364. new Error(`Feed API Error
  365. status: ${res.status}`),
  366. );
  367. return await res.text();
  368. })
  369. .then((text) => {
  370. const match = text.match("<title>([^<].*)</title>");
  371. if (match !== null) {
  372. return decodeString(match[1]);
  373. } else {
  374. debugErr("XML title not found");
  375. return "";
  376. }
  377. });
  378. }
  379. async function fetchBrowse(id) {
  380. return await fetch(
  381. `https://www.youtube.com/youtubei/v1/browse?key=AIzaSyAO_FJ2SlqU8Q4STEHLGCilw_Y9_11qcW8&prettyPrint=false`,
  382. {
  383. method: "POST",
  384. headers: {
  385. cache: "default",
  386. accept: "*/*",
  387. "accept-encoding": "gzip, deflate, br",
  388. "accept-language": "en",
  389. "content-type": "application/json",
  390. dnt: "1",
  391. referer: `https://www.youtube.com/channel/${id}`,
  392. },
  393. body: JSON.stringify({
  394. context: {
  395. client: {
  396. hl: window.yt.config_.HL,
  397. gl: window.yt.config_.GL,
  398. clientName: "WEB",
  399. clientVersion: "2.20230628.01.00",
  400. platform: "DESKTOP",
  401. acceptHeader:
  402. "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7",
  403. },
  404. user: { lockedSafetyMode: false },
  405. request: {
  406. useSsl: true,
  407. },
  408. },
  409. browseId: id,
  410. params: "EgVhYm91dPIGBAoCEgA%3D",
  411. }),
  412. },
  413. )
  414. .then(async (res) => {
  415. if (res.status !== 200)
  416. throw debugErr(
  417. new Error(`Browse API Error
  418. status: ${res.status}`),
  419. );
  420. return await res.json();
  421. })
  422. .then((text) => {
  423. const name = text.header.c4TabbedHeaderRenderer.title;
  424. return decodeString(name);
  425. });
  426. }
  427.  
  428. // src/rewrites/rewriteOfCommentRenderer/nameRewriteOfCommentRenderer.ts
  429. function nameRewriteOfCommentRenderer(
  430. commentRenderer,
  431. isNameContainerRender,
  432. userId,
  433. ) {
  434. const commentRendererBody =
  435. commentRenderer.__shady_native_children.namedItem("body");
  436. if (commentRendererBody === null) {
  437. throw debugErr(new Error("Comment renderer body is null."));
  438. }
  439. let nameElem = commentRendererBody.querySelector(
  440. "#main > #header > #header-author > h3 > a > yt-formatted-string",
  441. );
  442. if (isNameContainerRender) {
  443. const containerMain =
  444. commentRendererBody.__shady_native_children.namedItem("main");
  445. if (containerMain !== null) {
  446. nameElem = containerMain.querySelector(
  447. "#header > #header-author > #author-comment-badge > ytd-author-comment-badge-renderer > a > #channel-name > #container > #text-container > yt-formatted-string",
  448. );
  449. }
  450. }
  451. void getUserName(userId)
  452. .then((name) => {
  453. if (nameElem !== null) {
  454. if (nameElem.getAttribute("is-empty") !== null) {
  455. nameElem.removeAttribute("is-empty");
  456. }
  457. if (isNameContainerRender) {
  458. nameElem.textContent = escapeString(name);
  459. } else {
  460. nameElem.textContent = name;
  461. }
  462. } else {
  463. debugErr(new Error("Name element is null"));
  464. }
  465. })
  466. .catch((e) => {
  467. debugErr(e);
  468. });
  469. }
  470.  
  471. // src/rewrites/rewriteOfCommentRenderer/mentionRewriteOfCommentRenderer.ts
  472. function mentionRewriteOfCommentRenderer(commentRenderer) {
  473. const commentRendererBody =
  474. commentRenderer.__shady_native_children.namedItem("body");
  475. const main2 = commentRendererBody?.querySelector("#main");
  476. if (main2 !== void 0 && main2 !== null) {
  477. const aTags = main2.querySelectorAll(
  478. "#comment-content > ytd-expander > #content > #content-text > a",
  479. );
  480. for (let i = 0; i < aTags.length; i++) {
  481. if (aTags[i].getAttribute("href")?.match("/channel/.*") !== null) {
  482. const href = aTags[i].getAttribute("href");
  483. if (href !== null) {
  484. void getUserName(href.split("/")[2])
  485. .then((name) => {
  486. aTags[i].textContent = `@${name} `;
  487. })
  488. .catch((e) => {
  489. debugErr(e);
  490. });
  491. } else {
  492. debugErr(new Error("Mention Atag has not Href attr."));
  493. }
  494. }
  495. }
  496. }
  497. }
  498.  
  499. // src/rewrites/rewriteOfCommentRenderer/nameRewriteOfCommentViewModel.ts
  500. function isCommentViewModelElement(obj) {
  501. if (obj === null || typeof obj !== "object") {
  502. return false;
  503. }
  504. return (
  505. typeof obj.authorChannelName === "string" &&
  506. (obj.authorCommentBadge === null ||
  507. typeof obj.authorCommentBadge === "object") &&
  508. typeof obj.authorNameEndpoint === "object" &&
  509. obj.authorNameEndpoint !== null &&
  510. typeof obj.authorNameEndpoint.browseEndpoint === "object" &&
  511. obj.authorNameEndpoint.browseEndpoint !== null &&
  512. typeof obj.authorNameEndpoint.browseEndpoint.browseId === "string" &&
  513. typeof obj.authorNameEndpoint.browseEndpoint.canonicalBaseUrl ===
  514. "string"
  515. );
  516. }
  517. function nameRewriteOfCommentViewModel(commentViewModel) {
  518. const commentViewModelBody =
  519. commentViewModel.__shady_native_children.namedItem("body");
  520. if (commentViewModelBody === null) {
  521. throw debugErr(new Error("Comment view model body is null."));
  522. }
  523. const isNameContainerRender =
  524. commentViewModel.authorCommentBadge !== null;
  525. let nameElem = commentViewModel.querySelector(
  526. "#body > #main > #header > #header-author > h3 > a > span",
  527. );
  528. const userId =
  529. commentViewModel.authorNameEndpoint.browseEndpoint.browseId;
  530. const userHandle =
  531. commentViewModel.authorNameEndpoint.browseEndpoint.canonicalBaseUrl.substring(
  532. 1,
  533. );
  534. if (isNameContainerRender) {
  535. const containerMain =
  536. commentViewModelBody.__shady_native_children.namedItem("main");
  537. if (containerMain !== null) {
  538. nameElem = containerMain.querySelector(
  539. "#header > #header-author > #author-comment-badge > ytd-author-comment-badge-renderer > a > #channel-name > #container > #text-container > yt-formatted-string",
  540. );
  541. }
  542. }
  543. void getUserName(userId)
  544. .then((name) => {
  545. if (nameElem !== null) {
  546. if (nameElem.getAttribute("is-empty") !== null) {
  547. nameElem.removeAttribute("is-empty");
  548. }
  549. let innerText = name;
  550. if (window.__rycu.settings.isShowNameToHandle) {
  551. innerText = decodeURI(userHandle) + ` ( ${name} )`;
  552. }
  553. if (window.__rycu.settings.isShowHandleToName) {
  554. innerText = name + ` ( ${decodeURI(userHandle)} )`;
  555. }
  556. if (isNameContainerRender) {
  557. nameElem.textContent = escapeString(innerText);
  558. } else {
  559. nameElem.textContent = innerText;
  560. }
  561. } else {
  562. debugErr(new Error("Name element is null"));
  563. }
  564. })
  565. .catch((e) => {
  566. debugErr(e);
  567. });
  568. }
  569.  
  570. // src/rewrites/rewriteOfCommentRenderer/mentionRewriteOfCommentRendererV2.ts
  571. function mentionRewriteOfCommentRendererV2(commentRenderer) {
  572. const commentRendererBody =
  573. commentRenderer.__shady_native_children.namedItem("body");
  574. const main2 = commentRendererBody?.querySelector("#main");
  575. if (main2 !== void 0 && main2 !== null) {
  576. const aTags = main2.querySelectorAll(
  577. "#expander > #content > #content-text > span > span > a",
  578. );
  579. for (let i = 0; i < aTags.length; i++) {
  580. if (aTags[i].getAttribute("href")?.match("/channel/.*") !== null) {
  581. const href = aTags[i].getAttribute("href");
  582. if (href !== null) {
  583. void getUserName(href.split("/")[2])
  584. .then((name) => {
  585. aTags[i].textContent = `@${name} `;
  586. })
  587. .catch((e) => {
  588. debugErr(e);
  589. });
  590. } else {
  591. debugErr(new Error("Mention Atag has not Href attr."));
  592. }
  593. }
  594. }
  595. }
  596. }
  597.  
  598. // src/rewrites/reply.ts
  599. function rewriteReplytNameFromContinuationItems(continuationItems) {
  600. debugLog("Rewrite Reply.");
  601. if (isReplyContinuationItemsV1(continuationItems)) {
  602. debugLog("Rewrite reply of continuationItems.");
  603. for (let i = 0; i < continuationItems.length; i++) {
  604. const { commentRenderer } = continuationItems[i];
  605. if (commentRenderer !== void 0) {
  606. void getReplyElem(commentRenderer.trackingParams, "V1").then(
  607. (replyElem) => {
  608. reWriteReplyElem(replyElem, commentRenderer);
  609. },
  610. );
  611. }
  612. }
  613. }
  614. if (isReplyContinuationItemsV2(continuationItems)) {
  615. debugLog("Rewrite reply of comment view model.");
  616. for (let i = 0; i < continuationItems.length; i++) {
  617. const { commentViewModel } = continuationItems[i];
  618. if (commentViewModel !== void 0) {
  619. void getReplyElem(
  620. commentViewModel.rendererContext.loggingContext.loggingDirectives
  621. .trackingParams,
  622. "V2",
  623. ).then((replyElem) => {
  624. reWriteReplyElemV2(replyElem);
  625. });
  626. }
  627. }
  628. }
  629. }
  630. function reWriteReplyElem(replyElem, rendererData) {
  631. let isContainer = rendererData.authorIsChannelOwner;
  632. if (rendererData.authorCommentBadge !== void 0) {
  633. isContainer = true;
  634. }
  635. nameRewriteOfCommentRenderer(
  636. replyElem,
  637. isContainer,
  638. rendererData.authorEndpoint.browseEndpoint.browseId,
  639. );
  640. mentionRewriteOfCommentRenderer(replyElem);
  641. replyInputRewrite(replyElem);
  642. }
  643. function reWriteReplyElemV2(replyElem) {
  644. nameRewriteOfCommentViewModel(replyElem);
  645. mentionRewriteOfCommentRendererV2(replyElem);
  646. replyInputRewrite(replyElem);
  647. }
  648. async function getReplyElem(trackedParams, version) {
  649. return await new Promise((resolve) => {
  650. const selector =
  651. "#replies > ytd-comment-replies-renderer > #expander > #expander-contents > #contents > " +
  652. (version === "V1"
  653. ? "ytd-comment-renderer"
  654. : "ytd-comment-view-model");
  655. const commentRenderer = findElementByTrackingParams(
  656. trackedParams,
  657. selector,
  658. );
  659. if (commentRenderer !== null) {
  660. resolve(commentRenderer);
  661. } else {
  662. void reSearchElement(trackedParams, selector).then(
  663. (commentRenderer2) => {
  664. resolve(commentRenderer2);
  665. },
  666. );
  667. }
  668. });
  669. }
  670. function rewriteTeaserReplytNameFromContinuationItems(continuationItems) {
  671. debugLog("Rewrite teaser Reply.");
  672. for (let i = 0; i < continuationItems.length; i++) {
  673. if (isReplyContinuationItemsV1(continuationItems)) {
  674. debugLog("Teaser reply of continuationItems.");
  675. const { commentRenderer } = continuationItems[i];
  676. if (commentRenderer !== void 0) {
  677. void reSearchElementAllByCommentId(
  678. commentRenderer.commentId,
  679. "ytd-comment-replies-renderer > #teaser-replies > ytd-comment-renderer",
  680. ).then((replyElems) => {
  681. replyElems.forEach((replyElem) => {
  682. reWriteReplyElem(replyElem, commentRenderer);
  683. });
  684. });
  685. void reSearchElementAllByCommentId(
  686. commentRenderer.commentId,
  687. "ytd-comment-replies-renderer > #expander > #expander-contents > #contents > ytd-comment-renderer",
  688. ).then((replyElems) => {
  689. replyElems.forEach((replyElem) => {
  690. reWriteReplyElem(replyElem, commentRenderer);
  691. });
  692. });
  693. }
  694. }
  695. if (isReplyContinuationItemsV2(continuationItems)) {
  696. debugLog("Teaser reply of comment view model.");
  697. const { commentViewModel } = continuationItems[i];
  698. if (commentViewModel !== void 0) {
  699. const elem = findElementByTrackingParams(
  700. commentViewModel.rendererContext.loggingContext.loggingDirectives
  701. .trackingParams,
  702. "#teaser-replies > ytd-comment-view-model",
  703. );
  704. if (elem === null) {
  705. throw debugErr(
  706. new Error("Can not found Teaser Reply in V2 Elem."),
  707. );
  708. }
  709. reWriteReplyElemV2(elem);
  710. }
  711. }
  712. }
  713. }
  714. function replyInputRewrite(replyElem) {
  715. const replyToReplyBtn = replyElem.querySelector(
  716. "#reply-button-end > ytd-button-renderer",
  717. );
  718. const replyToReplyHander = () => {
  719. const replyLink = replyElem.querySelector("#contenteditable-root > a");
  720. const href = replyLink?.getAttribute("href");
  721. const channelId = href?.split("/")[2];
  722. if (channelId !== void 0 && replyLink !== null) {
  723. void getUserName(channelId).then((name) => {
  724. replyLink.textContent = ` @${name}`;
  725. });
  726. }
  727. replyToReplyBtn?.removeEventListener("click", replyToReplyHander);
  728. };
  729. replyToReplyBtn?.addEventListener("click", replyToReplyHander);
  730. document.addEventListener("rycu-pagechange", () => {
  731. replyToReplyBtn?.removeEventListener("click", replyToReplyHander);
  732. });
  733. }
  734.  
  735. // src/rewrites/comment.ts
  736. function rewriteCommentNameFromContinuationItems(continuationItems) {
  737. debugLog("Rewrite Comment.");
  738. for (let i = 0; i < continuationItems.length; i++) {
  739. if (continuationItems[i].commentThreadRenderer !== void 0) {
  740. void getCommentElem(
  741. continuationItems[i].commentThreadRenderer.trackingParams,
  742. ).then((commentElem) => {
  743. reWriteCommentElem(
  744. commentElem,
  745. continuationItems[i].commentThreadRenderer,
  746. );
  747. });
  748. const teaserContents =
  749. continuationItems[i].commentThreadRenderer.replies
  750. ?.commentRepliesRenderer.teaserContents;
  751. if (teaserContents !== void 0) {
  752. rewriteTeaserReplytNameFromContinuationItems(teaserContents);
  753. }
  754. }
  755. }
  756. }
  757. function reWriteCommentElem(commentElem, commentThreadRenderer) {
  758. const commentRenderer =
  759. commentElem.__shady_native_children.namedItem("comment");
  760. if (commentRenderer !== null && commentRenderer !== void 0) {
  761. if (isConfinuationItemV1(commentThreadRenderer)) {
  762. debugLog("Rewrite of Comment Renderer.");
  763. let isContainer =
  764. commentThreadRenderer.comment.commentRenderer.authorIsChannelOwner;
  765. if (
  766. commentThreadRenderer.comment.commentRenderer.authorCommentBadge !==
  767. void 0
  768. ) {
  769. isContainer = true;
  770. }
  771. nameRewriteOfCommentRenderer(
  772. commentRenderer,
  773. isContainer,
  774. commentThreadRenderer.comment.commentRenderer.authorEndpoint
  775. .browseEndpoint.browseId,
  776. );
  777. }
  778. if (isConfinuationItemV2(commentThreadRenderer)) {
  779. debugLog("Rewriteing a comment by using comment view model.");
  780. if (isCommentViewModelElement(commentRenderer)) {
  781. nameRewriteOfCommentViewModel(commentRenderer);
  782. } else {
  783. debugErr("It type is not comment view model.");
  784. }
  785. }
  786. }
  787. }
  788. async function getCommentElem(trackingParams) {
  789. return await new Promise((resolve) => {
  790. const commentElem = findElementByTrackingParams(
  791. trackingParams,
  792. "#comments > #sections > #contents > ytd-comment-thread-renderer",
  793. );
  794. if (commentElem !== null) {
  795. resolve(commentElem);
  796. } else {
  797. void reSearchElement(trackingParams, "ytd-comment-thread-renderer")
  798. .then((commentElem2) => {
  799. resolve(commentElem2);
  800. })
  801. .catch((e) => {
  802. debugErr(e);
  803. });
  804. }
  805. });
  806. }
  807.  
  808. // src/handlers/handleYtAppendContinuationItemsAction.ts
  809. function handleYtAppendContinuationItemsAction(detail) {
  810. const continuationItems =
  811. detail.args[0].appendContinuationItemsAction.continuationItems;
  812. if (
  813. isCommentRenderer(continuationItems) ||
  814. isCommentRendererV2(continuationItems)
  815. ) {
  816. const replyDetail = detail;
  817. setTimeout(() => {
  818. rewriteReplytNameFromContinuationItems(
  819. replyDetail.args[0].appendContinuationItemsAction.continuationItems,
  820. );
  821. }, 100);
  822. } else {
  823. const commentDetail = detail;
  824. setTimeout(() => {
  825. rewriteCommentNameFromContinuationItems(
  826. commentDetail.args[0].appendContinuationItemsAction
  827. .continuationItems,
  828. );
  829. }, 400);
  830. }
  831. }
  832.  
  833. // src/handlers/handleYtCreateCommentAction.ts
  834. function handleYtCreateCommentAction(detail) {
  835. const createCommentDetail = detail;
  836. const continuationItems = [
  837. {
  838. commentThreadRenderer:
  839. createCommentDetail.args[0].createCommentAction.contents
  840. .commentThreadRenderer,
  841. },
  842. ];
  843. setTimeout(() => {
  844. rewriteCommentNameFromContinuationItems(continuationItems);
  845. }, 100);
  846. }
  847.  
  848. // src/handlers/handleYtCreateCommentReplyAction.ts
  849. function handleYtCreateCommentReplyAction(detail) {
  850. const createReplyDetail = detail;
  851. const continuationItems = [
  852. {
  853. commentRenderer:
  854. createReplyDetail.args[0].createCommentReplyAction.contents
  855. .commentRenderer,
  856. },
  857. ];
  858. setTimeout(() => {
  859. rewriteTeaserReplytNameFromContinuationItems(continuationItems);
  860. }, 100);
  861. }
  862.  
  863. // src/rewrites/highlightedReply.ts
  864. function rewriteHighlightedReply(trackedParams) {
  865. getReplyElem2(trackedParams, "V1").then((replyElem) => {
  866. reWriteReplyElemV2(replyElem);
  867. });
  868. }
  869. function rewriteHighlightedReplyV2(trackedParams) {
  870. getReplyElem2(trackedParams, "V2").then((replyElem) => {
  871. reWriteReplyElemV2(replyElem);
  872. });
  873. }
  874. async function getReplyElem2(trackedParams, version) {
  875. return await new Promise((resolve) => {
  876. const selector =
  877. "ytd-comment-replies-renderer > #teaser-replies > " +
  878. (version === "V1"
  879. ? "ytd-comment-renderer"
  880. : "ytd-comment-view-model");
  881. const commentRenderer = findElementByTrackingParams(
  882. trackedParams,
  883. selector,
  884. );
  885. if (commentRenderer !== null) {
  886. resolve(commentRenderer);
  887. } else {
  888. void reSearchElement(trackedParams, selector).then(
  889. (commentRenderer2) => {
  890. resolve(commentRenderer2);
  891. },
  892. );
  893. }
  894. });
  895. }
  896.  
  897. // src/handlers/handleYtGetMultiPageMenuAction.ts
  898. function handleYtGetMultiPageMenuAction(detail) {
  899. debugLog("handleYtGetMultiPageMenuAction");
  900. const getMultiPageMenuDetail = detail;
  901. const continuationItems =
  902. getMultiPageMenuDetail.args[0].getMultiPageMenuAction.menu
  903. .multiPageMenuRenderer.sections[1].itemSectionRenderer?.contents;
  904. const highLightedTeaserContents =
  905. getMultiPageMenuDetail.args[0]?.getMultiPageMenuAction?.menu
  906. ?.multiPageMenuRenderer.sections[1].itemSectionRenderer?.contents[0]
  907. ?.commentThreadRenderer.replies?.commentRepliesRenderer
  908. ?.teaserContents;
  909. if (continuationItems !== void 0) {
  910. setTimeout(() => {
  911. rewriteCommentNameFromContinuationItems(continuationItems);
  912. if (highLightedTeaserContents !== void 0) {
  913. debugLog("HighLighted Teaser Reply found.");
  914. if (isReplyContinuationItemsV1(highLightedTeaserContents)) {
  915. debugLog("highLighted Teaser Reply V1");
  916. const highLightedReplyRenderer =
  917. highLightedTeaserContents[0]?.commentRenderer;
  918. rewriteHighlightedReply(highLightedReplyRenderer.trackingParams);
  919. } else {
  920. debugLog("highLighted Teaser Reply V2");
  921. const commentViewModel =
  922. highLightedTeaserContents[0]?.commentViewModel;
  923. const trackingParams =
  924. commentViewModel.rendererContext.loggingContext
  925. .loggingDirectives.trackingParams;
  926. rewriteHighlightedReplyV2(trackingParams);
  927. }
  928. }
  929. }, 100);
  930. }
  931. }
  932.  
  933. // src/handlers/handleYtHistory.ts
  934. function handleYtHistory(detail) {
  935. const historyDetail = detail;
  936. const continuationItems =
  937. historyDetail.args[1].historyEntry?.rootData.response.contents
  938. .twoColumnWatchNextResults?.results?.results?.contents[3]
  939. ?.itemSectionRenderer?.contents;
  940. if (continuationItems !== void 0) {
  941. setTimeout(() => {
  942. rewriteCommentNameFromContinuationItems(continuationItems);
  943. }, 100);
  944. }
  945. }
  946.  
  947. // src/handlers/handleYtReloadContinuationItemsCommand.ts
  948. function handleYtReloadContinuationItemsCommand(detail) {
  949. const reloadDetail = detail;
  950. const { slot } = reloadDetail.args[0].reloadContinuationItemsCommand;
  951. if (slot === "RELOAD_CONTINUATION_SLOT_BODY") {
  952. const continuationItems =
  953. reloadDetail.args[0].reloadContinuationItemsCommand.continuationItems;
  954. if (continuationItems !== void 0) {
  955. setTimeout(() => {
  956. rewriteCommentNameFromContinuationItems(continuationItems);
  957. }, 100);
  958. }
  959. }
  960. }
  961.  
  962. // src/index.ts
  963. function main() {
  964. const settings = {
  965. isShowHandleToName: false,
  966. isShowNameToHandle: false,
  967. };
  968. window.__rycu = {
  969. settings,
  970. };
  971. if (getRunningRuntime() === "Extension") {
  972. bypassSendMessage(
  973. {
  974. type: "getShowHandleToName",
  975. value: null,
  976. },
  977. {},
  978. (isShowHandleToName) => {
  979. window.__rycu.settings.isShowHandleToName = isShowHandleToName;
  980. },
  981. );
  982. bypassSendMessage(
  983. {
  984. type: "getShowNameToHandle",
  985. value: null,
  986. },
  987. {},
  988. (isShowNameToHandle) => {
  989. window.__rycu.settings.isShowNameToHandle = isShowNameToHandle;
  990. },
  991. );
  992. }
  993. const handleYtAction = (e) => {
  994. switch (e.detail.actionName) {
  995. case "yt-append-continuation-items-action":
  996. handleYtAppendContinuationItemsAction(e.detail);
  997. break;
  998. case "yt-reload-continuation-items-command":
  999. handleYtReloadContinuationItemsCommand(e.detail);
  1000. break;
  1001. case "yt-history-load":
  1002. handleYtHistory(e.detail);
  1003. break;
  1004. case "yt-get-multi-page-menu-action":
  1005. handleYtGetMultiPageMenuAction(e.detail);
  1006. break;
  1007. case "yt-create-comment-action":
  1008. handleYtCreateCommentAction(e.detail);
  1009. break;
  1010. case "yt-create-comment-reply-action":
  1011. handleYtCreateCommentReplyAction(e.detail);
  1012. break;
  1013. }
  1014. };
  1015. document.addEventListener("yt-action", handleYtAction);
  1016. document.addEventListener("yt-navigate-finish", () => {
  1017. document.dispatchEvent(new Event("rycu-pagechange"));
  1018. outputDebugInfo();
  1019. });
  1020. }
  1021. main();
  1022. })();
  1023. }
  1024.  
  1025. if (location.href.match("https://www.youtube.com/*") !== null) {
  1026. document.addEventListener("DOMContentLoaded", () => {
  1027. const script = document.createElement("script");
  1028. if (unsafeWindow.trustedTypes !== undefined) {
  1029. const policy = unsafeWindow.trustedTypes.createPolicy(
  1030. "crx-monkey-trusted-inject-policy",
  1031. { createScript: (input) => input },
  1032. );
  1033. script.text = policy.createScript(
  1034. script.text + `(${c3JjL2luZGV4LnRz.toString()})();`,
  1035. );
  1036. } else {
  1037. script.innerHTML =
  1038. script.innerHTML + `(${c3JjL2luZGV4LnRz.toString()})();`;
  1039. }
  1040. unsafeWindow.document.body.appendChild(script);
  1041. });
  1042. }