├── .gitignore ├── AriaEh ├── AriaEh.user.js └── README.md ├── DetailPageTagColor ├── EhDetailPageTagColor.user.js └── README.md ├── LICENSE ├── MPVScrollbar ├── E-hentai Multi-Page-Viewer Scrollbar.user.js ├── README.md └── screenshot.png ├── MagnetHelper ├── EhTagMagnetHelper.user.js ├── README.md └── images │ └── screenshot.png ├── README.md ├── ReadStatus ├── EhReadStatus.css ├── EhReadStatus.user.js ├── README.md └── images │ └── screenshot.png ├── TagEditor ├── ETTHelper-TagEditer.user.js ├── README.md └── images │ ├── Add1.png │ ├── Add2.png │ ├── Add3.png │ ├── Add4.png │ ├── GuessAdd1.png │ ├── GuessAdd2.png │ ├── OpenFloat.png │ ├── Preview.png │ ├── Reset.png │ ├── ShortTag1.png │ └── ShortTag2.png ├── Thumbnail ├── ETTHelper-Thumbnail.ui.css ├── ETTHelper-Thumbnail.user.js ├── README.md └── images │ ├── Preview.webp │ └── SetLargeThumbnail.webp └── TranslatedJump ├── EhTagTranslatedJump.user.js ├── README.md └── images ├── screenshot-01.png └── screenshot-02.png /.gitignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/.gitignore -------------------------------------------------------------------------------- /AriaEh/AriaEh.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name EhAria2下载助手 3 | // @namespace com.xioxin.AriaEh 4 | // @version 1.2 5 | // @description 发送任务到Aria2,并查看下载进度 6 | // @author xioxin, SchneeHertz 7 | // @homepage https://github.com/EhTagTranslation/UserScripts 8 | // @supportURL https://github.com/EhTagTranslation/UserScripts/issues 9 | // @include *://exhentai.org/* 10 | // @include *://e-hentai.org/* 11 | // @include *hath.network/archive/* 12 | // @require https://openuserjs.org/src/libs/sizzle/GM_config.js 13 | // @grant GM_registerMenuCommand 14 | // @grant GM_xmlhttpRequest 15 | // @grant GM_addValueChangeListener 16 | // @grant GM_removeValueChangeListener 17 | // @grant GM_setClipboard 18 | // @grant GM_getValue 19 | // @grant GM_setValue 20 | // @grant GM.getValue 21 | // @grant GM.setValue 22 | // @connect localhost 23 | // @connect 127.0.0.1 24 | // ==/UserScript== 25 | 26 | 27 | const IS_EX = window.location.host.includes("exhentai"); 28 | const gmc = new GM_config({ 29 | 'id': 'AriaEhSetting', 30 | 'title': 'AriaEh设置', 31 | 'fields': { 32 | 'ARIA2_RPC': { 33 | 'section': [ 'ARIA2配置', '如果你的下载服务器不是本机,需要的将域名添加到:
设置 - XHR 安全 - 用户域名白名单'], 34 | 'label': 'ARIA2_RPC地址(必填)', 35 | 'title': 'ARIA2_RPC地址, 例如: http://127.0.0.1:6800/jsonrpc', 36 | 'labelPos': 'left', 37 | 'type': 'text', 38 | 'default': '' 39 | }, 40 | 'ARIA2_SECRET': { 41 | 'label': 'ARIA2_RPC密钥', 42 | 'title': 'ARIA2_RPC密钥', 43 | 'type': 'text', 44 | 'default': '' 45 | }, 46 | 'ARIA2_DIR': { 47 | 'label': '保存文件位置', 48 | 'title': '例如 /Downloads 或者 D:\\Downloads, 留空将下载到默认位置', 49 | 'type': 'text', 50 | 'default': '' 51 | }, 52 | 'USE_ONE_CLICK_DOWNLOAD': { 53 | 'section': [ '一键下载存档', '该功能是将"存档下载"的连接发送给Aria2.在列表页面与详情页增加橙色下载按钮.注意该功能会产生下载费用!'], 54 | 'labelPos': 'left', 55 | 'label': '启用', 56 | 'title': '在列表页与详情页增加存档一键下载按钮', 57 | 'type': 'checkbox', 58 | 'default': true 59 | }, 60 | 'ONE_CLICK_DOWNLOAD_DLTYPE': { 61 | 'label': '一键下载画质', 62 | 'type': 'select', 63 | 'labelPos': 'left', 64 | 'options': ['org(原始档案)', 'res(重采样档案)'], 65 | 'default': 'org(原始档案)' 66 | }, 67 | 'USE_TORRENT_POP_LIST': { 68 | 'section': [ '种子下载快捷弹窗', '鼠标指向详情页的"种子下载",或者列表的绿色箭头.将显示种子列表浮窗.并高亮最大体积,最新更新.' ], 69 | 'labelPos': 'left', 70 | 'label': '启用', 71 | 'title': '使用种子下载快捷弹窗', 72 | 'type': 'checkbox', 73 | 'default': true 74 | }, 75 | 'REPLACE_EX_TORRENT_URL': { 76 | 'label': '里站使用表站种子连接', 77 | 'title': '替换里站种子域名为ehtracker.org', 78 | 'type': 'checkbox', 79 | 'default': true 80 | }, 81 | 'USE_MAGNET': { 82 | 'label': '使用磁力链替代种子链接', 83 | 'title': '先将种子转换为磁力链,再发送给Aria2', 84 | 'type': 'checkbox', 85 | 'default': false 86 | }, 87 | 'USE_LIST_TASK_STATUS': { 88 | 'section': [ '下载进度展示'], 89 | 'labelPos': 'left', 90 | 'label': '在搜索列表页', 91 | 'title': '在搜索列表页', 92 | 'type': 'checkbox', 93 | 'default': true 94 | }, 95 | 'USE_GALLERY_DETAIL_TASK_STATUS': { 96 | 'label': '在画廊详情页', 97 | 'title': '在画廊详情页', 98 | 'type': 'checkbox', 99 | 'default': true 100 | }, 101 | 'USE_HATH_ARCHIVE_TASK_STATUS': { 102 | 'label': '在存档下载页面', 103 | 'title': '在存档下载页面', 104 | 'type': 'checkbox', 105 | 'default': true 106 | }, 107 | 'USE_TORRENT_TASK_STATUS': { 108 | 'label': '在种子下载页面', 109 | 'title': '在种子下载页面', 110 | 'type': 'checkbox', 111 | 'default': true 112 | }, 113 | 'INITIALIZED': { 114 | 'type': 'hidden', 115 | 'default': false, 116 | } 117 | }, 118 | 'events': { 119 | 'init': onConfigInit 120 | }, 121 | css: ` 122 | #AriaEhSetting { background: #E3E0D1; } 123 | #AriaEhSetting .config_header { margin-bottom: 8px; } 124 | #AriaEhSetting .section_header { font-size: 12pt; } 125 | #AriaEhSetting .section_header_holder { margin-top: 16pt; } 126 | #AriaEhSetting input, #AriaEhSetting select { background:#E3E0D1; border: 2px solid #B5A4A4; border-radius: 3px; } 127 | #AriaEhSetting .field_label { display: inline-block; min-width: 150px; text-align: right;} 128 | ${IS_EX ? ` 129 | #AriaEhSetting { background:#4f535b; color: #FFF; } 130 | #AriaEhSetting .section_header { border: 1px solid #000; } 131 | #AriaEhSetting .section_desc { background:#34353b; border: 1px solid #000; color: #CCC; } 132 | #AriaEhSetting input, #AriaEhSetting select { background:#34353b; color: #FFF; border: 2px solid #8d8d8d; border-radius: 3px; } 133 | #AriaEhSetting_resetLink { color: #FFF; } 134 | `: ''} 135 | ` 136 | }) 137 | console.log('gmc', gmc); 138 | 139 | function onConfigInit() { 140 | // 如果没有配置地址, 在首页弹出配置页面 141 | if((!gmc.get('ARIA2_RPC')) && window.location.pathname == '/') { 142 | gmc.open(); 143 | AriaEhSetting.style = iframeCss 144 | throw new Error("未设置ARIA2_RPC地址"); 145 | } 146 | init() 147 | } 148 | 149 | const iframeCss = ` 150 | width: 400px; 151 | height: 480px; 152 | border: 1px solid; 153 | border-radius: 4px; 154 | position: fixed; 155 | z-index: 9999; 156 | ` 157 | 158 | GM_registerMenuCommand("设置", () => { 159 | gmc.open() 160 | AriaEhSetting.style = iframeCss 161 | }) 162 | 163 | 164 | let ARIA2_CLIENT_ID = GM_getValue('ARIA2_CLIENT_ID', ''); 165 | if (!ARIA2_CLIENT_ID) { 166 | ARIA2_CLIENT_ID = "EH-" + new Date().getTime(); 167 | GM_setValue("ARIA2_CLIENT_ID", ARIA2_CLIENT_ID); 168 | } 169 | 170 | const IS_TORRENT_PAGE = window.location.href.includes("gallerytorrents.php"); 171 | const IS_HATH_ARCHIVE_PAGE = window.location.href.includes("hath.network/archive"); 172 | const IS_GALLERY_DETAIL_PAGE = window.location.href.includes("/g/"); 173 | 174 | const STYLE = ` 175 | .aria2helper-box { 176 | height: 27px; 177 | line-height: 27px; 178 | } 179 | .aria2helper-button { } 180 | .aria2helper-loading { } 181 | .aria2helper-message { cursor: pointer; } 182 | .aria2helper-status { 183 | display: none; 184 | padding: 4px 4px; 185 | font-size: 12px; 186 | text-align: center; 187 | background: rgba(${IS_EX ? '0,0,0': '255,255,255'}, 0.6); 188 | margin: 4px 8px; 189 | border-radius: 4px; 190 | font-weight: normal; 191 | white-space: normal; 192 | box-shadow: 0 1px 3px rgb(0 0 0 / 30%); 193 | } 194 | .glname .aria2helper-status { 195 | margin: 4px 0px; 196 | } 197 | .gl3e .aria2helper-status { 198 | margin: 0px 4px; 199 | padding: 4px 4px; 200 | width: 112px !important; 201 | white-space: normal; 202 | box-sizing: border-box; 203 | text-align: center !important; 204 | } 205 | .gl3e .aria2helper-status span { 206 | display: block; 207 | } 208 | .gl1t .aria2helper-status{ 209 | margin: 4px 4px; 210 | } 211 | `; 212 | 213 | 214 | const ONE_CLICK_STYLE = ` 215 | .aria2helper-one-click { 216 | width: 15px; 217 | height: 15px; 218 | background: radial-gradient(#ffc36b,#c56a00); 219 | border-radius: 15px; 220 | border: 1px #666 solid; 221 | box-sizing: border-box; 222 | color: #ebeae9; 223 | text-align: center; 224 | line-height: 15px; 225 | cursor: pointer; 226 | user-select: none; 227 | } 228 | .aria2helper-one-click:hover { 229 | background: radial-gradient(#bf893b,#985200); 230 | } 231 | .aria2helper-one-click.bt { 232 | background: radial-gradient(#a2d04f,#5fb213); 233 | } 234 | .aria2helper-one-click.bt:hover { 235 | background: radial-gradient(#95cf2b,#427711); 236 | } 237 | .aria2helper-one-click i { 238 | font-style: initial; 239 | transform: scale(0.7); 240 | margin-left: -1.5px; 241 | } 242 | .gldown { 243 | width: 35px !important; 244 | display: flex; 245 | flex-direction: row; 246 | justify-content: space-between; 247 | } 248 | .gl3e>div:nth-child(6) { 249 | left: 45px; 250 | } 251 | .aria2helper-one-click svg circle { 252 | stroke: #fff !important; 253 | stroke-width: 15px !important; 254 | } 255 | .aria2helper-one-click svg { 256 | width: 10px; 257 | display: inline-block; 258 | height: 10px; 259 | padding-top: 1.3px; 260 | } 261 | .gsp .aria2helper-one-click { 262 | display: inline-block; 263 | margin-left: 8px; 264 | vertical-align: -1.5px; 265 | } 266 | 267 | #gd5 .g2 { 268 | position: relative; 269 | } 270 | #btList{ 271 | display: none; 272 | background: #f00; 273 | width: 90%; 274 | position: absolute; 275 | border-radius: 4px; 276 | border: 1px; 277 | z-index: 999; 278 | padding: 8px 0; 279 | font-size: 12px; 280 | text-align: left; 281 | background: rgba(${IS_EX ? '0,0,0': '255,255,255'}, 0.6); 282 | border-radius: 4px; 283 | font-weight: normal; 284 | white-space: normal; 285 | box-shadow: 0 1px 3px rgb(0 0 0 / 30%); 286 | backdrop-filter: saturate(180%) blur(20px); 287 | font-size: 12px; 288 | width: max-content; 289 | margin-top: 8px; 290 | } 291 | .nowrap { 292 | white-space:nowrap; 293 | } 294 | #gmid #btList { 295 | right: 0; 296 | margin-top: 16px; 297 | } 298 | .gldown #btList{ 299 | left: 0; 300 | } 301 | .gldown #btList table { 302 | max-width: 60vw; 303 | } 304 | .btListShow #btList{ 305 | display: block; 306 | } 307 | #btList .bt-item { 308 | padding: 4px 8px; 309 | } 310 | #btList .bt-name { 311 | font-weight: bold; 312 | } 313 | #btList .quality { 314 | font-weight: bold; 315 | } 316 | #btList td span { 317 | display: inline-block; 318 | padding: 2px 4px; 319 | height: 16px; 320 | line-height: 16px; 321 | } 322 | #btList td span.quality { 323 | font-weight: bold; 324 | border-radius: 4px; 325 | background: ${IS_EX ? '#fff': '#5c0d12'}; 326 | color: ${IS_EX ? '#000': '#fff'}; 327 | } 328 | #btList table { 329 | border-spacing:0; 330 | border-collapse:collapse; 331 | max-width: 80vw; 332 | } 333 | #btList tr th { 334 | padding-bottom: 8px; 335 | text-align: center; 336 | } 337 | #btList tr th span { 338 | font-weight: 400; 339 | } 340 | #btList tr td { 341 | padding: 2px 4px; 342 | } 343 | #btList tr:hover td { 344 | background: rgba(${IS_EX ? '0,0,0': '255,255,255'}, 0.6); 345 | } 346 | #btList tr>td:first-of-type, #btList tr>th:first-of-type { 347 | padding: 0 8px; 348 | } 349 | #btList tr>td:last-child, #btList tr>th:last-child { 350 | padding-right: 8px; 351 | } 352 | `; 353 | 354 | const SVG_LOADING_ICON = ` 355 | 356 | 357 | `; 358 | 359 | const ARIA2_ERROR_MSG = { 360 | '2': '操作超时', 361 | '3': '无法找到指定资源', 362 | '4': "无法找到指定资源.", 363 | '5': "由于下载速度过慢, 下载已经终止.", 364 | '6': "网络问题", 365 | '8': "服务器不支持断点续传", 366 | '9': "可用磁盘空间不足", 367 | '10': "分片大小与 .aria2 控制文件中的不同.", 368 | '11': "aria2 已经下载了另一个相同文件.", 369 | '12': "aria2 已经下载了另一个相同哈希的种子文件.", 370 | '13': "文件已经存在.", 371 | '14': "文件重命名失败.", 372 | '15': "文件打开失败.", 373 | '16': "文件创建或删除已有文件失败.", 374 | '17': "文件系统出错.", 375 | '18': "无法创建指定目录.", 376 | '19': "域名解析失败.", 377 | '20': "解析 Metalink 文件失败.", 378 | '21': "FTP 命令执行失败.", 379 | '22': "HTTP 返回头无效或无法识别.", 380 | '23': "指定地址重定向过多.", 381 | '24': "HTTP 认证失败.", 382 | '25': "解析种子文件失败.", 383 | '26': '指定 ".torrent" 种子文件已经损坏或缺少 aria2 需要的信息.', 384 | '27': '指定磁链地址无效.', 385 | '28': '设置错误.', 386 | '29': '远程服务器繁忙, 无法处理当前请求.', 387 | '30': '处理 RPC 请求失败.', 388 | '32': '文件校验失败.' 389 | }; 390 | 391 | 392 | class AriaClientLite { 393 | constructor(opt = {}) { 394 | this.rpc = opt.rpc; 395 | this.secret = opt.secret; 396 | this.id = opt.id; 397 | } 398 | 399 | async addUri(url, dir = '') { 400 | const response = await this.post(this.rpc, this._addUriParameter(url, dir)); 401 | return this.singleResponseGuard(response); 402 | } 403 | 404 | async tellStatus(id) { 405 | const response = await this.post(this.rpc, this._tellStatusParameter(id)); 406 | return this.singleResponseGuard(response); 407 | } 408 | 409 | async batchTellStatus(ids = []) { 410 | if(!ids.length) return []; 411 | const dataList = ids.map(id => this._tellStatusParameter(id)); 412 | const response = await this.post(this.rpc, dataList); 413 | if(response.responseType !== 'json') throw `不支持的数据格式: ${response.status}`; 414 | const json = JSON.parse(response.responseText); 415 | if(!Array.isArray(json)) throw "批量请求数据结构错误"; 416 | return json.map(v => v.result); 417 | } 418 | 419 | request(url, opt={}) { 420 | return new Promise((resolve, reject) => { 421 | opt.onerror = opt.ontimeout = reject 422 | opt.onload = resolve 423 | GM_xmlhttpRequest({ 424 | url, 425 | timeout: 2000, 426 | responseType: 'json', 427 | ...opt 428 | }); 429 | }) 430 | } 431 | 432 | post(url, data = {}) { 433 | return this.request(url, { 434 | method: "POST", 435 | data: JSON.stringify(data), 436 | }); 437 | } 438 | 439 | singleResponseGuard(response) { 440 | if(response.responseType !== 'json') { 441 | throw `不支持的数据格式: ${response.status}`; 442 | } 443 | const json = JSON.parse(response.responseText); 444 | if(response.status !== 200 && json && json.error) throw `${json.error.code} ${json.error.message}`; 445 | if(response.status !== 200) throw `错误: ${response.status}`; 446 | return json.result; 447 | } 448 | 449 | _jsonRpcPack(method, params) { 450 | if (this.secret) params.unshift("token:" + this.secret); 451 | return { 452 | "jsonrpc": "2.0", 453 | "method": method, 454 | "id": ARIA2_CLIENT_ID, 455 | params 456 | } 457 | } 458 | 459 | _addUriParameter(url, dir = '') { 460 | const opt = {"follow-torrent": 'true'}; 461 | if(dir) opt['dir'] = dir; 462 | return this._jsonRpcPack('aria2.addUri', [ [url], opt ]); 463 | } 464 | 465 | _tellStatusParameter(id) { 466 | return this._jsonRpcPack('aria2.tellStatus', [id]); 467 | } 468 | 469 | } 470 | 471 | class SendTaskButton { 472 | constructor(gid, link) { 473 | this.element = document.createElement("div");; 474 | this.link = link; 475 | this.gid = gid; 476 | 477 | this.element.className = "aria2helper-box"; 478 | this.loading = document.createElement("div"); 479 | this.loading.className = "aria2helper-loading"; 480 | this.loading.innerHTML = SVG_LOADING_ICON; 481 | 482 | this.message = document.createElement("div"); 483 | this.message.className = "aria2helper-message"; 484 | 485 | this.button = document.createElement("input"); 486 | this.button.type = "button"; 487 | this.button.value = "发送到Aria2"; 488 | this.button.className = 'stdbtn aria2helper-button'; 489 | this.button.onclick = () => this.buttonClick(); 490 | this.element.appendChild(this.button); 491 | this.element.appendChild(this.loading); 492 | this.element.appendChild(this.message); 493 | this.message.onclick = () => this.show(this.button); 494 | this.show(this.button); 495 | } 496 | 497 | show(node) { 498 | this.loading.style.display = 'none'; 499 | this.message.style.display = 'none'; 500 | this.button.style.display = 'none'; 501 | node.style.display = ''; 502 | } 503 | 504 | showMessage(msg) { 505 | this.message.textContent = msg; 506 | this.show(this.message); 507 | } 508 | showLoading() { 509 | this.show(this.loading); 510 | } 511 | 512 | async buttonClick() { 513 | this.showLoading(); 514 | try { 515 | const id = await ariaClient.addUri(getTorrentLink(this.link), gmc.get('ARIA2_DIR')); 516 | Tool.setTaskId(this.gid, id); 517 | this.showMessage("成功"); 518 | } catch (error) { 519 | console.error(error); 520 | if(typeof error === 'string') return this.showMessage(error || "请求失败"); 521 | if(error.status) return this.showMessage("请求失败 HTTP" + error.status); 522 | this.showMessage(error.message || "请求失败"); 523 | } 524 | } 525 | } 526 | 527 | class TaskStatus { 528 | constructor() { 529 | this.element = document.createElement("div"); 530 | this.element.className = 'aria2helper-status' 531 | this.monitorCount = 0; 532 | } 533 | setStatus(task) { 534 | this.monitorCount ++; 535 | const statusBox = this.element; 536 | statusBox.style.display = 'block' 537 | const completedLength = parseInt(task.completedLength, 10) || 0; 538 | const totalLength = parseInt(task.totalLength, 10) || 0; 539 | const downloadSpeed = parseInt(task.downloadSpeed, 10) || 0; 540 | const uploadLength = parseInt(task.uploadLength, 10) || 0; 541 | const uploadSpeed = parseInt(task.uploadSpeed, 10) || 0; 542 | const connections = parseInt(task.connections, 10) || 0; 543 | const file = task.files[0]; 544 | const filePath = file ? file.path : ''; 545 | const name = filePath.split(/[\/\\]/).pop(); 546 | // 显示扩展名 用于区分当前下载的是种子 还是文件。 547 | const ext = name.includes(".") ? name.split('.').pop() : ''; 548 | 549 | let progress = '-'; 550 | 551 | if(totalLength) { 552 | progress = (completedLength/totalLength * 100).toFixed(2) + '%'; 553 | } 554 | 555 | // ⠓ ⠚ ⠕ ⠪ 556 | const iconList = "⠓⠋⠙⠚".split(""); 557 | const icon = iconList[this.monitorCount % iconList.length]; 558 | let info = []; 559 | 560 | if (task.status === 'active') { 561 | if (task.verifyIntegrityPending) { 562 | info.push(`${icon} 🔍 等待验证`); 563 | } else if (task.verifiedLength) { 564 | info.push(`${icon} 🔍 正在验证`); 565 | if (task.verifiedPercent) { 566 | info.push(`已验证 (${task.verifiedPercent})`); 567 | } 568 | } else if (task.seeder === true || task.seeder === 'true') { 569 | info.push(`${icon} 📤 做种`); 570 | info.push(`已上传:${Tool.fileSize(uploadLength)}`); 571 | info.push(`速度:${Tool.fileSize(uploadSpeed)}/s`); 572 | } else { 573 | info.push(`${icon} 📥 下载中`); 574 | info.push(`进度:${progress}`); 575 | info.push(`速度:${Tool.fileSize(downloadSpeed)}/s`); 576 | } 577 | } else if (task.status === 'waiting') { 578 | info.push(`${icon} ⏳ 排队`); 579 | } else if (task.status === 'paused') { 580 | info.push(`${icon} ⏸ 暂停`); 581 | info.push(`进度:${progress}`); 582 | } else if (task.status === 'complete') { 583 | info.push(`${icon} ☑️ 完成`); 584 | } else if (task.status === 'error') { 585 | const errorMessageCN = ARIA2_ERROR_MSG[task.errorCode] 586 | info.push(`${icon} 错误 (${task.errorCode}: ${errorMessageCN || task.errorMessage || "未知错误"})`); 587 | info.push(`进度:${progress}`); 588 | } else if (task.status === 'removed') { 589 | info.push(`${icon} ⛔️ 已删除`); 590 | } 591 | 592 | info.push(`类型:${ext}`); 593 | 594 | statusBox.innerHTML = info.map(v => `${v}`).join(' '); 595 | 596 | if(task.followedBy && task.followedBy.length) { 597 | // BT任务跟随 598 | Tool.setTaskId(GID, task.followedBy[0]); 599 | } 600 | } 601 | 602 | } 603 | 604 | class Tool { 605 | 606 | 607 | static htmlDecodeByRegExp (str) { 608 | let temp = ""; 609 | if(str.length == 0) return ""; 610 | temp = str.replace(/&/g,"&"); 611 | temp = temp.replace(/</g,"<"); 612 | temp = temp.replace(/>/g,">"); 613 | temp = temp.replace(/ /g," "); 614 | temp = temp.replace(/'/g,"\'"); 615 | temp = temp.replace(/"/g,"\""); 616 | return temp; 617 | } 618 | 619 | static addStyle(styles) { 620 | var styleSheet = document.createElement("style") 621 | styleSheet.innerText = styles 622 | document.head.appendChild(styleSheet) 623 | } 624 | 625 | static urlGetGId(url) { 626 | let m; 627 | m = /gid=(\d+)/i.exec(url); 628 | if(m) return parseInt(m[1], 10); 629 | m = /archive\/(\d+)\//i.exec(url); 630 | if(m) return parseInt(m[1], 10); 631 | m = /\/g\/(\d+)\//i.exec(url); 632 | if(m) return parseInt(m[1], 10); 633 | } 634 | 635 | static urlGetToken(url) { 636 | let m; 637 | m = /&t(oken)?=(\d+)/i.exec(url); 638 | if(m) return m[2]; 639 | m = /\/g\/(\d+)\/(\w+)\//i.exec(url); 640 | if(m) return m[2]; 641 | } 642 | 643 | static setTaskId(ehGid, ariaGid) { 644 | GM_setValue("task-" + ehGid, ariaGid); 645 | } 646 | 647 | static getTaskId(ehGid) { 648 | return GM_getValue("task-" + ehGid, 0); 649 | } 650 | 651 | static fileSize(_size, round = 2) { 652 | const divider = 1024; 653 | 654 | if (_size < divider) { 655 | return _size + ' B'; 656 | } 657 | 658 | if (_size < divider * divider && _size % divider === 0) { 659 | return (_size / divider).toFixed(0) + ' KB'; 660 | } 661 | 662 | if (_size < divider * divider) { 663 | return `${(_size / divider).toFixed(round)} KB`; 664 | } 665 | 666 | if (_size < divider * divider * divider && _size % divider === 0) { 667 | return `${(_size / (divider * divider)).toFixed(0)} MB`; 668 | } 669 | 670 | if (_size < divider * divider * divider) { 671 | return `${(_size / divider / divider).toFixed(round)} MB`; 672 | } 673 | 674 | if (_size < divider * divider * divider * divider && _size % divider === 0) { 675 | return `${(_size / (divider * divider * divider)).toFixed(0)} GB`; 676 | } 677 | 678 | if (_size < divider * divider * divider * divider) { 679 | return `${(_size / divider / divider / divider).toFixed(round)} GB`; 680 | } 681 | 682 | if (_size < divider * divider * divider * divider * divider && 683 | _size % divider === 0) { 684 | const r = _size / divider / divider / divider / divider; 685 | return `${r.toFixed(0)} TB`; 686 | } 687 | 688 | if (_size < divider * divider * divider * divider * divider) { 689 | const r = _size / divider / divider / divider / divider; 690 | return `${r.toFixed(round)} TB`; 691 | } 692 | 693 | if (_size < divider * divider * divider * divider * divider * divider && 694 | _size % divider === 0) { 695 | const r = _size / divider / divider / divider / divider / divider; 696 | return `${r.toFixed(0)} PB`; 697 | } else { 698 | const r = _size / divider / divider / divider / divider / divider; 699 | return `${r.toFixed(round)} PB`; 700 | } 701 | } 702 | 703 | } 704 | 705 | class MonitorTask { 706 | constructor() { 707 | this.gids = []; 708 | this.taskIds = []; 709 | this.taskToGid = {}; 710 | this.statusMap = {}; 711 | this.timerId = 0; 712 | this.run = false; 713 | } 714 | 715 | start() { 716 | this.run = true; 717 | this.refreshTaskIds(); 718 | this.loadStatus(); 719 | } 720 | 721 | stop() { 722 | this.run = false; 723 | if(this.timerId)clearTimeout(this.timerId); 724 | } 725 | 726 | addGid(gid) { 727 | this.gids.push(gid); 728 | GM_addValueChangeListener("task-" + gid, () => { 729 | this.refreshTaskIds(); 730 | }); 731 | this.statusMap[gid] = new TaskStatus(); 732 | return this.statusMap[gid]; 733 | } 734 | 735 | refreshTaskIds() { 736 | if(!this.gids.length) return; 737 | this.taskIds = this.gids.map(v => { 738 | const agid = Tool.getTaskId(v); 739 | if(agid) this.taskToGid[agid] = v; 740 | return agid; 741 | }).filter(v => v); 742 | } 743 | 744 | async loadStatus() { 745 | if(!this.run) return; 746 | let hasActive = false; 747 | try { 748 | const batch = await ariaClient.batchTellStatus(this.taskIds); 749 | batch.forEach(task => { 750 | this.setStatusToUI(task); 751 | if(task) hasActive = hasActive || "active" === task.status; 752 | }); 753 | } catch (error) { 754 | console.error(error); 755 | } 756 | this.timerId = setTimeout(() => { 757 | this.loadStatus() 758 | }, hasActive ? 500 : 5000); 759 | } 760 | 761 | setStatusToUI(task) { 762 | if(!task) return; 763 | const gid = this.taskToGid[task.gid]; 764 | if(!gid) return; 765 | const ui = this.statusMap[gid]; 766 | if(!ui) return; 767 | ui.setStatus(task); 768 | } 769 | } 770 | 771 | const GID = Tool.urlGetGId(window.location.href); 772 | const TOKEN = Tool.urlGetToken(window.location.href); 773 | 774 | let ariaClient; 775 | 776 | console.log({GID, TOKEN}); 777 | 778 | 779 | function oneClickButton(gid, pageLink, archiverLink) { 780 | const oneClick = document.createElement('div'); 781 | oneClick.textContent = "🡇"; 782 | oneClick.title = "[Aria2] 一键下载"; 783 | oneClick.classList.add("aria2helper-one-click"); 784 | let loading = false; 785 | oneClick.onclick = async () => { 786 | if(loading === true) return; 787 | oneClick.innerHTML = SVG_LOADING_ICON; 788 | loading = true; 789 | try { 790 | if (pageLink && !archiverLink) { 791 | const g = await fetch(pageLink, { credentials: "include" }).then(v => v.text()); 792 | const archiverLinkMatch = /'(https:\/\/e.hentai\.org\/archiver\.php?.*?)'/i.exec(g); 793 | archiverLink = Tool.htmlDecodeByRegExp(archiverLinkMatch[1]).replace("--", "-"); 794 | } 795 | let formData = new FormData(); 796 | formData.append("dltype", gmc.get('ONE_CLICK_DOWNLOAD_DLTYPE').slice(0, 3)); 797 | formData.append("dlcheck","Download Original Archive"); 798 | const archiverHtml = await fetch( 799 | archiverLink, 800 | {method: "POST", credentials: "include", body: formData} 801 | ).then(v => v.text()); 802 | const downloadLinkMatch = /"(http.*?\.hath.network\/archive.*?)"/i.exec(archiverHtml); 803 | const downloadLink = downloadLinkMatch[1] + '?start=1'; 804 | const taskId = await ariaClient.addUri(downloadLink, gmc.get('ARIA2_DIR')); 805 | Tool.setTaskId(gid, taskId); 806 | oneClick.innerHTML = "✔"; 807 | setTimeout(() => { 808 | oneClick.innerHTML = "🡇"; 809 | }, 2000); 810 | } catch (error) { 811 | alert("一键下载失败:" + error.message); 812 | oneClick.innerHTML = "🡇"; 813 | } 814 | loading = false; 815 | } 816 | return oneClick; 817 | } 818 | 819 | 820 | async function getTorrentList(gid, token, lifeTime = 0) { 821 | const html = await fetch(`${document.location.origin}/gallerytorrents.php?gid=${gid}&t=${token}`, {credentials: "include"}).then(v => v.text()); 822 | const safeHtml = html.replace(/^.*(.*)<\/body>.*$/igms,"$1").replace(/(.*?)<\/script>/igms, ''); 823 | const dom = document.createElement('div') 824 | dom.innerHTML = safeHtml; 825 | const formList = [...dom.querySelectorAll("form")]; 826 | const list = formList.map((e, i) => { 827 | const link = e.querySelector("table > tbody > tr:nth-child(3) > td > a"); 828 | if(!link) return null; 829 | const posted = e.querySelector("table > tbody > tr:nth-child(1) > td:nth-child(1)"); 830 | const size = e.querySelector("table > tbody > tr:nth-child(1) > td:nth-child(2)"); 831 | const seeds = e.querySelector("table > tbody > tr:nth-child(1) > td:nth-child(4)"); 832 | const peers = e.querySelector("table > tbody > tr:nth-child(1) > td:nth-child(5)"); 833 | const downloads = e.querySelector("table > tbody > tr:nth-child(1) > td:nth-child(6)"); 834 | const uploader = e.querySelector("table > tbody > tr:nth-child(2) > td:nth-child(1)"); 835 | const getNumber = (text = '') => parseFloat((text.match(/[\d\.]+/) || [0])[0]); 836 | const getValueText = (text = '') => (text.match(/:(.*)/) || ['',''])[1].trim(); 837 | const sizeText = getValueText(size.textContent); 838 | const sizeNumber = getNumber(sizeText); 839 | const unit = [sizeText.match(/[KMGT]i?B/) || ['']][0]; 840 | const magnification = { 841 | "KB": 1000, 842 | "MB": 1000 * 1000, 843 | "GB": 1000 * 1000 * 1000, 844 | "TB": 1000 * 1000 * 1000 * 1000, 845 | "KiB": 1024, 846 | "MiB": 1024 * 1024, 847 | "GiB": 1024 * 1024 * 1024, 848 | "TiB": 1024 * 1024 * 1024 * 1024, 849 | } 850 | if(!magnification[unit]) { 851 | console.warn("未知单位: ", unit, size); 852 | } 853 | let bytes = magnification[unit] ? sizeNumber * magnification[unit] : -1; 854 | const time = new Date(getValueText(posted.textContent)); 855 | return { 856 | index: i, 857 | time: time, 858 | readableTime: dateStr(time), 859 | size: sizeText, 860 | bytes: bytes, 861 | seeds: getNumber(seeds.textContent), // 做种 862 | peers: getNumber(peers.textContent), // 下载中 863 | downloads: getNumber(downloads.textContent), // 完成 864 | user: getValueText(uploader.textContent), 865 | name: link.textContent, 866 | link: link.getAttribute('href'), 867 | achievements: new Set(), 868 | } 869 | }).filter(v => v); 870 | 871 | let maxBytes = 0; 872 | let maxTime = 0; 873 | let maxSeeds = 0; 874 | let maxPeers = 0; 875 | let maxDownloads = 0; 876 | list.forEach(v => { 877 | maxBytes = Math.max(maxBytes, v.bytes); 878 | maxTime = Math.max(maxTime, v.time.getTime()); 879 | maxSeeds = Math.max(maxSeeds, v.seeds); 880 | maxPeers = Math.max(maxPeers, v.peers); 881 | maxDownloads = Math.max(maxDownloads, v.downloads); 882 | }); 883 | list.forEach(v => { 884 | const time = v.time.getTime(); 885 | if(v.bytes == maxBytes) v.achievements.add("size"); 886 | if(time == maxTime) v.achievements.add("time"); 887 | if(v.seeds == maxSeeds && maxSeeds > 0) v.achievements.add("seeds"); 888 | if(v.peers == maxPeers && maxPeers > 0) v.achievements.add("peers"); 889 | if(v.downloads == maxDownloads && maxDownloads > 0) v.achievements.add("downloads"); 890 | if(time < lifeTime) v.achievements.add("overdue"); 891 | }); 892 | 893 | list.sort((a,b) => { 894 | return b.time.getTime() - a.time.getTime(); 895 | }) 896 | 897 | console.log('list', list); 898 | return list; 899 | } 900 | 901 | function dateStr(date = new Date()){ 902 | const today = new Date(); 903 | const now = today.getTime(); 904 | const time = Math.floor((now - date.getTime())/1000) + ( today.getTimezoneOffset() * 60); 905 | if(time <= 60){ 906 | return '刚刚'; 907 | }else if(time<=60*60){ 908 | return Math.floor(time/60)+"分钟前"; 909 | }else if(time<=60*60*24){ 910 | return Math.floor(time/60/60)+"小时前"; 911 | }else if(time<=60*60*24*7) { 912 | return Math.floor(time/60/60/24) + "天前"; 913 | }else if(time<=60*60*24*7*4) { 914 | return Math.floor(time/60/60/24/7) + "周前"; 915 | }else if(time<=60*60*24*365) { 916 | return (date.getMonth()+1).toString().padStart(2, '0')+"月"+date.getDate().toString().padStart(2, '0')+"日" 917 | } 918 | return date.getFullYear()+'年'; 919 | } 920 | 921 | async function torrentsPopDetail(btButtonBox, gid = GID, token = TOKEN, buttonLeft = false, twoLines = false) { 922 | if(!btButtonBox) { 923 | btButtonBox = document.querySelector('#gd5 .g2:nth-child(3)'); 924 | } 925 | if(btButtonBox) { 926 | boxA = btButtonBox.querySelector('a'); 927 | boxA.onmouseenter = async () => { 928 | let btListBox = btButtonBox.querySelector('#btList'); 929 | btButtonBox.classList.add('btListShow'); 930 | if(!btListBox) { 931 | btListBox = document.createElement("div"); 932 | btListBox.id = 'btList'; 933 | btButtonBox.appendChild(btListBox); 934 | btListBox.innerHTML = SVG_LOADING_ICON; 935 | try { 936 | const torents = await getTorrentList(gid, token); 937 | if(torents.length) { 938 | const achievement = (item, name) => item.achievements.has(name) ? 'quality' : ''; 939 | let th = ''; 940 | if(twoLines) { 941 | th = ` 942 | 名称 943 | 体积 944 | 时间 945 | 📤 946 | 📥 947 | ✔️`; 948 | }else { 949 | th = ` 950 | ${buttonLeft ? "" : ""} 951 | 名称 952 | 体积 953 | 时间 954 | 📤 955 | 📥 956 | ✔️`; 957 | } 958 | 959 | 960 | btListBox.innerHTML = ` 961 | 962 | ${th} 963 | 964 | ${ 965 | torents.map(item => { 966 | 967 | const button1 = ``; 968 | const button2 = ``; 969 | const nameHtml = ``; 970 | const infoHtml = ` 971 | 972 | 973 | 974 | `; 975 | if(twoLines) { 976 | return ` 977 | 978 | ${nameHtml} 979 | 980 | 981 | ${button1 + button2} 982 | ${infoHtml} 983 | 984 | `; 985 | } 986 | return ` 987 | 988 | ${buttonLeft ? button1 + button2 : ''} 989 | ${nameHtml} 990 | ${infoHtml} 991 | ${buttonLeft ? '' : button2 + button1 } 992 | 993 | ` 994 | }).join('') 995 | }
🡇
${item.name}${item.size}${item.readableTime}${item.seeds}${item.peers}${item.downloads}
`; 996 | 997 | btListBox.onclick = async (event) => { 998 | if(event.target.classList.contains("bt-download-button")) { 999 | const link = event.target.dataset.link; 1000 | const gid = parseInt(event.target.dataset.gid, 10); 1001 | if(event.target.dataset.loading === true) return; 1002 | event.target.innerHTML = SVG_LOADING_ICON; 1003 | event.target.dataset.loading = true; 1004 | try { 1005 | const taskId = await ariaClient.addUri(getTorrentLink(link), gmc.get('ARIA2_DIR')); 1006 | Tool.setTaskId(gid, taskId); 1007 | event.target.innerHTML = "✔"; 1008 | setTimeout(() => { 1009 | event.target.innerHTML = "🡇"; 1010 | }, 2000); 1011 | } catch (error) { 1012 | alert("一键下载失败:" + error.message); 1013 | event.target.innerHTML = "🡇"; 1014 | } 1015 | event.target.dataset.loading = false; 1016 | } 1017 | if(event.target.classList.contains("bt-copy-button") || event.target.parentNode.contains("bt-copy-button")) { 1018 | const link = event.target.dataset.link; 1019 | const magnet = torrentLink2magnet(link); 1020 | if (magnet) { 1021 | GM_setClipboard(magnet); 1022 | event.target.innerHTML = "✔"; 1023 | setTimeout(() => { 1024 | event.target.innerHTML = "✂"; 1025 | }, 2000); 1026 | } 1027 | } 1028 | 1029 | } 1030 | }else { 1031 | btListBox.innerHTML = "没有可用种子" 1032 | } 1033 | } catch (error) { 1034 | btListBox.innerHTML = error.message; 1035 | } 1036 | } 1037 | } 1038 | btButtonBox.onmouseleave = () => { 1039 | btButtonBox.classList.remove('btListShow'); 1040 | } 1041 | } 1042 | } 1043 | 1044 | function getTorrentInfo(link) { 1045 | let match = link.match(/\/(\d+)\/([0-9a-f]{40})/i); 1046 | if(!match) return; 1047 | return { 1048 | hash: match[2], 1049 | trackerId: match[1], 1050 | } 1051 | } 1052 | 1053 | function torrentLink2magnet (link) { 1054 | const info = getTorrentInfo(link); 1055 | if(!info) return; 1056 | return `magnet:?xt=urn:btih:${info.hash}&tr=${encodeURIComponent(`http://ehtracker.org/${info.trackerId}/announce`)}`; 1057 | } 1058 | 1059 | function torrentLinkForceEhTracker (link) { 1060 | const info = getTorrentInfo(link); 1061 | if(!info) return; 1062 | return `https://ehtracker.org/get/${info.trackerId}/${info.hash}.torrent`; 1063 | } 1064 | 1065 | function getTorrentLink(link) { 1066 | if(gmc.get('USE_MAGNET')) { 1067 | return torrentLink2magnet(link) || link; 1068 | } 1069 | if(link.includes('exhentai.org') && gmc.get('REPLACE_EX_TORRENT_URL')) { 1070 | return torrentLinkForceEhTracker(link) || link; 1071 | } 1072 | return link; 1073 | } 1074 | 1075 | 1076 | function init() { 1077 | ariaClient = new AriaClientLite({rpc: gmc.get('ARIA2_RPC'), secret: gmc.get('ARIA2_SECRET'), id: ARIA2_CLIENT_ID}); 1078 | Tool.addStyle(STYLE); 1079 | 1080 | const monitorTask = new MonitorTask(); 1081 | if(GID) { 1082 | 1083 | // button 1084 | if(IS_TORRENT_PAGE) { 1085 | let tableList = document.querySelectorAll("#torrentinfo form table"); 1086 | if(tableList && tableList.length){ 1087 | tableList.forEach(function (table) { 1088 | let insertionPoint = table.querySelector('input[type="submit"],button[type="submit"]'); 1089 | if(!insertionPoint)return; 1090 | let a = table.querySelector('a'); 1091 | if(!a) return; 1092 | const link = a.href; 1093 | const button = new SendTaskButton(GID, link); 1094 | insertionPoint.parentNode.insertBefore(button.element, insertionPoint); 1095 | }); 1096 | } 1097 | } 1098 | 1099 | if (IS_HATH_ARCHIVE_PAGE) { 1100 | let insertionPoint = document.querySelector("#db a"); 1101 | if(!insertionPoint)return; 1102 | const link = insertionPoint.href; 1103 | const button = new SendTaskButton(GID, link); 1104 | button.element.style.marginTop = '16px'; 1105 | insertionPoint.parentNode.insertBefore(button.element, insertionPoint); 1106 | } 1107 | 1108 | // 状态监听 1109 | const taskStatusUi = monitorTask.addGid(GID); 1110 | if (IS_HATH_ARCHIVE_PAGE && gmc.get('USE_HATH_ARCHIVE_TASK_STATUS')) { 1111 | taskStatusUi.element.style.marginTop = '8px'; 1112 | const insertionPoint = document.querySelector('#db strong'); 1113 | if(insertionPoint) insertionPoint.parentElement.insertBefore(taskStatusUi.element, insertionPoint.nextElementSibling); 1114 | } 1115 | if (IS_GALLERY_DETAIL_PAGE && gmc.get('USE_GALLERY_DETAIL_TASK_STATUS')) { 1116 | const insertionPoint = document.querySelector('#gd2'); 1117 | if(insertionPoint) insertionPoint.appendChild(taskStatusUi.element); 1118 | } 1119 | if (IS_TORRENT_PAGE && gmc.get('USE_TORRENT_TASK_STATUS')) { 1120 | const insertionPoint = document.querySelector('#torrentinfo p'); 1121 | if(insertionPoint) insertionPoint.parentElement.insertBefore(taskStatusUi.element, insertionPoint.nextElementSibling); 1122 | } 1123 | if(IS_GALLERY_DETAIL_PAGE && gmc.get('USE_TORRENT_POP_LIST')) { 1124 | torrentsPopDetail(); 1125 | } 1126 | } else if(gmc.get('USE_LIST_TASK_STATUS')) { 1127 | const trList = document.querySelectorAll(".itg tr, .itg .gl1t"); 1128 | if(trList && trList.length) { 1129 | const insertionPointMap = {}; 1130 | let textAlign = 'left'; 1131 | trList.forEach(function (tr) { 1132 | let glname = tr.querySelector(".gl3e, .glname"); 1133 | let a = tr.querySelector(".glname a, .gl1e a, .gl1t"); 1134 | if(tr.classList.contains('gl1t')) { 1135 | glname = tr; 1136 | a = tr.querySelector('a'); 1137 | textAlign = 'center'; 1138 | } 1139 | if(!(glname && a)) return; 1140 | const gid = Tool.urlGetGId(a.href); 1141 | const token = Tool.urlGetToken(a.href); 1142 | insertionPointMap[gid] = glname; 1143 | const statusUI = monitorTask.addGid(gid); 1144 | statusUI.element.style.textAlign = textAlign; 1145 | glname.appendChild(statusUI.element); 1146 | 1147 | const listTypeDom = document.querySelector("#dms select > option[selected]"); 1148 | const listType = listTypeDom ? listTypeDom.value : ''; 1149 | if(listType == 't') return; // 暂时不支持缩略图模式,显示问题 1150 | const gldown = tr.querySelector(".gldown"); 1151 | const torrentImg = gldown.querySelector('img'); 1152 | const torrentImgSrc = torrentImg.attributes.getNamedItem('src').value 1153 | const hasTorrent = torrentImgSrc.includes("g/t.png"); 1154 | if(gmc.get('USE_TORRENT_POP_LIST') && hasTorrent) { 1155 | torrentsPopDetail(gldown, gid, token, true, listType == 't'); 1156 | } 1157 | }); 1158 | } 1159 | } 1160 | 1161 | 1162 | monitorTask.start(); 1163 | 1164 | if(gmc.get('USE_ONE_CLICK_DOWNLOAD')) { 1165 | Tool.addStyle(ONE_CLICK_STYLE); 1166 | const trList = document.querySelectorAll(".itg tr, .itg .gl1t"); 1167 | if(trList && trList.length) { 1168 | trList.forEach(tr => { 1169 | let a = tr.querySelector(".glname a, .gl1e a, .gl1t"); 1170 | if(tr.classList.contains('gl1t')) a = tr.querySelector('a'); 1171 | if(!a) return; 1172 | const link = a.href; 1173 | const gid = Tool.urlGetGId(a.href); 1174 | let gldown = tr.querySelector(".gldown"); 1175 | gldown.appendChild(oneClickButton(gid, link, null)); 1176 | }) 1177 | } 1178 | if(IS_GALLERY_DETAIL_PAGE) { 1179 | const gldown = document.querySelector(".g2.gsp"); 1180 | const a = document.querySelector(".g2.gsp a"); 1181 | const archiverLinkMatch = /'(https:\/\/e.hentai\.org\/archiver\.php?.*?)'/i.exec(a.onclick.toString()); 1182 | const archiverLink = Tool.htmlDecodeByRegExp(archiverLinkMatch[1]).replace("--", "-"); 1183 | gldown.appendChild(oneClickButton(GID, null, archiverLink)); 1184 | } 1185 | } 1186 | 1187 | } 1188 | -------------------------------------------------------------------------------- /AriaEh/README.md: -------------------------------------------------------------------------------- 1 | EhAria2下载助手 2 | ------------------------------------- 3 | 4 | [安装脚本](https://sleazyfork.org/zh-CN/scripts/432210-eharia2%E4%B8%8B%E8%BD%BD%E5%8A%A9%E6%89%8B) 5 | 6 | * 增加Aria2快捷下载按钮 7 | * 在页面中直接查看Aria2下载进度与状态 (列表页面、详情页面、种子页面、存档下载页面) 8 | * 一键下载功能 (存档下载, 注意会消耗配额或GP点数) 9 | * 种子快捷查看,鼠标指向种子下载按钮列出所有种子,并标注出体积最大最新的种子. 方便下载. 10 | * 复制磁力链会增加Tracker地址 11 | * 点击名字下载种子 12 | * 点击下载按钮发送任务到Aria2 13 | 14 | ## 已知问题 15 | * 一键下载功能无法正确下载,下载后无文件名 16 | * 情况1:E站的归档设置,需要设置为“手动选择,手动下载(默认)”(目前没有精力适配其他模式,欢迎PR)[#6](https://github.com/EhTagTranslation/UserScripts/issues/6#issuecomment-1214385546) 17 | * 情况2:开启aria2配置中的 “获取服务器文件时间” 和 “使用UTF-8处理Content-Disposition” [#6](https://github.com/EhTagTranslation/UserScripts/issues/6#issuecomment-1214412506) 18 | 19 | ## 预览 20 | 21 | ### 列表页面下载状态与一键下载 22 | ![GIF](https://user-images.githubusercontent.com/5716100/132883089-d375791f-7865-4645-94ca-5a2f4dbe5327.gif) 23 | 24 | ![GIF](https://user-images.githubusercontent.com/5716100/132880948-41e3a88b-e340-424b-867c-4396dede4893.gif) 25 | 26 | ### 详情页下载状态与一键下载 27 | ![image](https://user-images.githubusercontent.com/5716100/132881993-61abdd70-7155-4285-b322-5754bd7cd71c.png) 28 | 29 | ### 种子页面 30 | ![image](https://user-images.githubusercontent.com/5716100/132882237-8238973f-88c3-4d99-858a-7b4105eaa76f.png) 31 | 32 | ### 存档下载页面 33 | ![image](https://user-images.githubusercontent.com/5716100/132882324-2ea8e27d-1c1c-488a-bf38-d0b08657e0e0.png) 34 | 35 | ### 种子快捷查看 36 | 37 | 3dc2b9b7c608841ef4a1db2dd336368 38 | b7e060ef6da4803371c21ff03b41066 39 | -------------------------------------------------------------------------------- /DetailPageTagColor/EhDetailPageTagColor.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name eh详情页标签颜色 3 | // @namespace com.xioxin.tag-color 4 | // @version 0.7 5 | // @description eh为详情页标签增加颜色 6 | // @author xioxin 7 | // @homepage https://github.com/EhTagTranslation/UserScripts 8 | // @supportURL https://github.com/EhTagTranslation/UserScripts/issues 9 | // @match *://exhentai.org/g/* 10 | // @match *://e-hentai.org/g/* 11 | // @match *://exhentai.org/mytags 12 | // @match *://e-hentai.org/mytags 13 | // @grant GM_addStyle 14 | // @grant GM_getValue 15 | // @grant GM_setValue 16 | // ==/UserScript== 17 | 18 | 19 | if(typeof GM_getValue == 'undefined') { 20 | function GM_getValue(key, val) {return JSON.parse(localStorage.getItem(key)||'null') || val } 21 | } 22 | 23 | if(typeof GM_addStyle == 'undefined') { 24 | function GM_addStyle(script){ 25 | var style = document.createElement("style"); 26 | style.type = "text/css"; 27 | style.innerHTML=script 28 | document.getElementsByTagName("HEAD").item(0).appendChild(style); 29 | } 30 | } 31 | if(typeof GM_setValue == 'undefined') { 32 | function GM_setValue(key, val) {localStorage.setItem(key, JSON.stringify(val))} 33 | } 34 | 35 | async function saveMyTagData() { 36 | const msg = document.createElement('div'); 37 | msg.style.clear = 'both'; 38 | msg.style.textAlign = 'center'; 39 | msg.style.width = '100%'; 40 | document.querySelector('#tagset_outer').appendChild(msg); 41 | const setMsg = (text) => msg.innerText = text; 42 | try { 43 | const tags = []; 44 | for(let id of [...document.querySelectorAll("#tagset_outer select option")].map(v=> v.value)) { 45 | setMsg(`[详情页标签颜色]正在加载 ${id}`); 46 | tags.push(...await loadMyTagData(id)); 47 | } 48 | // 从小到大排序,因为颜色渲染是css,靠后的权重更大. 49 | tags.sort((a,b) => a.weight - b.weight); 50 | GM_setValue("myTags", tags); 51 | setMsg(`[详情页标签颜色] 已更新 共${tags.length}个`); 52 | } catch (e) { 53 | setMsg(`[详情页标签颜色] 错误: ${e.message}`); 54 | } 55 | } 56 | 57 | 58 | async function loadMyTagData(tageset) { 59 | const html = await fetch(`${document.location.origin}/mytags?tagset=${tageset || 1}`, {credentials: "include"}).then(v => v.text()); 60 | const safeHtml = html.replace(/^.*(.*)<\/body>.*$/igms,"$1").replace(/(.*?)<\/script>/igms, ''); 61 | const dom = document.createElement('div'); 62 | dom.innerHTML = safeHtml; 63 | const tags = [...dom.querySelectorAll('#usertags_outer>div')].map(e => { 64 | if(e.querySelector('.gt') == null) return 65 | return { 66 | tag: e.querySelector('.gt').title, 67 | background: e.querySelector('.gt').style.background, 68 | color: e.querySelector('.gt').style.color, 69 | borderColor: e.querySelector('.gt').style.borderColor, 70 | weight: parseInt(e.querySelector('[id^=tagweight]').value, 10), 71 | } 72 | }).filter(v => v); 73 | return tags; 74 | } 75 | 76 | 77 | async function dyeing() { 78 | setTimeout(colorIcon, 0); 79 | const myTags = GM_getValue("myTags", []); 80 | let css = ''; 81 | myTags.forEach(v => { 82 | const key = v.tag.replaceAll(' ', '_'); 83 | css += ` 84 | [id="td_${key}"]{ 85 | border-color: ${v.borderColor} !important; 86 | background: ${v.background} !important; 87 | } 88 | [id="td_${key}"].gtw, [id="td_${key}"].gtl{ 89 | outline: solid 1px ${v.borderColor}; 90 | border-color: ${v.color} !important; 91 | } 92 | [id="td_${key}"] a { 93 | color: ${v.color}; 94 | } 95 | .tup::after, .tdn::after { 96 | display: inline-block; 97 | color: #fff; 98 | border-radius: 4px; 99 | margin-left: 4px; 100 | margin-right: -2px; 101 | background-color: green; 102 | content: ''; 103 | line-height: 14px; 104 | outline: solid 1px #fff; 105 | vertical-align: sub; 106 | width: 4px; 107 | height: 14px; 108 | } 109 | .tdn::after { 110 | background-color: red; 111 | } 112 | ` 113 | }); 114 | GM_addStyle(css); 115 | } 116 | 117 | function colorIcon() { 118 | const myTags = GM_getValue("myTags", []); 119 | const tagIds = [...document.querySelectorAll("#taglist td>div")].map(v => v.id.replace('td_', '').replaceAll('_', ' ')); 120 | const tags = tagIds.map(id => myTags.find(v => v.tag == id)).filter(v => v); 121 | tags.sort((a, b) => parseInt(a.weight) - parseInt(b.weight)); 122 | const weight = tags.reduce((accumulator, tag) => accumulator + parseInt(tag.weight), 0); 123 | const colors = tags.map(tag => (/(rgb\(.*?\))/ig.exec(tag.borderColor)||[])[0]).filter(v => v); 124 | if(!colors.length) return; 125 | const canvas = document.createElement('canvas'); 126 | canvas.width = canvas.height = 128; 127 | const edgeSize = 8; 128 | let ctx = canvas.getContext("2d"); 129 | colors.forEach((c, i) => { 130 | ctx.fillStyle = ctx.strokeStyle = c; 131 | ctx.fillRect((canvas.width - edgeSize*2) / colors.length * i + edgeSize, 0, (canvas.width - edgeSize*2) / colors.length, canvas.height); 132 | }); 133 | ctx.globalCompositeOperation="destination-in"; 134 | ctx.fillStyle = "rgb(0,0,0)"; 135 | ctx.roundRect(edgeSize/2,edgeSize/2,canvas.width - edgeSize,canvas.height - edgeSize, 20).fill(); 136 | ctx.globalCompositeOperation="source-over"; 137 | ctx.lineWidth = edgeSize; 138 | ctx.strokeStyle = "#5C0D11"; 139 | ctx.stroke(); 140 | ctx.font = '100px Consolas, Monaco, monospace'; 141 | const tw = ctx.measureText("w").width; 142 | const fs = Math.min((( 100 / (tw * 3))) * canvas.width, canvas.width ); 143 | ctx.font = `${fs.toFixed(2)}px Consolas, Monaco, monospace`; 144 | ctx.fillStyle = "#5C0D11"; 145 | ctx.strokeStyle = "#FFF"; 146 | const t = `${weight}`; 147 | const tl = t.length > 2 ? edgeSize : edgeSize * 2; 148 | ctx.strokeText(`${weight}`,tl, fs); 149 | ctx.fillText(`${weight}`, tl, fs); 150 | canvas.toBlob(function(blob) { 151 | const link = canvas.toDataURL('image/png'); 152 | const favicon = document.createElement("link"); 153 | favicon.rel = "icon"; 154 | favicon.href = URL.createObjectURL(blob); 155 | document.head.appendChild(favicon); 156 | }); 157 | } 158 | 159 | CanvasRenderingContext2D.prototype.roundRect = function (x, y, w, h, r) { 160 | if (w < 2 * r) r = w / 2; 161 | if (h < 2 * r) r = h / 2; 162 | this.beginPath(); 163 | this.moveTo(x+r, y); 164 | this.arcTo(x+w, y, x+w, y+h, r); 165 | this.arcTo(x+w, y+h, x, y+h, r); 166 | this.arcTo(x, y+h, x, y, r); 167 | this.arcTo(x, y, x+w, y, r); 168 | this.closePath(); 169 | return this; 170 | } 171 | 172 | 173 | if(window.location.pathname == "/mytags")saveMyTagData(); 174 | if(window.location.pathname.slice(0, 3) == '/g/')dyeing(); 175 | -------------------------------------------------------------------------------- /DetailPageTagColor/README.md: -------------------------------------------------------------------------------- 1 | 详情页标签颜色 2 | ========= 3 | 4 | [安装脚本](//sleazyfork.org/scripts/415728); 5 | 6 | E站推出的"我的标签"功能非常好用,但是漫画详情页却不像列表一样显示自定义的标签颜色. 7 | 8 | **需要先点开我的标签页,功能才会生效** 9 | 10 | 将标签颜色填充到网页图标中,方便在打开大量页面时候快速查找. 11 | ![image](https://user-images.githubusercontent.com/5716100/120911749-6eccd100-c6bc-11eb-898d-fc647aa3a75d.png) 12 | 新增加标签权重显示, 颜色顺序按照权重顺序排列 13 | 14 | 追加投票状态展示: 15 | ![img](https://github.com/user-attachments/assets/1301b4f4-66f2-4fc0-b229-7a4d0160f174) 16 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /MPVScrollbar/E-hentai Multi-Page-Viewer Scrollbar.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name E-hentai Multi-Page-Viewer Scrollbar 3 | // @version 0.2 4 | // @description 给E-hentai的多页查看器页面加上滚动条 5 | // @author SchneeHertz 6 | // @homepage https://github.com/SchneeHertz/EH-UserScripts 7 | // @supportURL https://github.com/SchneeHertz/EH-UserScripts/issues 8 | // @match https://e-hentai.org/mpv/* 9 | // @match https://exhentai.org/mpv/* 10 | // @grant GM_addStyle 11 | // ==/UserScript== 12 | 13 | 14 | GM_addStyle(` 15 | div#bar3 { 16 | left: -20px; 17 | } 18 | div#pane_images { 19 | overflow-x: hidden; 20 | overflow-y: scroll; 21 | } 22 | `); -------------------------------------------------------------------------------- /MPVScrollbar/README.md: -------------------------------------------------------------------------------- 1 | 多页查看器滚动条 2 | ========= 3 | 4 | * [安装脚本](//sleazyfork.org/scripts/43376); 5 | 6 | 我很意外EH一直没把这个加上去。 7 | ![预览](./screenshot.png) -------------------------------------------------------------------------------- /MPVScrollbar/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/MPVScrollbar/screenshot.png -------------------------------------------------------------------------------- /MagnetHelper/EhTagMagnetHelper.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name eh磁力链助手 3 | // @namespace com.xioxin.EhTagMagnetHelper 4 | // @version 0.4 5 | // @description 在种子列表直接复制磁力链 6 | // @author xioxin 7 | // @icon https://e-hentai.org/favicon.ico 8 | // @homepage https://github.com/EhTagTranslation/UserScripts 9 | // @supportURL https://github.com/EhTagTranslation/UserScripts/issues 10 | // @match *://exhentai.org/gallerytorrents.php* 11 | // @match *://e-hentai.org/gallerytorrents.php* 12 | // @grant GM_setClipboard 13 | // ==/UserScript== 14 | 15 | (function() { 16 | const tableList = document.querySelectorAll("#torrentinfo form table"); 17 | if (tableList && tableList.length) { 18 | tableList.forEach((table) => { 19 | const href = table.querySelector('a')?.href; 20 | if (!href) return; 21 | const magnet = href.replace(/.*?([0-9a-f]{40}).*$/i,"magnet:?xt=urn:btih:$1") ; 22 | if (magnet.length != 60) return; 23 | const insertionPoint = table.querySelector('input[type="submit"],button[type="submit"]'); 24 | if (!insertionPoint) return; 25 | const button = document.createElement("input"); 26 | button.type = "button"; 27 | button.value = "复制磁力链"; 28 | button.setAttribute('ehs-input', ''); 29 | button.style.marginBottom = '4px' 30 | button.onclick = () => { 31 | GM_setClipboard(magnet); 32 | button.value = "✅已复制"; 33 | button.disabled = true; 34 | setTimeout(() => { 35 | button.disabled = false; 36 | button.value = "复制磁力链"; 37 | }, 1000) 38 | }; 39 | insertionPoint.parentNode.insertBefore( button, insertionPoint ); 40 | }) 41 | } 42 | })(); 43 | -------------------------------------------------------------------------------- /MagnetHelper/README.md: -------------------------------------------------------------------------------- 1 | 磁力链复制助手 2 | ========= 3 | 4 | [安装脚本](//sleazyfork.org/scripts/415727); 5 | 6 | 在种子下载页面增加复制磁力链按钮 7 | ![预览](./images/screenshot.png) 8 | -------------------------------------------------------------------------------- /MagnetHelper/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/MagnetHelper/images/screenshot.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UserScripts 2 | 包含了一些与本项目相关的 UserScript。 3 | 4 | ## [缩略图工具](./Thumbnail/) 5 | 自动将 E 绅士大缩略图域名改为手机站域名,并可以一键复制各站点格式的缩略图。 6 | 7 | ## [标签编辑工具](./TagEditor/) 8 | 可将标签区域浮动和半透明,方便查阅底部图片的同时添加标签。 9 | 10 | ## [磁力链助手](./MagnetHelper/) 11 | 种子下载页增加复制磁力链按钮。 12 | 13 | ## [阅读状态](./ReadStatus/) 14 | 在标题前增加阅读状态指示。 15 | 16 | ## [漫画语言快捷按钮](./TranslatedJump/) 17 | 自动搜索其他语言版本的漫画,展示在右上角。 18 | 19 | ## [详情页标签颜色](./DetailPageTagColor/) 20 | 为漫画详情页的标签增加颜色。 21 | 22 | ## [EhAria2下载助手](./AriaEh) 23 | 发送下载任务到Aria2,并在E站直接查看下载状态。还有一键下载存档功能。 24 | -------------------------------------------------------------------------------- /ReadStatus/EhReadStatus.css: -------------------------------------------------------------------------------- 1 | /* ==UserStyle== 2 | @name eh阅读状态 3 | @namespace com.xioxin.EhTagReadStatus 4 | @description 利用css ":visited" 特性在标题前增加阅读状态指示 5 | @version 0.1 6 | @author xioxin 7 | ==/UserStyle== */ 8 | 9 | @-moz-document domain("e-hentai.org"), domain("exhentai.org") { 10 | .itg a .glink::before { 11 | content: "●"; 12 | color: #1a9317; 13 | padding-right: 4px; 14 | } 15 | .itg a:visited .glink::before { 16 | color: #aaa; 17 | } 18 | } -------------------------------------------------------------------------------- /ReadStatus/EhReadStatus.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name eh阅读状态 3 | // @namespace com.xioxin.EhTagReadStatus 4 | // @version 0.1 5 | // @description 利用css ":visited" 特性在标题前增加阅读状态指示 6 | // @author xioxin 7 | // @homepage https://github.com/EhTagTranslation/UserScripts 8 | // @supportURL https://github.com/EhTagTranslation/UserScripts/issues 9 | // @include *://exhentai.org/* 10 | // @include *://e-hentai.org/* 11 | // @grant GM_addStyle 12 | // ==/UserScript== 13 | 14 | GM_addStyle(` 15 | .itg a .glink::before { 16 | content: "●"; 17 | color: #1a9317; 18 | padding-right: 4px; 19 | } 20 | .itg a:visited .glink::before { 21 | color: #aaa; 22 | }`); -------------------------------------------------------------------------------- /ReadStatus/README.md: -------------------------------------------------------------------------------- 1 | 阅读状态 2 | ========= 3 | 4 | 此功能有两种实现方式,任选其一即可 5 | * [安装脚本](//sleazyfork.org/scripts/415726); 6 | * [使用Stylus安装样式](./EhReadStatus.css); 7 | 8 | 在标题前增加阅读状态指示,未打开过的为绿色,打开后为灰色. 利用浏览器`:visited`特性实现. 9 | ![预览](./images/screenshot.png) 10 | -------------------------------------------------------------------------------- /ReadStatus/images/screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/ReadStatus/images/screenshot.png -------------------------------------------------------------------------------- /TagEditor/ETTHelper-TagEditer.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name ETTHelper-TagEditer 3 | // @name:zh-CN E绅士标签翻译辅助工具-标签编辑 4 | // @namespace EhTagTranslation 5 | // @description Help to edit the gallery's tags. 6 | // @description:zh-CN 辅助编辑E绅士画廊的标签 7 | // @include *://exhentai.org/g/* 8 | // @include *://e-hentai.org/g/* 9 | // @version 1.4.6 10 | // @author Mapaler 11 | // @copyright 2019+, Mapaler 12 | // @homepage https://github.com/EhTagTranslation/UserScripts 13 | // @supportURL https://github.com/EhTagTranslation/UserScripts/issues 14 | // @grant GM_registerMenuCommand 15 | // @grant GM_getValue 16 | // @grant GM_setValue 17 | // @grant GM_deleteValue 18 | // ==/UserScript== 19 | 20 | const scriptVersion = (defaultVersion=>typeof GM_info != "undefined" ? GM_info.script.version.replace(/(^\s*)|(\s*$)/g, "") : defaultVersion)("unknown"); //本程序的版本 21 | const scriptName = (defaultName=>{ 22 | if (typeof(GM_info)!="undefined") 23 | { 24 | if (GM_info.script.name_i18n) 25 | { 26 | const i18n = navigator.language.replace("-","_"); //获取浏览器语言 27 | return GM_info.script.name_i18n[i18n]; //支持Tampermonkey 28 | } 29 | else 30 | { 31 | return GM_info.script.localizedName || //支持Greasemonkey 油猴子 3.x 32 | GM_info.script.name; //支持Violentmonkey 暴力猴 33 | } 34 | }else 35 | { 36 | return defaultName; 37 | } 38 | })("ETTWikiHelper-TagEditer"); //本程序的名称 39 | 40 | //限定数值最大最小值 41 | function limitMaxAndMin(num,max,min) 42 | { 43 | return Math.max(Math.min(num, max), min); 44 | } 45 | 46 | //默认CSS内容 47 | const ewh_tag_styleText_Default = ` 48 | /* fallback */ 49 | @font-face { 50 | font-family: 'Material Icons'; 51 | font-style: normal; 52 | font-weight: 400; 53 | src: url(https://fonts.gstatic.com/s/materialicons/v90/flUhRq6tzZclQEJ-Vdg-IuiaDsNc.woff2) format('woff2'); 54 | } 55 | 56 | .material-icons { 57 | font-family: 'Material Icons'; 58 | font-weight: normal; 59 | font-style: normal; 60 | line-height: 1; 61 | letter-spacing: normal; 62 | text-transform: none; 63 | display: inline-block; 64 | white-space: nowrap; 65 | word-wrap: normal; 66 | direction: ltr; 67 | -moz-font-feature-settings: 'liga'; 68 | -moz-osx-font-smoothing: grayscale; 69 | } 70 | 71 | #gd4.ewh-float { /*浮动窗体*/ 72 | position: fixed; 73 | top: 10%; 74 | left: 10%; 75 | background-color : inherit; 76 | margin: 0 !important; 77 | padding: 0 !important; 78 | border-style: ridge; 79 | border-width: 3px; 80 | border-color: #eee black black #eee; 81 | opacity: 0.8; 82 | } 83 | .ewh-bar-floatcaption { /*标题栏整体*/ 84 | height: 22px; 85 | position: relative; 86 | } 87 | .ewh-cpttext-box { /*标题栏文字框*/ 88 | width: 100%; 89 | height: 100%; 90 | float: left; 91 | color: white; 92 | line-height: 22px; 93 | font-size: 14px; 94 | background-image: linear-gradient(to right,#808080,#B7B5BB); 95 | } 96 | .ewh-float .ewh-cpttext-box { /*浮动时的标题颜色*/ 97 | background-image: linear-gradient(to right,#000280,#0F80CD); 98 | } 99 | .ewh-cpttext-box::before{ /*标题图标*/ 100 | content: "🏷️"; 101 | } 102 | .ewh-cpttext-box span { /*标题文字*/ 103 | pointer-events:none; 104 | user-select: none; 105 | } 106 | .ewh-cptbtn-box { /*标题按钮框*/ 107 | height: 100%; 108 | position: absolute; 109 | top: 0; 110 | right: 8px; 111 | line-height: 22px; 112 | } 113 | .ewh-cpt-btn { /*平时的按钮*/ 114 | vertical-align: middle; 115 | padding: 0; 116 | font-size: 14px; 117 | margin-top:-2px; 118 | height: 18px; 119 | width: 20px; 120 | background-color: #c0c0c0; 121 | border-style: outset; 122 | border-width: 2px; 123 | border-color: white black black white; 124 | } 125 | .ewh-cpt-rag { /*平时的范围拖动条*/ 126 | vertical-align: middle; 127 | padding: 0; 128 | font-size: 14px; 129 | margin-top:0; 130 | height: 18px; 131 | width: 100px; 132 | } 133 | .ewh-cpt-btn:active { /*按钮按下时的凹陷*/ 134 | background-color: #d8d8d8; 135 | padding-left: 1px !important; 136 | padding-top: 1px !important; 137 | border-style: inset; 138 | border-color: black white white black; 139 | } 140 | .ewh-cpt-btn:focus { /*激活后的虚线*/ 141 | outline: dotted 1px black; 142 | } 143 | .ewh-btn-closefloat,.ewh-rag-opacity { /*平时隐藏关闭浮动的按钮*/ 144 | display: none; 145 | } 146 | .ewh-float .ewh-btn-closefloat,.ewh-float .ewh-rag-opacity { /*浮动时显示关闭浮动的按钮*/ 147 | display: unset; 148 | } 149 | .ewh-float .ewh-btn-openfloat{ /*浮动时隐藏开启浮动的按钮*/ 150 | display: none; 151 | } 152 | .ewh-bar-tagsearch{ 153 | position: relative; 154 | } 155 | .ewh-ipt-tagsearch{ 156 | width: 200px; 157 | box-sizing: border-box; 158 | } 159 | .ewh-tagsearchtext,.ewh-tagsearchlink{ 160 | font-size: 10pt; 161 | } 162 | .ewh-bar-tagsearch a::before{ 163 | font-size: 10pt; 164 | font-weight: bold; 165 | } 166 | .ewh-bar-tagsearch a::after{ 167 | font-size: 10pt; 168 | background: #c0c0c0; 169 | color:black; 170 | border-style: ridge; 171 | border-width: 3px; 172 | border-color: #eee black black #eee; 173 | position:absolute; 174 | z-index:999; 175 | padding:8px; 176 | min-width:150px; 177 | max-width:500px; 178 | white-space:pre-wrap; 179 | opacity: 0; 180 | transition: opacity 0.1s; 181 | top:28px; 182 | left:45%; 183 | pointer-events:none; 184 | font-weight: 400; 185 | line-height: 20px; 186 | } 187 | .ewh-bar-tagsearch a:hover::after{ 188 | opacity: 1; 189 | } 190 | `; 191 | //获取Tag编辑区 192 | var ewhWindow = document.querySelector("#gd4"); 193 | 194 | //增加浮动窗标题栏 195 | var divCaptionBar = ewhWindow.insertBefore(document.createElement("div"),gd4.firstChild); 196 | divCaptionBar.className = "ewh-bar-floatcaption"; 197 | 198 | //生成辅助器CSS 199 | var ewh_tag_style = divCaptionBar.appendChild(document.createElement("style")); 200 | ewh_tag_style.type = "text/css"; 201 | ewh_tag_style.appendChild(document.createTextNode(ewh_tag_styleText_Default)); 202 | 203 | //生成标题栏文字 204 | var divCaption = divCaptionBar.appendChild(document.createElement("div")); 205 | divCaption.className = "ewh-cpttext-box"; 206 | divCaption.appendChild(document.createElement("span")).appendChild(document.createTextNode(scriptName)); 207 | 208 | //添加窗体鼠标拖拽移动 209 | var windowPosition = ewhWindow.position = [0, 0]; //[X,Y] 用以储存窗体开始拖动时的鼠标相对窗口坐标差值。 210 | divCaption.addEventListener("mousedown", function(e) { //按下鼠标则添加移动事件 211 | if (!ewhWindow.classList.contains("ewh-float")) return; //如果不是浮动窗体直接结束 212 | var eX = limitMaxAndMin(e.clientX,document.documentElement.clientWidth,0), eY = limitMaxAndMin(e.clientY,document.documentElement.clientHeight,0); //不允许鼠标超出网页。 213 | windowPosition[0] = eX - ewhWindow.offsetLeft; 214 | windowPosition[1] = eY - ewhWindow.offsetTop; 215 | var handler_mousemove = function(e) { //移动鼠标则修改窗体坐标 216 | var eX = limitMaxAndMin(e.clientX,document.documentElement.clientWidth,0), eY = limitMaxAndMin(e.clientY,document.documentElement.clientHeight,0); //不允许鼠标超出网页。 217 | ewhWindow.style.left = (eX - windowPosition[0]) + 'px'; 218 | ewhWindow.style.top = (eY - windowPosition[1]) + 'px'; 219 | }; 220 | var handler_mouseup = function(e) { //抬起鼠标则取消移动事件 221 | document.removeEventListener("mousemove", handler_mousemove); 222 | if (ewhWindow.style.left) GM_setValue("floatwindow-left",ewhWindow.style.left); //储存窗体位置 223 | if (ewhWindow.style.top) GM_setValue("floatwindow-top",ewhWindow.style.top); //储存窗体位置 224 | 225 | }; 226 | document.addEventListener("mousemove", handler_mousemove); 227 | document.addEventListener("mouseup", handler_mouseup, { once: true }); 228 | }); 229 | 230 | //生成标题栏按钮 231 | var divButtonBox = divCaptionBar.appendChild(document.createElement("div")); 232 | divButtonBox.className = "ewh-cptbtn-box"; 233 | 234 | //生成修改设置的按钮 235 | var ragOpacity = divButtonBox.appendChild(document.createElement("input")); 236 | ragOpacity.className = "ewh-cpt-rag ewh-rag-opacity"; 237 | ragOpacity.type = "range"; 238 | ragOpacity.max = 1; 239 | ragOpacity.min = 0.5; 240 | ragOpacity.step = 0.01; 241 | ragOpacity.title = "窗体不透明度"; 242 | ragOpacity.onchange = ragOpacity.oninput = function(){ 243 | ewhWindow.style.opacity = this.value; 244 | }; 245 | ragOpacity.onchange = function(){ 246 | ragOpacity.oninput(); 247 | if (ewhWindow.style.opacity) GM_setValue("floatwindow-opacity",ewhWindow.style.opacity); //储存窗体透明度 248 | }; 249 | 250 | //生成打开浮动状态的按钮 251 | var btnOpenFloat = divButtonBox.appendChild(document.createElement("button")); 252 | btnOpenFloat.className = "ewh-cpt-btn material-icons ewh-btn-openfloat"; 253 | btnOpenFloat.title = "浮动标签编辑框"; 254 | btnOpenFloat.appendChild(document.createElement("span").appendChild(document.createTextNode("open_in_new"))); 255 | btnOpenFloat.onclick = function(){ 256 | //ewhWindow.setAttribute("style",ewhWindow.getAttribute("style_bak")); 257 | //ewhWindow.removeAttribute("style_bak"); 258 | ewhWindow.classList.add("ewh-float"); 259 | ewhWindow.style.left = GM_getValue("floatwindow-left"); 260 | ewhWindow.style.top = GM_getValue("floatwindow-top"); 261 | ewhWindow.style.opacity = ragOpacity.value = GM_getValue("floatwindow-opacity") || 0.8; 262 | }; 263 | GM_registerMenuCommand("打开浮动标签编辑框", btnOpenFloat.onclick); 264 | 265 | //生成关闭浮动状态的按钮 266 | var btnCloseFloat = divButtonBox.appendChild(document.createElement("button")); 267 | btnCloseFloat.className = "ewh-cpt-btn material-icons ewh-btn-closefloat"; 268 | btnCloseFloat.title = "关闭浮动窗体"; 269 | btnCloseFloat.appendChild(document.createElement("span").appendChild(document.createTextNode("close"))); 270 | btnCloseFloat.onclick = function(){ 271 | //ewhWindow.setAttribute("style_bak",ewhWindow.getAttribute("style")); 272 | if (ewhWindow.style.left) GM_setValue("floatwindow-left",ewhWindow.style.left); //储存窗体位置 273 | if (ewhWindow.style.top) GM_setValue("floatwindow-top",ewhWindow.style.top); //储存窗体位置 274 | if (ewhWindow.style.opacity) GM_setValue("floatwindow-opacity",ewhWindow.style.opacity); //储存窗体透明度 275 | ewhWindow.removeAttribute("style"); 276 | ewhWindow.classList.remove("ewh-float"); 277 | }; 278 | GM_registerMenuCommand("重置浮动窗位置与透明度", function(){ 279 | btnCloseFloat.onclick(); //先关掉窗体,然后删除设置 280 | GM_deleteValue("floatwindow-left"); 281 | GM_deleteValue("floatwindow-top"); 282 | GM_deleteValue("floatwindow-opacity"); 283 | }); 284 | 285 | var nameSpaceC = { 286 | artist:"艺术家", 287 | female:"女性", 288 | male:"男性", 289 | parody:"原作", 290 | character:"角色", 291 | group:"团队", 292 | language:"语言", 293 | reclass:"重新分类", 294 | misc:"杂项" 295 | }; 296 | //获取标签数据列表 297 | var tagdatalist = document.querySelector("#tbs-tags"); 298 | var tagData; 299 | //获取真实标签输入框 300 | var newTagText = document.querySelector("#newtagfield"); 301 | if (!tagdatalist) //没有ETS,但有ETS扩展版的处理方式 302 | { 303 | var tagDataStr = localStorage.getItem("EhSyringe.tag-list"); //ETS扩展版1.2.1的数据 304 | if (typeof(tagDataStr) == "string") 305 | { 306 | tagData = JSON.parse(tagDataStr); 307 | tagdatalist = document.createElement("datalist"); 308 | tagdatalist.id = "tbs-tags"; 309 | newTagText.setAttribute("list","tbs-tags"); 310 | tagData.forEach(function(tag){ 311 | tagdatalist.appendChild(new Option(nameSpaceC[tag.namespace]+":"+tag.name,tag.search)); 312 | }) 313 | newTagText.insertAdjacentElement('afterend',tagdatalist); 314 | } 315 | } 316 | if (tagdatalist) //如果存在则生成标签搜索框 317 | { 318 | var taglist = tagdatalist.options; 319 | //增加标签搜索框箱子 320 | var divSearchBar = ewhWindow.insertBefore(document.createElement("div"),document.querySelector("#tagmenu_act")); 321 | divSearchBar.className = "ewh-bar-tagsearch"; 322 | 323 | //增加标签搜索框 324 | var iptTagSearch = divSearchBar.appendChild(document.createElement("input")); 325 | iptTagSearch.type = "text"; 326 | iptTagSearch.placeholder = "🔍标签搜索:回车附加到下方▼"; 327 | iptTagSearch.setAttribute("list","tbs-tags"); 328 | iptTagSearch.className="ewh-ipt-tagsearch"; 329 | //增加标签搜索提醒文字 330 | var spnTagSearchInfo = divSearchBar.appendChild(document.createElement("span")); 331 | spnTagSearchInfo.className="ewh-tagsearchtext"; 332 | //增加标签搜索提醒标签 333 | var aTagSearchInfo = divSearchBar.appendChild(document.createElement("a")); 334 | aTagSearchInfo.className="ewh-tagsearchlink"; 335 | 336 | iptTagSearch.onkeypress = function(e){ 337 | if(e.key == "Enter"){ //回车,将内容附加到真实Tag框,并清空搜索框 338 | var _this = this; 339 | if (_this.value.length == 0) 340 | { //如果什么都没输入,相当于提交 341 | spnTagSearchInfo.innerHTML = ""; 342 | aTagSearchInfo.removeAttribute("id"); 343 | aTagSearchInfo.innerHTML = ""; 344 | if (newTagText.value.length > 0)tag_from_field(); //如果输入框有内容点击Tag提交 345 | return; 346 | }; 347 | 348 | var clabel = false, useGuess = false, guess = false; 349 | var tagC; 350 | 351 | if (tagData) //如果有JSON数据,直接使用 352 | { //扩展版的JSON数据 353 | var searchRes = tagData.filter(function(tag){ //搜索绝对等于的 354 | return tag.search == _this.value; 355 | }); 356 | if (searchRes.length<1) 357 | { //猜测式搜索 358 | searchRes = tagData.filter(function(tag){ 359 | return tag.name.indexOf(_this.value)>=0 || tag.key.indexOf(_this.value)>=0; //有非tag字符时才搜索中文,其他时候搜索key 360 | }); 361 | if (searchRes.length>0) 362 | { 363 | guess = true; //标记为猜的 364 | _this.value = searchRes[0].search; //目前的输入修改为猜的tag 365 | } 366 | } 367 | if (searchRes.length>0) 368 | { 369 | tagC = searchRes[0]; 370 | clabel = tagC.name; 371 | } 372 | }else 373 | { //脚本版的数据 374 | if (_this.value.replace(/[\w\:\"\s\-\.\'\$]/,"").length>0) useGuess = true; //如果存在非tag字符,则尝试搜索中文。 375 | for (var ti=0;ti0) 382 | { 383 | clabel = taglist[ti].label; 384 | guess = true; //标记为猜的 385 | _this.value = taglist[ti].value; //目前的输入修改为猜的tag 386 | break; 387 | } 388 | } 389 | } 390 | if (clabel) 391 | { 392 | var shortTag; 393 | if (tagData) 394 | { //扩展版的JSON数据 395 | shortTag = (tagC.namespace=="misc"?"":(tagC.namespace.substr(0,1) + ":")) + tagC.key; //缩减Tag长度,以便一次能多提交一些Tag 396 | }else 397 | { //脚本版的数据 398 | var regArr = /^(\w+):"?([\w+\s\-\'\.]+)\$?"?$/ig.exec(_this.value); 399 | shortTag = (regArr[1]=="misc"?"":(regArr[1].substr(0,1) + ":")) + regArr[2]; //缩减Tag长度,以便一次能多提交一些Tag 400 | } 401 | if ((newTagText.value+","+shortTag).length>200) 402 | { 403 | spnTagSearchInfo.innerHTML = "⛔超长(原始标签输入框限定200字符)"; 404 | if (!tagData) aTagSearchInfo.removeAttribute("id"); 405 | aTagSearchInfo.innerHTML = ""; 406 | }else 407 | { 408 | newTagText.value = (newTagText.value.length>0)?(newTagText.value+","+shortTag):shortTag; 409 | spnTagSearchInfo.innerHTML = (guess?"程序猜测你想添加":"你添加了")+" " + (tagData?nameSpaceC[tagC.namespace]:clabel.split(":")[0]) + ":"; 410 | if (!tagData) aTagSearchInfo.id = "ta_" + (regArr[1]=="misc"?"":regArr[1]+":") + regArr[2].replace(/\s/igm,"_"); 411 | aTagSearchInfo.innerHTML = clabel; 412 | _this.value = ""; 413 | } 414 | }else 415 | { 416 | spnTagSearchInfo.innerHTML = "☹️数据库里没有这个标签"; 417 | if (!tagData) aTagSearchInfo.removeAttribute("id"); 418 | aTagSearchInfo.innerHTML = ""; 419 | } 420 | } 421 | }; 422 | } -------------------------------------------------------------------------------- /TagEditor/README.md: -------------------------------------------------------------------------------- 1 | 标签编辑工具 2 | ========= 3 | 可将标签区域浮动和半透明,方便查阅底部图片的同时添加标签。 4 | 5 | 6 | [安装脚本](//sleazyfork.org/scripts/381766) 7 | 8 | 默认情况下并不会浮动,点击浮动按钮即可开启浮动窗口 9 | ![打开浮动](./images/OpenFloat.png) 10 | 11 | 标题栏内的滑柄可以控制浮动窗体透明度。 12 | ![预览](./images/Preview.png) 13 | ### 以下功能需配合 [EhSyringe](../../../../EhSyringe)扩展 或 [E绅士翻译注射器(EhTagSyringe)](//sleazyfork.org/zh-CN/scripts/33136-ehtagsyringe)脚本 使用 14 | 原始的标签输入框一次只能搜索一个标签,每次都要等待新增标签提交成功后才能搜索下一个标签,比较花时间。 15 | 可以在本工具添加的搜索框内进行搜索添加到原始输入框,方便一次提交多个标签。 16 | 1. 输入文字搜索标签,点击对应标签,会得到标签值 17 | ![增加标签1](./images/Add1.png)![增加标签2](./images/Add2.png) 18 | 1. 按回车可将该标签增加到原始输入框,并会给出该标签提示。 19 | ![增加标签3](./images/Add3.png) 20 | 1. 搜索标签并回车将会自动附加到原始输入框,最后手动点击一次“Tag”按钮即可提交所有标签。 21 | ![增加标签4](./images/Add4.png) 22 | 1. 不支持搜索翻译数据库内不存在的标签,需要添加未收录标签请直接在原始输入框输入。 23 | 24 | ## 更新日志 25 | v1.2.0版本的新功能: 26 | * 支持简单猜测中文 27 | 1. 输入含有的中文 28 | ![猜测增加标签1](./images/GuessAdd1.png) 29 | 1. 回车后会自动填入第一个包含输入文字的标签 30 | ![猜测增加标签2](./images/GuessAdd2.png) 31 | * 支持记录窗体位置与透明度,如果窗体因为特殊情况消失不见了,可以从菜单重置窗体。 32 | ![重置窗体](./images/Reset.png) 33 | 34 | v1.3.0版本的新功能: 35 | * 自动缩短标签命名空间,以最短形式插入。由于原始标签输入框限定一次最多提交200字符,因此使用短名可以一次提交更多标签。 36 | ![原始完整标签](./images/ShortTag1.png) 37 | ![缩短为短标签](./images/ShortTag2.png) 38 | * 当标签搜索框为空时按下回车,会自动提交原始标签输入框。 39 | 40 | v1.4.0更新内容: 41 | * 增加对 EhSyringe 扩展的支持 -------------------------------------------------------------------------------- /TagEditor/images/Add1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/TagEditor/images/Add1.png -------------------------------------------------------------------------------- /TagEditor/images/Add2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/TagEditor/images/Add2.png -------------------------------------------------------------------------------- /TagEditor/images/Add3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/TagEditor/images/Add3.png -------------------------------------------------------------------------------- /TagEditor/images/Add4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/TagEditor/images/Add4.png -------------------------------------------------------------------------------- /TagEditor/images/GuessAdd1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/TagEditor/images/GuessAdd1.png -------------------------------------------------------------------------------- /TagEditor/images/GuessAdd2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/TagEditor/images/GuessAdd2.png -------------------------------------------------------------------------------- /TagEditor/images/OpenFloat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/TagEditor/images/OpenFloat.png -------------------------------------------------------------------------------- /TagEditor/images/Preview.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/TagEditor/images/Preview.png -------------------------------------------------------------------------------- /TagEditor/images/Reset.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/TagEditor/images/Reset.png -------------------------------------------------------------------------------- /TagEditor/images/ShortTag1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/TagEditor/images/ShortTag1.png -------------------------------------------------------------------------------- /TagEditor/images/ShortTag2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/TagEditor/images/ShortTag2.png -------------------------------------------------------------------------------- /Thumbnail/ETTHelper-Thumbnail.ui.css: -------------------------------------------------------------------------------- 1 | .EWHT-ul 2 | { 3 | padding: 0; 4 | margin: 0; 5 | list-style: none; 6 | } 7 | .EWHT-ul>li 8 | { 9 | display: inline-block; 10 | margin-right: 3px; 11 | } 12 | .EWHT-ul>li:last-of-type 13 | { 14 | margin-right: 0; 15 | } 16 | .EWHT-btn::before { 17 | content: attr(data-type); 18 | } 19 | div[id^="cell_"], 20 | div.gdtl, 21 | div.gi 22 | { 23 | height: unset !important; 24 | left: unset !important; 25 | top: unset !important; 26 | position: relative; 27 | display: inline-block; 28 | float: unset; 29 | } 30 | #t{ 31 | height: unset !important; 32 | } 33 | #t div[id^="cell_"]{ 34 | margin: 0; 35 | } -------------------------------------------------------------------------------- /Thumbnail/ETTHelper-Thumbnail.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name ETTHelper-Thumbnail 3 | // @name:zh-CN E绅士标签翻译辅助工具-缩略图 4 | // @namespace EhTagTranslation 5 | // @description Help to get thumbnail for write EhTagTranslation's translation detail. 6 | // @description:zh-CN 一键复制E绅士的缩略图,便于书写标签翻译项目的详细介绍。 7 | // @include *://exhentai.org/g/* 8 | // @include *://e-hentai.org/g/* 9 | // @include *://e-hentai.org/lofi/g/* 10 | // @include *://upl*d.e-hentai.org/managegallery* 11 | // @include *://upl*d.e-hentai.org/upl*d/managegallery* 12 | // @include *://exhentai.org/upl*d/managegallery* 13 | // @include *://upl*d.exhentai.org/upl*d/managegallery* 14 | // @resource ui-style https://github.com/EhTagTranslation/UserScripts/raw/master/Thumbnail/ETTHelper-Thumbnail.ui.css?v=3.1 15 | // @version 3.2.0 16 | // @grant GM_getResourceText 17 | // @grant GM_addStyle 18 | // @grant GM_setClipboard 19 | // @grant GM_notification 20 | // @author Mapaler 21 | // @copyright 2017+, Mapaler 22 | // @homepage https://github.com/EhTagTranslation/UserScripts 23 | // @supportURL https://github.com/EhTagTranslation/UserScripts/issues 24 | // ==/UserScript== 25 | 26 | (()=>{ 27 | const styleText = GM_getResourceText("ui-style"); 28 | GM_addStyle(styleText); 29 | const { thumbs, thumbType } = (()=>{ 30 | let thumbs, thumbType; 31 | if (/^\/g\//.test(location.pathname)) {//图像画廊 32 | thumbs = Array.from(document.querySelectorAll('#gdt>a')); 33 | thumbType = 'gallery'; 34 | } 35 | else if (/^\/lofi\//.test(location.pathname)) {//手机版画廊 36 | thumbs = Array.from(document.querySelectorAll('#gh>a')); 37 | thumbType = 'lofi'; 38 | } 39 | else if (/managegallery/.test(location.pathname)) {//画廊编辑 40 | thumbs = Array.from(document.querySelectorAll('#t>.nosel>div')); 41 | thumbType = 'upload'; 42 | } 43 | return { thumbs, thumbType }; 44 | })(); 45 | 46 | if (thumbType === 'gallery' || thumbType === 'lofi') { 47 | const pageLink = thumbs[0]?.nodeName == 'A' ? thumbs[0] : thumbs[0]?.querySelector('a'); 48 | const match = pageLink.pathname.match(/\-(\d+)$/); 49 | const firstPage = parseInt(match[1],10); 50 | const styleText2 = ` 51 | body{ 52 | counter-reset: page ${firstPage-1}; 53 | } 54 | .EWHT-ul::before{ 55 | counter-increment: page; 56 | content: "P" counter(page) ": "; 57 | } 58 | `; 59 | GM_addStyle(styleText2); 60 | } 61 | 62 | const getImgId = (src) => { 63 | const url = new URL(src); 64 | if (url.searchParams.get('fileid')) 65 | { 66 | return url.searchParams.get('fileid'); 67 | }else 68 | { 69 | const RegRes = /(\w+)\-(\d+)\-(\d+)\-(\d+)\-(\w+)(?:_l|_250)\b/i.exec(src); 70 | if (RegRes) { 71 | return `${RegRes[1]}-${RegRes[2]}-${RegRes[3]}-${RegRes[4]}-${RegRes[5]}`; 72 | } 73 | else { 74 | return null; 75 | } 76 | } 77 | } 78 | 79 | function copyString(event) { 80 | event.stopPropagation(); 81 | event.preventDefault(); 82 | 83 | const type = this.dataset.type; 84 | const imgNode = this.parentNode.parentNode.parentNode.querySelector(":where(img, [title][style*=background])"); 85 | 86 | const src_original = ((node)=>{ 87 | if (node.nodeName == "IMG") return node.src; 88 | const computedStyle = window.getComputedStyle(node, false); 89 | const bgiSrc = computedStyle.backgroundImage.replace(/url\(["']?(.+?)["']?\)/gi, "$1"); 90 | return URL.canParse(bgiSrc) && bgiSrc; 91 | })(imgNode); 92 | 93 | const fileId = getImgId(src_original); 94 | if (fileId === null) { 95 | alert('错误:\n未找到符合格式的图片 ID。'); 96 | return; 97 | } 98 | const src_out = `https://ehgt.org/${fileId.substring(0,2)}/${fileId.substring(2,4)}/${fileId}_l.jpg` 99 | 100 | let outstr,typeName; 101 | switch(type) { 102 | case "图":{ 103 | outstr = `![${type}](${src_out})`; 104 | typeName = "MD 格式图片地址"; 105 | break; 106 | } 107 | case "隐":{ 108 | outstr = `![${type}](# "${src_out}")`; 109 | typeName = "R18 MD 格式图片地址"; 110 | break; 111 | } 112 | case "限":{ 113 | outstr = `![${type}](## "${src_out}")`; 114 | typeName = "R18G 限制级 MD 格式图片地址"; 115 | break; 116 | } 117 | default:{ 118 | outstr = src_out; 119 | typeName = "单纯图片地址"; 120 | break; 121 | } 122 | } 123 | 124 | GM_setClipboard(outstr); 125 | GM_notification(outstr, //显示的文本 126 | `已复制到剪贴板 - ${typeName}`, //标题 127 | //src_original //显示原版地址,这样可以节省加载时间 128 | ); 129 | } 130 | 131 | const creat_li = (type) => { 132 | const li = document.createElement("li"); 133 | li.className = "EWHT-li"; 134 | const btn = li.appendChild(document.createElement("button")); 135 | btn.className = "EWHT-btn"; 136 | btn.dataset.type = type; 137 | btn.onclick = copyString; 138 | return li 139 | } 140 | const buildBtnList = () => { 141 | const list = ["纯","图","隐","限"].map(creat_li); 142 | const ul = document.createElement("ul"); 143 | ul.className = "EWHT-ul"; 144 | ul.append(...list); 145 | return ul; 146 | } 147 | 148 | thumbs.forEach(thumb=>{ 149 | // //获取到图像 150 | // const img = thumb.querySelector(":where(img, [title][style*=background])"); 151 | // //如果图像有title 152 | // if (img.title) { 153 | // const filename = document.createElement("div"); 154 | // filename.className = "filename"; 155 | // filename.textContent = img.title; 156 | // img.parentElement.append(filename); 157 | // } 158 | 159 | thumb.appendChild(buildBtnList()); 160 | }); 161 | })(); -------------------------------------------------------------------------------- /Thumbnail/README.md: -------------------------------------------------------------------------------- 1 | 缩略图工具 2 | ========= 3 | 自动将 E 绅士大缩略图域名改为手机站域名,并可以一键复制各站点格式的缩略图。 4 | 5 | 去到处找能在 E 站看的图还是很困难的一件工作,此脚本工具可以快速将E站作品缩略图复制为 Wiki 使用的格式。 6 | [安装脚本](//sleazyfork.org/scripts/31743) 7 | 8 | 在账号设置内设置缩略图大小为普通,以开启**独立图片**模式 9 | ![打开大图模式](./images/SetLargeThumbnail.webp) 10 | 11 | 点击按钮自动将对应的站点格式复制到剪贴板。(里站、表站、手机版) 12 | 13 | | 按钮名 | 格式 | 解释 | 14 | | ----- | ---- | ---- | 15 | | **纯** | `图片网址` | 单纯图片网址 | 16 | | **图** | `![图](图片网址)` | MarkDown 格式图片格式 | 17 | | **隐** | `![图](# "图片网址")` | 隐藏的 MarkDown 格式图片格式 | 18 | | **限** | `![图](## "图片网址")` | R18G 限制级的 MarkDown 格式图片格式 | 19 | 20 | ![预览](./images/Preview.webp) -------------------------------------------------------------------------------- /Thumbnail/images/Preview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/Thumbnail/images/Preview.webp -------------------------------------------------------------------------------- /Thumbnail/images/SetLargeThumbnail.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/Thumbnail/images/SetLargeThumbnail.webp -------------------------------------------------------------------------------- /TranslatedJump/EhTagTranslatedJump.user.js: -------------------------------------------------------------------------------- 1 | // ==UserScript== 2 | // @name eh漫画语言快捷按钮 3 | // @namespace com.xioxin.translatedJump 4 | // @version 0.7 5 | // @description 快速跳转到其他漫画语言版本 6 | // @author xioxin 7 | // @homepage https://github.com/EhTagTranslation/UserScripts 8 | // @supportURL https://github.com/EhTagTranslation/UserScripts/issues 9 | // @include *://exhentai.org/g/* 10 | // @include *://e-hentai.org/g/* 11 | // @grant GM_addStyle 12 | // ==/UserScript== 13 | const languagePriority = ['chinese', 'japanese', 'english']; 14 | const languages = ` 15 | albanian 阿尔巴尼亚语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvc3ZnPg== 16 | arabic 阿拉伯语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvc3ZnPg== 17 | bengali 孟加拉语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvc3ZnPg== 18 | catalan 加泰罗尼亚语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvc3ZnPg== 19 | cebuano 宿务语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvc3ZnPg== 20 | chinese 汉语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2QyMmYyNyIgZD0iTTUgMTdoNjJ2MzhINXoiLz48Y2lyY2xlIGN4PSIyNCIgY3k9IjM0IiByPSIxLjc1IiBmaWxsPSIjZjFiMzFjIi8+PGNpcmNsZSBjeD0iMjQiIGN5PSIyNCIgcj0iMS43NSIgZmlsbD0iI2YxYjMxYyIvPjxjaXJjbGUgY3g9IjI4IiBjeT0iMzEiIHI9IjEuNzUiIGZpbGw9IiNmMWIzMWMiLz48Y2lyY2xlIGN4PSIyOCIgY3k9IjI2IiByPSIxLjc1IiBmaWxsPSIjZjFiMzFjIi8+PHBhdGggZmlsbD0iI2YxYjMxYyIgc3Ryb2tlPSIjZjFiMzFjIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Ik0xMy41MjggMzIuNDQ1bDIuNDcyLTggMi40NzMgOEwxMiAyNy41aDhsLTYuNDcyIDQuOTQ1eiIvPjxnPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Ik01IDE3aDYydjM4SDV6Ii8+PC9nPjwvc3ZnPg== 21 | czech 捷克语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvc3ZnPg== 22 | danish 丹麦语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvc3ZnPg== 23 | dutch 荷兰语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvc3ZnPg== 24 | english 英语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iIzFlNTBhMCIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNDAgMjguODU2VjMyaDEwLjE4MUw2NyAyMS42OTFWMTdoLTcuNjU0TDQwIDI4Ljg1NnoiLz48cGF0aCBmaWxsPSIjZDIyZjI3IiBkPSJNNjcgMTdoLTMuODI3TDQwIDMxLjIwM1YzMmgzLjQ4Mkw2NyAxNy41ODZWMTd6Ii8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTU5LjM0NyA1NUg2N3YtNC42OTJMNTAuMTgyIDQwSDQwdjMuMTQzTDU5LjM0NyA1NXoiLz48cGF0aCBmaWxsPSIjZDIyZjI3IiBkPSJNNjcgNTV2LTIuMzQ3TDQ2LjM1NSA0MGgtNC43ODdsMjQuNDc0IDE1SDY3eiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0zMiA0My4xNDRWNDBIMjEuODE5TDUgNTAuMzA5VjU1aDcuNjU0TDMyIDQzLjE0NHoiLz48cGF0aCBmaWxsPSIjZDIyZjI3IiBkPSJNNSA1NWgzLjgyN0wzMiA0MC43OTdWNDBoLTMuNDgyTDUgNTQuNDE0VjU1eiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0xMi42NTMgMTdINXY0LjY5MkwyMS44MTggMzJIMzJ2LTMuMTQzTDEyLjY1MyAxN3oiLz48cGF0aCBmaWxsPSIjZDIyZjI3IiBkPSJNNSAxN3YyLjM0N0wyNS42NDYgMzJoNC43ODZMNS45NTggMTdINXoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNSAzMWg2MnYxMEg1eiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik0zMSAxN2gxMHYzOEgzMXoiLz48cGF0aCBmaWxsPSIjZDIyZjI3IiBkPSJNNSAzM2g2MnY2SDV6Ii8+PHBhdGggZmlsbD0iI2QyMmYyNyIgZD0iTTMzIDE3aDZ2MzhoLTZ6Ii8+PGc+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48L2c+PC9zdmc+ 25 | esperanto 世界语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGNpcmNsZSBjeD0iMzYiIGN5PSIzNiIgcj0iMjgiIGZpbGw9IiM5MkQzRjUiLz48ZyBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2Utd2lkdGg9IjIiPjxjaXJjbGUgY3g9IjM2IiBjeT0iMzYiIHI9IjI4Ii8+PHBhdGggZD0iTTM2IDh2NTZjLTguNTYgMC0xNS41LTEyLjUzNi0xNS41LTI4UzI3LjQ0IDggMzYgOGM4LjU2IDAgMTUuNSAxMi41MzYgMTUuNSAyOFM0NC41NiA2NCAzNiA2NE02NCAzNkg4TTYwIDIySDEyTTYwIDUwSDEyIi8+PC9nPjwvc3ZnPg== 26 | estonian 爱沙尼亚语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBkPSJNNSAzMGg2MnYxMkg1eiIvPjxwYXRoIGZpbGw9IiMxZTUwYTAiIGQ9Ik01IDE3aDYydjEzSDV6Ii8+PGc+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48L2c+PC9zdmc+ 27 | finnish 芬兰语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjMWU1MGEwIiBzdHJva2U9IiMxZTUwYTAiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNjcgMzNIMzBWMTdoLTZ2MTZINXY2aDE5djE2aDZWMzloMzd2LTZ6Ii8+PGc+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48L2c+PC9zdmc+ 28 | french 法语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjMWU1MGEwIiBkPSJNNSAxN2gyMXYzOEg1eiIvPjxwYXRoIGZpbGw9IiNkMjJmMjciIGQ9Ik00NiAxN2gyMXYzOEg0NnoiLz48Zz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvZz48L3N2Zz4= 29 | german 德语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjMWU1MGEwIiBkPSJNNSAxN2gyMXYzOEg1eiIvPjxwYXRoIGZpbGw9IiNkMjJmMjciIGQ9Ik00NiAxN2gyMXYzOEg0NnoiLz48Zz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvZz48L3N2Zz4= 30 | greek 希腊语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjMWU1MGEwIiBkPSJNNSAzNGg2MnY0SDV6TTUgMjUuNzVoNjJ2NEg1ek01IDQyLjI1aDYydjRINXpNNSA1MGg2MnY1SDV6TTUgMTdoNjJ2NUg1eiIvPjxwYXRoIGZpbGw9IiMxZTUwYTAiIGQ9Ik01IDE3aDIydjIxSDV6Ii8+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTE0LjUgMTdoNHYyMmgtNHoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNSAyNS43NWgyMnY0SDV6Ii8+PGc+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48L2c+PC9zdmc+ 31 | hebrew 希伯来语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvc3ZnPg== 32 | hindi 印地语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvc3ZnPg== 33 | hungarian 匈牙利语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iIzVjOWUzMSIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjZDIyZjI3IiBkPSJNNSAxN2g2MnYxM0g1eiIvPjxwYXRoIGZpbGw9IiNmZmYiIGQ9Ik01IDMwaDYydjEySDV6Ii8+PGc+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48L2c+PC9zdmc+ 34 | indonesian 印尼语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2QyMmYyNyIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNSAzNmg2MnYxOUg1eiIvPjxnPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Ik01IDE3aDYydjM4SDV6Ii8+PC9nPjwvc3ZnPg== 35 | italian 意大利语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjNWM5ZTMxIiBkPSJNNSAxN2gyMXYzOEg1eiIvPjxwYXRoIGZpbGw9IiNkMjJmMjciIGQ9Ik00NiAxN2gyMXYzOEg0NnoiLz48Zz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvZz48L3N2Zz4= 36 | japanese 日语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48Y2lyY2xlIGN4PSIzNiIgY3k9IjM2IiByPSI5IiBmaWxsPSIjZDIyZjI3Ii8+PGc+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48L2c+PC9zdmc+ 37 | korean 韩语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48Y2lyY2xlIGN4PSIzNiIgY3k9IjM2IiByPSI5IiBmaWxsPSIjZDIyZjI3Ii8+PGcgZmlsbD0iIzFlNTBhMCI+PHBhdGggZD0iTTI4LjEyNyAzMS42NzZBNC40OTIgNC40OTIgMCAwIDAgMzYgMzZjLjAyMy0uMDQuMDM0LS4wODMuMDU1LS4xMjNsLjAyNC4wMTRhNC40OTMgNC40OTMgMCAwIDEgNy43MjQgNC41OWwuMDAzLjAwMmE4Ljk5MiA4Ljk5MiAwIDAgMS0xNS42OC04LjgwN3pNMjguMzMxIDMxLjI4N2wuMDIuMDExYy0uMDMuMDQ2LS4wNjcuMDg1LS4wOTUuMTMzLjAyNy0uMDQ3LjA0Ny0uMDk4LjA3NS0uMTQ0eiIvPjwvZz48ZyBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCI+PHBhdGggZD0iTTI0LjIzMiA0MS45MDJsMyA1LjE5Nk0yMC43NjggNDMuOTAybDMgNS4xOTZNMjIuNSA0Mi45MDJsMSAxLjczMk0yNC41IDQ2LjM2NmwxIDEuNzMyIi8+PGc+PHBhdGggZD0iTTQ1LjUgNDguMDk4bDEtMS43MzJNNDcuNSA0NC42MzRsMS0xLjczMk00Ny4yMzIgNDkuMDk4bDEtMS43MzJNNDkuMjMyIDQ1LjYzNGwxLTEuNzMyTTQzLjc2OCA0Ny4wOThsMS0xLjczMk00NS43NjggNDMuNjM0bDEtMS43MzIiLz48L2c+PGc+PHBhdGggZD0iTTIwLjc2OCAyOC4wOThsMy01LjE5Nk0yMi41IDI5LjA5OGwzLTUuMTk2TTI0LjIzMiAzMC4wOThsMy01LjE5NiIvPjwvZz48Zz48cGF0aCBkPSJNNDQuNzY4IDI0LjkwMmwxIDEuNzMyTTQ2Ljc2OCAyOC4zNjZsMSAxLjczMk00OC4yMzIgMjIuOTAybDEgMS43MzJNNTAuMjMyIDI2LjM2NmwxIDEuNzMyTTQ2LjUgMjMuOTAybDMgNS4xOTYiLz48L2c+PC9nPjxnPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Ik01IDE3aDYydjM4SDV6Ii8+PC9nPjwvc3ZnPg== 38 | mongolian 蒙古语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iIzFlNTBhMCIgZD0iTTUuMjI3IDE3aDYydjM4aC02MnoiLz48cGF0aCBmaWxsPSIjZDIyZjI3IiBkPSJNNS4yMjcgMTdoMjF2MzhoLTIxek00Ni4yMjcgMTdoMjF2MzhoLTIxeiIvPjxjaXJjbGUgY3g9IjE2IiBjeT0iMjkiIHI9IjEiIGZpbGw9IiNmY2VhMmIiIHN0cm9rZT0iI2ZjZWEyYiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiLz48Y2lyY2xlIGN4PSIxNiIgY3k9IjQxIiByPSIxIiBmaWxsPSIjZmNlYTJiIiBzdHJva2U9IiNmY2VhMmIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIi8+PHBhdGggZmlsbD0iI2ZjZWEyYiIgc3Ryb2tlPSIjZmNlYTJiIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTE3IDI0YTEgMSAwIDAgMS0yIDBsMS0xek0xNSAzM2gybC0xIDEtMS0xek0xNSA0OGgybC0xIDEtMS0xek0xNSAzN2gyTTE1IDQ1aDJNMTEgMzNoMXYxNmgtMXpNMjAgMzNoMXYxNmgtMXoiLz48Zz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvZz48L3N2Zz4= 39 | norwegian 挪威语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2QyMmYyNyIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjMWU1MGEwIiBzdHJva2U9IiNmZmYiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNjcgMzNIMzBWMTdoLTZ2MTZINXY2aDE5djE2aDZWMzloMzd2LTZ6Ii8+PGc+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48L2c+PC9zdmc+ 40 | polish 波兰语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjZDIyZjI3IiBkPSJNNSAzNmg2MnYxOUg1eiIvPjxnPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Ik01IDE3aDYydjM4SDV6Ii8+PC9nPjwvc3ZnPg== 41 | portuguese 葡萄牙语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2QyMmYyNyIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjNWM5ZTMxIiBkPSJNNSAxN2gyMXYzOEg1eiIvPjxjaXJjbGUgY3g9IjI2IiBjeT0iMzYiIHI9IjEyIiBmaWxsPSJub25lIiBzdHJva2U9IiNmY2VhMmIiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLXdpZHRoPSIyIi8+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZmNlYTJiIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Ik0yNiAyNHYyNE0yNiAzOS41TDE3IDQ0aDE4bC05LTQuNXpNMjYgMzMuNWw5LTUuNS04LjUgMS41TDE3IDI4bDkgNS41eiIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZjZWEyYiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBkPSJNMzggMzZsLTEyIDUtMTItNSAxMi01IDEyIDV6Ii8+PHBhdGggZmlsbD0iI2ZmZiIgc3Ryb2tlPSIjZDIyZjI3IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTIwLjIgMjloMTEuNnYxMC4xYzAgMi41LTIuNiA0LjYtNS44IDQuNi0zLjIgMC01LjgtMi4xLTUuOC00LjZWMjl6Ii8+PGNpcmNsZSBjeD0iMjYiIGN5PSIzMi44IiByPSIuNyIgZmlsbD0iIzFlNTBhMCIgc3Ryb2tlPSIjMWU1MGEwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48Y2lyY2xlIGN4PSIyNiIgY3k9IjM4LjciIHI9Ii43IiBmaWxsPSIjMWU1MGEwIiBzdHJva2U9IiMxZTUwYTAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxjaXJjbGUgY3g9IjI2IiBjeT0iMzUuNyIgcj0iLjciIGZpbGw9IiMxZTUwYTAiIHN0cm9rZT0iIzFlNTBhMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIi8+PGNpcmNsZSBjeD0iMjkiIGN5PSIzNS43IiByPSIuNyIgZmlsbD0iIzFlNTBhMCIgc3Ryb2tlPSIjMWU1MGEwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48Y2lyY2xlIGN4PSIyMyIgY3k9IjM1LjciIHI9Ii43IiBmaWxsPSIjMWU1MGEwIiBzdHJva2U9IiMxZTUwYTAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxnPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Ik01IDE3aDYydjM4SDV6Ii8+PC9nPjwvc3ZnPg== 42 | romanian 罗马尼亚语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2YxYjMxYyIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjMWU1MGEwIiBkPSJNNSAxN2gyMXYzOEg1eiIvPjxwYXRoIGZpbGw9IiNkMjJmMjciIGQ9Ik00NiAxN2gyMXYzOEg0NnoiLz48Zz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvZz48L3N2Zz4= 43 | russian 俄语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2QyMmYyNyIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNSAxN2g2MnYxM0g1eiIvPjxwYXRoIGZpbGw9IiMxZTUwYTAiIGQ9Ik01IDMwaDYydjEySDV6Ii8+PGc+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48L2c+PC9zdmc+ 44 | slovak 斯洛伐克语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2QyMmYyNyIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjZDBjZmNlIiBkPSJNNSAxN2g2MnYxM0g1eiIvPjxwYXRoIGZpbGw9IiMxZTUwYTAiIGQ9Ik01IDMwaDYydjEySDV6Ii8+PHBhdGggZmlsbD0iI2QyMmYyNyIgc3Ryb2tlPSIjZmZmIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Ik0yMy44MzMgNDVzNi43NS0yLjI1IDYuNzUtOXYtOWgtMTMuNXY5YzAgNi43NSA2Ljc1IDkgNi43NSA5eiIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Ik0yMy44MzMgMjkuNXYxMk0yMS44MzMgMzEuNWg0TTIwLjMzMyAzNC41aDciLz48Y2lyY2xlIGN4PSIyMy44MzMiIGN5PSI0MS41IiByPSIzLjE2NyIgZmlsbD0iIzFlNTBhMCIvPjxwYXRoIGZpbGw9IiMxZTUwYTAiIGQ9Ik0xOS4yNSAzOS4zMzNhMi4xNDYgMi4xNDYgMCAwIDAtMS4zMjcuNDY1IDkuNTggOS41OCAwIDAgMCAyLjcwMyAzLjM2MyAyLjE1OCAyLjE1OCAwIDAgMC0xLjM3Ni0zLjgyOHpNMjguNDE3IDM5LjMzM2EyLjE1OCAyLjE1OCAwIDAgMC0xLjM3NiAzLjgyOCA5LjU4IDkuNTggMCAwIDAgMi43MDItMy4zNjMgMi4xNDYgMi4xNDYgMCAwIDAtMS4zMjYtLjQ2NXoiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTIzLjgzMyA0NXM2Ljc1LTIuMjUgNi43NS05di05aC0xMy41djljMCA2Ljc1IDYuNzUgOSA2Ljc1IDl6Ii8+PGc+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48L2c+PC9zdmc+ 45 | slovenian 斯洛文尼亚语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2QyMmYyNyIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjZmZmIiBkPSJNNSAxN2g2MnYxM0g1eiIvPjxwYXRoIGZpbGw9IiMxZTUwYTAiIGQ9Ik01IDMwaDYydjEySDV6Ii8+PHBhdGggZmlsbD0iIzFlNTBhMCIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Ik0yMi41ODMgMjJ2OWMwIDYuNzUtNi43NSA5LTYuNzUgOXMtNi43NS0yLjI1LTYuNzUtOXYtOXM2Ljg5Ni0zLjQwNiAxMy41IDB6Ii8+PHBhdGggZmlsbD0iI2ZmZiIgc3Ryb2tlPSIjZmZmIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIGQ9Ik0xMS45NjMgMzMuMzUybDcuOTc1LjMzOUE2LjgxNiA2LjgxNiAwIDAgMSAxNiAzOGMtMyAxLTQuMDM3LTQuNjQ5LTQuMDM3LTQuNjQ5Ii8+PHBhdGggZmlsbD0iI2ZmZiIgc3Ryb2tlPSIjZmZmIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIGQ9Ik0xNC41IDMzLjVsMS0yIDIuMjY1IDIuNTg0TDE0LjUgMzMuNSIvPjxjaXJjbGUgY3g9IjEzIiBjeT0iMjQiIHI9IjEiIGZpbGw9IiNmY2VhMmIiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNMjEgMzVsLTEtMy0yIDMtMi01LTIgNS0yLTMtMSAzczQgOSAxMCAweiIvPjxjaXJjbGUgY3g9IjE2IiBjeT0iMjYiIHI9IjEiIGZpbGw9IiNmY2VhMmIiLz48Y2lyY2xlIGN4PSIxOSIgY3k9IjI0IiByPSIxIiBmaWxsPSIjZmNlYTJiIi8+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjZDIyZjI3IiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGQ9Ik0yMi41ODMgMjJ2OWMwIDYuNzUtNi43NSA5LTYuNzUgOXMtNi43NS0yLjI1LTYuNzUtOXYtOXM2Ljg5Ni0zLjQwNiAxMy41IDB6Ii8+PGc+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48L2c+PC9zdmc+ 46 | spanish 西班牙语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2YxYjMxYyIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjZDIyZjI3IiBkPSJNMjMgMzN2N2EyLjAwNiAyLjAwNiAwIDAgMS0yIDJoLTRhMi4wMDYgMi4wMDYgMCAwIDEtMi0ydi03TTUgMTdoNjJ2OUg1ek01IDQ2aDYydjlINXoiLz48cGF0aCBmaWxsPSIjZjFiMzFjIiBkPSJNMTkgMzNoNHY0aC00eiIvPjxjaXJjbGUgY3g9IjE5IiBjeT0iMzciIHI9IjEuNSIgZmlsbD0iIzZhNDYyZiIvPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzZhNDYyZiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiPjxwYXRoIGQ9Ik0yNyAzM3Y5TTExIDMzdjlNMTUgMzBhOC41NjggOC41NjggMCAwIDEgNC0xTTIzIDMwYTguNTY4IDguNTY4IDAgMCAwLTQtMU0xNSAzM2g4TTIzIDMzdjdhMi4wMDYgMi4wMDYgMCAwIDEtMiAyaC00YTIuMDA2IDIuMDA2IDAgMCAxLTItMnYtN00xMCA0MmgyTTI2IDQyaDIiLz48L2c+PGc+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48L2c+PC9zdmc+ 47 | swedish 瑞典语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iIzFlNTBhMCIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjZmNlYTJiIiBzdHJva2U9IiNmY2VhMmIiIHN0cm9rZS1taXRlcmxpbWl0PSIxMCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNjcgMzNIMzBWMTdoLTZ2MTZINXY2aDE5djE2aDZWMzloMzd2LTZ6Ii8+PGc+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48L2c+PC9zdmc+ 48 | tagalog 他加禄语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvc3ZnPg== 49 | thai 泰语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2ZmZiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjMWU1MGEwIiBkPSJNNSAzMGg2MnYxMkg1eiIvPjxwYXRoIGZpbGw9IiNkMjJmMjciIGQ9Ik01IDUwaDYydjVINXpNNSAxN2g2MnY1SDV6Ii8+PGc+PHBhdGggZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48L2c+PC9zdmc+ 50 | turkish 土耳其语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2QyMmYyNyIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjZmZmIiBzdHJva2U9IiNmZmYiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTQwLjY0IDMzLjA1bDMuMDUyIDQuMDE5LTQuOTM0LTEuNTMyIDQuOTMyLTEuNTQxLTMuMDQ2IDQuMDI1LS4wMDQtNC45NzJNMzEuMjkgNDQuNjRhOC42NDMgOC42NDMgMCAxIDEgMy45NTgtMTYuMzQgMTEgMTEgMCAxIDAgMCAxNS4zOCA4LjcxNSA4LjcxNSAwIDAgMS0zLjk1OC45NXoiLz48Zz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvZz48L3N2Zz4= 51 | ukrainian 乌克兰语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iIzYxYjJlNCIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjZmNlYTJiIiBkPSJNNSAzNmg2MnYxOUg1eiIvPjxnPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Ik01IDE3aDYydjM4SDV6Ii8+PC9nPjwvc3ZnPg== 52 | vietnamese 越南语 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI2QyMmYyNyIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSIjZjFiMzFjIiBzdHJva2U9IiNmMWIzMWMiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgZD0iTTI4Ljg5IDQ3bDcuMzAzLTIyIDYuMjk1IDIxLjY2M0wyNSAzMy42MWwyMi0uNTQzTDI4Ljg5IDQ3eiIvPjxnPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Ik01IDE3aDYydjM4SDV6Ii8+PC9nPjwvc3ZnPg== 53 | speechless 无言 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGNpcmNsZSBjeD0iMzYiIGN5PSIzNS44IiByPSIyMyIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjRkNFQTJCIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMS44Ii8+PHBhdGggZmlsbD0iI0ZDRUEyQiIgZD0iTTQ2LjggNTYuM2MxMS4zLTYgMTUuNy0yMC4yIDkuNy0zMS41cy0yMC0xNS41LTMxLjMtOS41LTE1LjUgMjAtOS41IDMxLjNjMi4zIDQuMiA1LjggNy43IDEwLjEgOS44IDAgMS42LjcgMy4yIDEuOCA0LjQgMS40IDEuNiA4LjUgMy4zIDEyLjItLjIgMS4xLTEgNy4zLTQuMiA3LTQuM3oiLz48Zz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNTEuNSA1Mi44YzkuNC04LjYgMTAuMS0yMy4xIDEuNS0zMi41cy0yMy4xLTEwLjEtMzIuNS0xLjVTMTAuNCA0MS45IDE5IDUxLjNjLjkuOSAxLjggMS44IDIuOCAyLjYiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNMjEuNCAyMy42Yy43LTEuNCAxLjktMi40IDMuMy0yLjkgMS4zLS43IDIuOS0uOCA0LjMtLjRNNTAuNyAyMy42Yy0xLjUtMi43LTQuNi00LTcuNi0zLjNNMzQgNDEuNWMtMS40LS4yLTIuOC0uNy00LTEuNU00Mi4xIDQwYy0xLjMuNy0yLjcgMS4yLTQuMiAxLjVNMzcuOCAzOC42YzAtMS0uOS0xLjktMS45LTEuOXMtMS45LjktMS45IDEuOU0zNCA0Ny40di04LjhNMzcuOCAzOC42djguOSIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2Utd2lkdGg9IjIiIGQ9Ik0zNy4yIDQ3LjRjMS43IDAgMyAxLjMgMyAzdjEuOGg0LjFjMS4zLS4xIDIuNS43IDIuOCAycy0uMyAyLjYtMS40IDMuMmMtLjUuMy0xLjEuNi0xLjYuOS0yIDEuMS00LjEgMi4yLTYuMiAzLjMtMS45IDEtNCAxLjQtNi4xIDEtMy42LS42LTYtNC01LjQtNy43LjItMS41LjUtMyAxLTQuNC42LTEuOCAyLjMtMy4xIDQuMi0zLjFoNS42eiIvPjxwYXRoIGQ9Ik0zMCAyNy44YzAgMS43LTEuMyAzLTMgM3MtMy0xLjMtMy0zIDEuMy0zIDMtM2MxLjYgMCAzIDEuMyAzIDIuOXYuMU00OCAyNy44YzAgMS43LTEuMyAzLTMgM3MtMy0xLjMtMy0zIDEuMy0zIDMtM2MxLjYgMCAzIDEuMyAzIDIuOXYuMSIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTM2LjIgNTEuOGMtMS4zIDAtMiAuNy0yLjUgMS41LS42LjkgMCAyLjIgMSAyLjJIMzljLjcgMCAxLjItLjYgMS4yLTEuNFY1Mi4yIi8+PC9nPjwvc3ZnPg== 54 | text_cleaned 文字清除 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PGcgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjMDAwIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiPjxwYXRoIGQ9Ik01OS4wMzUgNjBoLTQ2LjA3YS45NjguOTY4IDAgMCAxLS45NjUtLjk2NXYtNDYuMDdhLjk2OC45NjggMCAwIDEgLjk2NS0uOTY1aDQ2LjA3YS45NjguOTY4IDAgMCAxIC45NjUuOTY1djQ2LjA3YS45NjguOTY4IDAgMCAxLS45NjUuOTY1eiIvPjxwYXRoIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgZD0iTTMyLjA3IDQyLjU3OGE1LjMxNCA1LjMxNCAwIDAgMS0zLjUzOCAxLjM0MyA1LjMzNCA1LjMzNCAwIDAgMS01LjMzNC01LjMzNHYtNC4yNjhhNS4zMzQgNS4zMzQgMCAwIDEgNS4zMzQtNS4zMzQgNS4zMTMgNS4zMTMgMCAwIDEgMy41MzggMS4zNDNNNDIuNDAxIDI5LjA0OXYxNC45MzZoNi40MDEiLz48L2c+PHBhdGggZmlsbD0iI2QyMmYyNyIgZD0iTTU5LjAzNSA2MC40NTNoLTQ2LjA3YS45NjguOTY4IDAgMCAxLS45NjUtLjk2NXYtNDYuMDdhLjk2OC45NjggMCAwIDEgLjk2NS0uOTY1aDQ2LjA3YS45NjguOTY4IDAgMCAxIC45NjUuOTY1djQ2LjA3YS45NjguOTY4IDAgMCAxLS45NjUuOTY1eiIvPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iI2ZmZiIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIj48cGF0aCBkPSJNNTkuMDM1IDYwaC00Ni4wN2EuOTY4Ljk2OCAwIDAgMS0uOTY1LS45NjV2LTQ2LjA3YS45NjguOTY4IDAgMCAxIC45NjUtLjk2NWg0Ni4wN2EuOTY4Ljk2OCAwIDAgMSAuOTY1Ljk2NXY0Ni4wN2EuOTY4Ljk2OCAwIDAgMS0uOTY1Ljk2NXoiLz48cGF0aCBzdHJva2UtbGluZWNhcD0icm91bmQiIGQ9Ik0zMi4wNyA0Mi41NzhhNS4zMTQgNS4zMTQgMCAwIDEtMy41MzggMS4zNDMgNS4zMzQgNS4zMzQgMCAwIDEtNS4zMzQtNS4zMzR2LTQuMjY4YTUuMzM0IDUuMzM0IDAgMCAxIDUuMzM0LTUuMzM0IDUuMzEzIDUuMzEzIDAgMCAxIDMuNTM4IDEuMzQzTTQyLjQwMSAyOS4wNDl2MTQuOTM2aDYuNDAxIi8+PC9nPjwvc3ZnPg== 55 | rewrite 重写 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTE2LjQwNSAxMS4zNzhINTUuOTd2NDkuMDY2SDE2LjQwNXoiLz48cGF0aCBmaWxsPSIjZjRhYTQxIiBkPSJNMzkuOTMxIDQwLjIyMmwxMS4yOTQtMTEuMjk0IDcuMzc2IDcuMzc2LTExLjE5OCAxMS4xOTgiLz48cGF0aCBmaWxsPSIjYTU3OTM5IiBkPSJNMzcuOTQxIDQ2LjgxOWwxLjk2MS01LjY0OSA2LjQ1NCA2LjQ1NC01LjY0OCAxLjk2Ii8+PHBhdGggZmlsbD0iI0VBNUE0NyIgZD0iTTU0LjYwOSAyNS4wNTJsMy45NzgtMy45NzkgNy44NTkgNy44NTktMy45NDUgMy45NDUiLz48cGF0aCBmaWxsPSIjZDBjZmNlIiBkPSJNNTAuNDk0IDI5LjY2bDQuNjA4LTQuNjA4IDcuMzc1IDcuMzc1LTQuNTY4IDQuNTY5Ii8+PGc+PHBhdGggZD0iTTM4LjA3IDQ3LjE5MWwyLjM4NiAyLjM4Ni0zLjQ2NCAxLjI4eiIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTIyLjMyMiAxOS44NzVoMjdNMjIuMzIyIDI3Ljg3NWgyNS42TTIyLjMyMiAzNS44NzVoMTcuMTA5TTIyLjMyMiA0My44NzVoMTIuMjA2TTIyLjMyMiA1MS44NzVoMTAuMTQ2Ii8+PHBhdGggZD0iTTM4LjA3IDQ3LjE5MWwyLjM4NiAyLjM4Ni0zLjQ2NCAxLjI4eiIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTQwLjY5MiA0OS41OTFsNS42NjQtMS45NjcgMTUuNTktMTUuNTktNi40NTQtNi40NTQtMTUuNTkgMTUuNTktMS45NzQgNS42NzF6TTU4LjA4NiAyMi45ODZsMS41NTUtMS41NTUgNi40NTQgNi40NTQtMS42MzIgMS42MzJNNDAuMjc5IDQwLjc5M2w2LjQ1NCA2LjQ1NCIvPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzAwMCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMTAiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTM3LjkzMiA0Ni44MjhsLTEuMzgzIDQuMTQ5IDQuMTU5LTEuMzkyTTUxLjMwMiAzMC4xNTdsNi4yMDIgNi4yMDIiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW1pdGVybGltaXQ9IjEwIiBzdHJva2Utd2lkdGg9IjIuMDIxIiBkPSJNNTUuMzIyIDQ0LjIyOHYxNS42NDdoLTM5di00OGgzOXY4LjQ1NiIvPjwvZz48L3N2Zz4= 56 | unknown 未知 data:image/svg+xml;base64,PHN2ZyB2aWV3Qm94PSIwIDAgNzIgNzIiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyI+PHBhdGggZmlsbD0iI0ZGRiIgZD0iTTUgMTdoNjJ2MzhINXoiLz48cGF0aCBmaWxsPSJub25lIiBzdHJva2U9IiMwMDAiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLXdpZHRoPSIyIiBkPSJNNSAxN2g2MnYzOEg1eiIvPjwvc3ZnPg==`.trim().split('\n').map(v => { 57 | const [value, name, icon] = v.split(' '); 58 | return {value: value.replace('_', ' '), name, icon}; 59 | }); 60 | 61 | GM_addStyle(` 62 | .tj-box { 63 | text-align: left; 64 | font-size: 12px; 65 | font-weight: 400; 66 | position: absolute; 67 | top: -1px; 68 | right: -1px; 69 | z-index: 10; 70 | background: inherit; 71 | border: inherit; 72 | padding: 0 8px; 73 | } 74 | .tj-box .tj-lang-icon{ 75 | background: inherit; 76 | display: inline-block; 77 | border: inherit; 78 | border-width: 0; 79 | } 80 | .tj-box .tj-lang-icon>a>img { 81 | width: 24px; 82 | } 83 | .tj-box .tj-lang-icon ul { 84 | display: none; 85 | white-space: nowrap; 86 | background: inherit; 87 | margin: 0; 88 | padding: 8px; 89 | border: inherit; 90 | border-width: 1px; 91 | position: absolute; 92 | top: 24px; 93 | right: -1px; 94 | list-style: none; 95 | } 96 | .tj-box .tj-lang-icon:hover ul { 97 | display: block; 98 | } 99 | .tj-box .tj-lang-icon ul li { 100 | padding: 2px 0; 101 | } 102 | .tj-box a { 103 | text-decoration: none; 104 | } 105 | .tj-box ul a.title::before { 106 | content: "●"; 107 | color: #1a9317; 108 | padding-right: 4px; 109 | } 110 | .tj-box ul a.title:visited::before { 111 | color: #aaa; 112 | } 113 | 114 | `); 115 | 116 | 117 | async function getDataList(name) { 118 | let searchUrl = `${window.location.origin}/?f_search=${encodeURIComponent(`"${name}"`)}`; 119 | const response = await fetch(searchUrl); 120 | const html = await response.text(); 121 | const safeHtml = html.replace(/^.*(.*)<\/body>.*$/igms,"$1").replace(/(.*?)<\/script>/igms, ''); 122 | const dom = document.createElement('div') 123 | dom.innerHTML = safeHtml; 124 | const list = [...dom.querySelectorAll('.itg>tbody>tr,.gl1t')]; 125 | let dataList = list.map(e => { 126 | if(e.querySelector('.glname') == null) return null; 127 | const pagesElement = e.querySelector('div.gl3e div:nth-child(5), div.gl5t > div:nth-child(2) > div:nth-child(2)'); 128 | const linkElement = e.querySelector('.gl1e a,.glname a,.gl2e a,.gl1t>a'); 129 | const torrentElement = e.querySelector('.gldown a'); 130 | const titleElement = e.querySelector('.glink'); 131 | return { 132 | distance: minDistance(cleanBookName(titleElement.textContent), name), 133 | href: linkElement.href, 134 | title: titleElement.textContent, 135 | pages: pagesElement ? pagesElement.textContent : null, 136 | torrentHref: torrentElement ? torrentElement.href : null, 137 | tags: [...e.querySelectorAll('.gt')].map(e2 => e2.title) 138 | } 139 | }).filter(v => v); 140 | return dataList; 141 | } 142 | 143 | 144 | function cleanBookName(name) { 145 | name = name.replace(/\[.*?\]/gi, ''); 146 | name = name.replace(/\(.*?\)/gi, ''); 147 | name = name.replace(/\sCh\.[0-9-]+/gi, ''); 148 | name = name.replace(/\s第[0-9-]+話/gi, ''); 149 | name = name.replace(/\s第[0-9-]+话/gi, ''); 150 | name = name.trim(); 151 | return name; 152 | } 153 | 154 | function minDistance(s1, s2) { 155 | const len1 = s1.length 156 | const len2 = s2.length 157 | let matrix = [] 158 | for (let i = 0; i <= len1; i++) { 159 | matrix[i] = new Array() 160 | for (let j = 0; j <= len2; j++) { 161 | if (i == 0) { 162 | matrix[i][j] = j 163 | } else if (j == 0) { 164 | matrix[i][j] = i 165 | } else { 166 | let cost = 0 167 | if (s1[i - 1] != s2[j - 1]) { 168 | cost = 1 169 | } 170 | const temp = matrix[i - 1][j - 1] + cost 171 | 172 | matrix[i][j] = Math.min(matrix[i - 1][j] + 1, matrix[i][j - 1] + 1, temp) 173 | } 174 | } 175 | } 176 | return matrix[len1][len2] 177 | } 178 | 179 | (async function() { 180 | const title1 = document.querySelector("#gn").textContent; 181 | const title2 = document.querySelector("#gj").textContent; 182 | const cleanTitle1 = cleanBookName(title1); 183 | const cleanTitle2 = cleanBookName(title2); 184 | console.log("搜索相似:", cleanTitle1, '&', cleanTitle2); 185 | const dataList = []; 186 | const urlSet = new Set([window.location.origin + window.location.pathname]); 187 | if(cleanTitle1) { 188 | (await getDataList(cleanTitle1)).forEach(v => { 189 | if(!urlSet.has(v.href)) { 190 | dataList.push(v); 191 | urlSet.add(v.href); 192 | } 193 | }); 194 | } 195 | if(cleanTitle2) { 196 | (await getDataList(cleanTitle2)).forEach(v => { 197 | if(!urlSet.has(v.href)) { 198 | dataList.push(v); 199 | urlSet.add(v.href); 200 | } 201 | }); 202 | } 203 | 204 | dataList.sort((a,b) => a.distance - b.distance); 205 | 206 | dataList.forEach(v => { 207 | for(let lang of languages) { 208 | if(v.tags.length && v.tags.includes(`language:${lang.value}`)) { 209 | v.language = lang; 210 | break; 211 | } 212 | } 213 | if(v.language) return; 214 | 215 | // 两个循环是因为优先判断tag 216 | for(let lang of languages) { 217 | if(v.title.toLowerCase().includes(lang.value)) { 218 | v.language = lang; 219 | break; 220 | } 221 | } 222 | if(v.language) return; 223 | 224 | // 没有找到语言的 并且没翻译的默认为日语 225 | if(v.tags.length && !v.tags.includes(`language:translated`)) { 226 | v.language = languages.find(v => v.value == 'japanese'); 227 | } else { 228 | v.language = languages.find(v => v.value == 'unknown'); 229 | } 230 | }); 231 | 232 | const languageGroupMap = {}; 233 | const languageGroup = []; 234 | dataList.forEach(v => { 235 | if(!languageGroupMap[v.language.value]) { 236 | languageGroupMap[v.language.value] = {language: v.language, list: []}; 237 | languageGroup.push(languageGroupMap[v.language.value]); 238 | } 239 | languageGroupMap[v.language.value].list.push(v); 240 | }); 241 | 242 | languageGroup.sort((a,b) => { 243 | let pa = languagePriority.indexOf(a.language.value); 244 | let pb = languagePriority.indexOf(b.language.value); 245 | if(pa == -1) pa = 999; 246 | if(pb == -1) pb = 999; 247 | return pa - pb; 248 | }); 249 | 250 | const box = document.createElement('div'); 251 | box.className = `tj-box`; 252 | document.querySelector(".gm").appendChild(box); 253 | 254 | if(languageGroup.length) { 255 | box.innerHTML = languageGroup.map(group => ` 256 |
257 | 258 |
    259 | ${group.list.map(item => ` 260 |
  • 261 | ${item.title} 262 | ${item.pages} ${item.torrentHref ? `T` : ''} 263 |
  • 264 | `).join('')} 265 |
266 |
267 | `).join(''); 268 | }else { 269 | box.innerHTML = "未找到"; 270 | } 271 | console.log("dataList", dataList, languageGroup); 272 | })(); 273 | -------------------------------------------------------------------------------- /TranslatedJump/README.md: -------------------------------------------------------------------------------- 1 | 漫画其他语言版本快捷跳转按钮 2 | ========= 3 | 4 | [安装脚本](//sleazyfork.org/scripts/415729); 5 | 6 | 自动搜索通过标题搜索漫画的其他的语言版本,并分类展示在右上角. 7 | ![预览](./images/screenshot-01.png) 8 | 9 | 推荐列表选择选择 "Compact" 或 "Extended", 会通过标签判断语言版本. 否则通过标题关键字判断语言版本. 前者更准确. 10 | ![列表模式](./images/screenshot-02.png) -------------------------------------------------------------------------------- /TranslatedJump/images/screenshot-01.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/TranslatedJump/images/screenshot-01.png -------------------------------------------------------------------------------- /TranslatedJump/images/screenshot-02.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/EhTagTranslation/UserScripts/f5b78067a7f29f5c98d7306be6febb510a6bf825/TranslatedJump/images/screenshot-02.png --------------------------------------------------------------------------------