remove title link / remove excess text / custom title format / click button to copy
// ==UserScript== // @name dlsite title reformat // @namespace https://github.com/x94fujo6rpg/SomeTampermonkeyScripts // @version 0.92 // @description remove title link / remove excess text / custom title format / click button to copy // @author x94fujo6 // @match https://www.dlsite.com/* // @grant GM_getValue // @grant GM_setValue // ==/UserScript== /* jshint esversion: 9 */ (function () { 'use strict'; let debug = true; let formatted_data = { id: "", title_original: "", title_formatted: "", circle: "", Year: "", year: "", month: "", day: "", series: "", author: "", scenario: "", illust: "", cv: "", age: "", type: "", tags: "", }; let data_list = Object.keys(formatted_data); let updateid; const forbidden = `<>:"/|?*\\`; const replacer = `<>:”/|?*\`; //----------------------------------------------------- const key_format = "format_seting"; const key_adv = "format_adv"; const key_f2h = "format_f2h"; const key_half = "format_falf"; const key_full = "format_full"; const key_show_ot = "format_show_ot"; const key_show_ft = "format_show_ft"; const key_sep = "format_sep"; //----------------------------------------------------- const default_format = "%id% %title_formatted%"; const default_adv = false; const default_f2h = true; const default_half = "1234567890()[]{}~!@#$%^&_+-=;':,.()~"; const default_full = "1234567890()[]{}~!@#$%︿&_+-=;’:,.()〜"; const default_show_ot = true; const default_show_ft = true; const default_sep = "、"; //----------------------------------------------------- let setting_format = GM_getValue(key_format, default_format); let setting_adv = GM_getValue(key_adv, default_adv); let setting_f2h = GM_getValue(key_f2h, default_f2h); let setting_half = default_half; let setting_full = default_full; let setting_show_ot = GM_getValue(key_show_ot, default_show_ot); let setting_show_ft = GM_getValue(key_show_ft, default_show_ft); let setting_sep = GM_getValue(key_sep, default_sep); //----------------------------------------------------- debug_msg("load setting"); debug_msg(`${key_format}: ${setting_format}`); debug_msg(`${key_adv}: ${setting_adv}`); debug_msg(`${key_f2h}: ${setting_f2h}`); debug_msg(`${key_show_ot}: ${setting_show_ot}`); debug_msg(`${key_show_ft}: ${setting_show_ft}`); debug_msg(`${key_sep}: ${setting_sep}`); //----------------------------------------------------- const container_list = [ "()", "[]", "{}", "()", "<>", "[]", "{}", "【】", "『』", "《》", "〈〉", "「」" ]; const regesc = t => t.replace(/[-\/\\^$*+?.()|[\]{}]/g, "\\$&"); const [container_start, container_end] = extracContainer(); const reg_container = containerRegexGenerator(); const reg_excess = new RegExp(`^\\s*${reg_container}\\s*|\\s*${reg_container}\\s*$`, "g"); const reg_blank = /[\s ]{2,}/g; const reg_muti_blank = /[\s \n\t]+/g; const reg_ascii = /[\x00-\x7F]/g; const reg_until_number = /[^\d]*[\d]+/; const reg_time = new RegExp(`[${regesc(container_start)}]*(\\d+:\\d+|約\\d*時*間*\\d+分\\d*秒*|合*計*\\d+分\\d+秒|\\d+時間\\d+分\\d*秒*)[${regesc(container_end)}]*`, "g"); /* \u0021-\u002f !"#$%&'()*+,-./ \u003a-\u0040 :;<=>?@ \u005b-\u0060 [\]^_` \u007b-\u007e {|}~ \uff5f-\uff63 ⦅⦆。「」 */ const reg_non_word_at_start = /^[\u0021-\u002f\u003a-\u0040\u005b-\u0060\u007b-\u007e\uff5f-\uff63 \s]/; const reg_text_start = /^(トラック|track)/; const reg_non_track = /(?<=[mM][pP]|[kK][uU])\d+|\d*\.*\d+(?=[kK][bB]|[kK][hH][zZ]|[bB][iI][tT]|[dD][iI][oO])/g; const reg_non_track2 = /([mM][pP]|[kK][uU])\d+|\d*\.*\d+([kK][bB]|[kK][hH][zZ]|[bB][iI][tT]|[dD][iI][oO])/g; const max_depth = 10; debug_msg("container_start | ", container_start); debug_msg("container_end | ", container_end); debug_msg("reg_container | ", reg_container); debug_msg("reg_excess | ", reg_excess); debug_msg("reg_time | ", reg_time); window.document.body.onload = main(); async function main() { let link = window.location.href, match_list = [ "/circle/profile/", "/fsr", "/genres/works", ]; await wait_tab(); if (link.includes("/product_id/")) { myCss(); productHandler(); fix_switch_link(); return debug_msg(productHandler.name); } if (match_list.some(key => link.includes(key))) { myCss(); waitHTML(".display_normal,.display_block", () => searchHandler()); fix_switch_link(); return debug_msg("match link"); } if (link.includes("/announce/list")) { debug_msg("announce list"); return wait_for_fav(); } return debug_msg("not in support list"); } function wait_tab() { return new Promise(resolve => { if (document.visibilityState === "visible") return resolve(); debug_msg("tab in background, script paused"); document.addEventListener("visibilitychange", () => { if (document.visibilityState === "visible") { debug_msg("script unpaused"); return resolve(); } }); }); } function wait_for_fav(max_retry = 10) { let list = document.querySelector(".n_worklist"), ck = [...list.children].every(item => item.querySelector(".work_sales_info") ? true : false); if (max_retry <= 0) return debug_msg(`max retries exceeded, abort`); if (!ck) return retry(); debug_msg(`found fav`); return setTimeout(sort_ann_list, 1500); function retry() { debug_msg(`wait for ele, retry ${max_retry}`); setTimeout(() => wait_for_fav(--max_retry), 1000); } } function sort_ann_list() { let container = document.querySelector(".n_worklist"), item = container.querySelectorAll("div.n_worklist_item"), data = [], no_fav = item.length, likes = 0, type = "unknown"; data = [...item].map(i => { likes = i.querySelector(".work_sales_info>div>span"); type = i.querySelector(".work_category"); if (!likes) no_fav++; return { item: i, likes: likes ? parseInt(likes.textContent) : 0, type: type ? [...type.classList].pop() : "_unknown", }; }); data = sort_by(data, "likes"); data = sort_by(data, "type"); data.forEach(i => container.appendChild(i.item)); } function sort_by(data, key = "", dec = true) { if (dec) { return data.sort((a, b) => s(b[key], a[key])); } else { return data.sort((a, b) => s(a[key], b[key])); } function s(a, b) { if (a < b) return -1; if (a > b) return 1; return 0; } } function extracContainer() { let start = "", end = ""; container_list.forEach(c => { start += c[0]; end += c[1]; }); return [start, end]; } function containerRegexGenerator() { let reg = []; container_list.forEach(c => { let esc = regesc(c); let end = esc.slice(esc.length / 2); let start = esc.replace(end, ""); reg.push(`${start}[^${esc}]*${end}`); }); return `(${reg.join("|")})`; } function newCheckbox(id, onclick) { let ck = document.createElement("input"); ck.type = "checkbox"; ck.id = id; ck.onclick = onclick; return ck; } function newLable(forid = "", text = "", className = "dtr_textsize05") { let lable = document.createElement("label"); lable.className = className; lable.htmlFor = forid; lable.textContent = text; return lable; } function waitHTML(css_selector, run) { let id = setInterval(() => { if (document.querySelectorAll(css_selector).length) { clearInterval(id); run(); console.log(`found [${css_selector}]`); } else { console.log(`[${css_selector}] not found`); } }, 1000); } function searchHandler() { let display_list = document.querySelector(".display_normal.on"), display_grid = document.querySelector(".display_block.on"); console.log(`[${searchHandler.name}] display_list:${Boolean(display_list)}, display_grid:${Boolean(display_grid)}`); setRefreshPage(); if (display_list) { listHandler(); } else if (display_grid) { gridHandler(); } function setRefreshPage() { document.querySelectorAll(".display_normal,.display_block") .forEach(ele => ele.addEventListener("click", () => { let href_o = document.location.href, timer_id = setInterval(() => { let href_new = document.location.href; if (href_new != href_o) { clearInterval(timer_id); document.location.reload(); } }, 500); })); } } function getMutipleDataToList(pos, type = "a") { let list = []; let es = pos.querySelectorAll(type); if (es) { es.forEach(e => list.push(e.textContent)); list = stringFormatter(list.join(setting_sep)); return list; } else { return ""; } } function fix_switch_link() { let links = document.querySelectorAll(".floorNavLink-item a"); if (!links || links.length == 0) return; let current = window.location.href.replace(/.+www\.dlsite\.com\/\w+\/(.+)/, "$1"); links.forEach(link => link.href += current); } const to_full_size_image = url => url.replace(/(.*)resize(.*)_\d+x\d+(.*)/, "$1modpub$2$3"); const getCover = (id) => { let ele = document.querySelector(`#_link_${id} img`); if (ele) { // grid / list if (ele.src.includes("data:image")) { return to_full_size_image(`https:${ele.getAttribute("data-src")}`); } else { return to_full_size_image(ele.src); } } ele = document.querySelector(`img[src*="${id}_img_main"`); if (ele) { return to_full_size_image(ele.src); } else { return false; } }; function newCoverUrl(id, is_b = false) { let url = getCover(id); if (url) { if (is_b) { return newCopyButton(url, "Cover(Url)"); } else { return url; } } else { url = "no cover"; if (is_b) { return newCopyButton(url, url); } else { return url; } } } function newCoverDownload(id) { let b = document.createElement("button"); b.id = "dtr_cover_dl"; b.textContent = "Cover(DL)"; b.onclick = () => { let url = getCover(id); let rq = new XMLHttpRequest(); rq.open("GET", url, true); rq.responseType = "blob"; rq.onload = () => dl(rq.response, url.match(/[Rr][Jj]\d+[^\/]*\.[a-zA-Z]+/)[0]); rq.send(); function dl(blob, filename) { let file_url = window.URL.createObjectURL(blob); let a = document.querySelector("#dtr_img_dl_url"); if (!a) { a = document.createElement("a"); a.id = "dtr_img_dl_url"; document.body.insertAdjacentElement("afterbegin", a); } a.href = file_url; a.download = filename; a.click(); window.URL.revokeObjectURL(file_url); } }; return b; } function addSortButton() { let classname = "reSortByID"; let ele = document.querySelector(`.${classname}`); if (ele) return; let pos = document.querySelector(".sort_box .status_select"); if (!pos) return; ele = document.createElement("div"); ele.textContent = "SortByID:"; ele.style = "margin: 0.5rem;"; pos.appendChild(ele); ele = document.createElement("button"); ele.textContent = "Descent"; ele.className = classname; ele.onclick = () => sortByID(true); pos.appendChild(ele); ele = document.createElement("button"); ele.textContent = "Ascent"; ele.className = classname; ele.onclick = () => sortByID(); pos.appendChild(ele); ele = document.createElement("div"); ele.textContent = "SortByType:"; ele.style = "margin: 0.5rem;"; pos.appendChild(ele); ele = document.createElement("button"); ele.textContent = "Descent"; ele.className = classname; ele.onclick = () => sortByType(true); pos.appendChild(ele); ele = document.createElement("button"); ele.textContent = "Ascent"; ele.className = classname; ele.onclick = () => sortByType(); pos.appendChild(ele); } function sortByType(descent = false) { let grid_mode = document.querySelector(".display_block.on") ? true : false; let eles = document.querySelectorAll(grid_mode ? ".search_r###lt_img_box_inner" : "#search_r###lt_list tr"); let pos = document.querySelector(grid_mode ? "#search_r###lt_img_box" : "#search_r###lt_list tbody"); let data = [], type = ""; data = [...eles].map(e => { type = e.querySelector(".work_category"); return { ele: e, type: type ? [...type.classList].pop() : "_unknown", }; }); data = sort_by(data, "type", descent); data.forEach(item => pos.appendChild(item.ele)); } async function sortByID(descent = false) { console.time(sortByID.name); let grid_mode = document.querySelector(".display_block.on") ? true : false; let eles = document.querySelectorAll(grid_mode ? ".search_r###lt_img_box_inner" : "#search_r###lt_list tr"); let pos = document.querySelector(grid_mode ? "#search_r###lt_img_box" : "#search_r###lt_list tbody"); let attrname = "sort_id"; await new Promise(r => { eles.forEach(e => { let id = e.querySelector(grid_mode ? ".search_img" : ".work_thumb_inner") .id.replace("_link_", ""); e.setAttribute(attrname, id); }); r(); }); let id_list = [...eles].map(e => e.getAttribute(attrname).replace("RJ", "")); id_list = id_list.sort((a, b) => descent ? b - a : a - b); console.log(id_list); id_list.forEach(id => pos.appendChild(document.querySelector(`[${attrname}="RJ${id}"]`))); console.timeEnd(sortByID.name); } function listHandler() { console.time(listHandler.name); let list = document.querySelectorAll("#search_r###lt_list"); if (!list) { debug_msg("list not found"); } else { list = list[list.length - 1].querySelectorAll("tr"); list.forEach(tr => { let id, title_o_text, title_f_text, circle_text, cv, tags, pos, newbox, node_list; if (tr.querySelector(".page_no")) return; pos = tr.querySelector("dl"); id = tr.querySelector(".work_thumb a[href*='/product_id/']").id.replace("_link_", ""); title_o_text = pos.querySelector(".work_name a[href*='/product_id/']").textContent; title_f_text = stringFormatter(title_o_text); circle_text = stringFormatter(pos.querySelector(".maker_name a").textContent); node_list = [ newLine(), newCopyButton(title_o_text), newLine(), newCopyButton(title_f_text), newLine(), newCopyButton(id), newSeparate(), newCoverDownload(id), newSeparate(), newCoverUrl(id, true), newSeparate(), newCopyButton(circle_text), ]; if (title_o_text == title_f_text) node_list.splice(3, 2); newbox = appendAll(document.createElement("dd"), node_list); cv = pos.querySelector(".author"); cv = cv ? getMutipleDataToList(cv) : ""; tags = pos.querySelector(".search_tag"); tags = tags ? getMutipleDataToList(tags) : ""; if (cv != "") appendAll(newbox, [newSeparate(), newCopyButton(cv, "CV/Author")]); if (tags != "") appendAll(newbox, [newSeparate(), newCopyButton(tags, "Tags")]); pos.appendChild(newbox); }); } console.timeEnd(listHandler.name); addSortButton(); } function gridHandler() { console.time(gridHandler.name); let list = document.querySelectorAll(".search_r###lt_img_box_inner"); if (!list) { debug_msg("list not found"); } else { let w = document.createElement("div"); w.appendChild(newSpan("Can't get full CV/Author list in grid view. If you need it, switch to list view.", "dtr_list_w_text")); document.querySelector(".sort_box").insertAdjacentElement("afterend", w); list.forEach(box => { let id, title_o_text, title_f_text, circle_text, cv, pos, newbox, node_list; pos = box.querySelector(".work_price_wrap"); id = box.querySelector(".search_img.work_thumb").id.replace("_link_", ""); title_o_text = box.querySelector(".work_name a").textContent; title_f_text = stringFormatter(title_o_text); circle_text = stringFormatter(box.querySelector(".maker_name a").textContent); cv = box.querySelector(".author"); cv = cv ? getMutipleDataToList(cv) : ""; node_list = [ newCopyButton(id), newLine(), newCoverDownload(id), newSeparate(), newCoverUrl(id, true), newLine(), newCopyButton(title_o_text, "Original"), newSeparate(), newCopyButton(title_f_text, "Formatted"), newLine(), newCopyButton(circle_text, "Circle"), ]; if (title_o_text == title_f_text) node_list.splice(5, 2); newbox = appendAll(document.createElement("dd"), node_list); if (cv != "") appendAll(newbox, [newSeparate(), newCopyButton(cv, "CV/Author")]); pos.insertAdjacentElement("beforebegin", newbox); }); } console.timeEnd(gridHandler.name); addSortButton(); } function appendAll(node, nodeList) { nodeList.forEach(e => node.appendChild(e)); return node; } function saveSetting() { debug_msg("[saveSetting]"); if (setting_format.length > 0) { GM_setValue(key_format, setting_format); debug_msg(`saved ${key_format}: ${setting_format}`); } else { debug_msg(`${key_format} not saved cus is empty`); } GM_setValue(key_adv, setting_adv); debug_msg(`saved ${key_adv}: ${setting_adv}`); GM_setValue(key_f2h, setting_f2h); debug_msg(`saved ${key_f2h}: ${setting_f2h}`); GM_setValue(key_show_ot, setting_show_ot); debug_msg(`saved ${key_show_ot}: ${setting_show_ot}`); GM_setValue(key_show_ft, setting_show_ft); debug_msg(`saved ${key_show_ft}: ${setting_show_ft}`); GM_setValue(key_sep, setting_sep); debug_msg(`saved ${key_sep}: ${setting_sep}`); } function getData() { console.time(getData.name); //------------------------------------------------------ let sitedata = contents.detail[0]; let [Y, m, d] = sitedata.regist_date.split("/"); let y = Y.slice(2); let circle = document.getElementById("work_maker").querySelector("span.maker_name[itemprop='brand']"); let circle_text = stringFormatter(circle.querySelector("a").textContent); circle.insertAdjacentElement("afterbegin", newCopyButton(circle_text, "Copy")); Object.assign(formatted_data, { id: sitedata.id, title_original: sitedata.name, title_formatted: stringFormatter(sitedata.name), circle: circle_text, Year: Y, year: y, month: m, day: d, }); //------------------------------------------------------ let datapart = document.getElementById("work_right_inner").querySelectorAll("th"); let parselist = { series: ["Series name", "シリーズ名", "系列名", "系列名"], author: ["Author", "作者", "作者", "作者"], scenario: ["Scenario", "シナリオ", "剧情", "劇本"], illust: ["Illustration", "イラスト", "插画", "插畫"], cv: ["Voice Actor", "声優", "声优", "聲優"], age: ["Age", "年齢指定", "年龄指定", "年齡指定"], type: ["Product format", "作品形式", "作品类型", "作品形式"], }; let release = ["Release date", "販売日", "贩卖日", "販賣日"]; let text; let all = []; datapart.forEach(th => { for (let key in parselist) { text = th.textContent; if (isInList(text, parselist[key], formatted_data[key])) { all = []; if (key == ("age" || "type")) { th.parentNode.querySelectorAll("span").forEach(span => all.push(span.textContent)); } else { th.parentNode.querySelectorAll("a").forEach(a => all.push(a.textContent)); } formatted_data[key] = stringFormatter(all.join(setting_sep)); insertCopyDataButton(th, formatted_data[key]); delete parselist[key]; break; } else if (release) { if (release.some(t => text.includes(t))) { let date = `${Y}${m}${d}`; insertCopyDataButton(th, date, date); date = `${y}${m}${d}`; insertCopyDataButton(th, date, date); release = false; } } } }); //------------------------------------------------------ let tagpart = document.querySelector("#work_right_inner div.main_genre"); let insertpos = tagpart; tagpart = tagpart.querySelectorAll("a"); let tags = []; tagpart.forEach(a => tags.push(a.textContent)); formatted_data.tags = stringFormatter(tags.join(setting_sep)); insertCopyDataButton(insertpos, formatted_data.tags); console.timeEnd(getData.name); } function insertCopyDataButton(ele, copytext = "", btext = "Copy") { ele = searchNodeNameInParents(ele, "TR"); if (!ele) return; let pos = ele.querySelector("div"); if (!pos) pos = ele.querySelector("td"); if (!pos) return; pos.insertAdjacentElement("afterbegin", newCopyButton(copytext, btext)); } function searchNodeNameInParents(ele, nodename = "") { if (!ele || !nodename) return false; nodename = nodename.toUpperCase(); let count = 0; while (true) { ele = ele.parentNode; count++; if (!ele || count > 100) return false; if (ele.nodeName == nodename) break; } return ele; } function isInList(text, list, data) { if (!data) if (list.some(t => text.includes(t))) return true; return false; } function stringFormatter(text) { text = removeExcess(text); if (setting_f2h) text = toHalfWidth(text); text = repalceForbiddenChar(text); return text; } function removeExcess(text) { let o_text = text; // remove excess text let count = 0; while (count < 100 && text.match(reg_excess)) { text = text.replace(reg_excess, ""); count++; } if (text.length == 0) text = o_text; // remove container if it at start or end count = 0; while (count < 100) { let index_start = container_start.indexOf(text[0]); if (index_start != -1) { if (container_end[index_start] == text[text.length - 1]) { // found start & end text = text.slice(1, text.length - 1).trim(); debug_msg(`[removeExcess] remove container:"${text}"`); } else if (!text.includes(container_end[index_start])) { // found start but no end text = text.slice(1).trim(); debug_msg(`[removeExcess] remove start:"${text}"`); } } let index_end = container_end.indexOf(text[text.length - 1]); if (index_end != -1) { if (!text.includes(container_start[index_end])) { // found end but no start text = text.slice(0, text.length - 1).trim(); debug_msg(`[removeExcess] remove end:"${text}"`); } } if (index_start == -1 && index_end == -1) break; count++; } text = text.replace(reg_blank, " "); text = text.length > 0 ? text : o_text; debug_data(`o:[${o_text}]\np:[${text}]`); return text; } function toHalfWidth(text) { for (let i in setting_full) { text = text.replace(new RegExp(setting_full[i], "g"), setting_half[i]); } return text; } function repalceForbiddenChar(text) { for (let index in forbidden) { text = text.replace(new RegExp(regesc(forbidden[index]), "g"), replacer[index]); } return text; } function updateSetting() { let s = document.getElementById("format_title_setting"); let p = document.getElementById("format_title_preview"); let cs = document.getElementById("format_title_custom_span"); let cb = document.getElementById("format_title_custom_button"); if (s.value.length > 0) { if (setting_format != s.value) { setting_format = s.value; let formatted = parseFormatString(setting_format); cs.textContent = p.value = formatted; cb.onclick = () => navigator.clipboard.writeText(formatted); } } let sep = document.getElementById(`dtr_${key_sep}`); setting_sep = sep.value; } function parseFormatString(string = "") { let formatted_text = string; data_list.forEach(key => { formatted_text = formatted_text.replace(new RegExp(`%${key}%`, "g"), formatted_data[key]); }); formatted_text = repalceForbiddenChar(formatted_text); return formatted_text; } function setting() { console.time(setting.name); //------------------------------------------------------ let pos = document.getElementById("work_name"); let button = document.createElement("button"); Object.assign(button, { textContent: "Open Setting", value: "open", onclick: function () { let ele = document.getElementById("format_setting_ui"); if (this.value === "close") { ele.style.display = "none"; this.value = "open"; this.textContent = "Open Setting"; clearInterval(updateid); } else { ele.style.display = ""; this.value = "close"; this.textContent = "Close Setting"; updateid = setInterval(updateSetting, 100); } }, }); //------------------------------------------------------ let box = document.createElement("div"); box.id = "format_setting_ui"; box.className = "dtr_setting_box"; box.style.display = "none"; let textarea; //------------------------------------------------------ pos.insertAdjacentElement("afterbegin", box); pos.insertAdjacentElement("afterbegin", newLine()); pos.insertAdjacentElement("afterbegin", button); //------------------------------------------------------ button = document.createElement("button"); let mode = setting_adv ? "on" : "off"; Object.assign(button, { className: "dtr_textsize05", id: "format_title_setting_advance_model", textContent: `Advance mode: ${mode}`, value: mode, onclick: function () { let t = document.getElementById("format_title_setting"); if (this.value === "off") { Object.assign(this, { value: "on", textContent: "Advance mode: on", }); t.readOnly = false; setting_adv = true; } else { Object.assign(this, { value: "off", textContent: "Advance mode: off", }); t.readOnly = true; setting_adv = false; } } }); box.appendChild(button); box.appendChild(newSpan(" enable this to direct edit format setting", "dtr_textsize05 dtr_setting_w_text")); appendNewLine(box); //------------------------------------------------------ // all data data_list.forEach(s => box.appendChild(newDataButton(`+${s}`, `%${s}%`))); appendNewLine(box); //------------------------------------------------------ textarea = document.createElement("textarea"); textarea.className = "dtr_textsize05 dtr_max_width"; textarea.id = "format_title_setting"; textarea.rows = 1; textarea.value = setting_format; textarea.readOnly = !setting_adv; box.appendChild(newSpan("Format setting:")); appendNewLine(box); box.appendChild(textarea); appendNewLine(box); textarea = document.createElement("textarea"); textarea.className = "dtr_textsize05 dtr_max_width"; textarea.id = "format_title_preview"; textarea.readOnly = true; textarea.rows = 1; textarea.value = parseFormatString(setting_format); box.appendChild(newSpan("Preview:")); appendNewLine(box); box.appendChild(textarea); appendNewLine(box); //------------------------------------------------------ box.appendChild(newButton("save", saveSetting)); box.appendChild(newSeparate()); box.appendChild(newButton("default", () => { document.getElementById("format_title_setting").value = default_format; })); box.appendChild(newSeparate()); box.appendChild(newButton("clear", () => { document.getElementById("format_title_setting").value = ""; })); appendNewLine(box); appendNewLine(box); //------------------------------------------------------ let ck_area = document.createElement("div"); ck_area.className = "dtr_setting_ck_box"; appendAll(ck_area, [ newSpan(`Save & Refresh to make these setting work`, ""), newLine(), ]); let checkbox; let lable; checkbox = newCheckbox(`dtr_${key_f2h}`, function () { setting_f2h = this.checked ? true : false; debug_msg(`${key_f2h}: ${setting_f2h}`); }); lable = newLable(`dtr_${key_f2h}`, "Replace some half-width to full-width"); if (setting_f2h) checkbox.checked = true; appendAll(ck_area, [checkbox, lable, newLine()]); checkbox = newCheckbox(`dtr_${key_show_ot}`, function () { setting_show_ot = this.checked ? true : false; debug_msg(`${key_show_ot}: ${setting_show_ot}`); }); lable = newLable(`dtr_${key_show_ot}`, "Show Original / ID+Original"); if (setting_show_ot) checkbox.checked = true; appendAll(ck_area, [checkbox, lable, newLine()]); checkbox = newCheckbox(`dtr_${key_show_ft}`, function () { setting_show_ft = this.checked ? true : false; debug_msg(`${key_show_ft}: ${setting_show_ft}`); }); lable = newLable(`dtr_${key_show_ft}`, "Show Formatted / ID+Formatted"); if (setting_show_ft) checkbox.checked = true; appendAll(ck_area, [checkbox, lable, newLine()]); textarea = document.createElement("textarea"); textarea.className = "dtr_textsize05"; textarea.id = `dtr_${key_sep}`; textarea.rows = 1; textarea.cols = 1; textarea.value = setting_sep; textarea.style = "resize: none;"; lable = newLable(`dtr_${key_sep}`, "Separator: "); appendAll(ck_area, [ lable, textarea, newLable(`dtr_${key_sep}`, " for data have muti value like tags"), newLine(), ]); box.appendChild(ck_area); appendNewLine(box); //------------------------------------------------------ box.appendChild(newSpan("data list:")); appendNewLine(box); textarea = document.createElement("textarea"); textarea.className = "dtr_textsize05"; textarea.id = "format_title_all_data"; textarea.readOnly = true; box.appendChild(textarea); listAllData(); updateSetting(); console.timeEnd(setting.name); } function listAllData() { let textbox = document.getElementById("format_title_all_data"); textbox.value = ""; let count = 0; let maxlength = 0; let s; for (let key in formatted_data) { s = `%${key}%: ${formatted_data[key]}\n`; textbox.value += s; count++; if (formatted_data[key] && s.length > maxlength) maxlength = s.length; } textbox.rows = count + 1; textbox.cols = maxlength << 1; } function updateSettingString(id, format_string) { let textarea = document.getElementById(id); let list = [ "year", "Year", "month", "day", ]; let o = textarea.value; if (list.some(s => format_string.includes(s)) && list.some(s => o.endsWith(`%${s}%`))) { textarea.value += format_string; } else { textarea.value += ` ${format_string}`; } textarea.value = textarea.value.trim(); } function productHandler() { getData(); console.time(productHandler.name); //------------------------------------------------------ let pos = document.querySelector("#work_name"); pos.innerHTML = `<div style="${setting_show_ot ? '' : 'display:none;'} user-select: text;">${pos.innerText}</div>`; let id = formatted_data.id; let title_o = formatted_data.title_original; let title_f = formatted_data.title_formatted; let title_id_c = parseFormatString(setting_format); let title_id_o = `${id} ${title_o}`; let title_id_f = `${id} ${title_f}`; let notSame_o_c = Boolean(title_id_o != title_id_c); let notSame_f_c_o = Boolean(title_id_f != title_id_c && title_id_f != title_id_o); //------------------------------------------------------ // Cover url let span_cover = newSpan(newCoverUrl(id), ""); span_cover.style = "user-select: text;"; pos.append(span_cover); appendNewLine(pos); //------------------------------------------------------ // ID + original title if (notSame_o_c && setting_show_ot) { let span = newSpan(title_id_o, ""); span.style = "user-select: text;"; pos.append(span); appendNewLine(pos); } //------------------------------------------------------ // ID + formatted title if (notSame_f_c_o && setting_show_ft) { let span = newSpan(title_id_f, ""); span.style = "user-select: text;"; pos.append(span); appendNewLine(pos); } //------------------------------------------------------ // custom title let span = newSpan(title_id_c, ""); span.style = "user-select: text;"; span.id = "format_title_custom_span"; pos.append(span); appendNewLine(pos); //------------------------------------------------------ // add copy ID button pos.append(newCopyButton(id)); pos.append(newSeparate()); //------------------------------------------------------ // add download cover pos.append(newCoverDownload(id)); pos.append(newSeparate()); //------------------------------------------------------ // add copy cover url pos.append(newCopyButton(newCoverUrl(id), "Cover(Url)")); pos.append(newSeparate()); //------------------------------------------------------ // add copy custom format button let custom_button = newCopyButton(title_id_c, "CustomTitle"); custom_button.id = "format_title_custom_button"; pos.append(custom_button); //------------------------------------------------------ // add copy Original / ID+Original button if (notSame_o_c && setting_show_ot) { pos.append(newSeparate()); pos.append(newCopyButton(title_o, "Original")); pos.append(newCopyButton(title_id_o, "ID+Original")); } //------------------------------------------------------ // add copy Formatted / ID+Formatted button if (notSame_f_c_o && setting_show_ft) { pos.append(newSeparate()); pos.append(newCopyButton(title_f, "DefaultFormat")); pos.append(newCopyButton(title_id_f, "ID+DefaultFormat")); } setting(); //------------------------------------------------------ // creat track list if any let list = gettracklist(); if (list) { addTracklist(list, "Official"); } else { list = extractTrackListFromText(); let spantext = "Extract from article (Less accurate. Can't get the track that has no number.)"; if (list) list.reverse().forEach((r###lt, index) => addTracklist(r###lt, spantext, index)); } //------------------------------------------------------ console.timeEnd(productHandler.name); } function naturalSort(a, b) { let setting = { numeric: false, ignorePunctuation: false }; return String(a).localeCompare(String(b), navigator.languages[0] || navigator.language, setting); } function extractTrackListFromText() { let raw_text = document.querySelector(".work_parts_container"); if (!raw_text) return false; if (debug) console.groupCollapsed(); //------------------------------------------------------ let extract_r###lt = []; let reg_number = /[\d1234567890]+/; // pre process raw_text = raw_text.textContent.split("\n").filter(line => line.replace(reg_muti_blank, "") != ""); let newtext = []; raw_text.forEach(line => { newtext.push(shiftCode(line)); }); //debug_msg("newtext", newtext); // get all line with number & remove excess text let reg_prefix = /^\D+(?=\d+)/; let reg_suffix = /(?<=\d)\D/; let reg_exclude = /https*:\/\/|総再生|総時間|合計/; let reg_age = /[rR]\-*\d+/g; let extract = []; newtext.forEach((line, index) => { let text = line.replace(reg_age, "").replace(reg_non_track2, "").replace(reg_time, ""); if (!text.match(reg_exclude)) { let number = text.match(reg_number); let prefix = text.match(reg_prefix); let suffix = text.match(reg_suffix); if (number) { extract.push({ number: parseInt(number[0], 10), text: text, o_index: index, prefix: prefix ? prefix[0] : "_no_prefix_", suffix: suffix ? suffix[0] : "_no_suffix_", }); } } }); debug_msg("extract", extract); if (extract.length == 0) return false; // ====================================================================== // old extractor let track_list = []; let offset = 1; let not_add = 0; let extract_copy = Object.assign([], extract); let skip = 0; for (let index = 1; index < extract_copy.length; index += offset) { if (offset == -1) offset = 1; debug_msg(""); let this_n = extract_copy[index].number; let previous_n = extract_copy[index - offset].number; debug_msg(`index:${index} | this_n:${this_n} | previous_n:${previous_n} | offset:${offset} | not_add:${not_add} | skip:${skip} | this:${extract[index].text}`); if (skip != 0 && skip == index) { skip = 0; continue; } if (offset == 1) { if (this_n == previous_n) { // see same number as previous, skip this one debug_msg(`skip | ${extract[index].text}`); continue; } else if (this_n == previous_n + 1) { if (track_list.length == 0) { track_list.push(extract[index - 1].text); debug_msg(`add | ${extract[index - 1].text}`); } track_list.push(extract[index].text); debug_msg(`add | ${extract[index].text}`); not_add = 0; continue; } else if (index >= 2) { if (this_n == extract[index - 2].number + 1) { offset = 2; if (track_list.length == 0) { track_list.push(extract[index - 2].text); debug_msg(`add | ${extract[index - 2].text}`); } track_list.push(extract[index].text); debug_msg(`add | ${extract[index].text}, offset: 2`); not_add = 0; continue; } } } else if (offset == 2) { if (this_n == previous_n) { // see same number as previous, skip this one debug_msg(`skip | ${extract[index].text}`); continue; } else if (this_n == previous_n + 1) { track_list.push(extract[index].text); debug_msg(`add | ${extract[index].text}`); not_add = 0; continue; } else if (this_n == extract[index - 1].number + 1) { offset = -1; skip = index; track_list.push(extract[index].text); debug_msg(`add | ${extract[index].text}, offset: 1`); not_add = 0; continue; } } not_add++; if (not_add > 1 || this_n == 1) { if (track_list.length > 0) extract_r###lt.push(track_list); track_list = []; not_add = 0; debug_msg("_____reset_____"); } } if (track_list.length > 0) extract_r###lt.push(track_list); debug_data("extractor 1\n" + "======================================================================\n" + "extractor 2"); function remove_non_track(list = []) { let new_list = Object.assign([], list); let reglist = [ reg_time, reg_non_track, ]; let reg_total = /総[^時間]*時間/; new_list = new_list.filter(o => { let clean_text = o.text; reglist.forEach(reg => clean_text = clean_text.replace(reg, "")); if (clean_text.match(reg_number) && !clean_text.match(reg_total)) { return true; } else { debug_msg(`remove "${o.text}"`); return false; } }); debug_msg("remove_non_track", Object.assign([], new_list)); return new_list; } function remove_single_track(list = []) { let new_list = Object.assign([], list); let no_single = []; let tmp = []; while (new_list.length > 0) { let o = new_list.shift(); if (tmp.length == 0 || tmp[0].number == o.number - 1) { tmp.unshift(o); } else { if (tmp.length > 1) tmp.reverse().forEach(x => no_single.push(x)); tmp = [o]; } } if (tmp.length > 1) tmp.reverse().forEach(x => no_single.push(x)); new_list = no_single.reverse(); debug_msg("remove_single_track", Object.assign([], new_list)); return new_list; } function match_index2title(list = []) { let new_list = Object.assign([], list); let match_list = {}; new_list.forEach(o => { if (!match_list[o.number]) { match_list[o.number] = [o.text]; } else { match_list[o.number].push(o.text); } }); debug_msg("match_index2title", Object.assign({}, match_list)); return match_list; } function remove_non_continuous(obj = {}) { let match_list = Object.assign({}, obj); let pre_index = false; for (let index in match_list) { let i = parseInt(index, 10); if (!pre_index || i == pre_index + 1) { pre_index = i; } else { debug_msg(`remove "${match_list[index]}"`); delete match_list[index]; } } debug_msg("remove_non_continuous", Object.assign({}, match_list)); return match_list; } function regroup_by_index(obj = {}, start = 1) { let match_list = Object.assign({}, obj); let new_list = Array.from(match_list[start], x => []); for (let index in new_list) { for (let key in match_list) { new_list[index] = new_list[index].concat(match_list[key]); } } debug_msg("re-group by index", Object.assign([], new_list)); return new_list.length > 0 ? new_list : false; } function group_by_key(list = [], key = "") { let copy = Object.assign([], list); let tmp = {}; copy.forEach(o => { if (!tmp[o[key]]) { tmp[o[key]] = [o]; } else { tmp[o[key]].push(o); } }); copy = tmp; for (let i in tmp) { // remove group that list.length < 2 if (tmp[i].length < 2) delete tmp[i]; } debug_msg(`group by key[${key}]`, Object.assign({}, copy)); return copy; } extract_copy = Object.assign([], extract); extract_copy = extract_copy.sort((a, b) => naturalSort(a.text, b.text)); debug_msg("pre-sort by text", Object.assign([], extract_copy)); //extract_copy = remove_non_track(extract_copy); //extract_copy = remove_single_track(extract_copy); let group_prefix = group_by_key(extract_copy, "prefix"); for (let prefix in group_prefix) { debug_msg(`group [${prefix}]`); let group = group_prefix[prefix]; let match_list = match_index2title(group); //match_list = remove_non_continuous(match_list); if (match_list[0]) { match_list = regroup_by_index(match_list, 0); if (match_list) match_list.forEach(list => extract_r###lt.push(list)); } else if (match_list[1]) { match_list = regroup_by_index(match_list, 1); if (match_list) match_list.forEach(list => extract_r###lt.push(list)); } } let group_suffix = group_by_key(extract_copy, "suffix"); for (let suffix in group_suffix) { debug_msg(`group [${suffix}]`); let group = group_suffix[suffix]; let match_list = match_index2title(group); if (match_list[0]) { match_list = regroup_by_index(match_list, 0); if (match_list) match_list.forEach(list => extract_r###lt.push(list)); } else if (match_list[1]) { match_list = regroup_by_index(match_list, 1); if (match_list) match_list.forEach(list => extract_r###lt.push(list)); } } if (debug) console.groupEnd(); //------------------------------------------------------ if (debug) console.groupCollapsed(); debug_msg("extract_r###lt | ", extract_r###lt); if (extract_r###lt.length == 0) return false; extract_r###lt.forEach((r###lt, r###lt_index) => { let check_list = removeExcessInTrackList(r###lt, true); debug_msg("===================="); debug_msg("check_list", check_list); if (check_list.some(line => line == "")) { debug_msg(`check_list is empty, try to extract from offset line`); let extract_from = []; r###lt.forEach(track => { let original = extract.find(ex => ex.text == track); if (original) extract_from.push(original.o_index); }); if (extract_from.length == r###lt.length) { let search_title = []; let offset = 0; while (offset < max_depth) { offset++; extract_from.forEach(o_index => search_title.push(newtext[o_index + offset])); if (search_title.some(t => r###lt.indexOf(t) != -1)) { debug_msg("some offset title already in original list, list overlapped, abort"); break; } else if (search_title.length == extract_from.length) { let newlist = []; search_title.forEach((st, st_index) => { newlist.push(r###lt[st_index] + st); }); debug_msg("found newlist | ", search_title); extract_r###lt[r###lt_index] = newlist; break; } else if (search_title.length != extract_from.length) { debug_msg("2 list have different length, abort"); break; } } } } else { debug_msg("pass"); } }); if (debug) console.groupEnd(); return extract_r###lt.length > 0 ? extract_r###lt.sort((a, b) => b.length - a.length) : false; } function shiftCode(string = "") { let reg_fullwidth_code = /[\uFF01-\uFF63]/g; return string.replace(reg_fullwidth_code, match => String.fromCharCode(match.charCodeAt(0) - 0xFEE0)).replace(reg_muti_blank, " ").trim(); } function removeExcessInTrackList(list = [], no_index = false) { if (!list) return false; let reglist = [ reg_time, reg_text_start, reg_non_word_at_start, reg_text_start, ]; debug_msg(removeExcessInTrackList.name); let new_list = doRegList(list, reglist, no_index); let have2index = /\d+\D+(?=\d+)/; if (new_list.every(line => line.match(have2index))) { debug_data("\n<<<2nd pass>>>\n\n"); new_list = doRegList(new_list, [have2index], no_index); } return new_list; function doRegList(_datalist, _reglist = [], _no_index = false) { let _new_list = []; let _reg_total = /総[^時間]*時間/; //debug_msg("reglist", _reglist); debug_msg("list", _datalist); _datalist.forEach(_line => { let _new_line = _line; _reglist.forEach(reg => { _new_line = _new_line.replace(reg, "").trim(); //debug_msg(`"${_new_line}" by reg [${reg}]`); }); _new_line = removeBracket(_new_line); _new_line = _new_line.replace(reg_muti_blank, " ").trim(); if (_no_index) { _new_line = _new_line.replace(reg_muti_blank, "").replace(/^\d+/, "").trim(); } if (!_line.match(_reg_total)) _new_list.push(_new_line); }); debug_msg("after reglist", _new_list); return _new_list; } function removeBracket(t = "") { let index = container_start.indexOf(t[0]); if (index != -1) { if (container_end[index] == t[t.length - 1]) { // found start & end t = t.slice(1, t.length - 1).trim(); debug_msg(`remove container:"${t}"`); } else if (!t.includes(container_end[index])) { // found start but no end t = t.slice(1).trim(); debug_msg(`remove start:"${t}"`); } } else { index = container_end.indexOf(t[t.length - 1]); if (index != -1) { if (!t.includes(container_start[index])) { // found end but no start t = t.slice(0, t.length - 1).trim(); debug_msg(`remove end:"${t}"`); } } } return t; } } async function SHA(text = "") { const msgUint8 = new TextEncoder().encode(text); // encode as (utf-8) Uint8Array const hashBuffer = await crypto.subtle.digest('SHA-1', msgUint8); // hash the message SHA-1/256/384/512 const hashArray = Array.from(new Uint8Array(hashBuffer)); // convert buffer to byte array const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join(''); // convert bytes to hex string return hashHex; } async function addTracklist(list, from, index = 0) { debug_msg("===================="); let textlist = removeExcessInTrackList(list); if (textlist.some(line => line == "")) return debug_msg("found empty line, abort"); if (textlist.every(line => line.match(/^\d+/))) { textlist = textlist.sort((a, b) => a.localeCompare(b, navigator.languages[0] || navigator.language, { numeric: true, }) ); } textlist = textlist.map((line, index) => `${index + 1}. ${line}`); debug_msg("final | ", textlist); let row_count = textlist.length; let maxlength = Math.max(...textlist.map(t => getTrueLength(t))); textlist = textlist.join("\n"); let hash = await SHA(textlist); let duplicate = document.querySelector(`[hash="${hash}"]`); if (duplicate) return debug_msg(`found duplicate[${hash}], abort`); let box = document.createElement("div"); box.className = "dtr_tracklist"; box.setAttribute("hash", hash); let textbox = document.createElement("textarea"); textbox.id = `dtr_tracklist${index}`; textbox.rows = row_count + 1; textbox.cols = maxlength; textbox.value = textlist; let copyall = document.createElement("button"); copyall.textContent = "Copy"; copyall.onclick = () => { navigator.clipboard.writeText(document.getElementById(`dtr_tracklist${index}`).value); }; let span = newSpan(from); if (from != "Official") span.className = "dtr_setting_w_text"; let pos = document.querySelector("[itemprop='description']"); pos.insertAdjacentElement("afterbegin", box); appendAll(box, [textbox, newLine(), copyall, newLine(), span,]); function getTrueLength(string = "") { let length = 0; [...string].forEach(char => { length += char.match(/[\w\s_]/) ? 1 : 2; }); return length; } } function gettracklist() { let list = document.querySelector(".work_tracklist"); if (!list) return false; list = list.querySelectorAll(".work_tracklist_item"); if (!list) return false; let tracklist = []; list.forEach(ele => { let title = ele.querySelector(".title").textContent; if (title) tracklist.push(title); }); debug_msg("Official list | ", tracklist); return tracklist; } function appendNewLine(ele) { ele.appendChild(document.createElement("br")); } function newLine() { return document.createElement("br"); } function newSeparate() { return newSpan(` / `); } function newButton(btext, onclick) { let button = document.createElement("button"); button.innerHTML = btext; button.onclick = onclick; return button; } function newDataButton(btext, format_string) { return newButton(btext, () => { updateSettingString("format_title_setting", format_string); }); } function newCopyButton(copytext, btext = "") { return newButton(btext === "" ? copytext : btext, async () => { await navigator.clipboard.writeText(copytext); }); } function newSpan(text = "", className = "dtr_textsize05") { let span = document.createElement("span"); span.className = className; span.textContent = text; return span; } function myCss() { let s = document.createElement("style"); s.className = "myCssSheet"; document.head.appendChild(s); s.textContent = ` .dtr_textsize05 { font-size: 0.5rem; vertical-align: middle; } .dtr_setting_box { border: 0.2rem; border-style: solid; padding: 0.5rem; } .dtr_setting_w_text { color: red; } .dtr_max_width { width: 100%; } .dtr_list_w_text { color: red; float: right; } .dtr_setting_ck_box { border: black 0.1rem solid; width: max-content; padding: 0.5rem; } .dtr_tracklist{ display: inline-grid; margin: 1rem 1rem 2rem; } `; } function debug_msg(...any) { if (debug) console.log(`[dlsite title reformat] `, ...any); } function debug_data(...any) { if (debug) console.log(...any); } })();