MediaFetchAPI

Media Database API

Dit script moet niet direct worden geïnstalleerd - het is een bibliotheek voor andere scripts om op te nemen met de meta-richtlijn // @require https://update.greasyfork.org/scripts/387700/718767/MediaFetchAPI.js

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

// ==UserScript==
// @name         MediaFetchAPI
// @namespace    https://greasyfork.org/users/152136
// @version      0.1
// @description  Media Database API
// @author       TYT
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js
// @require      https://unpkg.com/number-to-chinese-words@^1.0/number-to-chinese-words.min.js
// @grant        GM_xmlhttpRequest
// ==/UserScript==
/* globals $ */
(function() {
  'use strict';
  class MediaFetchAPI {
    constructor(){
      console.log(window);
      this.converter = window.index.NumberToChineseWords;
      this.converter.labels = Object.assign(this.converter.labels, {
        digits : ['零','一', '二', '三', '四', '五', '六', '七', '八', '九'],
        units: ['','十', '百', '千', '万', '十', '百', '千', '亿', '十', '百', '千', '兆', '十', '百', '千', '京', '十', '百', '千', '垓']
      });
      this.doubanAPIKeyB64 = 'MGIyYmRlZGE0M2I1Njg4OTIxODM5YzhlY2IyMDM5OWI=';
      this.timeOut = 1e4;
    }
    errorStatus(reject){
      return {
        ontimeout: ()=>{
          reject({
            status: 440,
            statusText: 'Time Out'
          })
        },
        onerror: ()=>{
          reject({
            status: 441,
            statusText: 'Request Error'
          })
        },
        onabort: ()=>{
          reject({
            status: 442,
            statusText: 'Abort'
          })
        }
      }
    };
    url2Blob(url){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: url,
          timeout: 10000,
          responseType: 'blob',
          onload: (res)=>{
            resolve(res.response);
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    blob2DataURL(blob){
      return new Promise((resolve, reject)=>{
        let fr = new FileReader();
        fr.onload = (e)=>{
          resolve(e.target.result);
        }
        Object.assign(fr, this.errorStatus(reject));
        fr.readAsDataURL(blob);
      });
    }
    url2DataURL(url){
      return this.url2Blob(url)
        .then(this.blob2DataURL);
    }
    //Douban
    doubanSuggest(content){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: `https://movie.douban.com/j/subject_suggest?q=${encodeURIComponent(content)}`,
          timeout: this.timeOut,
          responseType: 'json',
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              resolve(res.response)
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          },
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    doubanDetail(id, api_key = atob(this.doubanAPIKeyB64)){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: `https://api.douban.com/v2/movie/${id}?apikey=${api_key}`,
          timeout: this.timeOut,
          responseType: 'json',
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              resolve(res.response)
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    doubanPage(id){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: `https://movie.douban.com/subject/${id}`,
          timeout: 10000,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              resolve(res.response)
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    doubanCard(id, type = 'png', options = {}){
      const type_mapper = {
        png: 'toPng',
        blob: 'toBlob',
        jpg: 'toJpeg',
        svg: 'toSvg',
        pixel: 'toPixelData'
      };
      if(typeof type_mapper[type] === 'undefined'){
        return new Promise((resolve, reject)=>{
          reject({status: 443, statusText: 'Wrong Type'});
        });
      }
      let iframe = $('<iframe/>');
      iframe.css({
        position: 'absolute',
        width: 0,
        height: 0,
        top: -1e4,
        left: -1e4
      });
      iframe.appendTo($('body'));
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: `https://movie.douban.com/subject/${id}/output_card`,
          timeout: this.timeOut,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              function resolveMessage(message){
                if(message.data.status === 444){
                  reject(message.data);
                }
                else{
                  resolve(message.data);
                }
                window.removeEventListener('message', resolveMessage);
                iframe.remove();
              }
              window.addEventListener('message', resolveMessage);
              let snippet = `domtoimage.${type_mapper[type]}(document.getElementById("wrapper"),${JSON.stringify(options)}).then(t=>{window.top.postMessage(t,"*")}).catch(t=>{void 0===t||void 0===t.status||void 0===t.statusText?window.top.postMessage({status:444,statusText:"DOM to Image Error"},"*"):window.top.postMessage(t,"*")});`;
              let response = res.response.replace(/<script[^>]+output\-card\.js"><\/script>/, `<script>${snippet}</script>`);
              let bg_reg = /<div class="picture\-wrapper" style="background\-image\: url\(([^\)]+)\)">/;
              let target = bg_reg.exec(response);
              this.url2DataURL(target[1]).then((durl)=>{
                response = `${response.slice(0, target.index)}<div class="picture-wrapper" style="background-image: url(${durl})">${response.slice(target.index + target[0].length)}`;
                let idoc = iframe[0].contentWindow.document;
                idoc.write(response);
              }).catch((err)=>{
                reject(err);
              });
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    doubanAwards(id){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: `https://movie.douban.com/subject/${id}/awards`,
          timeout: this.timeOut,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              const vd = document.implementation.createHTMLDocument('virtual');
              const article = $(res.response, vd).find('#content .article');
              let awards = [];
              for (let _awards of article.children('.awards')){
                let award = {};
                let temp = $(_awards).children('.hd');
                award.name = temp.find('a').text().trim();
                award.link = temp.find('a')[0].href;
                award.year = temp.find('.year').text().slice(2, -1);
                award.cats = [];
                for (let _award of $(_awards).children('.award')){
                  let item = {};
                  let temp = $(_award).children();
                  [item.category, item.recipients] = [temp[0].innerText, temp[1].innerText.split(' / ')];
                  if(item.recipients[0] === ''){
                    item.recipients = [];
                  }
                  award.cats.push(item);
                }
                awards.push(award);
              }
              resolve(awards);
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    doubanCrew(id){
      function decouple(mixed){
        let match = mixed.match(/(^.*\p{Unified_Ideograph}[^ ]*) ([^\p{Unified_Ideograph}]+)$/u);
        if(match){
          return [match[1], match[2]];
        }
        else{
          return [mixed];
        }
      }
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: `https://movie.douban.com/subject/${id}/celebrities`,
          timeout: this.timeOut,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              const vd = document.implementation.createHTMLDocument('virtual');
              const celebrities = $(res.response, vd).find('#celebrities');
              let crew = [];
              for (let _list of $(celebrities).children('.list-wrapper')){
                let list = {
                  profession: {}
                };
                [list.profession.pre, list.profession.alt = list.profession.pre] = $(_list).children('h2').text().split(' ');
                list.people = [];
                for (let _person of $(_list).find('.celebrities-list>.celebrity')){
                  let person = {
                    name: {},
                    title: {}
                  };
                  [person.name.pre,
                   person.name.alt = person.name.pre] = decouple(
                    $(_person).find('.info>.name>a.name').text()
                  );
                  [person.title.pre,
                   person.title.alt = person.title.pre] = decouple(
                    $(_person).find('.info>.role').text().replace(/ \([^\)]+\)$/, '')
                  ).map((e)=>e.split('/').map((e)=>e.trim()));
                  if(list.profession.pre === '演员'){
                    person.role = {
                      pre: [],
                      alt: []
                    };
                    let temp = $(_person).find('.info>.role').text().match(/ \([饰配] ([^\)]+)\)$/);
                    if(temp){
                      temp[1].split(' / ').forEach((e)=>{
                        let temp_1, temp_2;
                        [temp_1, temp_2 = temp_1] = decouple(e);
                        person.role.pre.push(temp_1);
                        person.role.alt.push(temp_2);
                      });
                    }
                  }
                  person.link = $(_person).find('.info>.name>a.name')[0].href;
                  list.people.push(person);
                }
                crew.push(list);
              }
              resolve(crew);
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    doubanDetailPlus(id, api_key = atob(this.doubanAPIKeyB64)){
      return Promise.all([this.doubanDetail(id, api_key), this.doubanPage(id)])
        .then(
        ([json, doc]) => {
          const vd = document.implementation.createHTMLDocument('virtual');
          const vdoc = $(doc, vd);
          json.alt_title = [vdoc.filter('title').text().replace(/\(豆瓣\)\s?$/, '').trim()];
          json.title = vdoc.find('h1>span[property="v:itemreviewed"]').text().replace(json.alt_title[0], '').trim() || json.alt_title[0];
          let temp;
          if((temp = vdoc.find('#info>span.pl:contains("又名:")')[0]) !== undefined){
            json.alt_title = json.alt_title.concat(temp.nextSibling.nodeValue.trim().split(' / '))
          }
          json.imdb_id = null;
          return Promise.resolve(doubanRealIMDb(doc)).then(
            id=>{
              json.imdb_id = id;
              return json;
            }
          )
        }
      )
      function doubanRealIMDb(res){
        let e = douban1stSeason(res);
        if(typeof e !== 'undefined'){
          return this.doubanPage(e).then((e)=>doubanIMDb(e));
        }
        return doubanIMDb(res);
        function douban1stSeason(res){
          const vd = document.implementation.createHTMLDocument('virtual');
          const list = $(res, vd).find('#season');
          if(typeof list === 'undefined' || list.find(':selected').text() === '1'){
            return undefined
          }
          return list.children(':first').val();
        }
        function doubanIMDb(res){
          const vd = document.implementation.createHTMLDocument('virtual');
          const link = $(res, vd).find('#info>a[href*="imdb"]')[0];
          let id;
          if(typeof link === 'undefined' || typeof link.href === 'undefined' || typeof (id = link.href.match(/\d+$/)) === 'undefined'){
            return undefined;
          }
          return id[0];
        }
      }
    }
    //IMDb
    imdbRating(id){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: `https://p.media-imdb.com/static-content/documents/v1/title/tt${id}/ratings%3Fjsonp=imdb.rating.run:imdb.api.title.ratings/data.json`,
          timeout: this.timeOut,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              resolve(JSON.parse(res.response.slice(16, -1)))
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    imdbSuggest(content){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: `https://v2.sg.media-imdb.com/suggestion/${content[0].toLowerCase()}/${encodeURIComponent(content).replace(/%20/g, '+')}.json`,
          timeout: this.timeOut,
          responseType: 'json',
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              resolve(res.response)
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    //MTime
    mtimeStatistic(id){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: 'http://service.library.mtime.com/Movie.api'
          + '?Ajax_CallBack=true'
          + '&Ajax_CallBackType=Mtime.Library.Services'
          + '&Ajax_CallBackMethod=GetMovieOverviewRating'
          + `&Ajax_CallBackArgument0=${id}`,
          timeout: this.timeOut,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              let m = res.response.match(/^[^{]+({.*});\s*$/);
              let val;
              if(m && (val = JSON.parse(m[1]).value)){
                resolve(val);
              }
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    mtimeRating(id_array){
      if((typeof id_array === 'string' && id_array.match(/^\d+$/)) || Number.isInteger(id_array)){
        id_array = [id_array];
      }
      else if(!(id_array instanceof Array)){
        return Promise.reject({
          status: 446,
          statusText: 'Mtime Rating Argument Wrong Format'
        })
      }
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: 'http://service.mtime.com/Service/Movie.msi'
          + '?Ajax_CallBack=true'
          + '&Ajax_CallBackType=Mtime.MemberCenter.Pages.MovieService'
          + '&Ajax_CallBackMethod=GetRatingsByMovieIds'
          + `&Ajax_CallBackArgument0=${id_array.join('|')}`,
          timeout: this.timeOut,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              let m = res.response.match(/^[^{]+({.*});\s*$/);
              let val;
              if(m && (val = JSON.parse(m[1]).value)){
                resolve(val);
              }
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    mtimeQuery(content, count = 20){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: 'http://my.mtime.com/Service/Movie.mc'
          + '?Ajax_CallBack=true'
          + '&Ajax_CallBackType=Mtime.MemberCenter.Pages.MovieService'
          + '&Ajax_CallBackMethod=GetSearchMoviesByTitle'
          + `&Ajax_CallBackArgument0=${encodeURIComponent(content)}`
          + `&Ajax_CallBackArgument1=${count}`,
          timeout: 10000,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              let val;
              if((val = JSON.parse(res.response).value)){
                resolve(val);
              }
              resolve(JSON.parse(res.response));
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    mtimeSearch(content){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: 'http://service-channel.mtime.com/Search.api'
          + '?Ajax_CallBack=true'
          + '&Ajax_CallBackType=Mtime.Channel.Services'
          + '&Ajax_CallBackMethod=GetSearchResult'
          + `&Ajax_CallBackArgument0=${encodeURIComponent(content)}`
          + '&Ajax_CallBackArgument2=1'
          + '&Ajax_CallBackArgument4=10000',
          timeout: this.timeOut,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              let m = res.response.match(/^[^{]+({.*});\s*$/);
              let val;
              if(m && (val = JSON.parse(m[1]).value)){
                resolve(val.movieResult);
              }
              else{
                reject({
                  status: 445,
                  statusText: 'Mtime Error',
                });
              }
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    mtimeBehindTheScene(id){
      //revealed_list
      //revealed_lines
      //revealed_news
      //revealed_other
      //revealed_album
      let result = [];
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: `http://movie.mtime.com/${id}/behind_the_scene.html`,
          timeout: 10000,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              const vd = document.implementation.createHTMLDocument('virtual');
              const bts = $(res.response, vd).find('.revealed_modle');
              bts.map((i, e)=>{
                result[i] = {};
                result[i].preferred_title = $(e).find('h3').text().trim();
                result[i].alternative_title = $(e).find('h4').text().trim();
                switch($(e).children().filter('[class^="revealed_"]').attr('class')){
                  case 'revealed_list':
                    result[i].content = $(e).find('dl.revealed_list>dd').toArray()
                      .map((e, i)=>e.innerText.trim().replace(new RegExp(`^${i+1}`), ''));
                    result[i].type = 'list';
                    break;
                  case 'revealed_lines':
                    result[i].content = $(e).find('div.revealed_lines>dl>dd').toArray()
                      .map((e, i)=>{$(e).find('br').before(document.createTextNode('\n')).remove(); return e.innerText.trim()});
                    result[i].type = 'lines';
                    if (result[i].content.length === 0){
                      result[i].content = $(e).find('div.revealed_album>div.revealed_album_list li').toArray()
                        .map((e, i)=>`${$(e).attr('title')}:${$(e).attr('imgabstract')}`);
                      result[i].type = 'album';
                    }
                    break;
                  case 'revealed_other':
                    $(e).find('div.revealed_other>p:empty').before(document.createTextNode('\x1995')).remove();
                    $(e).find('div.revealed_other>p>br').before(document.createTextNode('\n')).remove();
                    result[i].content = $(e).find('div.revealed_other').text().trim().split(/\x1995|^ {2}|\n {2}/u).filter(e=>e.length).map(e=>e.trim().replace(/^\-+|\-+$|^·/g, '').trim());
                    result[i].type = 'other';
                    break;
                  case 'revealed_album':
                    result[i].content = $(e).find('div.revealed_album>div.revealed_album_list li').toArray()
                      .map((e, i)=>`${$(e).attr('title')}:${$(e).attr('imgabstract')}`);
                    result[i].type = 'album';
                    break;
                  case 'revealed_news':
                    result[i].content = $(e).find('h3>a').attr('href');
                    result[i].type = 'news';
                    break;
                }
              });
              resolve(result);
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    //MetaCritic
    metacriticSuggest(content, type = 'movie'){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'POST',
          url: 'https://www.metacritic.com/autosearch',
          data: `search_term=${encodeURIComponent(content).replace(/%20/g, '+')}`,
          headers: {
            'content-type': 'application/x-www-form-urlencoded',
            'x-requested-with': 'XMLHttpRequest',
            'referer': `//www.metacritic.com/${type}`
          },
          responseType: 'json',
          timeout: this.timeOut,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              resolve(res.response);
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    //RottenTomatoes
    rottentomatoesSuggest(content, limit = 10000){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: `https://www.rottentomatoes.com/napi/search/?query=${encodeURIComponent(content).replace(/%20/g, '+')}&limit=${limit}`,
          responseType: 'json',
          timeout: this.timeOut,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              resolve(res.response);
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    rottentomatoesSearch(content, limit = 10000){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: `https://www.rottentomatoes.com/api/private/v2.0/search?q=${encodeURIComponent(content).replace(/%20/g, '+')}&limit=${limit}`,
          responseType: 'json',
          timeout: this.timeOut,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              resolve(res.response);
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    rottentomatoesId(url){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: url,
          timeout: this.timeOut,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              const vd = document.implementation.createHTMLDocument('virtual');
              const id = $(res.response, vd).find('#rating-root').attr('data-movie-id');
              const emsId = (res.response.match(/emsId":"([^"]+)"/)||[undefined])[1];
              resolve({id: id, emsId: emsId});
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    rottentomatoesDetail(id){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: `https://www.rottentomatoes.com/api/private/v1.0/movies/${id}`,
          responseType: 'json',
          timeout: this.timeOut,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              resolve(res.response);
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    rottentomatoesAudienceScore(emsId){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: `https://www.rottentomatoes.com/napi/audienceScore/${emsId}`,
          responseType: 'json',
          timeout: this.timeOut,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              resolve(res.response);
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    //Flixster
    flixsterDetail(id){
      return new Promise((resolve, reject)=>{
        let request = Object.assign({
          method: 'GET',
          url: `https://api.flixster.com/android/api/v2/movies/${id}`,
          responseType: 'json',
          timeout: this.timeOut,
          onload: (res)=>{
            if(res.status >= 200 && res.status < 300){
              resolve(res.response);
            }
            else{
              reject({
                status: res.status,
                statusText: res.statusText,
              });
            }
          }
        }, this.errorStatus(reject));
        GM_xmlhttpRequest(request);
      });
    }
    //
    parseTorrentName(name){
      name = name.trim();
      let regexp_array = [
        {
          name: 'season',//S01E05 Season01episode05
          pattern: /\b(?:season[\s.]?|s)(\d{1,3})(?:\b|(?:episode[\s.]?|ep?)\d{1,3}\b)/gi,
          fun: (temp)=>Number.parseInt(temp[1])
        },
        {
          name: 'year',//2018
          pattern: /\b([12]\d{3})\b/g,
          fun: (temp)=>Number.parseInt(temp[1])
        },
        {
          name: 'resp',//1080p
          pattern: /\b(\d{3,4})p\b/gi,
          fun: (temp)=>Number.parseInt(temp[1])
        },
        {
          name: 'resi',//1080i
          pattern: /\b(\d{3,4})i\b/gi,
          fun: (temp)=>Number.parseInt(temp[1])
        },
        {
          name: 'resk',//4k
          pattern: /\b(\d)k\b/gi,
          fun: (temp)=>Number.parseInt(temp[1])
        },
        {
          name: 'resd', //SD
          pattern: /\b((?:s|h|uh)d)\b/gi,
          fun: (temp)=>temp[1]
        },
        {
          name: 'source',//bluray web-dl webrip web hdtv hddvd dvd
          pattern: /\b(?:(?<bluray>blue?\-?ray|b[dr]\-?(?:rip)?)|(?<webdl>web\-?dl)|(?<webrip>web\-?rip)|(?<web>web)|(?<hdtv>hdtv)|(?<hddvd>hd-?dvd)|(?<dvd>dvd(?:rip|scr)?))\b/gi,
          fun: (temp)=>Object.keys(temp.groups).find(k=>temp.groups[k] !== undefined)
        }
      ];
      let result = {};
      let temp = null, start_index = -1, end_index = -1;
      let start_index_log = [];
      for(let [index, regexp] of regexp_array.entries()){
        while ((temp = regexp.pattern.exec(name)) !== null) {
          result[regexp.name] = regexp.fun(temp);
          start_index = temp.index;
          end_index = regexp.pattern.lastIndex;
        }
        if(start_index !== -1){
          start_index_log.push(start_index);
          start_index = -1;
          end_index = -1;
        }
      }
      if(start_index_log.length === 0){
        result.name = name;
      }
      else{
        result.name = name.slice(0, Math.min(...start_index_log)).trim().replace(/^\.|\.$/, '');
      }
      return result;
    }
  }
  window.MediaFetchAPI = MediaFetchAPI;
})();