AnimeWorld Scrobbling

Segna automaticamente gli episodi visualizzati su Trakt.TV

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

// ==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);