]*id=["']torrent-description["'][^>]*>([\s\S]*?)<\/div>/i)[1];
+ let urls = result.match(/https?:\/\/[^\]&)* ]+/g);
+ if (urls) {
+ for (let u of urls) {
+ /\.(jpe?g|png|gif|avif|bmp|webp)/i.test(u) ? image.add(u) : site.add(u);
+ }
}
- else {
- data.new = true;
- openWebPreview(data);
+ if (image.size > 0) {
+ info.image = [...image][0];
}
- }
- else {
- data.none = true;
- noValidPreview(data);
- }
+ else if (site.size > 0) {
+ info.site = [...site][0];
+ }
+ GM_setValue(id, info);
+ tr.classList.add('nyaa-cached');
+ delete working[url];
+ return info;
+ }).catch((err) => {
+ delete working[url];
+ if (retries === 0) throw {};
+ return new Promise((resolve) => {
+ setTimeout(() => {
+ resolve(fetchTorrent(id, url, tr, --retries));
+ }, 5000);
+ });
+ });
}
-function getWebPreview(node, data, mouse) {
- data.image = node.querySelector(data.sel);
- if (data.image) {
- createPreview(data, mouse);
- }
- else {
- alert(data.name + '\n' + data.src + '\nFailed Previewing');
- action[data.id] = false;
- }
+
+async function getTorrentDetail(tr) {
+ let { id, url, name, torrent, magnet, size } = tr.info;
+ let info = GM_getValue(id) ?? fetchTorrent(id, url, tr);
+ Object.assign(info, tr.info);
+ return info;
}
-function openWebPreview(data) {
- if (confirm(data.name + '\n' + data.src + '\nNot Supported!\nOpen in New Tab?')) {
- open(data.src, '_blank');
- }
- action[data.id] = false;
+
+// copy info to clipboard
+async function getClipboardInfo(tr) {
+ let { url, name, torrent, magnet, size, image, site } = await getTorrentDetail(tr);
+ return `${i18n.name}
+ ${name} (${size})
+${i18n.preview}
+ ${image ?? site ?? 'Null'}
+${torrent ? `${i18n.torrent}\n ${torrent}\n` : ''}${i18n.magnet}\n ${magnet.slice(0, magnet.indexOf('&'))}`;
}
-function noValidPreview(data) {
- alert(data.name + '\nNo Preview!');
- action[data.id] = false;
+
+// show/open preview
+async function getTorrentPreview(tr, top, left) {
+ let { image, site } = await getTorrentDetail(tr);
+ if (image) {
+ let img = preview[image];
+ if (!img) {
+ img = document.createElement('img');
+ img.src = redirectURL(image);
+ img.className = 'nyaa-preview';
+ img.addEventListener('click', event => img.remove());
+ preview[image] = img;
+ }
+ img.style.cssText = `top: ${top}px; left: ${left}px;`;
+ document.body.append(img);
+ } else if (site) {
+ GM_openInTab(site);
+ }
}
-// Create preview
-function createPreview(data, mouse) {
- data.image.className = 'previewItem';
- data.image.style.cssText = 'max-height: 800px; width: auto; top: ' + (mouse.top + 900 > screen.availHeight ? screen.availHeight - 900 : mouse.top) + 'px; left: ' + (mouse.left + 600 > screen.availWidth ? screen.availWidth - 600 : mouse.left) + 'px;';
- data.image.addEventListener('click', (event) => data.image.remove());
- document.body.appendChild(data.image);
- action[data.id] = false;
+function redirectURL(url) {
+ let start = url.indexOf('//') + 2;
+ let end = url.indexOf('/', start);
+ let host = url.substring(start, end);
+ if (host === 'files.catbox.moe') {
+ return url.replace('files.catbox.moe', 'i0.wp.com/files.catbox.moe') + '?ssl=1';
+ }
+ return url;
}
+
+// torrent multiple selection with drag'n'drop
+let startX;
+let startY;
+let startClick = false;
+let startSelect = false;
+let selectBox = document.createElement('div');
+selectBox.className = 'nyaa-select';
+document.body.appendChild(selectBox);
+
+document.addEventListener('mousedown', (event) => {
+ if (event.button !== 0 || event.altKey) return;
+ startX = event.pageX;
+ startY = event.pageY;
+ startSelect = true;
+ document.body.classList.add('nyaa-noselect');
+});
+
+document.addEventListener('mousemove', (event) => {
+ if (!startSelect) return;
+ let dx = event.pageX - startX;
+ let dy = event.pageY - startY;
+ if (Math.abs(dx) > 5 || Math.abs(dy) > 5) {
+ let x = dx < 0 ? event.pageX : startX;
+ let y = dy < 0 ? event.pageY : startY;
+ let w = Math.abs(dx);
+ let h = Math.abs(dy);
+ selectBox.style.cssText = `left: ${x}px; top: ${y}px; width: ${w}px; height: ${h}px; display: block;`;
+ }
+});
+
+document.addEventListener('mouseup', (event) => {
+ if (!startSelect) return;
+
+ startSelect = false;
+ let boxRect = selectBox.getBoundingClientRect();
+ let { shiftKey } = event;
+
+ for (let tr of torrents) {
+ let { left, right, top, bottom } = tr.getBoundingClientRect();
+ let overlap = right < boxRect.left || left > boxRect.right || bottom < boxRect.top || top > boxRect.bottom;
+
+ for (let tr of torrents) {
+ let { left, right, top, bottom } = tr.getBoundingClientRect();
+ let overlap = right < boxRect.left || left > boxRect.right || bottom < boxRect.top || top > boxRect.bottom;
+
+ if (!overlap) {
+ tr.classList.add('nyaa-checked');
+ selected.add(tr);
+ } else if (!shiftKey) {
+ tr.classList.remove('nyaa-checked');
+ selected.delete(tr);
+ }
+ }
+
+ }
+
+ document.body.classList.remove('nyaa-noselect');
+ selectBox.style.cssText = 'display: none; width: 0; height: 0;';
+});
diff --git a/README.md b/README.md
index accf4bc..842da45 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,34 @@
-# How to use
+# UserScript for TamperMonkey
-- Download `Tampermonkey` extension for `Chromium` or `Mozilla Firefox`
-- Click `Raw` then `Tampermonkey` will install the script automatically
+## How to use
+- Download [Tampermonkey](https://www.tampermonkey.net/) browser extension
+- Click `Userscript` below to install them
+
+## Recommended
+- [BilibiliLiveRoomFilter.user.js](https://jc3213.github.io/userscript/BilibiliLiveRoomFilter.user.js)
+- [BilibiliVideoDownloader.user.js](https://jc3213.github.io/userscript/BilibiliVideoDownloader.user.js)
+- [NGAEmojiManager.user.js](https://jc3213.github.io/userscript/NGAEmojiManager.user.js)
+- [NGAUserBlocker.user.js](https://jc3213.github.io/userscript/NGAUserBlocker.user.js)
+- [tsdmAutoSignAndWork.user.js](https://jc3213.github.io/userscript/tsdmAutoSignAndWork.user.js)
+
+## Special Usage
+- [NarouSyosetuManager.user.js](https://jc3213.github.io/userscript/NarouSyosetuManager.user.js)
+- [NyaaTorrentHelper.user.js](https://jc3213.github.io/userscript/NyaaTorrentHelper.user.js)
+ - `RightClick`: Preview the thumbnail in current torrent's description
+ - `Ctrl` + `RightClick`: Copy current torrent's info to clipboard
+ - `Alt` + `RightClick`: Send magnet uri to aria2 JSON-RPC
+ - Requires [Download with Aria2](https://github.com/jc3213/download_with_aria2)
+ - `Ctrl` + `ArrowLeft`: Previous page
+ - `Ctrl` + `ArrowRight`: Next page
+ - `Shift` + `LeftClick`: Select/unselect torrents
+ - `Alt` + `C`: Copy selected torrents' info to clipboard
+ - Will store torrents' info
+ - `Alt` + `D`: Send selected magnet uris to aria2 JSON-RPC
+ - Requires [Download with Aria2](https://github.com/jc3213/download_with_aria2)
+ - `Alt` + `F`: Filter current page of torrent based on keyword prompt
+ - `Alt` + `Shift` + `E`: Clear all stored torrents' info
+- [KakuyomuAssistant.user.js](https://jc3213.github.io/userscript/KakuyomuAssistant.user.js)
+- [SpeedRunHelper.user.js](https://jc3213.github.io/userscript/SpeedRunHelper.user.js)
+ - `RightCLick` to open the player instead of open a new page
+- [BilibiliEmojisExtractor.user.js](https://jc3213.github.io/userscript/BilibiliEmojisExtractor.user.js)
+ - `Alt` + `Shift` + `E`: Trigger filter prompt based on images' **alt** property
diff --git a/RawMangaAssistant.user.js b/RawMangaAssistant.user.js
index b7589f0..c6cab19 100644
--- a/RawMangaAssistant.user.js
+++ b/RawMangaAssistant.user.js
@@ -1,624 +1,322 @@
// ==UserScript==
// @name Raw Manga Assistant
-// @namespace https://github.com/jc3213/userscript
// @name:zh 漫画生肉网站助手
-// @version 55
-// @description Assistant for raw manga online (LoveHeaven, MangaSum, BatoScan, Komiraw and etc.)
-// @description:zh 漫画生肉网站 (LoveHeaven, MangaSum, BatoScan, Komiraw等) 助手脚本
+// @namespace https://github.com/jc3213/userscript
+// @version 2.0
+// @description Assistant for raw manga online website
+// @description:zh 漫画生肉网站助手脚本
// @author jc3213
-// @match *://loveheaven.net/*
-// @match *://loveha.net/*
-// @match *://mangasum.com/*
-// @match *://mangant.com/*
-// @match *://manga1000.com/*
-// @match *://manga1001.com/*
-// @match *://batoscan.net/*
-// @match *://manga11.com/*
-// @match *://komiraw.com/*
+// @match https://jestful.net/*
+// @match https://weloma.art/*
+// @match https://rawdevart.art/*
+// @match https://manga1000.top/*
// @connect *
-// @grant GM_getValue
-// @grant GM_setValue
-// @grant GM_addValueChangeListener
// @grant GM_xmlhttpRequest
-// @grant GM_webRequest
-// @webRequest {"selector": "*.adtrue.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.bidgear.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.googlesyndication.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.googletagservices.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.cloudfront.net/*", "action": "cancel"}
-// @webRequest {"selector": "*.bidadx.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.adxpub.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.vdo.ai/*", "action": "cancel"}
-// @webRequest {"selector": "*.vidazoo.com/*", "action": "cancel"}
-// @webRequest {"selector": "*simrubwan.com/*", "action": "cancel"}
-// @webRequest {"selector": "*dyecowwhy.com/*", "action": "cancel"}
-// @webRequest {"selector": "*mehebborc.com/*", "action": "cancel"}
-// @webRequest {"selector": "*alignclamstram.com/*", "action": "cancel"}
-// @webRequest {"selector": "*wowjogsot.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.optad360.io/*", "action": "cancel"}
-// @webRequest {"selector": "*cogleapad.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.vlitag.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.your-notice.com/*", "action": "cancel"}
-// @webRequest {"selector": "*beiven.pw/*", "action": "cancel"}
-// @webRequest {"selector": "*runative-syndicate.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.mgid.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.exdynsrv.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.exosrv.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.adsco.re/*", "action": "cancel"}
-// @webRequest {"selector": "*engine.4dsply.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.spolecznosci.net/*", "action": "cancel"}
-// @webRequest {"selector": "*prosumsit.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.lzrikate.com/*", "action": "cancel"}
-// @webRequest {"selector": "*jiltlargosirk.com/*", "action": "cancel"}
-// @webRequest {"selector": "*eyefuneve.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.popcash.net/*", "action": "cancel"}
-// @webRequest {"selector": "*.betteradsystem.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.popads.net/*", "action": "cancel"}
-// @webRequest {"selector": "*.leadzutw.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.cacafly.net/*", "action": "cancel"}
-// @webRequest {"selector": "*.ycxtpbfcsl.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.clfvfumqqok.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.akhlkkdrxwav.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.pgqpibyycasfvl.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.ntkjbweenycfq.com/*", "action": "cancel"}
-// @webRequest {"selector": "*badskates.com/*", "action": "cancel"}
+// @run-at document-idle
// ==/UserScript==
'use strict';
-// Initial variables
-var urls = [];
-var save = [];
-var aria2 = [];
-var fail = [];
-var download;
-var observer;
-var images;
-var watching;
-var mousedown;
-var moving = false;
-var position = GM_getValue('position', {top: screen.availHeight * 0.3, left: screen.availWidth * 0.15});
-var offset = {};
-var lazyload;
-var warning;
-var header = ['Cookie: ' + document.cookie, 'Referer: ' + location.href, 'User-Agent: ' + navigator.userAgent];
+let { host, pathname } = location;
+let sites = {
+ 'jestful.net': {
+ viewer: /-chapter-/,
+ manga: { selector: 'img.chapter-img', attr: 'data-aload', except: ['olimposcan2', 'knet_64ba650e3ad61.png', 'cr_649a4491439a0.jpg'] },
+ title: () => {
+ let [, title, chap] = document.title.match(/^(.+)(?:\s-\sRaw)?\sChapter\s(\d+\.?\d?)/);
+ return { name: title.replace(' - Raw', ''), chap };
+ },
+ shortcut: { prev: 'a.btn.btn-info.prev', next: 'a.btn.btn-info.next' },
+ patch: () => {
+ localStorage.setItem('shown_at', 3000000000000);
+ document.querySelector('#list-imga').oncontextmenu = '';
+ },
+ ads: ', #list-imga > center'
+ },
+ 'weloma.art': {
+ viewer: /^\/\d+\/\d+/,
+ manga: { selector: 'img.chapter-img', attr: 'data-src', except: ['/uploads/'] },
+ title: { selector: 'img.chapter-img', attr: 'alt', regexp: /^(.+)(?:\s-\sRAW)\sChapter\s(\d+(?:\.\d)?)/ },
+ shortcut: { prev: 'a.btn.btn-info.prev', next: 'a.btn.btn-info.next' }
+ },
+ 'rawdevart.art': {
+ viewer: /\/chapter-/,
+ manga: { selector: 'canvas[data-srcset]', attr: 'data-srcset', except: ['450x375.png', '800x700.jpeg'] },
+ title: { selector: 'meta[property="og:title"]', attr: 'content', regexp: /^(.+)\s(?:RAW)?\s?\sChapter\s(\d+(?:\.\d)?)/ },
+ shortcut: { prev: '#sub-app .chapter-btn.prev > a', next: '#sub-app .chapter-btn.next > a' },
+ },
+ 'manga1000.top': {
+ viewer: /-chapter-/,
+ manga: { selector: '#listImgs > img', attr: 'data-src' },
+ title: { selector: 'meta[name="description"]', attr: 'content', regexp: /^Read\sraw\smanga\sjp\s(.+)\s-\s(?:RAW)?\schap\s(\d+(?:\.\d)?)/ },
+ shortcut: { prev: 'a.rd_top-left', next: 'a.rd_top-right' },
+ },
+};
+let watch = sites[host];
+
+if (!watch.viewer.test(pathname)) {
+ return;
+}
-// i18n strings and labels
-var messages = {
+let folder;
+let urls = [];
+let headers = { 'cookie': document.cookie, 'referer': location.href, 'user-agent': navigator.userAgent };
+watch?.patch();
+
+let locale = {
'en-US': {
save: {
- label: 'Download',
- done: 'All %n% images
have been successfully downloaded',
- error: 'Some image
can\'t be downloaded'
+ label: 'Download Chapter',
+ done: '%n% images downloaded successfully'
},
copy: {
- label: 'Copy Urls',
- done: 'All %n% urls have been
copied to clipboard'
+ label: 'Copy to Clipboard',
+ done: '%n% URLs copied to clipboard'
},
aria2: {
- label: 'Send to Aria2 RPC',
- done: 'All %n% image urls
have been sent to Aria2 RPC',
- norpc: '
No response from Aria2 RPC server',
- nokey: 'Aria2 RPC secret
token is invalid'
+ label: 'Download with Aria2',
+ done: '%n% images are sent to Download with Aria2',
+ error: 'Please config Download with Aria2 correctly',
+ fatal: '"Download With Aria2" is either not installed, disabled, or lower than v4.17.0.3548'
},
- secret: 'Secret token updated, reloading page in 5 seconds',
- btop: {
+ gotop: {
label: 'Back to Top',
},
- lazy: {
- label: 'Preload All Images'
- },
- menu: {
- label: 'Context Menu Mode'
- },
extract: {
- start: '
Extracting manga source',
- done: 'A total of %n% image urls
has been extracted',
- fail: '
Download function not available due to extraction failure',
- error: '
Can\'t be extracted image extension',
- fatal: '
No manga source for extraction. Please send feedback'
+ start: 'Extracting manga images...',
+ done: 'Extracted %n% image URLs',
+ error: 'Unsupported image format'
}
},
'zh-CN': {
save: {
- label: '下载图像',
- done: '已
成功下载全部 %n% 图像',
- error: '
无法下载>%部分图像',
+ label: '下载本章',
+ done: '已成功下载 %n% 张图片'
},
copy: {
- label: '复制链接',
- done: '%n% 图像链接已复制到剪切板'
+ label: '复制到剪切板',
+ done: '已复制 %n% 个图片链接到剪贴板'
},
aria2: {
- label: '发送至 Aria2 RPC',
- icon: '🖅',
- done: '全部 %n% 图像链接已发送至Aria2 RPC',
- norpc: 'Aria2 RPC 服务器没有响应',
- nokey: 'Aria2 RPC 密钥不正确'
+ label: '通过 Aria2 下载',
+ done: '%n% 个图片已传入"通过 Aria2 下载"',
+ error: '请检查"通过 Aria2 下载"是否正确配置',
+ fatal: '"通过 Aria2 下载" 未安装,或未启用,或版本低于4.17.0.3548'
},
- secret: '密钥已更新,5秒后自动刷新页面',
- btop: {
+ gotop: {
label: '回到顶部',
},
- lazy: {
- label: '预加载所有图像',
- },
- menu: {
- label: '右键菜单模式',
- },
extract: {
- start: '正在解析图像来源',
- done: '已成功解析全部 %n% 图像来源',
- fail: '无法解析图像来源,下载功能无法使用',
- error: '无法解析图像后缀',
- fatal: '无法获取图像来源,请反馈问题'
+ start: '正在提取漫画图片...',
+ done: '已成功提取 %n% 张图片链接',
+ error: '不支持的图片格式'
}
}
};
-var i18n = messages[navigator.language] || messages['en-US'];
+let i18n = locale[navigator.language] ?? locale['en-US'];
-// Supported sites
-var mangas = {
- 'loveheaven.net': {
- chapter: /\/read-(.+)-raw-chapter-(.+)\.html/,
- folder: () => {return chapter[1].replace(/-manga/, '') + '\\' + chapter[2]},
- selector: 'img.chapter-img',
- ads: ['h3', 'br:nth-child(-n+3)', 'div.float-ck', 'div.chapter-content center'],
- shortcut: {prev: 'a[class="btn btn-info prev"]', next: 'a[class="btn btn-info next"]'}
- },
- 'mangasum.com': {
- chapter: /\/manga\/(.+)-raw\/chapter-(.+)\//,
- folder: () => {return chapter[1].replace(/-manga/, '') + '\\' + chapter[2]},
- selector: 'div[id^="page_"] > img',
- lazyload: 'data-original'
- },
- 'batoscan.net': {
- chapter: /\/read-(.+)-raw-chapter-(.+)\.html/,
- folder: () => {return chapter[1].replace(/-manga/, '') + '\\' + chapter[2]},
- selector: 'img[class="chapter-img"]',
- lazyload: 'data-original',
- },
- 'manga1000.com': {
- chapter: /%E3%80%90%E7%AC%AC(.+)%E8%A9%B1%E3%80%91(.+)-raw/,
- folder: () => {return decodeURI(chapter[2]) + '\\' + chapter[1]},
- selector: 'figure[class="wp-block-image"] > img',
- shortcut: 'div[class="linkchap"] > a'
- },
- 'komiraw.com': {
- chapter: /\/([^\/]+)-raw-chap-(.+)/,
- folder: () => {return chapter.slice(1).join('\\')},
- selector: 'img[class^="chapter-img"]',
- shortcut: {prev: 'a[id="prev_chap"]', next: 'a[id="next_chap"]'}
- }
-};
-mangas['loveha.net'] = mangas['loveheaven.net'];
-mangas['mangant.com'] = mangas['mangasum.com'];
-mangas['manga1001.com'] = mangas['manga1000.com'];
-mangas['manga11.com'] = mangas['komiraw.com'];
-watching = mangas[location.host];
+let overlay = document.createElement('div');
+overlay.className = 'assist-overlay';
-var css = document.createElement('style');
-css.innerHTML = '.menuOverlay {position: fixed; z-index: 999999999; background-color: white;}\
-.menuContainer {min-width: fit-content; max-width: 330px; border: 1px ridge darkblue; font-size: 14px;}\
-.assistantIcon {width: 30px; display: inline-block; text-align: center}\
-.assistantMenu {color: black; width: 190px; padding: 10px; height: 40px; display: block; user-select: none;}\
-.assistantMenu:hover {background-color: darkviolet; color: white; cursor: default;}\
-.menuAria2Item {width: 300px; height: 42px; overflow: hidden;}\
-.menuAria2Item:focus {background-color: darkblue; color: white;}'
-document.head.appendChild(css);
+let css = document.createElement('style');
+css.textContent = `
+.assist-overlay { position: fixed; top: 0px; left: 0px; z-index: 999999999; width: 100%; inset: 0; display: flex; flex-direction: column; pointer-events: none; align-items: center; }
+.assist-notify { pointer-events: auto; width: fit-content; margin-top: 10px; }
+.assist-mainmenu, .assist-menuitem, .assist-notify { color: #000; background-color: #fff; border: 1px solid darkviolet; }
+.assist-mainmenu { position: fixed; z-index: 999999999; }
+.assist-menuitem:hover { filter: brightness(1.0) contrast(0.7); }
+.assist-menuitem:active { filter: brightness(0.9) contrast(0.6); }
+.assist-menuitem, .assist-notify { padding: 5px; text-align: center; font-size: 16px; user-select: none; }
+.assist-menuitem { width: 180px; }
+.assist-hidden ${watch.ads} { display: none !important }
+`;
-var button = document.createElement('span');
-button.id = 'assistant_button';
-button.innerHTML = '🖱️';
-button.className = 'menuOverlay menuContainer';
-button.draggable = true;
-button.style.cssText = 'top: ' + position.top + 'px; left: ' + position.left + 'px; text-align: center; padding-top: 10px; width: 42px; height: 42px;';
-document.body.appendChild(button);
+let ctxmenu = document.createElement('div');
+ctxmenu.className = 'assist-mainmenu assist-hidden';
+ctxmenu.innerHTML = `
+
+
+
+
+`;
-var container = document.createElement('div');
-container.id = 'assistant_container';
-container.className = 'menuOverlay';
-container.style.cssText = 'top: ' + position.top + 'px; left: ' + (position.left + button.offsetWidth) + 'px; display: none';
-document.body.appendChild(container);
+let aria2btn = ctxmenu.children[2];
-// Draggable button and menu
-document.addEventListener('dragstart', (event) => {
- offset.top = event.clientY;
- offset.left = event.clientX
-});
-document.addEventListener('dragend', (event) => {
- position.top += event.clientY - offset.top;
- position.left += event.clientX - offset.left;
- GM_setValue('position', position);
-});
-GM_addValueChangeListener('position', (name, old_value, new_value, remote) => movingIconAndContainer(new_value));
-function movingIconAndContainer(position) {
- button.style.top = position.top + 'px';
- button.style.left = position.left + 'px';
- container.style.top = button.offsetTop + 'px';
- container.style.left = button.offsetLeft + button.offsetWidth + 'px';
-}
-document.addEventListener('click', (event) => {
- if (event.target.id === 'assistant_aria2_server' || event.target.id === 'assistant_aria2_secret') {
- return;
- }
- if (event.target.id === 'assistant_button') {
- if (container.style.display === 'none') {
- container.style.display = 'block';
- }
- else {
- container.style.display = 'none';
- }
- }
- else {
- container.style.display = 'none';
- }
+ctxmenu.addEventListener('click', (event) => {
+ let { id } = event.target;
+ ctxMenuEvent[id]?.();
});
-// Create menuitems
-var downBox = document.createElement('div');
-downBox.id = 'assistant_down';
-downBox.className = 'menuContainer';
-downBox.style.display = 'none';
-container.appendChild(downBox);
+document.body.append(ctxmenu, css, overlay);
-var downMenu = {
- save: {
- icon: '💾',
- click: () => {
- download = [];
- save.forEach((item, index) => {
- GM_xmlhttpRequest({
- method: 'GET',
- url: item[0],
- responseType: 'blob',
- onload: (details) => {
- var a = document.createElement('a');
- a.href = URL.createObjectURL(details.response);
- a.download = item[1];
- a.click();
- download.push(index);
- if (download.length === images.length) {
- notification('save', 'done');
- download = [];
- }
- },
- onerror: () => notification('save', 'error', item[0])
- });
- });
- }
- },
- copy: {
- icon: '📄',
- click: () => {
- navigator.clipboard.writeText(urls.join('\n'));
- notification('copy', 'done');
- }
- },
- aria2: {
- icon: '🖅',
- click: () => {
- downMenu.aria2.handler({
- method: 'aria2.getGlobalOption'
- }, (details) => {
- if (details.status === 200) {
- if (details.response.includes('Unauthorized')) {
- notification('aria2', 'nokey');
- }
- else {
- var dir = details.response.match(/"dir":"([^"]+)"/)[1] + '\\' + watching.folder();
- var aria2 = save.map((item, index) => downMenu.aria2.handler({
- method: 'aria2.addUri',
- options: [[item[0]], {out: item[1], dir: dir, header: header}]
- }, () => {
- if (index === images.length - 1) {
- notification('aria2', 'done');
- }
- }));
- }
- }
- else {
- notification('aria2', 'norpc');
- }
- }, (error) => {
- notification('aria2', 'norpc');
- });
- },
- handler: (property, onload, onerror) => {
- GM_xmlhttpRequest({
- url: document.getElementById('assistant_aria2_server').value,
- method: 'POST',
- data: JSON.stringify({
- id: '',
- jsonrpc: '2.0',
- method: property.method,
- params: ['token:' + document.getElementById('assistant_aria2_secret').value].concat(property.options)
- }),
- onload: onload,
- onerror: onerror
- });
- },
- event: {
- contextmenu: (event) => {
- event.preventDefault();
- if (aria2Box.style.display === 'block') {
- aria2Box.style.display = 'none';
- }
- else {
- aria2Box.style.display = 'block';
- }
- }
- }
+const ctxMenuEvent = {
+ 'assist-download': downloadAllUrls,
+ 'assist-clipboard': copyAllUrls,
+ 'assist-message': downloadWithAria2,
+ 'assist-scrolltop'() {
+ document.documentElement.scrollTop = 0;
}
};
-Object.entries(downMenu).forEach((item) => downBox.appendChild(click_menu_item(...item)));
-
-var clickBox = document.createElement('div');
-clickBox.id = 'assistant_click';
-clickBox.className = 'menuContainer';
-container.appendChild(clickBox);
-var clickMenu = {
- btop: {
- icon: '⬆️',
- click: () => {
- document.documentElement.scrollTop = 0;
- }
+async function downloadAllUrls() {
+ let index = 0;
+ let download = [];
+ for (let url of urls) {
+ let props = { url, headers, method: 'GET', responseType: 'blob' };
+ let time = index++ * 200;
+ let name = String(index).padStart(3, '0');
+ download.push(new Promise((resolve, reject) => {
+ props.onload = (details) => {
+ setTimeout(() => {
+ let blob = details.response;
+ let a = document.createElement('a');
+ a.href = URL.createObjectURL(blob);
+ a.download = name + '.' + blob.type.slice(blob.type.indexOf('/') + 1);
+ a.click();
+ URL.revokeObjectURL(a.href);
+ a = null;
+ resolve(true);
+ }, time);
+ };
+ props.onerror = reject;
+ GM_xmlhttpRequest(props);
+ }));
}
-};
-Object.entries(clickMenu).forEach((item) => clickBox.appendChild(click_menu_item(...item)));
-
-var switchBox = document.createElement('div');
-switchBox.id = 'assistant_switch';
-switchBox.className = 'menuContainer';
-container.appendChild(switchBox);
-
-var switchMenu = {
- lazy: {
- value: GM_getValue('lazy', false),
- on: () => {
- if (!images || !watching.lazyload) {
- return;
- }
- lazyload = setInterval(() => {
- if (images.length === urls.length) {
- images.forEach((element) => element.setAttribute('src', element.getAttribute(watching.lazyload)));
- clearInterval(lazyload);
- }
- }, 100);
- },
- off: () => {
- clearInterval(lazyload);
- }
- },
- menu: {
- value: GM_getValue('menu', true),
- on: () => {
- button.style.display = 'none';
- document.addEventListener('contextmenu', switchMenu.menu.handler);
- },
- off: () => {
- document.removeEventListener('contextmenu', switchMenu.menu.handler);
- button.style.display = 'block';
- container.style.top = button.offsetTop + 'px';
- container.style.left = button.offsetLeft + button.offsetWidth + 'px';
- },
- handler: (event) => {
- if (event.target.id === 'assistant_menu_aria2' || event.shiftKey) {
- if (container.style.display = 'block') {
- container.style.display = 'none';
- }
- return;
- }
- event.preventDefault();
- container.style.top = event.clientY + 'px';
- container.style.left = event.clientX + 'px';
- container.style.display = 'block';
- }
- },
+ await Promise.all(download);
+ notification('save', 'done');
}
-Object.entries(switchMenu).forEach((item) => switchBox.appendChild(switch_menu_item(...item)));
-
-var aria2Box = document.createElement('div');
-aria2Box.id = 'assistant_aria2';
-aria2Box.className = 'menuContainer';
-aria2Box.style.cssText = 'position: absolute; display: none; top: ' + Object.keys(downMenu).indexOf('aria2') * 40 + 'px; left: 190px;';
-container.appendChild(aria2Box);
-var aria2Menu = {
- // Aria2 menu items
- server: {
- value: GM_getValue('server', 'http://localhost:6800/jsonrpc')
- },
- secret: {
- type: 'password',
- value: GM_getValue('secret', '')
- }
-};
-Object.entries(aria2Menu).forEach((item) => aria2Box.appendChild(input_menu_item(...item)));
-
-// UI maker callback handler
-function iconic_menu_item(name, props) {
- var menu = document.createElement('span');
- menu.id = 'assistant_menu_' + name;
- menu.className = 'assistantMenu';
- var icon = document.createElement('span');
- icon.className = 'assistantIcon';
- icon.innerHTML = props.icon || '';
- var label = document.createTextNode(i18n[name].label || name);
- menu.appendChild(icon);
- menu.appendChild(label);
- return menu;
+function copyAllUrls() {
+ navigator.clipboard.writeText(urls.join('\n'));
+ notification('copy', 'done');
}
-function click_menu_item(name, props) {
- var menu = iconic_menu_item(name, props);
- menu.addEventListener('click', props.click);
- if (props.event) {
- Object.entries(props.event).forEach((item) => menu.addEventListener(...item));
- }
- return menu;
-}
-function switch_menu_item(name, props) {
- var menu = iconic_menu_item(name, props);
- menu.value = props.value;
- menu.addEventListener('click', () => GM_setValue(name, !menu.value));
- switch_item_handler(menu, props);
- GM_addValueChangeListener(name, (name, old_value, new_value, remote) => {
- menu.value = new_value;
- switch_item_handler(menu, props);
- });
- return menu;
-}
-function switch_item_handler(menu, props) {
- if (menu.value) {
- menu.firstElementChild.innerHTML = '✅';
- props.on();
- }
- else {
- menu.firstElementChild.innerHTML = '';
- props.off();
- }
-}
-function input_menu_item(name, props) {
- var menu = document.createElement('input');
- menu.id = 'assistant_aria2_' + name;
- menu.className = 'assistantMenu menuAria2Item';
- menu.value = props.value;
- menu.setAttribute('type', props.type);
- menu.addEventListener('change', (event) => GM_setValue(name, event.target.value));
- menu.addEventListener('focus', (event) => event.target.setAttribute('type', 'text'));
- menu.addEventListener('blur', (event) => event.target.setAttribute('type', props.type));
- GM_addValueChangeListener(name, (name, old_value, new_value, remote) => {
- menu.value = new_value;
- });
- return menu;
+
+function downloadWithAria2() {
+ bridge('aria2c_status').then(async ({ options }) => {
+ let params = [];
+ options.dir += folder;
+ for (let url of urls) {
+ params.push({ url, options });
+ }
+ let { result, error } = await bridge('aria2c_download', params);
+ let action = result ? 'done' : 'error';
+ notification('aria2', action);
+ }).catch(notification);
}
-// Extract images data
-if (watching) {
- var chapter = location.pathname.match(watching.chapter);
- if (chapter) {
- images = document.querySelectorAll(watching.selector);
- removeMultipleElement(watching.ads);
- extractImage(watching.lazyload);
- shortcuts(watching.shortcut);
+document.addEventListener('contextmenu', (event) => {
+ let { shiftKey, ctrlKey, altKey, clientY, clientX } = event;
+ if (shiftKey || ctrlKey || altKey) {
+ return;
}
-}
+ event.preventDefault();
+ ctxmenu.classList.remove('assist-hidden');
+ let { innerWidth, innerHeight } = document.defaultView;
+ let { offsetWidth, offsetHeight } = ctxmenu;
+ let top = clientY + offsetHeight > innerHeight ? innerHeight - offsetHeight : clientY;
+ let left = clientX + offsetWidth > innerWidth ? innerWidth - offsetWidth : clientX;
+ ctxmenu.style.cssText = `top: ${top}px; left: ${left}px;`;
+});
-function removeMultipleElement(selector, node) {
- node = node || document;
- Array.isArray(selector) ? selector.forEach(item => removeElement(item)) : removeElement(selector);
+document.addEventListener('click', (event) => {
+ ctxmenu.classList.add('assist-hidden');
+});
- function removeElement(sel) {
- node.querySelectorAll(sel).forEach(item => item.remove());
+document.addEventListener('keydown', (event) => {
+ if (!watch.shortcut) return;
+ let { code } = event;
+ let hotkey = code === 'ArrowLeft'
+ ? document.querySelector(watch.shortcut.prev)
+ : code === 'ArrowRight' ? document.querySelector(watch.shortcut.next)
+ : null;
+ if (hotkey) {
+ hotkey.href ? open(hotkey.href , '_self') : hotkey.click();
}
-}
+});
-function extractImage(lazyload) {
+let started = notification('extract', 'start');
+setTimeout(() => {
+ let { selector, except } = watch.manga;
+ let images = document.querySelectorAll(selector);
if (images.length === 0) {
- return notification('extract', 'fatal');
+ notification('extract', 'error');
+ return;
}
- warning = notification('extract', 'start');
- images.forEach((element, index) => {
- var source = element.getAttribute(lazyload || 'src');
- var url = source.trim().replace(/^\/\//, 'http://');
- var name = ('000' + index).substr(index.toString().length);
- getExtension(index, url, name);
- });
- observer = setInterval(() => {
- if (images.length === urls.length + fail.length) {
- warning.remove();
- clearInterval(observer);
- if (fail.length === 0) {
- document.getElementById('assistant_down').style.display = 'block';
- notification('extract', 'done');
- }
- else {
- notification('extract', 'fail', '\n' + fail.join('\n'));
+ for (let i of document.querySelectorAll(selector)) {
+ let src = i.getAttribute(watch.manga.attr) ?? i.getAttribute('src');
+ let url = src.trim();
+ if (url.startsWith('//')) {
+ url.replace('//', 'http://');
+ }
+ if (except) {
+ let matched = false;
+ for (let s of except) {
+ if (url.includes(s)) {
+ i.remove();
+ matched = true;
+ break;
+ }
}
+ if (matched) continue;
}
- }, 100);
-}
-function getExtension(index, url, name) {
- var ext = url.match(/(png|jpg|jpeg|webp)/);
- if (ext) {
- storeImageInfo(index, url, name, ext);
+ urls.push(url);
}
- else {
- GM_xmlhttpRequest({
- url, url,
- method: 'HEAD',
- onload: (details) => {
- ext = details.responseHeaders.match(/(png|jpg|jpeg|webp)/);
- storeImageInfo(index, url, name, ext);
- },
- onerror: () => {
- fail.push(url);
- }
- });
+ let name;
+ let chap;
+ let symbol = navigator.platform === 'Win32' ? '\\' : '/';
+ if (typeof watch.title === 'function') {
+ let title = watch.title();
+ name = title.name;
+ chap = title.chap;
+ } else {
+ let title = document.querySelector(watch.title.selector).getAttribute(watch.title.attr);
+ let temp = title.match(watch.title.regexp);
+ console.log(title, watch.title.regexp);
+ console.log(temp);
+ name = temp[1];
+ chap = temp[2]
}
-}
-function storeImageInfo(index, url, name, ext) {
- if (ext) {
- name += '.' + ext[0];
+ let title = symbol + name + symbol + chap.padStart(2, '0');
+ folder = title.replace(/[:\?\"\']/g, '_');
+ console.log(folder);
+ started.remove();
+ notification('extract', 'done');
+}, 2000);
+
+function notification(action, status, url) {
+ let text = i18n[action][status] ?? i18n[action];
+ if (text.includes('%n%')) {
+ text = text.replace('%n%', urls.length);
}
- urls.push(url);
- save.push([url, name]);
+ let notify = document.createElement('div');
+ notify.className = 'assist-notify';
+ notify.textContent = `🔔 ${text}`;
+ notify.addEventListener('click', (e) => notify.remove());
+ setTimeout(() => notify.remove(), 5000);
+ overlay.appendChild(notify);
+ return notify;
}
-// Append shortcut event
-function shortcuts(shortcut) {
- if (typeof shortcut === 'string') {
- var prev = document.querySelectorAll(shortcut)[0];
- var next = document.querySelectorAll(shortcut)[1];
- }
- else if (Array.isArray(shortcut)) {
- prev = document.querySelector(shortcut[0]);
- next = document.querySelector(shortcut[1]);
- }
- else if (typeof shortcut === 'object') {
- prev = document.querySelector(shortcut.prev);
- next = document.querySelector(shortcut.next);
- }
- document.addEventListener('keydown', (event) => {
- shortcut_event_handler(event, 'ArrowLeft', prev);
- shortcut_event_handler(event, 'ArrowRight', next);
+let message = {};
+
+function bridge(aria2c, params) {
+ return new Promise((resolve, reject) => {
+ let id = crypto.randomUUID();
+ let timer = setTimeout(() => {
+ delete message[id];
+ reject( new Error() );
+ }, 3000);
+ message[id] = (result) => {
+ clearTimeout(timer);
+ delete message[id];
+ resolve(result);
+ }
+ window.postMessage({ aria2c, id, params });
});
}
-function shortcut_event_handler(event, key, element) {
- if (!element || element.hasAttribute('disabled')) {
- return;
- }
- if (typeof key === 'string' && event.key === key ||
- typeof key === 'number' && event.keyCode === key) {
- element.click();
- }
-}
-// Notifications
-function notification(action, status, url) {
- var warn = i18n[action][status] || i18n[action];
- var html = '⚠️' + warn.replace('%n%', '' + images.length + '');
- if (url) {
- html += '' + url + '
';
- }
- var caution = document.createElement('div');
- caution.id = 'assistant_caution';
- caution.className = 'menuOverlay assistantMenu';
- caution.style.cssText = 'width: fit-content; height: 50px; text-align: center; font-size: 16px; padding: 12px; border: 1px ridge darkviolet; border-radius: 10px;';
- caution.innerHTML = html;
- document.body.appendChild(caution);
- align_notification();
- if (action === 'extract' && ['start', 'fail'].includes(status)) {
- return caution;
+window.addEventListener('message', (event) => {
+ let { aria2c, id, result } = event.data;
+ if (aria2c === 'aria2c_response') {
+ message[id]?.(result);
}
- setTimeout(() => {
- caution.remove();
- align_notification();
- }, 3000);
-}
-function align_notification() {
- document.querySelectorAll('#assistant_caution').forEach((element, index) => {
- element.style.top = index * (element.offsetHeight + 5) + 10 + 'px';
- element.style.left = (screen.availWidth - element.offsetWidth) / 2 + 'px';
- });
-}
+});
diff --git a/SpeedRunHelper.user.js b/SpeedRunHelper.user.js
index 53dba80..5802c1b 100644
--- a/SpeedRunHelper.user.js
+++ b/SpeedRunHelper.user.js
@@ -1,99 +1,181 @@
// ==UserScript==
// @name Speedrun.com Helper
// @namespace https://github.com/jc3213/userscript
-// @version 4
+// @version 1.6.0
// @description Easy way for speedrun.com to open record window
// @author jc3213
-// @match *://www.speedrun.com/*
-// @grant GM_xmlhttpRequest
-// @grant GM_webRequest
-// @webRequest {"selector": "https://www.speedrun.com/assets/js/ads.js", "action": "cancel"}
-// @webRequest {"selector": "*.amazon-adsystem.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.doubleclick.net/*", "action": "cancel"}
-// @webRequest {"selector": "*lngtd.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.hotjar.com/*", "action": "cancel"}
-// @webRequest {"selector": "*.scorecardresearch.com/*", "action": "cancel"}
-// @webRequest {"selector": "*adservice.google.com/*", "action": "cancel"}
-// @webRequest {"selector": "*quantcast.mgr.consensu.org/*", "action": "cancel"}
+// @match https://www.speedrun.com/*
// ==/UserScript==
'use strict';
-var logger = {};
-var offset = {};
+let speedrun = {};
+let worker = {};
+let style = {};
+let srDrag;
+let srY;
+let srX;
+let srPane = -1;
+let srWatch = location.pathname.split('/')?.[1];
-var css = document.createElement('style');
-css.innerHTML = '.speedrun-menu {background-color: #FFF; cursor: pointer; border: 1px outset #F00; padding: 1px; font-size: 14px;}\
-.speedrun-menu:hover {filter: opacity(60%);}\
-.speedrun-menu:active {border: 2px inset #00F; padding: 0px; filter: opacity(30%);}';
-document.head.appendChild(css);
+let { innerHeight, innerWidth } = window;
+let fixedX;
+let fixedY;
+if (innerHeight < 720) {
+ fixedX = 854;
+ fixedY = 480;
+} else if (innerHeight < 1080) {
+ fixedX = 1280;
+ fixedY = 720;
+} else if (innerHeight < 1440) {
+ fixedX = 1920;
+ fixedY = 1080;
+} else if (innerHeight < 2160) {
+ fixedX = 2560;
+ fixedY = 1440;
+} else {
+ fixedX = 3840;
+ fixedY = 2160;
+}
-document.querySelectorAll('div[data-ad]').forEach(item => item.remove());
-document.querySelector('body > main > div > div:nth-child(5)').remove();
+let css = document.createElement('style');
+css.innerHTML = `
+#app-main [class$="lg:w-[400px]"] { display: none !important; }
+.speedrun-window { position: absolute; z-index: 999999; display: grid; grid-template-columns: calc(100% - 66px) 66px; }
+.speedrun-window iframe { width: ${fixedX}px; height: ${fixedY}px; grid-column: span 2; }
+.speedrun-record, .speedrun-menu { background-color: #181B1C; display: flex; height: 22px; }
+.speedrun-record > * { flex: 1; margin: auto; padding: 0px 5px 0px 3px; }
+.speedrun-record * { display: inline-block !important; }
+.speedrun-record img { height: 16px !important; width: 16px !important; position: relative !important; margin-right: 3px; }
+.speedrun-menu {margin-left: auto;}
+.speedrun-menu > * { background-color: #fff; color: #000; cursor: pointer; height: 20px; width: 20px; font-size: 14px; text-align: center; vertical-align: top; margin-left: 2px; }
+.speedrun-menu > :hover { filter: opacity(60%); }
+.speedrun-menu > :active { filter: opacity(30%); }
+#speedrun-minimum { line-height: 30px; }
+.speedrun-minimum { bottom: 0px; left: 0px; width: 30% !important; height: 20px !important; z-index: 99999; }
+.speedrun-minimum iframe { display: none !important; }
+.speedrun-maximum { top: 0px; left: 0px; width: ${innerWidth - 4}px !important; height: ${innerHeight - 4}px !important; z-index: 999999; }
+.speedrun-maximum iframe { width: ${innerWidth - 4}px !important; height: ${innerHeight - 24}px !important; }
+#speedrun-restore, .speedrun-minimum #speedrun-minimum, .speedrun-maximum #speedrun-maximum { display: none; }
+.speedrun-minimum #speedrun-restore, .speedrun-maximum #speedrun-restore { display: block; }`;
+document.body.append(css);
-document.getElementById('leaderboarddiv').addEventListener('contextmenu', (event) => {
- event.preventDefault();
- var element;
- document.querySelectorAll('tr[data-target]').forEach(item => { if (item.contains(event.target)) element = item; });
- if (element) {
- var src = element.getAttribute('data-target');
- var id = src.split('/').pop();
- viewSpeedrunRecord(id, src, event.clientY, event.clientX);
+document.addEventListener('dragstart', (event) => {
+ let pane = event.target.closest('div[id^=speedrun-');
+ if (pane.draggable) {
+ srDrag = pane;
+ srX = event.clientX - srDrag.offsetLeft;
+ srY = event.clientY - srDrag.offsetTop;
}
});
-function viewSpeedrunRecord(id, src, top, left) {
- if (document.getElementById('speedrun-' + id)) {
- document.getElementById('speedrun-' + id).remove();
+document.addEventListener('dragover', (event) => {
+ event.preventDefault();
+});
+
+document.addEventListener('drop', (event) => {
+ event.preventDefault();
+ let left = event.clientX - srX;
+ let top = event.clientY - srY;
+ srDrag.offset = srDrag.style.cssText = `left: ${left}px; top: ${top}px;`;
+ srDrag = null;
+});
+
+document.querySelector('main').addEventListener('contextmenu', async (event) => {
+ if (event.ctrlKey || event.altKey || event.shiftKey) {
+ return;
}
- if (logger[id]) {
- createRecordWindow(id, logger[id], top, left);
+ let result;
+ if (srWatch === '') {
+ let record = event.target.closest('div.cursor-pointer.x-focus-outline-offset.overflow-hidden');
+ if (record) {
+ let [rank, time, player] = record.querySelectorAll('a > .truncate, a.x-username-truncate');
+ result = { url: rank.parentNode.href, rank, player, time };
+ }
+ } else if (srWatch === 'series') {
+ let record = event.target.closest('div.cursor-pointer.x-focus-outline-offset.overflow-hidden');
+ if (record) {
+ let [rank, time, player] = record.querySelectorAll('a > .truncate, a.x-username-truncate');
+ result = { url: rank.parentNode.href, rank, player, time };
+ }
+ } else if (srWatch === 'users') {
+ let record = event.target.closest('div.cursor-pointer.x-focus-outline-offset.overflow-hidden');
+ if (record) {
+ let player = document.querySelector('.x-username > span');
+ let [rank, time] = record.querySelectorAll('a > .truncate');
+ result = { url: rank.parentNode.href, rank, player, time };
+ }
+ } else {
+ let record = event.target.closest('tr');
+ if (record) {
+ let [rank, player, time] = record.querySelectorAll('a');
+ result = { url: rank.href, rank, player, time };
+ }
}
- else {
- GM_xmlhttpRequest({
- url: src,
- method: 'GET',
- onload: (response) => {
- var xml = document.createElement('div');
- xml.innerHTML = response.responseText;
- logger[id] = xml.querySelector('#centerbar').querySelector('iframe');
- if (!logger[id]) {
- logger[id] = xml.querySelector('#centerbar > div > div > center > a').href;
- }
- createRecordWindow(id, logger[id], top, left);
- }
- });
+ if (!result) {
+ return;
}
-}
-
-function createRecordWindow(id, content, top, left) {
- if (typeof content === 'string') {
- return open(content, '_blank');
+ event.preventDefault();
+ let { url, rank, player, time } = result;
+ let id = url.substring(url.lastIndexOf('/') + 1);
+ if (worker[id]) {
+ return;
}
-
- var container = document.createElement('div');
- container.id = 'speedrun-' + id;
- container.draggable = 'true';
- container.style.cssText = 'position: fixed; top: ' + top / 2 + 'px; left: ' + left / 2 + 'px; z-index: 3213; width: 850px; height: 500px;';
- document.body.appendChild(container);
-
- var box = document.createElement('div');
- box.style.cssText = 'background-color: #000; height: 25px; width: 100%; text-align: right; user-select: none;';
- container.appendChild(box);
-
- var close = document.createElement('span');
- close.className = 'speedrun-menu';
- close.addEventListener('click', (event) => container.remove())
- close.innerHTML = '✖';
- box.appendChild(close);
-
- container.appendChild(content);
-}
-
-document.addEventListener('dragstart', (event) => {
- offset.top = event.clientY;
- offset.left = event.clientX
-});
-document.addEventListener('dragend', (event) => {
- event.target.style.top = event.target.offsetTop + event.clientY - offset.top + 'px';
- event.target.style.left = event.target.offsetLeft + event.clientX - offset.left + 'px';
+ worker[id] = true;
+ let title = `
Rank: ${rank.innerHTML}
Player: ${player.innerHTML}
Time: ${time.textContent}
`;
+ let pane = speedrun[id];
+ if (pane) {
+ worker[id] = false;
+ pane.style.cssText = pane.offset;
+ pane.classList.remove('speedrun-maximum', 'speedrun-minimum');
+ document.body.appendChild(speedrun[id]);
+ return;
+ }
+ let response = await fetch(url);
+ let html = await response.text();
+ let start = html.indexOf('