// ==UserScript== // @name Web漫画アンテナお気に入り管理 // @description d:作者名を読み込む a:作者をお気に入りに追加/削除 e:検索ワード入力 Shift+E:全編集 // @match *://webcomics.jp/* // @version 0.1.7 // @grant GM_setValue // @grant GM_getValue // @grant GM.openInTab // @grant GM_deleteValue // @namespace https://greasyfork.org/users/181558 // @require https://code.jquery.com/jquery-3.4.1.min.js // @downloadURL https://update.greasyfork.icu/scripts/448712/Web%E6%BC%AB%E7%94%BB%E3%82%A2%E3%83%B3%E3%83%86%E3%83%8A%E3%81%8A%E6%B0%97%E3%81%AB%E5%85%A5%E3%82%8A%E7%AE%A1%E7%90%86.user.js // @updateURL https://update.greasyfork.icu/scripts/448712/Web%E6%BC%AB%E7%94%BB%E3%82%A2%E3%83%B3%E3%83%86%E3%83%8A%E3%81%8A%E6%B0%97%E3%81%AB%E5%85%A5%E3%82%8A%E7%AE%A1%E7%90%86.meta.js // ==/UserScript== (function() { var keyFunc = []; var INTERVAL = function() { return 5000; } const isBusy = function() { return Number(pref("busy") || 0) > Date.now() } const setBusy = function(delay = INTERVAL()) { if (Date.now() + delay > Number(pref("busy") || 0)) pref("busy", Date.now() + delay) } var scrollForGet = 0; const V = 0; // 1-3:verbose let addstyle = { added: [], add: function(str) { if (this.added.some(v => v[1] === str)) return; var S = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" // var S="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_" var d = Date.now() var uid = Array.from(Array(12)).map(() => S[Math.floor((d + Math.random() * S.length) % S.length)]).join('') document.head.insertAdjacentHTML("beforeend", ``); this.added.push([uid, str]); return uid; }, remove: function(str) { // str:登録したCSSでもaddでreturnしたuidでも良い let uid = this.added.find(v => v[1] === str || v[0] === str)?.[0] if (uid) { eleget0(`#${uid}`)?.remove() this.added = this.added.filter(v => v[0] !== uid) } } } var db = {}; db.manga = pref("db.manga") || []; db.favo = pref('db.favo') || []; db.favhis = pref('db.favhis') || []; var latestget = Date.now() - INTERVAL() 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, '`') } String.prototype.esc = function() { return this.replace(/[&<>"'`]/g, match => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''', '`': '`' } [match])) } 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 >= 3 && 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 >= 3 && 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 >= 2 && 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]|.//a[contains(@class,"entry-detail")]', 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 && !isBusy()) { busy = 1; setBusy(); box.dataset.wait = 1 v.stop = 1 var quee = eleget0('.autq', box) quee.style.color = "#f0f" V >= 1 && notify(`${"get:" + tit}\n${(Date.now() - latestget)/1000} sec.`, document.title) latestget = Date.now() if (scrollForGet) box.scrollIntoView({ behavior: "smooth", block: "center", inline: "center" }) $.get(descurl).done(got => { setBusy(INTERVAL() / 2) busy = 0 delete box.dataset.que; aut = $('div.comic-info-right div.comic-author', got)?.text()?.replace("作者: ", "").trim() || "-" V > 1 && notify(`${"done:" + tit}\n${aut}`, document.title) if (aut) { storemanga(tit, aut, box.parentNode) if (key == "a") addaut(aut, box.parentNode); document.dispatchEvent(new CustomEvent('plzGKSI')) } }).fail(err => { alert(`通信エラー\n${descurl}`) this.q = [] delete box.dataset.que; location.reload(); }) autsearch() } }) this.q = this.q.filter(v => !v.stop) }, } setInterval(() => { que.do() }, 1000) 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') || []; db.favhis = pref('db.favhis') || []; 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) pref("db.favhis", db.favhis); 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") { // Shift+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) db.favhis = (pref('db.favhis') || []).concat(JSON.parse(tmp || "").favhis) tmp = JSON.stringify(db) } var dbtmp = JP(tmp || '{"favo":[],"manga":[],"favhis":[]}') 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 dbtmp.favhis = (Array.from(new Set(dbtmp.favhis.map(v => JSON.stringify(v))))).map(v => JSON.parse(v)) // uniq:オブジェクトの配列→JSON文字列配列→uniq→オブジェクトの配列 db = dbtmp pref("db.favo", db.favo || []) pref("db.manga", db.manga || []) pref("db.favhis", db.favhis || []); $("#favpanel").remove() run(); } catch (e) { alert(e + "\n入力された文字列がうまくparseできなかったので設定を変更しません\n正しいJSON書式になっているか確認してください"); return false } } return 1 } // if (key == "0") { // 0::メモ一覧一括削除画面 (async () => { db.favhis = pref('db.favhis') || []; let coll = new Intl.Collator("ja", { numeric: true, sensitivity: 'base' }) var newstr = db.favhis.sort((a, b) => coll.compare(a[1].t, b[1].t)).sort((a, b) => coll.compare(a[1].a, b[1].a)).sort((a, b) => b[1].p - a[1].p) var words = newstr $("#favpanel").remove() end(document.body, `
削除 | No. | メモ長 | 著者 | 作品名 | url | サイト | 詳細 | 記帳 |
---|