DMM・FANZAで商品(など)のリンクに(だいたいの)フルタイトルをポップアップ
/* jshint esversion: 6 */
// ==UserScript==
// @name popupTitleOverAnchorsOnDMM
// @namespace https://greasyfork.org/ja/users/289387-unagionunagi
// @description DMM・FANZAで商品(など)のリンクに(だいたいの)フルタイトルをポップアップ
// @author unagiOnUnagi
// @match *://*.dmm.co.jp/*
// @match *://*.dmm.com/*
// @grant none
// @license GPL-2.0-or-later
// @version 0.7.0
// ==/UserScript==
function getTrimText(node) {
return node.textContent.trim().replace(/\s+/g, ' ');
}
function getTitle(anchor) {
// タイトルの取得
// alt がなかったら a タグの onclick を見てみる
// onclick もダメだったら a タグ内のタイトル文字列をそのまま
// %%DMM 動画top: altなし、div/div(title, (price)?, summary)%%
// VR top: altなし、span(title), span(price)
// Idol top: altなし、span(title), span(price)
// IdolCh top: altなし、a直下<img><br>title
// %%見放題ch top: altなし、a直下%%(終了)
// %%FANZA 動画top pickup: altなし、span(title, maker, price)%%
let title = anchor.querySelector('img').getAttribute('alt');
if (title) return title;
let nameNode = anchor.querySelector(
'.mainListLinkWork__txt,.responsive-name,.c-product-card__ttl,.bx-title,.productCard-title');
if (nameNode) {
if ((title = getTrimText(nameNode))) return title;
}
let onclick = anchor.getAttribute('onclick');
if (onclick && onclick.startsWith('_gaq.push') && !onclick.endsWith("'');")) {
if ((title = onclick.split("'")[13])) return title;
}
for (let c of anchor.childNodes) {
if (c.nodeName == '#text') {
if ((title = c.nodeValue.trim())) return title;
}
}
for (let s of anchor.getElementsByTagName('span')) {
let txt = getTrimText(s);
if (txt) {
title = txt;
break;
}
}
if (!title || title == 't') {
if ((title = getTrimText(anchor))) return title;
}
return title;
}
function addTitleAttrAll(nodes) {
// a タグに子要素の img の alt から取ってきたタイトルを title 属性として追加
// ランキングのところは別
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 = getTitle(anchor);
if (title) {
anchor.setAttribute('title', title);
} else {
console.log(`Could not get title: {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;
}
const ranking = document.evaluate(
prefix + '//li[div[a[not(@title) or @title=""]]]',
target, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
const rankingLength = ranking.snapshotLength;
for ( let i=0 ; i < rankingLength; i++ ) {
let li = ranking.snapshotItem(i);
let alt = li.getElementsByTagName('img')[0].getAttribute('alt');
if (!alt || alt == 't') alt = getTrimText(li);
if (alt) {
li.querySelectorAll('div > a').forEach(
textEl => textEl.setAttribute('title', alt));
} else {
console.log(`Could not get title on ranking: {li}`);
}
}
}
(function() {
// とりあえず title のない画像つきリンクに一律処理
addTitleAttrAll(
document.evaluate('.//a[.//img and not(@title)]',
document.body, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null));
// カテゴリトップページランキング
// 最初に一発やっとかないとあとで更新されないページがある
addTitleAttr2Rankings();
// ページの動的変更を監視
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
let target = mutation.target;
addTitleAttrAll(document.evaluate(
'.//a[.//img and not(@title)]',
target, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null)
);
if (target.id.match(/(ranking|s-tb-sect)/)) {
addTitleAttr2Rankings(target);
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
})();