hololyzer 排序工具

让 hololyzer 的超级留言清单支持姓名排序功能

目前为 2023-03-08 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name hololyzer Sort Tool
  3. // @name:zh hololyzer 排序工具
  4. // @name:zh-TW hololyzer 排序工具
  5. // @name:zh-HK hololyzer 排序工具
  6. // @name:zh-CN hololyzer 排序工具
  7. // @name:zh-SG hololyzer 排序工具
  8. // @name:en hololyzer Sort Tool
  9. // @name:ja hololyzerのソートツール
  10. // @name:ko hololyzer 정렬 도구
  11. // @name:fr Outil de tri hololyzer
  12. // @name:es Herramienta de ordenación hololyzer
  13. // @name:ar أداة ترتيب هولوليزر
  14. // @name:de hololyzer-Sortierwerkzeug
  15. // @name:hi होलोलाइज़र सॉर्ट टूल
  16. // @name:ru Инструмент сортировки hololyzer
  17. // @name:pt Ferramenta de classificação hololyzer
  18. // @name:sw Chombo cha utaratibu wa hololyzer
  19. // @name:sr Alat za sortiranje hololajzera
  20. // @name:hr Alat za sortiranje hololajzera
  21. // @name:it Strumento di ordinamento hololyzer
  22. // @name:ms Alat penyusunan hololyzer
  23. // @name:id Alat pengurutan hololyzer
  24. // @name:nl hololyzer sorteertool
  25. // @name:fa ابزار مرتب سازی hololyzer
  26. // @namespace https://github.com/kevin823lin
  27. // @version 0.3
  28. // @description Add support for sorting hololyzer's super chat list by name.
  29. // @description:zh 讓 hololyzer 的超級留言清單支援姓名排序功能
  30. // @description:zh-TW 讓 hololyzer 的超級留言清單支援姓名排序功能
  31. // @description:zh-HK 讓 hololyzer 的超級留言清單支援姓名排序功能
  32. // @description:zh-CN 让 hololyzer 的超级留言清单支持姓名排序功能
  33. // @description:zh-SG 让 hololyzer 的超级留言清单支持姓名排序功能
  34. // @description:en Add support for sorting hololyzer's super chat list by name.
  35. // @description:ja hololyzerのスーパーチャットリストを名前でソートする機能を追加する
  36. // @description:ko hololyzer의 슈퍼 챗 목록을 이름별로 정렬하는 기능
  37. // @description:fr Ajouter la prise en charge du tri de la liste de super chats hololyzer par nom.
  38. // @description:es Agregar soporte para ordenar la lista de super chats de hololyzer por nombre.
  39. // @description:ar إضافة دعم لترتيب قائمة الدردشة الخارقة لـ hololyzer حسب الاسم.
  40. // @description:de Unterstützung zum Sortieren der Super-Chat-Liste von hololyzer nach Namen hinzufügen.
  41. // @description:hi नाम द्वारा होलोलाइजर के सुपर चैट सूची को सॉर्ट करने के लिए समर्थन जोड़ें।
  42. // @description:ru Добавить поддержку сортировки списка супер-чатов hololyzer по имени.
  43. // @description:pt Adicionar suporte para classificar a lista de superchats do hololyzer por nome.
  44. // @description:sw Ongeza msaada wa kupanga orodha ya gumzo kuu la hololyzer kwa jina.
  45. // @description:sr Dodajte podršku za sortiranje super-čet liste hololyzer-a po imenu.
  46. // @description:hr Dodajte podršku za sortiranje super chat liste hololyzer po imenu.
  47. // @description:it Aggiungere il supporto per ordinare l'elenco dei super chat di hololyzer per nome.
  48. // @description:ms Tambah sokongan untuk mengurutkan senarai sembang super hololyzer mengikut nama.
  49. // @description:id Tambahkan dukungan untuk mengurutkan daftar super chat hololyzer berdasarkan nama.
  50. // @description:nl Voeg ondersteuning toe voor het sorteren van de superchatlijst van hololyzer op naam.
  51. // @description:fa پشتیبانی از مرتب سازی لیست چت های فوق العاده hololyzer بر اساس نام.
  52. // @author kevin823lin
  53. // @match https://www.hololyzer.net/*/superchat/*
  54. // @icon https://www.google.com/s2/favicons?domain=hololyzer.net
  55. // @grant none
  56. // @date 2023-03-08
  57. // ==/UserScript==
  58. /*Translate and optimize with ChatGPT*/
  59.  
  60. (function () {
  61. 'use strict';
  62.  
  63. // Your code here...
  64. init();
  65.  
  66. function i18n(name, param) {
  67. const lang = navigator.appName == "Netscape" ? navigator.language : navigator.userLanguage;
  68. let config = {};
  69. switch (lang) {
  70. case "zh":
  71. case "zh-TW":
  72. case "zh-HK":
  73. config = {
  74. sortByTime: "時間排序",
  75. sortByName: "姓名排序",
  76. copyTable: "複製表格",
  77. copyFailed: "複製失敗"
  78. };
  79. break;
  80. case "zh-CN":
  81. case "zh-SG":
  82. config = {
  83. sortByTime: "时间排序",
  84. sortByName: "姓名排序",
  85. copyTable: "复制表格",
  86. copyFailed: "复制失败"
  87. };
  88. break;
  89. case "en":
  90. config = {
  91. sortByTime: "Sort by time",
  92. sortByName: "Sort by name",
  93. copyTable: "Copy table",
  94. copyFailed: "Copy failed"
  95. };
  96. break;
  97. case "ja":
  98. config = {
  99. sortByTime: "時間で並び替え",
  100. sortByName: "名前で並び替え",
  101. copyTable: "表をコピーする",
  102. copyFailed: "コピーに失敗しました"
  103. };
  104. break;
  105.  
  106. case "ko":
  107. config = {
  108. sortByTime: "시간순 정렬",
  109. sortByName: "이름순 정렬",
  110. copyTable: "표 복사하기",
  111. copyFailed: "복사 실패"
  112. };
  113. break;
  114. case "fr":
  115. config = {
  116. sortByTime: "Trier par temps",
  117. sortByName: "Trier par nom",
  118. copyTable: "Copier le tableau",
  119. copyFailed: "Copie échouée"
  120. };
  121. break;
  122. case "es":
  123. config = {
  124. sortByTime: "Ordenar por tiempo",
  125. sortByName: "Ordenar por nombre",
  126. copyTable: "Copiar tabla",
  127. copyFailed: "Error al copiar"
  128. };
  129. break;
  130. case "ar":
  131. config = {
  132. sortByTime: "ترتيب حسب الوقت",
  133. sortByName: "ترتيب حسب الاسم",
  134. copyTable: "نسخ الجدول",
  135. copyFailed: "فشل النسخ"
  136. };
  137. break;
  138. case "de":
  139. config = {
  140. sortByTime: "Nach Zeit sortieren",
  141. sortByName: "Nach Name sortieren",
  142. copyTable: "Tabelle kopieren",
  143. copyFailed: "Kopieren fehlgeschlagen"
  144. };
  145. break;
  146. case "hi":
  147. config = {
  148. sortByTime: "समय के अनुसार क्रमबद्ध करें",
  149. sortByName: "नाम के अनुसार क्रमबद्ध करें",
  150. copyTable: "टेबल कॉपी करें",
  151. copyFailed: "कॉपी असफल"
  152. };
  153. break;
  154. case "ru":
  155. config = {
  156. sortByTime: "Сортировать по времени",
  157. sortByName: "Сортировать по имени",
  158. copyTable: "Копировать таблицу",
  159. copyFailed: "Ошибка копирования"
  160. };
  161. break;
  162. case "pt":
  163. config = {
  164. sortByTime: "Ordenar por hora",
  165. sortByName: "Ordenar por nome",
  166. copyTable: "Copiar tabela",
  167. copyFailed: "Falha ao copiar"
  168. };
  169. break;
  170. case "sw":
  171. config = {
  172. sortByTime: "Sort by Time",
  173. sortByName: "Sort by Name",
  174. copyTable: "Copy Table",
  175. copyFailed: "Kushindwa nakala"
  176. };
  177. break;
  178. case "sr":
  179. case "hr":
  180. case "it":
  181. config = {
  182. sortByTime: "Sortiraj po vremenu",
  183. sortByName: "Sortiraj po imenu",
  184. copyTable: "Kopiraj tabelu",
  185. copyFailed: "Kopiranje nije uspelo"
  186. };
  187. break;
  188. case "ms":
  189. case "id":
  190. config = {
  191. sortByTime: "Susun mengikut masa",
  192. sortByName: "Susun mengikut nama",
  193. copyTable: "Salin Jadual",
  194. copyFailed: "Salinan gagal"
  195. };
  196. break;
  197. case "nl":
  198. config = {
  199. sortByTime: "Sorteren op tijd",
  200. sortByName: "Sorteren op naam",
  201. copyTable: "Tabel kopiëren",
  202. copyFailed: "Kopiëren mislukt"
  203. };
  204. break;
  205. case "fa":
  206. config = {
  207. sortByTime: "مرتب سازی بر اساس زمان",
  208. sortByName: "مرتب سازی بر اساس نام",
  209. copyTable: "رونوشت جدول",
  210. copyFailed: "کپی ناموفق"
  211. };
  212. break;
  213. default:
  214. config = {
  215. sortByTime: "Sort by time",
  216. sortByName: "Sort by name",
  217. copyTable: "Copy table",
  218. copyFailed: "Copy failed"
  219. };
  220. break;
  221. }
  222. return config[name] ? config[name].replace("#t#", param) : name;
  223. }
  224.  
  225. async function init() {
  226. document.body.dataset.sortBy = "time";
  227. insertButton();
  228. await waitElementsLoaded('table[border]');
  229. const { tbody, newTbody } = getTbodyAndFakeTbody();
  230. insertNoByBame(newTbody);
  231. replaceTbody(tbody, newTbody);
  232. }
  233.  
  234. function insertButton() {
  235. const sortByTimeBtn = document.createElement('button');
  236. const sortByNameBtn = document.createElement('button');
  237. const copyTableBtn = document.createElement('button');
  238. sortByTimeBtn.innerText = i18n('sortByTime');
  239. sortByNameBtn.innerText = i18n('sortByName');
  240. copyTableBtn.innerText = i18n('copyTable');
  241. sortByTimeBtn.addEventListener("click", function () {
  242. if (document.body.dataset.sortBy !== "time") {
  243. document.body.dataset.sortBy = "time";
  244. main("time");
  245. }
  246. });
  247. sortByNameBtn.addEventListener("click", async function () {
  248. if ((document.body.dataset.sortBy) !== "name") {
  249. document.body.dataset.sortBy = "name";
  250. main("name");
  251. }
  252. });
  253. copyTableBtn.addEventListener("click", function () {
  254. copyTable();
  255. });
  256. document.body.insertAdjacentElement('afterbegin', copyTableBtn);
  257. document.body.insertAdjacentElement('afterbegin', sortByNameBtn);
  258. document.body.insertAdjacentElement('afterbegin', sortByTimeBtn);
  259. }
  260.  
  261. function insertNoByBame(tbody) {
  262. const ths = [...tbody.querySelectorAll("th")];
  263. const trs = [...tbody.querySelectorAll("tr:has(>td):nth-child(odd)")];
  264.  
  265. const insertIndex = ((index) => index === -1 ? 0 : index)(ths.findIndex(th => /^(n|N)o$/.test(th.innerText)));
  266. const sortIndex = ((index) => index === -1 ? 6 : index)(ths.findIndex(th => /name|チャンネル名/.test(th.innerText)));
  267.  
  268. const sortedTrs = sortTrs(trs, sortIndex);
  269.  
  270. let count = 0;
  271. const countList = sortedTrs.map((tr, i) => {
  272. const preName = sortedTrs[i - 1]?.element.children[sortIndex]?.childNodes[0].textContent;
  273. const name = tr.element.children[sortIndex]?.childNodes[0].textContent;
  274. return preName !== name ? ++count : count;
  275. });
  276.  
  277. sortedTrs.forEach((tr, i) => {
  278. const insertEle = tr.element.insertCell(insertIndex + 1);
  279. insertEle.innerText = countList[i];
  280. insertEle.setAttribute('rowspan', 2);
  281. insertEle.style.textAlign = "right";
  282. });
  283.  
  284. const parentRow = tbody.querySelector("tr");
  285. const childrenEle = parentRow.children[insertIndex + 1];
  286. const insertEle = document.createElement("th");
  287. insertEle.innerText = "no by name";
  288. insertEle.setAttribute('rowspan', 2);
  289. parentRow.insertBefore(insertEle, childrenEle);
  290. }
  291.  
  292. async function main(sortBy) {
  293. const { tbody, newTbody } = getTbodyAndFakeTbody();
  294. sort(newTbody, sortBy);
  295. replaceTbody(tbody, newTbody);
  296. }
  297.  
  298. function sort(tbody, sortBy) {
  299. const trs = [...tbody.querySelectorAll("tr:has(>td):nth-child(odd)")];
  300.  
  301. let sortIndex;
  302.  
  303. switch (sortBy) {
  304. case "time":
  305. sortIndex = ((index) => index === -1 ? 0 : index)([...tbody.querySelectorAll('th')].findIndex(th => /^(n|N)o$/.test(th.innerText)));
  306. break;
  307. case "name":
  308. sortIndex = ((index) => index === -1 ? 1 : index)([...tbody.querySelectorAll('th')].findIndex(th => /no by name/.test(th.innerText)));
  309. break;
  310. }
  311. const sortedTrs = sortTrs(trs, sortIndex, true);
  312.  
  313. sortedTrs.forEach(item => { tbody.appendChild(item.element); tbody.appendChild(item.nextElementSibling) });
  314. }
  315.  
  316. function sortTrs(trs, sortIndex, num = false) {
  317. return trs.map(tr => ({
  318. element: tr,
  319. previousElementSibling: tr.previousElementSibling,
  320. nextElementSibling: tr.nextElementSibling,
  321. key: tr.children[sortIndex].innerText
  322. })).sort((a, b) => num ? (a.key - b.key) : a.key.localeCompare(b.key, 'ja'));
  323. }
  324.  
  325. function getTbodyAndFakeTbody() {
  326. const tbody = document.querySelector('table[border] > tbody');
  327.  
  328. const clonedTbody = tbody.cloneNode(true);
  329. const fragment = new DocumentFragment();
  330. fragment.append(clonedTbody);
  331.  
  332. const newTbody = fragment.querySelector('tbody')
  333.  
  334. return { tbody, newTbody };
  335. }
  336.  
  337. function replaceTbody(tbody, newTbody) {
  338. tbody.replaceWith(newTbody);
  339. }
  340.  
  341. function copyTable() {
  342. copyElement(document.querySelector('table[border] > tbody'));
  343. }
  344.  
  345. function copyElement(ele) {
  346. if (document.createRange && window.getSelection) {
  347. const sel = window.getSelection();
  348. const oldRange = Array.from({ length: sel.rangeCount }, (_, i) => sel.getRangeAt(i));
  349. const copyRange = document.createRange();
  350. sel.removeAllRanges();
  351. try {
  352. copyRange.selectNode(ele);
  353. sel.addRange(copyRange);
  354. } catch (e) {
  355. copyRange.selectNodeContents(ele);
  356. sel.addRange(copyRange);
  357. }
  358. navigator.clipboard.writeText(sel.toString());
  359. sel.removeAllRanges();
  360. oldRange.forEach(range => { sel.addRange(range) });
  361. } else {
  362. alert(i18n('copyFailed'));
  363. }
  364. }
  365.  
  366. function waitElementsLoaded(...eles) {
  367. return Promise.all(eles.map(ele => {
  368. return new Promise(async resolve => {
  369. while (!document.querySelector(ele)) {
  370. await wait(100);
  371. }
  372. resolve();
  373. });
  374. }));
  375. }
  376.  
  377. function wait(ms) {
  378. try {
  379. return new Promise(r => setTimeout(r, ms));
  380. } catch (e) {
  381. console.error(`wait: ${e}`);
  382. }
  383. }
  384. })();