/* jshint esversion: 6 */
// ==UserScript==
// @name popupTitleOverAnchorsOnDMM
// @namespace https://greasyfork.org/ja/users/289387-unagionunagi
// @version 0.5
// @description DMM・FANZAで商品(など)のリンクに(だいたいの)フルタイトルをポップアップ
// @author unagiOnUnagi
// @match *://*.dmm.co.jp/*
// @match *://*.dmm.com/*
// @grant none
// @license GPL-2.0-or-later
// ==/UserScript==
function getTrimText(node) {
return node.textContent.trim().replace(/\s+/g, ' ');
}
function addTitleAttrAll(nodes, verbose=false) {
// a タグに子要素の img の alt から取ってきたタイトルを title 属性として追加
// alt がなかったら a タグの onclick を見てみる
// onclick もダメだったら a タグ内のタイトル文字列をそのまま
// DMM 動画top: altなし、div/div(title, (price)?, sammary)
// VR top: altなし、span(title, price)
// Idol top: altなし、span(title, price)
// IdolCh top: altなし、a直下
// 見放題ch top: altなし、a直下
// FANZA 動画top pickup: altなし、span(title, maker, price)
// ランキングのところは別
// XPathResult はウ○コ
let length = nodes.length || nodes.snapshotLength;
let getItem = nodes.item || nodes.snapshotItem;
getItem = getItem.bind(nodes);
for ( let i=0 ; i < length; i++ ) {
let anchor = getItem(i);
let title = anchor.getElementsByTagName('img')[0].getAttribute('alt');
let txt = null;
if (!title) {
let onclick = anchor.getAttribute('onclick');
if (onclick && onclick.startsWith('_gaq.push') && !onclick.endsWith("'');")) {
title = onclick.split("'")[13];
}
if (!title) {
for (let c of anchor.childNodes) {
if (c.nodeName == '#text') {
txt = c.nodeValue.trim();
if (txt) title = txt;
break;
}
}
if (!title) {
for (let s of anchor.getElementsByTagName('span')) {
txt = getTrimText(s);
if (txt) title = txt;
break;
}
if (!title || title == 't') {
txt = getTrimText(anchor);
if (txt) title = txt;
}
}
}
}
if (title) {
anchor.setAttribute('title', title);
if (verbose) {
console.log(title);
console.log(anchor);
}
}
}
}
function addTitleAttr2Rankings(target) {
// ランキング内商品 anchorに title= 追加
// DMM DVD通販 IdolTop: altがt、タイトルがa直下 (非固定spanのtail)
// FANZA 動画 ビデオtop ranking[1:]: altなし、span(rank) a直下 title br latest
let prefix = '.';
if (!target) {
prefix = './/div[@id="side-rank-tab"]';
target = document.body;
}
let ranking = document.evaluate(
prefix + '//li[a[img[not(@title)]]]',
target, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
for ( let i=0 ; i < ranking.snapshotLength; i++ ) {
let li = ranking.snapshotItem(i);
let alt = li.getElementsByTagName('img')[0].getAttribute('alt');
if (!alt || alt == 't') {
alt = getTrimText(li);
}
if (alt) {
for (let a of li.getElementsByTagName('a')) {
a.setAttribute('title', alt);
}
}
}
}
function createObserver(target, callback) {
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => callback(mutation));
});
for (let t of target()) {
observer.observe(t, { childList: true, subtree: true });
}
}
function addTitleAttr2Anchors() {
// とりあえず title のない画像つきリンクに一律処理
addTitleAttrAll(
document.evaluate('.//a[.//img and not(@title)]',
document.body, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null));
// カテゴリトップページランキング
// 最初に一発やっとかないとあとで更新されないページがある
addTitleAttr2Rankings();
// ランキングの日間・週間・月間切り換えを監視
createObserver(
() => document.querySelectorAll('#side-rank-tab'),
mutation => addTitleAttr2Rankings(mutation.target)
);
// <,>ボタンで回転するところを監視
createObserver(
() => document.querySelectorAll(
'.clist,#recent_check_list,#actorother_main,.d-boxslidelist,#recommend_parts'),
mutation => {
addTitleAttrAll(document.evaluate(
'.//a[.//img and not(@title)]',
mutation.target, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
)}
);
}
(function() {
// load 後に追加される要素を待ってから
let waitAppear = [];
let waitDisappear = [];
if (document.querySelector('#browserecommend,#browserecommend1')) {
waitAppear = ['#list-history-min a img[alt]',];
} else if (document.querySelector('#recommend')) {
waitAppear = ['#recommend_main li a img[alt]',
'.d-modtogglelink-close,.d-modtogglelink-open'];
} else if (document.querySelector('#recommend_all')) {
waitAppear = ['#recent_check_list',];
}
if (document.getElementsByClassName('loading').length) {
waitDisappear = ['.loading',]
};
if (waitAppear.length || waitDisappear.length) {
// 要素が追加されない場合もあるので setInterval でタイムアウトするように
const isExists = targets => document.querySelectorAll(targets).length;
let waitId = null;
let c = 1;
const waiter = () => {
let isAppeared = waitAppear.length ? waitAppear.every(t => isExists(t)) : true;
let isDisappeared = waitDisappear.length ? waitDisappear.every(t => !isExists(t)) : true;
if ((isAppeared && isDisappeared) || c > 15) {
clearInterval(waitId);
setTimeout(addTitleAttr2Anchors, 300);
}
c++
}
waitId = setInterval(waiter, 200);
} else {
addTitleAttr2Anchors();
}
})();