popupTitleOverAnchorsOnDMM

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

2019-04-06 يوللانغان نەشرى. ئەڭ يېڭى نەشرىنى كۆرۈش.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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

})();