popupTitleOverAnchorsOnDMM

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

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

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

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

})();