Sleazy Fork is available in English.

popupTitleOverAnchorsOnDMM

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

Verzia zo dňa 05.04.2019. Pozri najnovšiu verziu.

/* jshint esversion: 6 */
// ==UserScript==
// @name         popupTitleOverAnchorsOnDMM
// @namespace    https://greasyfork.org/ja/users/289387-unagionunagi
// @version      0.1
// @description  DMM・FANZAで商品(など)のリンクに(だいたいの)フルタイトルをポップアップ
// @author       unagiOnUnagi
// @match        *://*.dmm.co.jp/*
// @match        *://*.dmm.com/*
// @grant        none
// @license      GPL-2.0-or-later
// ==/UserScript==

function addTitleAttrAll(nodes) {
    // a タグに子要素の img の alt から取ってきたタイトルを title 属性として追加
    // alt がなかったら a タグの onclick を見てみる
    // onclick もダメだったら a タグ内のタイトル文字列をそのまま
    // 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) {
                txt = anchor.textContent.trim();
                if (txt) title = txt;
            }
        }
        if (title) {
            anchor.setAttribute('title', title);
//             console.log(title);
//             console.log(anchor.href);
        }
    }
}

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 addTitleAttr2Rankings() {
    // ランキング内商品 anchorに title= 追加
    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 rank = ranking.snapshotItem(i);
        let alt = rank.getElementsByTagName('img')[0].getAttribute('alt');
        if (!alt) {
            alt = rank.textContent.trim();
        } else if (alt == 't') {
            alt = rank.getElementsByTagName('a')[1].textContent.trim();
        }
        if (alt) {
            for (let a of rank.getElementsByTagName('a')) {
                a.setAttribute('title', alt);
            }
        }
    }
}

function addEvtListener2Carousels() {
    // <,>ボタン onclick で商品 anchor に title= を追加するイベントリスナーを追加
    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) {
            button.addEventListener('click', function() {
                const target = () => button.parentElement.querySelectorAll(cs[1]);
                waitActualLoading(target, addTitleAttrAll, target);
            }, 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= を追加するイベントリスナーを追加
    // resize など他イベントには未対応
    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>div.clist  a:not([title])');
            waitActualLoading(
                targets,
                t => {
                    addTitleAttrAll(t);
                    addEvtListener2Carousels();
                },
                targets);
        }, false);
    }

}


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

})();