Get Twitch Emotes

整合twitch表情

  1. // ==UserScript==
  2. // @name Get Twitch Emotes
  3. // @namespace http://tampermonkey.net/
  4. // @version 1.0.1
  5. // @description 整合twitch表情
  6. // @author Lee-7723
  7. // @match https://www.twitch.tv/*
  8. // @icon https://static.twitchcdn.net/assets/favicon-16-52e571ffea063af7a7f4.png
  9. // @grant none
  10. // @require https://cdn.jsdelivr.net/npm/jszip@3.10.1/dist/jszip.min.js
  11. // @require https://cdn.jsdelivr.net/npm/file-saver@2.0.5/dist/FileSaver.min.js
  12.  
  13. // ==/UserScript==
  14.  
  15. 'use strict';
  16.  
  17. function sleep(ms) {
  18. return new Promise((resolve) => setTimeout(resolve, ms));
  19. }
  20.  
  21. var body = document.getElementsByTagName('body')[0];
  22. var emote_dict = new Object();
  23. var old_href = document.location.href;
  24. // var emote_btn;
  25. // var all_emotes = new Array();
  26. // var emotes;
  27.  
  28. let css = document.createElement('style');
  29. css.innerHTML = `
  30. #emote_url_box {
  31. position: absolute;
  32. top: 5rem; left: 25%;
  33. padding: 10px;
  34. z-index: 9999;
  35. height: 70%;
  36. width: 50%;
  37. min-height: 30rem;
  38. min-width: 50rem;
  39. resize: both;
  40. overflow: scroll;
  41. background-color: var(--color-background-alt);
  42. border-radius: 10px;
  43. border: 1.6px solid var(--color-background-pill);
  44. box-shadow: 4px 4px 10px #000a;
  45. }
  46. #emote_url_text {
  47. height: calc(100% - 8.5rem); width: 100%;
  48. resize: none;
  49. padding: 12px;
  50. background-color: var(--color-background-input);
  51. color: var(--color-text-input);
  52. overflow: scroll;
  53. display: flex;
  54. }
  55. #emote_url_box > p:last-of-type {
  56. margin-top: 0.5rem;
  57. position: relative;
  58. }
  59. #emote_tool_header {
  60. display: flex;
  61. justify-content: space-between;
  62. margin-bottom: 0.5rem;
  63. font-weight: bold;
  64. }
  65. #emote_url_box > :is(button, a) {
  66. position: relative;
  67. right: 8px;
  68. bottom: 0rem;
  69. border-radius: 6px;
  70. margin: 0 0.5rem;
  71. padding: 1rem;
  72. float: right;
  73. }
  74. #emote_url_preview {
  75. height: calc(100% - 8.5rem); width: 100%;
  76. resize: none;
  77. padding: 12px;
  78. background-color: var(--color-background-input);
  79. color: var(--color-text-input);
  80. border: 1px solid rgb(118, 118, 118);
  81. border-radius: 2px;
  82. overflow-y: scroll;
  83. display: flex;
  84. flex-wrap: wrap;
  85. justify-content: center;
  86. }
  87. .emote_preview {
  88. margin: 5px 5px 1px;
  89. border: 2px solid #FFF;
  90. border-radius: 4px;
  91. width: 112px; height: 112px;">
  92. <p style="text-align: center; margin-bottom: 3px
  93. }
  94.  
  95. `;
  96. body.appendChild(css)
  97.  
  98. async function get_emote_picker_button() {//监视获取聊天区的表情按钮
  99. 'use strict';
  100. let emote_btn;
  101. for (var i = 0; !emote_btn; i) {
  102. await sleep(1000);
  103. emote_btn = document.querySelectorAll('button[data-a-target="emote-picker-button"]')[0];
  104. //console.log(emote_btn)
  105. }
  106. return emote_btn;
  107. }
  108.  
  109.  
  110. get_emote_after_button();
  111. async function get_emote_after_button() {//加载出表情按钮之后
  112. let emote_btn = await get_emote_picker_button();
  113. // console.log(emote_btn);
  114. emote_btn.onclick = function () { output() }//给聊天区的表情按钮添加功能,指向output()
  115. }
  116.  
  117.  
  118. async function get_emotes() {//获取表情名img_alt,及表情链接img_srcset
  119. let emotes_grid;
  120. while (!emotes_grid) {
  121. console.log('fetching emotes')
  122. await sleep(1000);
  123. var emote_grid_header = document.getElementsByClassName('emote-grid-section__header-title');
  124. if (emote_grid_header.length == 1) emotes_grid = emote_grid_header[0].parentElement;
  125. else emotes_grid = emote_grid_header[1].parentElement;
  126. };
  127.  
  128. let emote_imgs = emotes_grid.getElementsByTagName("img");
  129. let emote_dict = new Object();
  130. for (let j of emote_imgs) {
  131. emote_dict[j.alt] = j.src.replace('1.0', '3.0');
  132. }
  133. console.log(emote_dict);
  134. return emote_dict
  135. }
  136.  
  137.  
  138. async function output() {
  139. emote_dict = await get_emotes();//等待获取表情完成
  140. let emote_json = JSON.stringify(emote_dict);
  141. var button_class_name = document.querySelectorAll('.chat-input__buttons-container > div:last-of-type button')[1].className;
  142. let emote_grid_header = document.querySelectorAll('.emote-grid-section__header-title')
  143. if (emote_grid_header.length == 1) emote_grid_header = emote_grid_header[0];//未登录(不可用)状态表情区只有一个分类
  144. else emote_grid_header = emote_grid_header[1];//登录(不可用)状态选择表情区第二个分类
  145. // console.log(emote_grid_header)
  146.  
  147.  
  148.  
  149. let log_button = document.createElement('div');
  150. log_button.className = "Layout-sc-nxg1ff-0 jreOmo";
  151. log_button.id = 'emote_url_pop_box';
  152. log_button.innerHTML = `<div class="Layout-sc-nxg1ff-0 emWtQg">
  153. <div style="display: inherit;">
  154. <button id='emote_url_pop' class="${emote_grid_header.lastChild.getElementsByTagName('button')[0].className}">
  155. <svg xmlns="http://www.w3.org/2000/svg" height="24px" viewBox="0 0 24 24" width="24px" fill="currentcolor">
  156. <path d="M0 0h24v24H0V0z" fill="none"/><path d="M19 9h-4V3H9v6H5l7 7 7-7zm-8 2V5h2v6h1.17L12 13.17 9.83 11H11zm-6 7h14v2H5z"/></svg>
  157. </button>
  158. </div>
  159. </div>`;
  160. if (!document.querySelector('#emote_url_pop')) emote_grid_header.insertBefore(log_button, emote_grid_header.children[1]);
  161. document.getElementById('emote_url_pop').onclick = () => {
  162. body.append(output_box);
  163. };
  164.  
  165.  
  166. var output_box = document.createElement("div")
  167. output_box.id = "emote_url_box";
  168.  
  169. var output_text = document.createElement("textarea")
  170. output_text.id = "emote_url_text";
  171. output_text.style.display = 'none';
  172.  
  173. var preview_box = document.createElement("div");
  174. preview_box.id = "emote_url_preview";
  175. preview_box.style.display = 'flex';
  176.  
  177. for (let j in emote_dict) {
  178. var pics = document.createElement("a");
  179. var pic_img = `<img src=${emote_dict[j]} alt=${j} class='emote_preview'>${j}</p>`;
  180. pics.innerHTML = pic_img;
  181. pics.href = emote_dict[j];
  182. pics.target = 'view emote';
  183. pics.download = j;
  184. pics.style.cssText = 'width: 122px; overflow: hidden;';
  185. preview_box.appendChild(pics);
  186. };
  187.  
  188.  
  189. var output_author = document.createElement("p")
  190. output_author.innerHTML = `script by Lee-7723`;
  191.  
  192. var output_close_btn = document.createElement("button")
  193. output_close_btn.className = button_class_name;
  194. output_close_btn.id = "emote_url_close_btn";
  195. output_close_btn.onclick = function () { close_emote_box() };
  196. output_close_btn.innerHTML = '关闭';
  197.  
  198. var preview_btn = document.createElement("button")
  199. preview_btn.className = button_class_name;
  200. preview_btn.id = "emote_url_preview_btn";
  201. preview_btn.onclick = function () { return_emote_url_box() };
  202. preview_btn.innerHTML = '预览json';
  203.  
  204. var download_zip_btn = document.createElement("button")
  205. download_zip_btn.className = button_class_name;
  206. download_zip_btn.id = "emote_download_zip_btn";
  207. download_zip_btn.onclick = function () { downloadToZip(emote_dict) };
  208. download_zip_btn.innerHTML = '下载zip';
  209.  
  210. var download_json_btn = document.createElement("a")
  211. download_json_btn.className = button_class_name;
  212. download_json_btn.id = "emote_download_json_btn";
  213. download_json_btn.href = `data:text/json;charset=utf-8,${encodeURIComponent(JSON.stringify(emote_dict))}`;
  214. download_json_btn.download = 'emotes.json';
  215. download_json_btn.innerHTML = '下载json';
  216.  
  217. var usage = document.createElement("div");
  218. usage.id = 'emote_tool_header';
  219. usage.innerHTML = `
  220. <p>下列表情点击可跳转,可以手动保存</p>
  221. <div id="drag_to_move" style="width: 2rem; cursor: move"><svg class="icon" style="width: 100%;height: 100%;vertical-align: middle;fill: currentColor;overflow: hidden;" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="944"><path d="M1013.333333 486.4l-153.6-153.6c-10.666667-10.666667-27.733333-2.133333-27.733333 10.666667v132.266666H544V189.866667h132.266667c14.933333 0 21.333333-17.066667 10.666666-27.733334L533.333333 8.533333c-12.8-12.8-32-12.8-44.8 0l-153.6 153.6c-10.666667 10.666667-2.133333 27.733333 10.666667 27.733334h132.266667v285.866666L192 477.866667v-132.266667c0-14.933333-17.066667-21.333333-27.733333-10.666667L10.666667 488.533333c-12.8 12.8-12.8 32 0 44.8l153.6 153.6c10.666667 10.666667 27.733333 2.133333 27.733333-10.666666v-132.266667h285.866667v285.866667h-132.266667c-14.933333 0-21.333333 17.066667-10.666667 27.733333l153.6 153.6c12.8 12.8 32 12.8 44.8 0l153.6-153.6c10.666667-10.666667 2.133333-27.733333-10.666666-27.733333h-132.266667V544H832v132.266667c0 14.933333 17.066667 21.333333 27.733333 10.666666l153.6-153.6c10.666667-12.8 10.666667-34.133333 0-46.933333z" fill="currentColor" p-id="945"></path></svg></div>
  222. `;
  223.  
  224. output_text.value = emote_json;
  225. output_box.append(usage, preview_box, output_text, output_author, output_close_btn, preview_btn, download_json_btn, download_zip_btn);
  226. dragElement(output_box, output_box.querySelector('#drag_to_move'));
  227. }
  228.  
  229. function close_emote_box() {
  230. document.getElementById('emote_url_box').remove();
  231. document.getElementById('emote_url_pop_box').remove();
  232. };
  233.  
  234. function return_emote_url_box() {
  235. document.getElementById('emote_url_text').style.display = 'flex';
  236. document.getElementById('emote_url_preview').style.display = 'none';
  237. document.getElementById("emote_url_preview_btn").innerHTML = '预览图像';
  238. document.querySelector("#emote_tool_header > p").innerHTML = '下载emote.json,将文件放在下载器相同路径下';
  239. document.getElementById("emote_url_preview_btn").onclick = function () { return_emote_preview_box() };
  240. };
  241.  
  242. function return_emote_preview_box() {
  243. document.getElementById('emote_url_text').style.display = 'none';
  244. document.getElementById('emote_url_preview').style.display = 'flex';
  245. document.getElementById("emote_url_preview_btn").innerHTML = '预览json';
  246. document.querySelector("#emote_tool_header > p").innerHTML = '下列表情点击可跳转,可以手动保存';
  247. document.getElementById("emote_url_preview_btn").onclick = function () { return_emote_url_box() };
  248. };
  249.  
  250. async function downloadToZip(emote_dict) {
  251. let btn = document.querySelector('#emote_download_zip_btn');
  252. btn.style.cursor = 'not-allowed';
  253. btn.onclick = '';
  254. let counter = 0;
  255. btn.innerHTML = `下载中 ${counter}/${Object.keys(emote_dict).length}`;
  256. let zip = JSZip();
  257. for (let emote_name in emote_dict) {
  258. let url = emote_dict[emote_name];
  259. let request = new Promise((resolve) => {
  260. let http_request = new XMLHttpRequest();
  261. http_request.open('GET', url);
  262. http_request.responseType = "arraybuffer"
  263. http_request.onload = () => {
  264. let ext = http_request.getResponseHeader('Content-Type').split('/')[1];
  265. let filename = emote_name + '.' + ext;
  266. zip.file(filename, http_request.response);
  267. counter ++;
  268. btn.innerHTML = `下载中 ${counter}/${Object.keys(emote_dict).length}`;
  269. resolve();
  270. };
  271. http_request.send();
  272. });
  273. await request;
  274. };
  275. btn.innerHTML = '下载完成';
  276. zip.generateAsync({ type: "blob" })
  277. .then(function (blob) {
  278. saveAs(blob, "emotes.zip");
  279. });
  280. return zip;
  281. };
  282.  
  283.  
  284.  
  285. window.onload = () => {
  286. var observer = new MutationObserver(
  287. function (mutation) {
  288. if (old_href != document.location.href) {
  289. old_href = document.location.href;
  290. get_emote_after_button();
  291. }
  292. }
  293. );
  294. observer.observe(body, { childList: true, subtree: true });
  295. };
  296.  
  297. function dragElement(elmnt, mvelmnt) {
  298. var pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
  299. mvelmnt.onmousedown = dragMouseDown;
  300.  
  301. function dragMouseDown(e) {
  302. e = e || window.event;
  303. e.preventDefault();
  304. // get the mouse cursor position at startup:
  305. pos3 = e.clientX;
  306. pos4 = e.clientY;
  307. document.onmouseup = closeDragElement;
  308. // call a function whenever the cursor moves:
  309. document.onmousemove = elementDrag;
  310. };
  311.  
  312. function elementDrag(e) {
  313. e = e || window.event;
  314. e.preventDefault();
  315. // calculate the new cursor position:
  316. pos1 = pos3 - e.clientX;
  317. pos2 = pos4 - e.clientY;
  318. pos3 = e.clientX;
  319. pos4 = e.clientY;
  320. // set the element's new position:
  321. elmnt.style.top = (elmnt.offsetTop - pos2) + "px";
  322. elmnt.style.left = (elmnt.offsetLeft - pos1) + "px";
  323. };
  324.  
  325. function closeDragElement() {
  326. // stop moving when mouse button is released:
  327. document.onmouseup = null;
  328. document.onmousemove = null;
  329. };
  330. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址