MediaFetchAPI

Media Database API

Dette script bør ikke installeres direkte. Det er et bibliotek, som andre scripts kan inkludere med metadirektivet // @require https://update.greasyfork.org/scripts/387700/718767/MediaFetchAPI.js

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

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