AnimeWorld Scrobbling

Segna automaticamente gli episodi visualizzati su Trakt.TV

当前为 2020-11-30 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         AnimeWorld Scrobbling
// @namespace    https://www.pizidavi.altervista.org/
// @description  Segna automaticamente gli episodi visualizzati su Trakt.TV
// @author       pizidavi
// @version      1.5.4
// @copyright    2020, PIZIDAVI
// @license      MIT
// @homepageURL  https://www.pizidavi.altervista.org/AnimeWorldScrobbling/
// @require      https://greasyfork.org/scripts/401626-notify-library/code/Notify%20Library.js
// @include      https://*.animeworld.*/play/*
//
// @connect      api.trakt.tv
// @connect      api.themoviedb.org
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_xmlhttpRequest
// @run-at       document-end
// ==/UserScript==

(function($) {
    'use strict';

    // USER INFO
    const CLIENT_ID = '';
    const ACCESS_TOKEN = '';
    const EXPIRES = '';

    // SETTINGS
    const SHOW_HELPER = true;
    const AUTO_NEXT_EPISODE = false;


    /* --------------------- */
    const CSS = '#body .sidebar { float: right; width: 300px; position: relative; z-index: 1; } #trakt-results .item .info a.name::after { font-family: "Font Awesome 5 Free"; font-size: 9px; font-weight: 900; content: "\\f35d"; margin-left: 5px; vertical-align: super; }';
    const TEMPLATE = '<div id="trakt" class="sidebar"><div class="widget simple-film-list"><div class="widget-title"><div class="title">Trakt.TV</div></div><div class="widget-body"><div class="row mb-3"><div class="col-sm-10" style="padding-right: 0;"><button class="btn btn-primary btn-block" id="watched">Episodio Guardato<span class="spinner-border spinner-border-sm ml-2" role="status" style="display:none;"></span></button></div><div class="col-sm-2"><input type="checkbox" id="autoNext" style="margin-top: 8px;" title="Prossimo episodio automatico"></div></div><div class="row"><div class="col-sm-6" style=" padding-right: 0;"><input type="text" class="form-control" placeholder="Trakt Slug"><small id="helper" style="display:none;margin:0.3em 0px -5px 0.5em;"><a href="https://www.pizidavi.altervista.org/AnimeWorldScrobbling/#trakt" target="_blank" style="color:grey;">Dove lo trovo?</a></small></div><div class="col-sm-2" style=" padding-right: 0;"><input type="number" class="form-control" value="1" min="0" placeholder="Season"></div><div class="col-sm-4"><button id="save-trakt" class="btn btn-success btn-block">Salva</button></div></div><div id="trakt-results" class="mt-3" style="display:none;"><hr class="my-3"/><h5 class="mb-1">Risultati di ricerca su Trakt.TV</h5></div></div></div></div>';
    const TEMPLATE_ITEM = '<div class="item" role="button" title="Click per selezionare questo risultato"><img src="#" class="thumb" style="opacity:0;"><div class="info"><a class="name" href="#" target="_blank"></a><p class="year mb-0"></p></div></div>';

    const AnimeID = getAnimeID();
    const Trakt = GM_getValue(AnimeID, {});
    var section = $(TEMPLATE);

    $('#body #body-container').append(section);
    var style = document.createElement('style');
        style.innerText = CSS;
    document.head.appendChild(style);

    if(!CLIENT_ID || !ACCESS_TOKEN || !EXPIRES) {
        section.find('div.widget-body').html('Dati Trakt mancanti. Segui la <a href="https://www.pizidavi.altervista.org/AnimeWorldScrobbling/" target="_blank">guida</a>');
        return; }
    if(new Date() >= new Date(EXPIRES)) {
        section.find('div.widget-body').html('Access-Token Trakt Scaduto. <a href="https://www.pizidavi.altervista.org/AnimeWorldScrobbling/#login" target="_blank">Aggiorna</a>');
        return; }
    if(AnimeID == undefined) {
        section.find('div.widget-body').html('Errore. AnimeID non trovato');
        return; }

    section.find('input[type="text"]').val(Trakt.slug);
    section.find('input[type="number"]').val((Trakt.season || '1'));
    if(AUTO_NEXT_EPISODE) {
        section.find('input[type="checkbox"]').attr('checked', ''); }
    if(SHOW_HELPER) {
        section.find('#helper').css('display', 'block'); }

    section.find('#watched').on('click', function() {
        var _this = $(this);
        var episode = $('div.server ul a.active').attr('data-base');
        var type = $('#main div.widget.info div.info > div.row > .meta:nth-child(1) dd:nth-child(2)').text().trim();
        type = (type == 'Movie' ? 'movies' : 'shows');

        if(Trakt.slug == undefined || Trakt.season == undefined || episode == undefined) {
            new Notify({
                text: 'Errore',
                type: 'error'
            }).show();
            return;
        }
        _this.attr('disabled', '');
        _this.find('span.spinner-border').show();

        var joData = {};
        joData[type] = [
            {
                'ids': {
                    'slug': Trakt.slug,
                },
                'seasons': [
                    {
                        'number': parseInt(Trakt.season),
                        'episodes': [
                            {
                                'watched_at': new Date().toJSON(),
                                'number': parseFloat(episode)
                            }
                        ]
                    }
                ]
            }
        ];
        request({
            method: 'POST',
            url: '/sync/history',
            data: joData,
            success: function(data) {
                if(data.added[(type == 'movies' ? 'movies' : 'episodes')] > 0) {
                    new Notify({
                        text: (type == 'movies' ? 'Film' : 'Episodio')+' '+episode+' salvato',
                        type: 'success'
                    }).show();

                    if(AUTO_NEXT_EPISODE || section.find('#autoNext').prop('checked')) {
                        $('#controls > div.prevnext[data-value="next"]').click(); }
                }
                else {
                    new Notify({
                        text: 'Errore. '+(type == 'movies' ? 'Film' : 'Episodio')+' non trovato',
                        type: 'error'
                    }).show();
                }
                _this.removeAttr('disabled');
                _this.find('span.spinner-border').hide();
            }
        });
    });

    section.find('#save-trakt').on('click', function() {
        var title = $('#main div.widget.info div.info > div.head h2').text();
        var slug = $(this).parent().parent().find('input[type="text"]').val();
        var season = $(this).parent().parent().find('input[type="number"]').val();

        if(slug != '' && season != '') {
            Trakt.title = title.trim();
            Trakt.slug = slug.trim();
            Trakt.season = season;
            GM_setValue(AnimeID, Trakt);

            section.find('button#watched').removeAttr('disabled');

            new Notify({
                text: 'Dati salvati',
                type: 'success',
                timeout: 3000
            }).show();
        } else {
            new Notify({
                text: 'Completa tutti i campi',
                type: 'warn'
            }).show();
        }
    });

    $(document).on('click', 'div.userbookmark li:not(:nth-child(1)):not(.divider)', function() {
        var deleted = deleteOne(AnimeID);
        if(deleted) {
            section.find('input[type="text"]').val('');
            section.find('input[type="number"]').val('1');
            section.find('button#watched').attr('disabled', '');

            Trakt.title = null;
            Trakt.slug = null;
            Trakt.season = null;

            new Notify({
                text: 'Dati Trakt rimossi',
                type: 'success',
                timeout: 3000
            }).show();
        }
    });

    if(Trakt.slug == undefined || Trakt.season == undefined) {
        section.find('#watched').attr('disabled', '');

        var type = $('#main div.widget.info div.info > div.row > .meta:nth-child(1) dd:nth-child(2)').text().trim();
        if(type == 'Special' || type == 'OVA') {
            return; }
        type = (type == 'Movie' ? 'movie' : 'show');

        var title = $('#main div.widget.info div.info > div.head h2').text();
        var season = parseInt(title.split(' ')[title.split(' ').length - 1]);

        title = title.replace('(ITA)', '').replace('(TV)', '');
        title = title.replace( (!isNaN(season) ? season : ''), '');
        title = title.trim();
        season = (isNaN(season) ? 1 : season);

        request({
            method: 'GET',
            url: '/search/'+type+'?query='+encodeURI(title),
            success: function(data) {
                if(!data.length) { return; }

                section.find('#trakt-results').show();
                $.each(data, function(index) {
                    if(index >= 3) { return; }

                    var item = $(TEMPLATE_ITEM);
                    item.attr('data-slug', this[this.type].ids.slug);

                    item.find('.info .name').text( (this[this.type].title.length > 45 ? this[this.type].title.substring(0, 45)+'...' : this[this.type].title));
                    item.find('.info .name').attr('href', 'https://trakt.tv/'+this.type+'s/'+this[this.type].ids.slug);
                    item.find('.info .name').attr('title', this[this.type].title);
                    item.find('.info .year').text(this[this.type].year);

                    item.on('click', function(e) {
                        if(e.target.tagName == 'A') { return; }
                        var slug = $(this).attr('data-slug');
                        section.find('input[type="text"]').val(slug);
                        section.find('input[type="number"]').val(season);
                        section.find('#save-trakt').click();
                    });
                    section.find('#trakt-results').append(item);

                    if(this[this.type].ids.tmdb != null) {//
                        item.attr('data-tmdb-id', this[this.type].ids.tmdb);

                        GM_xmlhttpRequest({
                            method: 'GET',
                            url: 'https://api.themoviedb.org/3/'+ (this.type == 'show' ? 'tv' : 'movie') +'/'+ this[this.type].ids.tmdb +'/images?api_key=52a23d06812ad987218e2e41ec6eb79c',
                            onload: function() {
                                if (this.readyState === 4 && (this.status === 200 || this.status === 201)) {
                                    var data = JSON.parse(this.responseText);
                                    if(data.posters.length > 0) {
                                        var image = section.find('#trakt-results').find('[data-tmdb-id="'+data.id+'"] > img');
                                        image.attr('src', 'https://image.tmdb.org/t/p/w92'+data.posters[0].file_path).css('opacity', '1'); }
                                }
                            }
                        });
                    }
                });
            }
        });
    }


    // Functions
    function request(options) {
        options.url = 'https://api.trakt.tv'+options.url;
        options.headers = {
            'Content-Type': 'application/json',
            'Authorization': 'Bearer '+ACCESS_TOKEN,
            'trakt-api-version': '2',
            'trakt-api-key': CLIENT_ID
        };
        options.data = JSON.stringify(options.data);
        options.onload = function() {
            if (this.readyState === 4 && (this.status === 200 || this.status === 201)) {
                options.success(JSON.parse(this.responseText));
            }
            else if (this.readyState === 4 && this.status === 403) {
                new Notify({
                    text: 'Errore. API Key invalida',
                    type: 'error',
                    timeout: false
                }).show();
            }
            else if (this.readyState === 4 && this.status === 404) {
                new Notify({
                    text: 'Errore. Elemento non trovato',
                    type: 'error',
                    timeout: false
                }).show();
            }
            else if (this.readyState === 4 && (this.status >= 500 && this.status <= 522)) {
                new Notify({
                    text: 'Errore. Service Unavailable',
                    type: 'error',
                    timeout: false
                }).show();
            }
            else if (this.readyState === 4) {
                new Notify({
                    text: 'Errore nella richiesta. Ricarica la pagina',
                    type: 'error',
                    timeout: false
                }).show();
            }
        }

        GM_xmlhttpRequest(options);
    }

    function getAnimeID() {
        var url = location.pathname;
        var start = url.indexOf('.')+1;
        var end = start + (url.substring(start).indexOf('/') >= 0 ? url.substring(start).indexOf('/') : url.substring(start).length);
        return url.substring(start, end) || undefined;
    }

    function deleteOne(key) {
        if(GM_listValues().includes(key)) {
            GM_deleteValue(key);
            return true;
        } else {
            return false;
        }
    }
    function deleteAll() {
        GM_listValues().forEach(function(key) {
            GM_deleteValue(key);
        });
        return true;
    }

})(jQuery);