popupTitleOverAnchorsOnDMM

DMM・FANZAで商品(など)のリンクに(だいたいの)フルタイトルをポップアップ

Dovrai installare un'estensione come Tampermonkey, Greasemonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Violentmonkey per installare questo script.

Dovrai installare un'estensione come Tampermonkey o Userscripts per installare questo script.

Dovrai installare un'estensione come ad esempio Tampermonkey per installare questo script.

Dovrai installare un gestore di script utente per installare questo script.

(Ho già un gestore di script utente, lasciamelo installare!)

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione come ad esempio Stylus per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

Dovrai installare un'estensione per la gestione degli stili utente per installare questo stile.

(Ho già un gestore di stile utente, lasciamelo installare!)

// ==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      1.0.3
// ==/UserScript==

function logger(target) {
    for (const child of target.childNodes) {
        const iText = child.innerText;
        if (iText) {
            console.log(child);
            console.log(iText);
        }
        logger(child);
    }
}

function getTrimText(node) {
    return node.innerText.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;

    const 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')) {
        const 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 属性として追加
    // ランキングのところは別
    const length = nodes.length || nodes.snapshotLength;
    let getItem = nodes.item || nodes.snapshotItem;
    getItem = getItem.bind(nodes);

    for (let i = 0; i < length; i++) {
        const anchor = getItem(i);
        const 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 (const target of targetNodes) {
        for (const child of target.childNodes) {
            let objNode, title;
            if (child.nodeType == Node.TEXT_NODE) {
                // FANZA TV ページ上のタイトル
                objNode = target;
                title = child.nodeValue;
            } else {
                // DMM/FANZA TV のポップアップ
                objNode = child;
                title = child.innerText;
            }
            // console.log(objNode);
            if (!objNode.title) {
                objNode.title = title;
            }
        }
    }
}

function addTitleFromInnerText(targetNodes) {
    for (let target of targetNodes) {
        const title = getTrimText(target);
        if (title) target.title = getTrimText(target);
    }
}

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

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

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

            addTitleAttrAll(xpathEval('.//a[.//img and not(@title)]', target));
            if (target.id.match(/(ranking|s-tb-sect)/)) {
                addTitleAttr2Rankings(target);
            }

            // .line-clamp-1  DMM TV ポップアップのタイトル
            // .line-clamp-2  DMM TV ポップアップの概要
            // .eu14kkw0      FANZA TV ポップアップのタイトル
            // .eu14kkw2      FANZA TV ポッポアップの概要
            // .mt-2          DMM/FANZA TV ページ上のタイトル
            // 対象ノードが変わるので :not() はここでは使わない
            let dmmTV = target.querySelectorAll(
                '.line-clamp-1,.line-clamp-2,' +
                    '.eu14kkw0,.eu14kkw2,' +
                    '.mt-2'
            );
            if (dmmTV.length > 0) {
                addTitlesOnDMMTV(dmmTV);
            }
            // .e1vo3wos4     DMM/FANZA TV ランキングページのタイトル
            // .e18obyht4     DMM/FANZA TV マイアイテムページのタイトル
            dmmTV = document.body.querySelectorAll(
                'h1.e1vo3wos4:not([title]),.e18obyht4 h1:not([title])'
            );
            if (dmmTV.length > 0) {
                addTitleFromInnerText(dmmTV);
            }
        }
    });
    observer.observe(document.body, { childList: true, subtree: true });
})();