AnimeWorld Scrobbling

Segna automaticamente gli episodi visualizzati su Trakt.TV

目前為 2020-11-30 提交的版本,檢視 最新版本

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

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

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

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

你需要先安裝一款使用者腳本管理器擴展,比如 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);