popupTitleOverAnchorsOnDMM

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

Versão de: 06/04/2019. Veja: a última versão.

Você precisará instalar uma extensão como Tampermonkey, Greasemonkey ou Violentmonkey para instalar este script.

You will need to install an extension such as Tampermonkey to install this script.

Você precisará instalar uma extensão como Tampermonkey ou Violentmonkey para instalar este script.

Você precisará instalar uma extensão como Tampermonkey ou Userscripts para instalar este script.

Você precisará instalar uma extensão como o Tampermonkey para instalar este script.

Você precisará instalar um gerenciador de scripts de usuário para instalar este script.

(Eu já tenho um gerenciador de scripts de usuário, me deixe instalá-lo!)

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar uma extensão como o Stylus para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

Você precisará instalar um gerenciador de estilos de usuário para instalar este estilo.

(Eu já possuo um gerenciador de estilos de usuário, me deixar fazer a instalação!)

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

})();