您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Allows to add direct links to chapters in MangaUpdates release lists
// ==UserScript== // @name MangaUpdates chapter links // @namespace http://tampermonkey.net/ // @version 0.2 // @description Allows to add direct links to chapters in MangaUpdates release lists // @author You // @match https://www.mangaupdates.com/* // @require https://cdnjs.cloudflare.com/ajax/libs/mithril/2.0.4/mithril.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/lib/coffeescript-browser-compiler-legacy/coffeescript.js // @grant GM_addStyle // @grant GM_getValue // @grant GM_setValue // @grant GM_listValues // ==/UserScript== var inline_src = String.raw` GM_addStyle "html {background: black} body {background: initial} .center-side-bar {background: rgba(0, 0, 0, 0.1)} button.inbox.-edit {height: 15px; margin-right: 3px; cursor: pointer} button.inbox[disabled] {opacity: .5; cursor: not-allowed} button.-edit.-none {background: darkgrey} a.-chapter:not([href]) {color: darkgrey} .-overlay {position: fixed; top: 0; left: 0; height: 100%; width: 100%; z-index: 10000; pointer-events: none} .-dialog {pointer-events: all; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); background: rgba(0, 0, 0, .8); color: white; padding: 2em} .-row {margin: 20px; display: block} .-buttons {display: flex; justify-content: space-between} .-dialog input {width: 500px} .-menu button.inbox {cursor: pointer; line-height: 1ex}" $merge = Object.assign merge = (os) -> $merge {}, ...os fromPairs = (xs) -> merge xs.map ([k, v]) -> k and [k]: v qstr = (s) -> if not s.includes('?') then "" else s[1 + s.indexOf '?'..] query = (s) -> fromPairs (z.split('=').map(decodeURIComponent) for z in qstr(s).split('&') when z.includes '=') chunks = (n, l) -> (l[i ... i+n] for i in [0...l.length] by n) slug = (s) -> "#{s}".replace(/[^a-zA-Z]+/g, ' ').trim().replace(/ /g, '-') select = (o, ks...) -> o and merge ks.map((k) -> o[k] and {[k]: o[k]}) fmt = (s, o) -> Object.keys(o).reduce ((s, k) -> s.replace '#{'+k+'}', o[k]), s $e = (tag, options...) -> $merge document.createElement(tag), options... $get = (xpath, e=document) -> document.evaluate(xpath, e, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue $find = (selector, e=document) -> e.querySelector selector $find_ = (selector, e=document) -> Array.from e.querySelectorAll selector fullWidth = (e) -> e.classList.contains 'col-12' URI = window.location.pathname QUERY = query window.location.search releaseTable = switch when URI is "/releases.html" then $get("div[2]/div", main_content) when URI is "/groups.html" and 'id' of QUERY then $get("div/div[3]/div[2]/div/div", main_content) if releaseTable console.log releaseTable releaseTable.style.position = 'relative' overlay = $e('div', className: '-overlay') document.body.appendChild overlay state = editing: null startEditing = (name, args...) -> -> $merge(state, editing: name, args...); m.redraw() closeDialog = -> $merge(state, {editing: null}) editName = -> switch state.editing when 'groupUrl' then "Group URL (#{state.groupName})" when 'titleUri' then "Title ID (#{state.titleName})" saveChanges = -> cfg = GM_getValue(state.groupId, {}) switch state.editing when 'groupUrl' then cfg.url = state.value when 'titleUri' then cfg.titles = $merge cfg.titles or {}, {[state.titleId]: state.value or undefined} GM_setValue state.groupId, cfg closeDialog() setTimeout recalc chapterNumber = (s) -> s.match(/\d+/)?[0] $ensureButton = (e) -> btn = $find 'button', e unless btn btn = $e('button', className: "inbox -edit", title: "Edit", innerText: '*') e.insertBefore btn, e.firstChild btn $ensureHref = (e) -> href = $find 'a', e unless href href = $e('a', className: '-chapter', target: '_blank') href.appendChild e.firstChild while e.firstChild e.appendChild href href recalc = -> _configs = {} config = (id) -> _configs[id] or (_configs[id] = GM_getValue(id, {})) items = $find_ ":scope > *", releaseTable separator = items.findIndex fullWidth columns = items[...separator].map (e) -> e.innerText.trim() _items = items[separator+1..] pager = _items.findIndex fullWidth cells = _items[...pager] rows = chunks columns.length, cells title = columns.indexOf "Title" chapter = columns.indexOf "Chp" group = columns.indexOf "Groups" for row in rows [titleName, groupName] = [title, group].map (k) -> row[k].innerText.replace(/^\*/, '') [titleId, groupId] = [title, group].map (k) -> query($find('a', row[k])?.href or "").id chapterId = chapterNumber row[chapter].innerText.trim() cfg = config [groupId] titleUri = cfg.titles?[titleId] [titleBtn, groupBtn] = [row[title], row[group]].map $ensureButton groupBtn.classList[if cfg.url then 'remove' else 'add']('-none') groupBtn.onclick = startEditing 'groupUrl', {groupId, groupName}, value: cfg.url or "" titleBtn.classList[if titleUri then 'remove' else 'add']('-none') titleBtn.onclick = startEditing 'titleUri', {groupId, groupName, titleId, titleName}, value: titleUri or "" titleBtn.disabled = not titleId chapterLink = $ensureHref row[chapter] if cfg.url and titleUri chapterLink.href = fmt(cfg.url, chapter: chapterId, title: titleUri) chapterLink.classList.add '-none' else chapterLink.removeAttribute 'href' chapterLink.classList.remove '-none' recalc() load = do (input = $e('input', type: 'file', accept: 'application/json')) -> -> new Promise (resolve) -> input.onchange = -> do (file = input.files[0], reader = new FileReader) -> if file reader.onload = -> resolve JSON.parse @result reader.readAsText file input.click() save = (name, data) -> $e('a', download: name, href: "data:application/json;base64,#{btoa JSON.stringify(data, null, 2)}").click() exportData = (type=state.editing) -> switch type when 'groupUrl' then save "#{slug state.groupName}.json", [state.groupId]: GM_getValue(state.groupId, {}) when 'titleUri' then save "#{slug state.groupName}_#{slug state.titleName}.json", do (o = GM_getValue(state.groupId, {})) -> [state.groupId]: {o..., titles: select(o.titles, state.titleId)} else save "MangaUpdates_#{new Date().toJSON()}.json", merge GM_listValues().map (k) -> {[k]: GM_getValue(k, {})} importData = -> load().then (data) -> bad = ({groupId, url} for groupId, {url} of data).find ({groupId, url}) -> url isnt GM_getValue(groupId, {url}).url if not bad or confirm "Non-matching group URL was found (##{bad.groupId}).\nGroups with a non-matching URL will be replaced." for groupId, {url, titles} of data oldValue = GM_getValue(groupId, {url}) GM_setValue groupId, {url, titles: (if url isnt oldValue.url then titles else merge [oldValue.titles, titles])} recalc() importExport = $e 'div', className: '-menu', style: "position: absolute; top: 0; right: 0" m.render importExport, [m 'button.inbox', {title: "Export", onclick: -> exportData 'all'}, '↓' m 'button.inbox', {title: "Import", onclick: importData}, '↑'] releaseTable.appendChild importExport m.mount overlay, view: -> state.editing and m '.-dialog', [ m 'label.-row', editName(state.editing), m 'input.inbox.-row', value: state.value, oninput: -> state.value = @value m '.-buttons.-row', m 'button.inbox', {onclick: closeDialog}, "Cancel" m 'button.inbox', {onclick: ->exportData()}, "Export" m 'button.inbox', {onclick: saveChanges}, "Ok" ] `; eval( CoffeeScript.compile(inline_src) );