/* jshint esversion: 6 */
// ==UserScript==
// @name restore2OriginalTitleOnFANZA (experimental)
// @namespace https://greasyfork.org/ja/users/289387-unagionunagi
// @version 0.1.6
// @description FANZA独自の作品タイトルへの伏せ字を元に戻す
// @author unagiOnUnagi
// @match *://*.dmm.co.jp/*/detail/*
// @match *://*.dmm.co.jp/*/list/*
// @match *://*.dmm.co.jp/*/search/*
// @grant GM_setValue
// @grant GM_getValue
// @license GPL-2.0-or-later
// ==/UserScript==
function resolvePropVal(isChecked) {
return isChecked ? ['inline', 'none'] : ['none', 'inline'];
}
function addStyleSheet() {
let styleEl = document.createElement('style');
styleEl.id = 'restoreoriginaltitle-css';
document.head.appendChild(styleEl);
let styleSheet = styleEl.sheet;
let [restored, masked] = resolvePropVal(GM_getValue('checked', true));
styleSheet.insertRule(`.restored-title {display: ${restored};}`, 0);
styleSheet.insertRule(`.masked-title {display: ${masked};}`, 1);
}
function toggleTitle(ev) {
let rules = document.getElementById('restoreoriginaltitle-css').sheet.rules;
let isChecked = ev.target.checked;
for (let [i, value] of resolvePropVal(isChecked).entries()) {
rules[i].style.display = value;
}
GM_setValue('checked', isChecked);
}
function addCheckbox(parent, isListPage=false, nor=null) {
let style = (isListPage)
? 'margin-left: 10px; font-size: 10px;'
: 'margin-left: 10px; font-size: 10px; font-weight: normal; float: right;';
let label = (nor == null)
? '伏せ字を復元'
: ` 伏せ字を復元(${nor})`;
let restoreSpan = document.createElement('span');
restoreSpan.title = 'FANZA独自の作品タイトルへの伏せ字の復元を試みます';
restoreSpan.style.cssText = style;
parent.appendChild(restoreSpan);
let restoreCb = document.createElement('input');
restoreCb.type = 'checkbox';
restoreCb.id = 'restoreoriginaltitle-cb';
restoreCb.name = 'restoreoriginaltitle-cb';
restoreCb.style.cssText = 'vertical-align: middle; transform: scale(0.8);';
restoreSpan.appendChild(restoreCb);
let restoreLabel = document.createElement('label');
restoreLabel.htmlFor = 'restoreoriginaltitle-cb';
restoreLabel.textContent = label;
restoreSpan.appendChild(restoreLabel);
restoreCb.checked = GM_getValue('checked', true);
restoreCb.addEventListener('change', toggleTitle);
}
function _restore(title) {
const _MASKED_WORDS = [
['犯●れ', '犯され'],
['●す', '犯す'],
[/犯●([\s\d])/g, '犯す$1'],
[/(?<!を)強●(?:(?=[BVしさ犯罪調魔事被白総映。!・\s\d])|$)/ig, '強姦'],
[/強●(?=[AFMS\u30a0-\u30ff\u3040-\u309f\u3005-\u3006\u30e0-\u9fcf])/ig, '強制'],
['レ●プ', 'レイプ'],
['陵●', '陵辱'],
['凌●', '凌辱'],
['夜●い', '夜這い'],
['●っ払', '酔っ払'],
['●っぱら', '酔っぱら'],
['痴●', '痴漢'],
['輪●', '輪姦'],
['催●', '催眠'],
['泥●', '泥酔'],
['奴●', '奴隷'],
['シ●タ', 'ショタ'],
[/(?<![子様])●校/g, '高校'],
['無●正', '無修正'],
['昏●', '昏睡'],
['折●', '折檻'],
[/(?<=[薬酒]を)●ませ/g, '飲ませ'],
['◆', '♥'],
// ['(ハート)', '♥'],
];
for (let [masked, repl] of _MASKED_WORDS) {
title = title.replaceAll(masked, repl);
}
return title;
}
function restore2OriginalTitle(titleElem) {
for (let c of titleElem.childNodes) {
if (c.nodeName == '#text') {
let textc = c.nodeValue;
if (!textc.trim()) continue;
let masked = textc;
let restored = _restore(masked);
if (masked == restored) return false;
console.log(`restore title:\n${masked.trim()} =>\n${restored.trim()}`);
titleElem.removeChild(c);
let restoreSpan = document.createElement('span');
restoreSpan.classList.add('restored-title');
restoreSpan.style.cssText = 'color: unset; font-size: unset; font-weight: unset;';
restoreSpan.textContent = restored;
titleElem.appendChild(restoreSpan);
let maskedSpan = document.createElement('span');
maskedSpan.classList.add('masked-title');
maskedSpan.style.cssText = 'color: unset; font-size: unset; font-weight: unset;';
maskedSpan.textContent = masked;
titleElem.appendChild(maskedSpan);
return true;
}
}
}
(function() {
if (window.frameElement) return;
addStyleSheet();
let checkbox, parent;
// 各商品ページの商品タイトル
let titleElem = document.querySelector('#title,h1.item');
if (titleElem) {
parent = titleElem.parentNode;
} else if ((titleElem = document.querySelector('.productTitle__txt'))) {
parent = document.querySelector('.productInfo');
}
if (titleElem && restore2OriginalTitle(titleElem)) {
addCheckbox(parent);
return;
}
// 商品一覧
let lineup = document.querySelectorAll('#list a .txt,table[summary="商品一覧"] .ttl a,' +
'.m-productList .tileListTtl__txt a,'+
'.l-areaListMain a .m-boxListBookProductTmb__ttl,' +
'.l-areaListMain .m-boxListBookProductBlock__main__info__ttl a,' +
'.list-favorite .ttl-data a,' +
'.bookmark-list .item-name a');
if (!lineup.length) return;
let numOfRepd = 0;
for (let elem of lineup) {
if (restore2OriginalTitle(elem)) numOfRepd++;
}
let control = document.querySelector('.list-boxcaptside div.list-unit,.d-boxcaptside div.d-unit');
if (control) {
addCheckbox(control.parentNode, true, numOfRepd);
} else if ((control = document.querySelector('#mu,.l-areaMainColumn,#l-contents,#main-bmk,#l_page_bookmark .basket-sortBox'))) {
addCheckbox(control, true, numOfRepd);
} else {
console.log('Breadcrumb list not found');
}
})();