Greasy Fork is available in English.

MediaFetchAPI

Media Database API

Ce script ne doit pas être installé directement. C'est une librairie destinée à être incluse dans d'autres scripts avec la méta-directive // @require https://update.greasyfork.org/scripts/387700/718767/MediaFetchAPI.js

Vous devrez installer une extension telle que Tampermonkey, Greasemonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Violentmonkey pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey ou Userscripts pour installer ce script.

Vous devrez installer une extension telle que Tampermonkey pour installer ce script.

Vous devrez installer une extension de gestionnaire de script utilisateur pour installer ce script.

(J'ai déjà un gestionnaire de scripts utilisateur, laissez-moi l'installer !)

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension telle que Stylus pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

Vous devrez installer une extension du gestionnaire de style pour utilisateur pour installer ce style.

(J'ai déjà un gestionnaire de style utilisateur, laissez-moi l'installer!)

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