Sleazy Fork is available in English.

popupTitleOverAnchorsOnDMM

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.10
// ==/UserScript==

function getTrimText(node) {
    return node.textContent.trim().replace(/\s+/g, ' ');
}

function xpathEval(xpath, cxtNode=document.body) {
    return document.evaluate(
        xpath, cxtNode,
        null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
}

function getTitle(anchor) {
    // タイトルの取得
    // alt がなかったら a タグの onclick を見てみる
    // onclick もダメだったら a タグ内のタイトル文字列をそのまま
    //  DMM 動画:
    //        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直下
    let title = anchor.querySelector('img').alt;
    if (title) return title;

    let nameNode = anchor.querySelector(
        '.mainListLinkWork__txt,.responsive-name,.c-product-card__ttl,' +
        '.bx-title,.productCard-title,.title1O8Dg');
    if (nameNode) {
        if ((title = getTrimText(nameNode))) 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);
        title && (anchor.title = title);
    }
}

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 = xpathEval(prefix + '//li[div[a[not(@title) or @title=""]]]',
                              target);
    const rankingLength = ranking.snapshotLength;
    for ( let i=0 ; i < rankingLength; i++ ) {
        let li = ranking.snapshotItem(i);
        let alt = li.getElementsByTagName('img')[0].alt;
        if (!alt || alt == 't') alt = getTrimText(li);
        if (alt) {
            for (let textEl of li.querySelectorAll('div > a')) {
                textEl.title = alt;
            }
        } else {
            console.log(`Could not get title on ranking: ${li}`);
        }
    }
}

function addTitlesOnDMMTV(targetNodes) {
    // DMM TV、FANZA TVポップアップ
    for (let nodes of targetNodes) {
        for (let child of nodes.childNodes) {
            let objNode, title;
            if (child.nodeType == Node.TEXT_NODE) {
                // FANZA TV ページ上のタイトル
                objNode = nodes;
                title = child.nodeValue;
            } else {
                // DMM/FANZA TV のポップアップ
                objNode = child;
                title = child.innerText;
            }
            // console.log(objNode);
            if (!objNode.title) {
                objNode.title = title;
            }
        }
    }
}

(function() {

    // とりあえず title のない画像つきリンクに一律処理
    addTitleAttrAll(xpathEval('.//a[.//img and not(@title)]', document.body));

    // カテゴリトップページランキング
    // 最初に一発やっとかないとあとで更新されないページがある
    addTitleAttr2Rankings();

    // ページの動的変更を監視
    const observer = new MutationObserver(mutations => {
        for (let mutation of mutations) {
            let target = mutation.target;
            // console.log(target);

            // .css-vn7tls DMM TV ポップアップ
            // .e13no8o59  FANZA TV ページ上のタイトル
            // .eu14kkw2   FANZA TV ポッポアップ
            let dmmTV = target.querySelectorAll('.css-vn7tls,.eu14kkw2,.e13no8o59');
            if (dmmTV.length > 0) {
                addTitlesOnDMMTV(dmmTV);
                continue;
            }

            addTitleAttrAll(xpathEval('.//a[.//img and not(@title)]', target));
            if (target.id.match(/(ranking|s-tb-sect)/)) {
                addTitleAttr2Rankings(target);
            }
        }
    });
    observer.observe(document.body, { childList: true, subtree: true });

})();