// ==UserScript== // @name Web漫画アンテナお気に入り管理 // @description d:作者名を読み込む a:作者をお気に入りに追加/削除 e:検索ワード入力 Shift+E:全編集 // @match *://webcomics.jp/* // @version 0.1.3 // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @namespace https://greasyfork.org/users/181558 // @require https://code.jquery.com/jquery-3.4.1.min.js // @downloadURL none // ==/UserScript== (function() { var keyFunc = []; var INTERVAL = function() { return 5000 } var scrollForGet = 0; const V = 0; // 1:verbose var db = {}; db.manga = pref("db.manga") || [] db.favo = pref('db.favo') || []; var latestget = 0 var busy = 0; var GF = {} document.querySelector(`head`).insertAdjacentHTML('beforeend', ``) String.prototype.autrep = function() { return this.replace(/\([^)]*\)|([^)]*)|原作|作画|漫画|キャラクター|ネーム|原案|著者|作者|シナリオ|[作|画][\::]|\:|:|・|\,|、|,|\/|/|\+|+|\&|&/gmi, " ").replace(/ +|\s+/gmi, " ").trim() } // gフラグ不可 String.prototype.match0 = function(re) { let tmp = this.match(re); if (!tmp) { return null } else if (tmp.length > 1) { return tmp[1] } else return tmp[0] } // gフラグ不可 String.prototype.sanit = function() { return this.replace(//g, ">").replace(/&/g, "&").replace(/"/g, """).replace(/'/g, "'").replace(/`/g, '`') } function adja(place = document.body, pos, html) { return place ? (place.insertAdjacentHTML(pos, html), place) : null; } var JS = (v) => { return JSON.stringify(v) } var JP = (v) => { return JSON.parse(v) } var mousex = 0; var mousey = 0; var hovertimer document.addEventListener("mousemove", e => ((mousex = e.clientX), (mousey = e.clientY), (hovertimer = 0), undefined), false) var keyListen = function(e) { if (e.target.tagName === 'INPUT' || e.target.tagName === 'TEXTAREA' || e.target.getAttribute('contenteditable') === 'true') return; var key = (e.shiftKey ? "Shift+" : "") + (e.altKey ? "Alt+" : "") + (e.ctrlKey ? "Ctrl+" : "") + e.key; var ele = document.elementFromPoint(mousex, mousey); var sel = (window.getSelection) ? window.getSelection().toString().trim() : "" if (pushkey(key, ele, sel)) { e.preventDefault(); return false } } document.addEventListener('keydown', keyListen, false) document.addEventListener("mousedown", function(e) { // クリック var ele = document.elementFromPoint(mousex, mousey); if (e.button == 0 && ele.dataset.key) { if (pushkey(ele.dataset.key, ele)) return false } }) document.addEventListener("contextmenu", function(e) { // クリック var ele = document.elementFromPoint(mousex, mousey); if (ele.dataset.keyr) { if (pushkey(ele.dataset.keyr, ele)) { e.preventDefault(); return false } } }) function storemanga(tit, aut, ele) { db.manga = pref("db.manga") || [] db.manga = db.manga.filter(v => v.t != tit) db.manga.push({ t: tit, a: aut }) db.manga = (Array.from(new Set(db.manga.map(v => JSON.stringify(v))))).map(v => JSON.parse(v)) // uniq:オブジェクトの配列→JSON文字列配列→uniq→オブジェクトの配列 pref("db.manga", db.manga) V && console.table(db.manga) run(ele) } function addaut(aut, ele = document) { if (!aut || aut == "-") return aut = aut.autrep() // 加工後の作者名で記憶する db.favo = pref("db.favo") || [] if (!db.favo.includes(aut)) { db.favo.push(aut) } else { db.favo = db.favo.filter(v => v !== aut) } db.favo = (Array.from(new Set(db.favo.map(v => JSON.stringify(v))))).map(v => JSON.parse(v)) // uniq:オブジェクトの配列→JSON文字列配列→uniq→オブジェクトの配列 pref("db.favo", db.favo) V && console.table(db.favo) run(ele) } var que = { q: [], //{ele,key} add: function(ele, key) { this.q.push({ ele: ele?.closest(".entry"), key: key }) }, do: function() { V && this.q.length && console.table(this.q) this.q.forEach(v => { var box = v.ele var key = v.key if (!box) { v.stop = 1; return 0 } var tit = eleget0('div.entry-title>a:first-child', box)?.textContent?.trim() var desc = eleget0('//span[@class="entry-detail"]/a[1]', box) var aut = eleget0('.aut', box)?.dataset?.author; if (aut == "-") { v.stop = 1; return 0 } if (aut) aut = decodeURI(aut) var descurl = desc?.href if (aut) { storemanga(tit, aut, box.parentNode) key == "a" && addaut(aut, box.parentNode) autsearch() v.stop = 1; return 0 } var q = eleget0('.autq:not(.waiting)', box); if (q) { q.classList.add("waiting"); } box.dataset.que = 1 if (descurl && !aut && !box.dataset.wait && Date.now() - latestget > INTERVAL() && !busy) { busy = 1; box.dataset.wait = 1 v.stop = 1 var quee = eleget0('.autq', box) quee.style.color = "#f0f" V && notify(Date.now() - latestget, "get:" + tit) latestget = Date.now() if (scrollForGet) box.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" }) $.get(descurl).done(got => { busy = 0 latestget = Date.now() delete box.dataset.que; aut = $('div.comic-info-right div.comic-author', got)?.text()?.replace("作者: ", "").trim() || "-" V && notify(aut, "done:" + tit) if (aut) { storemanga(tit, aut, box.parentNode) if (key == "a") addaut(aut, box.parentNode) } }) autsearch() } }) this.q = this.q.filter(v => !v.stop) }, } setInterval(() => { que.do() }, 200) function pushkey(key, ele = null, sel = "") { keyFunc.forEach(v => { if (v.key === key) { v.func(ele) } }) if (/^open:/.test(key)) { window.open(key.replace(/^open:/, "")) return 1 } if (key === "e") { // e:: db.manga = pref("db.manga") || [] db.favo = pref('db.favo') || []; var favo = [...db.favo] GF.sorttype = ((GF.sorttype || 0) % 3 + 1) var [order, finstrfunc] = [ ["登録順", a => a.join(" ")], ["abc順", a => a.sort(new Intl.Collator("ja", { numeric: true, sensitivity: 'base' }).compare).join(" ")], ["長さ→abc順", a => a.sort((a, b) => a.length == b.length ? (new Intl.Collator("ja", { numeric: true, sensitivity: 'base' }).compare)(a, b) : a.length > b.length ? 1 : -1).join(" ")] ][GF.sorttype - 1] var target = (window.getSelection() && window.getSelection().toString().trim()) || (prompt(`お気に入りに登録するキーワードを入力してください\nすでに登録されている文字列を入力するとそれを削除します\n\n現在登録済み(${favo.length}): (${["登録順","abc順","長さ→abc順"][GF.sorttype-1]})\n${finstrfunc([...favo])}\n\n`) || "")?.trim(); target = target?.trim() if (!target) return; if (db.favo.includes(target)) { if (confirm(`『${target}』は既に存在します\n削除しますか?\n`)) { V && alert(`『${target}』をメモから削除しました`) db.favo = db.favo.filter(v => v != target) } } else { db.favo.push(target) } pref("db.favo", db.favo) pref("db.manga", db.manga) run() } if (key === "d" || key == "a") { // d:: a:: let descele = eleget0(".comic-info .aut", ele?.closest("#main")) || ele; if (key == "a" && descele.dataset.author) { //alert("!"); var aut = decodeURI(descele.dataset.author) addaut(aut) autsearch() return 1 } que.add(ele, key); que.do() return 1 } if (key === "Shift+E") { // E:: var tmp = prompt(`作品情報(${db.manga.length}) / お気に入り作者(${db.favo.length})\n全設定値をJSON形式で編集してください\n空欄を入力すれば全削除できます\n先頭の{の前に+を付けると現在のデータに追加(マージ)します\n\n` + JS(db), JS(db)) if (tmp !== null) { // ESCで抜けたのでなければ try { if (tmp?.trim()?.match(/^\+|^+/)) { tmp = tmp?.trim()?.replace(/^\+|^+/, "")?.trim() db.manga = (pref("db.manga") || []).concat(JSON.parse(tmp || "").manga) db.favo = (pref('db.favo') || []).concat(JSON.parse(tmp || "").favo) tmp = JSON.stringify(db) } var dbtmp = JP(tmp || '{"favo":[],"manga":[]}') dbtmp.manga = (Array.from(new Set(dbtmp.manga.map(v => JSON.stringify(v))))).map(v => JSON.parse(v)) // uniq:オブジェクトの配列→JSON文字列配列→uniq→オブジェクトの配列 dbtmp.favo = [...new Set(dbtmp.favo)]; // uniq db = dbtmp pref("db.favo", db.favo || []) pref("db.manga", db.manga || []) run(); } catch (e) { alert(e + "\n入力された文字列がうまくparseできなかったので設定を変更しません\n正しいJSON書式になっているか確認してください"); return false } } return 1 } } run() document.body.addEventListener('AutoPagerize_DOMNodeInserted', function(evt) { run(evt.target); }, false); // タブにフォーカスが戻ったら再実行 window.addEventListener("focus", () => { db.manga = pref("db.manga") || [] db.favo = pref('db.favo') || []; run() }) // 詳細画面 var aut = $('div.comic-info-right div.comic-author')?.text()?.replace("作者: ", "")?.trim() || "-" var tit = eleget0('//div/div/div/div[@class="comic-title"]/h2/a[1]')?.textContent?.trim() if (aut && tit) { storemanga(tit, aut, document) } function autsearch() { elegeta(".autsearchele").forEach(e => e.remove()); let tmp = pref('db.favo') || []; if (tmp.length) { var aut = tmp[0] var u = `https://webcomics.jp/search?q=${encodeURI(aut.autrep())}` var u2 = `https://webcomics.jp/search?q=${(aut.autrep())}` var l = aut != "-" ? `data-keyr="open:${u}"` : "" var e = adja(eleget0('//div[@id="side"]'), "afterbegin", `