Greasy Fork is available in English.

Netflix影片显示豆瓣评分

根据影片标题or原英文标题获取豆瓣评分 获取豆瓣评分(未使用豆瓣API)

// ==UserScript==
// @name        Netflix影片显示豆瓣评分
// @namespace   http://tampermonkey.net/
// @include     https://netflix.com/*
// @include     https://www.netflix.com/*
// @connect     douban.com
// @connect     zeptojs.com
// @version     1.0.5
// @description 根据影片标题or原英文标题获取豆瓣评分 获取豆瓣评分(未使用豆瓣API)
// @author      naturezhm
// @grant       GM_xmlhttpRequest
// @grant       GM_addStyle
// @require     https://cdnjs.cloudflare.com/ajax/libs/zepto/1.2.0/zepto.min.js
// ==/UserScript==

(function () {

    var logger = {
        debug: console.debug,
        log: console.log,
        info: console.info,
        warn: console.warn,
        error: console.error
    }

    function setStorage(key, value) {
        let storage = window.localStorage;
        storage.setItem(JSON.stringify(key), JSON.stringify(value));
    }

    function getStorage(key) {
        let storage = window.localStorage;
        let rawDate = storage.getItem(JSON.stringify(key));
        return JSON.parse(rawDate);
    }

    function addCss() {
        GM_addStyle(".douban{display:block;background-color: rgba(0,0,0,0.5)} .is-bob-open .douban { display: block; opacity: 0.3;}");
    }

    function getMovieList(title, callback) {
        logger.debug("title: %s", title);
        if (null == title || undefined == title || null == callback || undefined == callback) {
            return null;
        }

        let subjectId = getStorage(title);
        let doubanInfo = getStorage(subjectId);

        if (null != doubanInfo && undefined != doubanInfo) {
            callback(doubanInfo);
            return;
        }

        GM_xmlhttpRequest({
            method: "GET",
            // url: 'https://movie.douban.com/j/subject_suggest?q=' + title,
            url: 'https://m.douban.com/search/?query=' + title,
            "headers": {
                "user-agent": 'Mozilla/5.0 (iPhone; CPU iPhone OS 13_2_3 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/13.0.3 Mobile/15E148 Safari/604.1'
            },
            onload: function (response) {
                // logger.debug("response, %o", response);
                if (response.status == 200) {
                    let subjectId = findElement(response.responseText, regex_search);
                    logger.debug("subjectId: %s", subjectId);
                    if (null == subjectId || undefined == subjectId || 0 == subjectId) {
                        callback(null, title);
                    }

                    let doubanInfo = getStorage(subjectId);
                    if (null == doubanInfo || undefined == doubanInfo) {
                        getMovieInfo(title, subjectId, callback);
                    } else {
                        callback(doubanInfo);
                    }
                } else {
                    logger.warn(response.statusText);
                    callback(null, title);
                }
            }
        });
    }

    function getMovieInfo(title, subjectId, callback) {
        if (null == callback || undefined == callback) {
            return;
        }
        let url = "https://movie.douban.com/subject/" + subjectId;

        logger.debug("url, %o", url);
        GM_xmlhttpRequest({
            method: "GET",
            url: url,
            onload: function (response) {
                if (response.status == 200) {
                    let responseText = response.responseText;

                    let doubanRate = findElement(responseText, regex_rate);
                    let doubanVotes = findElement(responseText, regex_votes);

                    logger.debug("doubanRate: %s, doubanVotes: %s, url: %s", doubanRate, doubanVotes, url);

                    let doubenInfo = { "subjectId": subjectId, "doubanRate": doubanRate, "doubanVotes": doubanVotes, "doubanUrl": url };
                    setStorage(subjectId, doubenInfo);
                    setStorage(title, subjectId);
                    callback(doubenInfo);
                } else {
                    logger.warn(response.statusText);
                    callback(null, title);
                }
            }
        });
    }

    function findElement(content, matchRegex) {
        let matchResult;
        let result = 0;

        let times = 0;
        while ((matchResult = matchRegex.exec(content)) !== null && times < 10) {
            // This is necessary to avoid infinite loops with zero-width matches
            if (matchResult.index === matchRegex.lastIndex) {
                matchRegex.lastIndex++;
            }

            if ($.isArray(matchResult)) {
                matchResult.forEach((match, groupIndex) => {
                    // logger.log(`Found match, group ${groupIndex}: ${match}`);
                    // only get first match element. (follow Douban page rules.)
                    result = match;
                    return;
                });
            } else {
                result = matchResult;
            }
            times++;
        }
        return result;
    }

    function addDoubanElementInBox(element, doubanInfo, title) {
        try {
            if (null == element || undefined == element) {
                return;
            }
            if (hasDoubanTag(element)) {
                return;
            }
            if (null == doubanInfo || undefined == doubanInfo) {
                let errorMovieTimes = errorMovieMap[title];
                if (errorMovieTimes > retryLimit) {
                    $(element).append('<div class="douban"></div>');
                    return;
                }
                if (null == errorMovieTimes || undefined == errorMovieTimes) {
                    errorMovieTimes = 1;
                    errorMovieMap[title] = errorMovieTimes;
                } else {
                    errorMovieTimes++;
                    errorMovieMap[title] = errorMovieTimes;
                }
                return;
            }

            let html = '<div class="douban" style="position: absolute;top: 0;right: 0;z-index: 999;"><div class="douban-tag" style="display: inline-flex;margin: 5px;"><a href="'
                + doubanInfo.doubanUrl
                + '" target="_blank"><img src="https://img3.doubanio.com/favicon.ico" style="width: 16px;"></a><span style="margin: 0 2px;">'
                + (doubanInfo.doubanRate > 0 ? doubanInfo.doubanRate : 0)
                + '</span><span>|</span><span style="margin: 0 2px;">'
                + (doubanInfo.doubanVotes > 0 ? doubanInfo.doubanVotes : 0)
                + '</span></div></div>';
            $(element).append(html);
            return;
        }
        catch (err) {
            logger.error(err);
        }

    }

    function hasDoubanTag(element) {
        return $(element).find(".douban").length > 0;
    }

    function trigerFetchDouban() {
        if (allMovieBoxlist.length <= 0) {
            return;
        }

        if (null != timer) {
            clearTimeout(timer);
        }
        timer = setTimeout(() => {
            let item = allMovieBoxlist.shift();
            let innerTextWrapper = $(item).find(".fallback-text-container .fallback-text");

            let movieTitle = null == innerTextWrapper ? null : innerTextWrapper[0].innerText;

            logger.debug("movieTitle: %o", movieTitle);
            // get rate vote and url from douban 豆瓣
            getMovieList(movieTitle, (doubanInfo, title) => {
                // callback
                logger.debug("doubanInfo: %o", doubanInfo);
                // add new element in movie box
                addDoubanElementInBox(item, doubanInfo, title);

                trigerFetchDouban();
            });
        }, 50);
    }

    function mainFunc() {
        setInterval(() => {

            // execute every 3 second
            let movieBoxes = $(".title-card-container .title-card");
            // logger.debug(movieBoxtitles.innerText);

            let times = 0;

            movieBoxes.each(function (index) {
                if (times > 500) {
                    return;
                }

                // logger.debug('index: %d this: %o', index, this);
                logger.debug("current box length: %s", allMovieBoxlist.length);

                let item = this;
                if (hasDoubanTag(item) || allMovieBoxlist.indexOf(item) >= 0) {
                    return;
                }
                allMovieBoxlist.push(item);
                times++;
            });

            trigerFetchDouban();

        }, 3000);
    }
    // match element like
    // <strong class="ll rating_num" property="v:average">2.2</strong>
    var regex_rate = /(?<=v:average.>)[^<]*/m;
    var regex_votes = /(?<=v:votes.>)[^<]*/m;
    var regex_search = /(?<=\/movie\/subject\/)[^\/]*/m;

    var allMovieBoxlist = [];
    var timer = null;

    var retryLimit = 3;
    var errorMovieMap = {};

    logger.info('Hello douban rate for Netflix.');

    // getMovieList("漫威杰西卡·琼斯");

    mainFunc();

    addCss();

})();