Sleazy Fork is available in English.

popupTitleOverAnchorsOnDMM

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

目前為 2019-04-27 提交的版本,檢視 最新版本

/* 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();
    }

})();