popupTitleOverAnchorsOnDMM

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

Version au 06/04/2019. Voir la dernière version.

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

/* jshint esversion: 6 */
// ==UserScript==
// @name         popupTitleOverAnchorsOnDMM
// @namespace    https://greasyfork.org/ja/users/289387-unagionunagi
// @version      0.3
// @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) {
    // 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');

        if (!title) {
            let onclick = anchor.getAttribute('onclick');
            if (onclick && onclick.startsWith('_gaq.push') && !onclick.endsWith("'');")) {
                title = onclick.split("'")[13];
            }
        }
        if (!title) {
            let txt = null;
            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) {
                txt = getTrimText(anchor);
                if (txt) title = txt;
            }
        }
        if (title) {
            anchor.setAttribute('title', title);
            //             console.log(title);
            //             console.log(anchor.href);
        }
    }
}

function addTitleAttr2Rankings() {
    // ランキング内商品 anchorに title= 追加
    // DMM DVD通販 IdolTop: altがt、タイトルがa直下 (非固定spanのtail)
    // FANZA 動画 ビデオtop ranking[1:]: altなし、span(rank) a直下 title br latest
    let ranking = document.evaluate(
        './/div[@id="side-rank-tab"]//li[a[img[not(@title)]]]',
        document.body, 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);
            }
        }
    }
}

const WAIT_INTERVAL = 200;
const MAX_RETRY_LIMIT = 15;

function waitActualLoading(target, func, arg=null) {
    let waitId = null;
    let c = 1;
    const waiter = function() {
        let result = target();
        if (result.length || c > MAX_RETRY_LIMIT) {
            clearInterval(waitId);
            func(arg());
        }
        c++
    }
    waitId = setInterval(waiter, WAIT_INTERVAL);
}

function addEvtListener2Carousels(csssels=[['div.prev,div.next', 'div.clist a:not([title])'],
                                           ['div.d-prev,div.d-next', 'ul a:not([title])']]) {
    // <,>ボタン onclick で商品 anchor に title= を追加するイベントリスナーを追加
    csssels.forEach(function(cs) {
        document.querySelectorAll(cs[0]).forEach(function(button) {
            button.addEventListener('click', function() {
                const target = () => button.parentElement.querySelectorAll(cs[1]);
                waitActualLoading(target, addTitleAttrAll, target);
            }, false);
        });
    });
}

function addEvtListener2UpdateRecommends() {
    // resize イベント後にレコメンド内を更新するイベントリスナーを追加
    let timer = 0;
    window.addEventListener('resize', function() {
        if (timer) {
            clearTimeout(timer);
        }

        timer = setTimeout(function() {
            let csssel = [['div.prev,div.next', 'div.clist a:not([title])'],
                          ['div.d-prev,div.d-next', 'ul a:not([title])']];
            csssel.forEach(function(cs) {
                document.querySelectorAll(cs[0]).forEach(function(button) {
                    const target = () => button.parentElement.querySelectorAll(cs[1]);
                    waitActualLoading(target, addTitleAttrAll, target);
                });
            });
        }, 200);
    }, false);
}

function addTitleAttr2Anchors() {

    // とりあえず画像つきリンクに一律処理
    addTitleAttrAll(
        document.evaluate('.//a[.//img]',
                          document.body, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null));

    // カテゴリトップページランキング
    addTitleAttr2Rankings();

    // ランキングの日間・週間・月間を onclick で商品 anchor に title= を追加する
    // イベントリスナーを追加
    // DMM側で伝播を抑止しているので同じ a タグに追加
    // こっちでも抑止するとページによっては副作用が起こる
    // DMM側で伝播を抑止しているのでこちらのイベントもしない模様?
    document.querySelectorAll('.s-tb-capt a:not([title])').forEach(function(tab) {
        tab.addEventListener('click', function() {
            setTimeout(addTitleAttr2Rankings, 300)
        }, false);
    });

    // <,>ボタン onclick で商品 anchor に title= を追加するイベントリスナーを追加
    addEvtListener2Carousels();

    // 最近チェックした商品を表示するリンクあたりに onclick で商品 anchor に
    // title= を追加するイベントリスナーを追加
    let recent = document.getElementById('recent_check_list');
    if (recent) {
        recent.addEventListener('click', function(e) {
            e.stopPropagation();
            if (recent.getElementsByClassName('d-modtogglelink-open').length) {
                waitActualLoading(
                    () => recent.getElementsByClassName('d-modtogglelink-close'),
                    addTitleAttrAll,
                    () => document.querySelectorAll('div#list-recommend01 a:not([title])'));
            }
        }, false);
    }

    // この作品に出演している〜リンクあたりに onclick で
    let others = document.getElementById('actorother_main');
    if (others) {
        others.addEventListener('click', function(e) {
            e.stopPropagation();
            const targets = () => document.querySelectorAll('#list-recommend07>.clist a:not([title])');
            waitActualLoading(
                targets,
                t => {
                    addTitleAttrAll(t);
                    addEvtListener2Carousels([['#list-recommend07>.prev,#list-recommend07>.next',
                                               'div.clist a:not([title])']]);
                },
                targets);
        }, false);
    }

    addEvtListener2UpdateRecommends();

}


(function() {

    const hasElementsAnyId = ids => ids.some(i => document.getElementById(i));

    let waitAppear = [];
    let waitDisappear = [];

    if (hasElementsAnyId(['browserecommend', 'browserecommend1'])) {
        waitAppear = ['#list-history-min a img[alt]',];
    } else if (hasElementsAnyId(['recommend',])) {
        waitAppear = ['#recommend_main li a img[alt]',
                      '#list-recommend07>div.clist li a img[alt]',
                      '.d-modtogglelink-close,.d-modtogglelink-open'];
    }

    if (document.getElementsByClassName('loading').length) waitDisappear = ['li.loading',];

    if (waitAppear.length || waitDisappear) {

        const isExists = targets => document.querySelectorAll(targets).length;

        let waitId = null;
        let c = 1;
        const waiter = function() {
            let isAppeared = waitAppear ? waitAppear.every(t => isExists(t)) : true;
            let isDisappeared = waitDisappear ? waitDisappear.every(t => !isExists(t)) : true;
            if ((isAppeared && isDisappeared) || c > MAX_RETRY_LIMIT) {
                clearInterval(waitId);
                setTimeout(addTitleAttr2Anchors, 300);
            }
            c++
        }
        waitId = setInterval(waiter, WAIT_INTERVAL);

    } else {
        addTitleAttr2Anchors();
    }

})();