MediaFetchAPI

Media Database API

此腳本不應該直接安裝,它是一個供其他腳本使用的函式庫。欲使用本函式庫,請在腳本 metadata 寫上: // @require https://update.cn-greasyfork.org/scripts/387700/718767/MediaFetchAPI.js

  1. // ==UserScript==
  2. // @name MediaFetchAPI
  3. // @namespace https://greasyfork.org/users/152136
  4. // @version 0.1
  5. // @description Media Database API
  6. // @author TYT
  7. // @require https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.min.js
  8. // @require https://unpkg.com/number-to-chinese-words@^1.0/number-to-chinese-words.min.js
  9. // @grant GM_xmlhttpRequest
  10. // ==/UserScript==
  11. /* globals $ */
  12. (function() {
  13. 'use strict';
  14. class MediaFetchAPI {
  15. constructor(){
  16. console.log(window);
  17. this.converter = window.index.NumberToChineseWords;
  18. this.converter.labels = Object.assign(this.converter.labels, {
  19. digits : ['零','一', '二', '三', '四', '五', '六', '七', '八', '九'],
  20. units: ['','十', '百', '千', '万', '十', '百', '千', '亿', '十', '百', '千', '兆', '十', '百', '千', '京', '十', '百', '千', '垓']
  21. });
  22. this.doubanAPIKeyB64 = 'MGIyYmRlZGE0M2I1Njg4OTIxODM5YzhlY2IyMDM5OWI=';
  23. this.timeOut = 1e4;
  24. }
  25. errorStatus(reject){
  26. return {
  27. ontimeout: ()=>{
  28. reject({
  29. status: 440,
  30. statusText: 'Time Out'
  31. })
  32. },
  33. onerror: ()=>{
  34. reject({
  35. status: 441,
  36. statusText: 'Request Error'
  37. })
  38. },
  39. onabort: ()=>{
  40. reject({
  41. status: 442,
  42. statusText: 'Abort'
  43. })
  44. }
  45. }
  46. };
  47. url2Blob(url){
  48. return new Promise((resolve, reject)=>{
  49. let request = Object.assign({
  50. method: 'GET',
  51. url: url,
  52. timeout: 10000,
  53. responseType: 'blob',
  54. onload: (res)=>{
  55. resolve(res.response);
  56. }
  57. }, this.errorStatus(reject));
  58. GM_xmlhttpRequest(request);
  59. });
  60. }
  61. blob2DataURL(blob){
  62. return new Promise((resolve, reject)=>{
  63. let fr = new FileReader();
  64. fr.onload = (e)=>{
  65. resolve(e.target.result);
  66. }
  67. Object.assign(fr, this.errorStatus(reject));
  68. fr.readAsDataURL(blob);
  69. });
  70. }
  71. url2DataURL(url){
  72. return this.url2Blob(url)
  73. .then(this.blob2DataURL);
  74. }
  75. //Douban
  76. doubanSuggest(content){
  77. return new Promise((resolve, reject)=>{
  78. let request = Object.assign({
  79. method: 'GET',
  80. url: `https://movie.douban.com/j/subject_suggest?q=${encodeURIComponent(content)}`,
  81. timeout: this.timeOut,
  82. responseType: 'json',
  83. onload: (res)=>{
  84. if(res.status >= 200 && res.status < 300){
  85. resolve(res.response)
  86. }
  87. else{
  88. reject({
  89. status: res.status,
  90. statusText: res.statusText,
  91. });
  92. }
  93. },
  94. }, this.errorStatus(reject));
  95. GM_xmlhttpRequest(request);
  96. });
  97. }
  98. doubanDetail(id, api_key = atob(this.doubanAPIKeyB64)){
  99. return new Promise((resolve, reject)=>{
  100. let request = Object.assign({
  101. method: 'GET',
  102. url: `https://api.douban.com/v2/movie/${id}?apikey=${api_key}`,
  103. timeout: this.timeOut,
  104. responseType: 'json',
  105. onload: (res)=>{
  106. if(res.status >= 200 && res.status < 300){
  107. resolve(res.response)
  108. }
  109. else{
  110. reject({
  111. status: res.status,
  112. statusText: res.statusText,
  113. });
  114. }
  115. }
  116. }, this.errorStatus(reject));
  117. GM_xmlhttpRequest(request);
  118. });
  119. }
  120. doubanPage(id){
  121. return new Promise((resolve, reject)=>{
  122. let request = Object.assign({
  123. method: 'GET',
  124. url: `https://movie.douban.com/subject/${id}`,
  125. timeout: 10000,
  126. onload: (res)=>{
  127. if(res.status >= 200 && res.status < 300){
  128. resolve(res.response)
  129. }
  130. else{
  131. reject({
  132. status: res.status,
  133. statusText: res.statusText,
  134. });
  135. }
  136. }
  137. }, this.errorStatus(reject));
  138. GM_xmlhttpRequest(request);
  139. });
  140. }
  141. doubanCard(id, type = 'png', options = {}){
  142. const type_mapper = {
  143. png: 'toPng',
  144. blob: 'toBlob',
  145. jpg: 'toJpeg',
  146. svg: 'toSvg',
  147. pixel: 'toPixelData'
  148. };
  149. if(typeof type_mapper[type] === 'undefined'){
  150. return new Promise((resolve, reject)=>{
  151. reject({status: 443, statusText: 'Wrong Type'});
  152. });
  153. }
  154. let iframe = $('<iframe/>');
  155. iframe.css({
  156. position: 'absolute',
  157. width: 0,
  158. height: 0,
  159. top: -1e4,
  160. left: -1e4
  161. });
  162. iframe.appendTo($('body'));
  163. return new Promise((resolve, reject)=>{
  164. let request = Object.assign({
  165. method: 'GET',
  166. url: `https://movie.douban.com/subject/${id}/output_card`,
  167. timeout: this.timeOut,
  168. onload: (res)=>{
  169. if(res.status >= 200 && res.status < 300){
  170. function resolveMessage(message){
  171. if(message.data.status === 444){
  172. reject(message.data);
  173. }
  174. else{
  175. resolve(message.data);
  176. }
  177. window.removeEventListener('message', resolveMessage);
  178. iframe.remove();
  179. }
  180. window.addEventListener('message', resolveMessage);
  181. 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,"*")});`;
  182. let response = res.response.replace(/<script[^>]+output\-card\.js"><\/script>/, `<script>${snippet}</script>`);
  183. let bg_reg = /<div class="picture\-wrapper" style="background\-image\: url\(([^\)]+)\)">/;
  184. let target = bg_reg.exec(response);
  185. this.url2DataURL(target[1]).then((durl)=>{
  186. response = `${response.slice(0, target.index)}<div class="picture-wrapper" style="background-image: url(${durl})">${response.slice(target.index + target[0].length)}`;
  187. let idoc = iframe[0].contentWindow.document;
  188. idoc.write(response);
  189. }).catch((err)=>{
  190. reject(err);
  191. });
  192. }
  193. else{
  194. reject({
  195. status: res.status,
  196. statusText: res.statusText,
  197. });
  198. }
  199. }
  200. }, this.errorStatus(reject));
  201. GM_xmlhttpRequest(request);
  202. });
  203. }
  204. doubanAwards(id){
  205. return new Promise((resolve, reject)=>{
  206. let request = Object.assign({
  207. method: 'GET',
  208. url: `https://movie.douban.com/subject/${id}/awards`,
  209. timeout: this.timeOut,
  210. onload: (res)=>{
  211. if(res.status >= 200 && res.status < 300){
  212. const vd = document.implementation.createHTMLDocument('virtual');
  213. const article = $(res.response, vd).find('#content .article');
  214. let awards = [];
  215. for (let _awards of article.children('.awards')){
  216. let award = {};
  217. let temp = $(_awards).children('.hd');
  218. award.name = temp.find('a').text().trim();
  219. award.link = temp.find('a')[0].href;
  220. award.year = temp.find('.year').text().slice(2, -1);
  221. award.cats = [];
  222. for (let _award of $(_awards).children('.award')){
  223. let item = {};
  224. let temp = $(_award).children();
  225. [item.category, item.recipients] = [temp[0].innerText, temp[1].innerText.split(' / ')];
  226. if(item.recipients[0] === ''){
  227. item.recipients = [];
  228. }
  229. award.cats.push(item);
  230. }
  231. awards.push(award);
  232. }
  233. resolve(awards);
  234. }
  235. else{
  236. reject({
  237. status: res.status,
  238. statusText: res.statusText,
  239. });
  240. }
  241. }
  242. }, this.errorStatus(reject));
  243. GM_xmlhttpRequest(request);
  244. });
  245. }
  246. doubanCrew(id){
  247. function decouple(mixed){
  248. let match = mixed.match(/(^.*\p{Unified_Ideograph}[^ ]*) ([^\p{Unified_Ideograph}]+)$/u);
  249. if(match){
  250. return [match[1], match[2]];
  251. }
  252. else{
  253. return [mixed];
  254. }
  255. }
  256. return new Promise((resolve, reject)=>{
  257. let request = Object.assign({
  258. method: 'GET',
  259. url: `https://movie.douban.com/subject/${id}/celebrities`,
  260. timeout: this.timeOut,
  261. onload: (res)=>{
  262. if(res.status >= 200 && res.status < 300){
  263. const vd = document.implementation.createHTMLDocument('virtual');
  264. const celebrities = $(res.response, vd).find('#celebrities');
  265. let crew = [];
  266. for (let _list of $(celebrities).children('.list-wrapper')){
  267. let list = {
  268. profession: {}
  269. };
  270. [list.profession.pre, list.profession.alt = list.profession.pre] = $(_list).children('h2').text().split(' ');
  271. list.people = [];
  272. for (let _person of $(_list).find('.celebrities-list>.celebrity')){
  273. let person = {
  274. name: {},
  275. title: {}
  276. };
  277. [person.name.pre,
  278. person.name.alt = person.name.pre] = decouple(
  279. $(_person).find('.info>.name>a.name').text()
  280. );
  281. [person.title.pre,
  282. person.title.alt = person.title.pre] = decouple(
  283. $(_person).find('.info>.role').text().replace(/ \([^\)]+\)$/, '')
  284. ).map((e)=>e.split('/').map((e)=>e.trim()));
  285. if(list.profession.pre === '演员'){
  286. person.role = {
  287. pre: [],
  288. alt: []
  289. };
  290. let temp = $(_person).find('.info>.role').text().match(/ \([饰配] ([^\)]+)\)$/);
  291. if(temp){
  292. temp[1].split(' / ').forEach((e)=>{
  293. let temp_1, temp_2;
  294. [temp_1, temp_2 = temp_1] = decouple(e);
  295. person.role.pre.push(temp_1);
  296. person.role.alt.push(temp_2);
  297. });
  298. }
  299. }
  300. person.link = $(_person).find('.info>.name>a.name')[0].href;
  301. list.people.push(person);
  302. }
  303. crew.push(list);
  304. }
  305. resolve(crew);
  306. }
  307. else{
  308. reject({
  309. status: res.status,
  310. statusText: res.statusText,
  311. });
  312. }
  313. }
  314. }, this.errorStatus(reject));
  315. GM_xmlhttpRequest(request);
  316. });
  317. }
  318. doubanDetailPlus(id, api_key = atob(this.doubanAPIKeyB64)){
  319. return Promise.all([this.doubanDetail(id, api_key), this.doubanPage(id)])
  320. .then(
  321. ([json, doc]) => {
  322. const vd = document.implementation.createHTMLDocument('virtual');
  323. const vdoc = $(doc, vd);
  324. json.alt_title = [vdoc.filter('title').text().replace(/\(豆瓣\)\s?$/, '').trim()];
  325. json.title = vdoc.find('h1>span[property="v:itemreviewed"]').text().replace(json.alt_title[0], '').trim() || json.alt_title[0];
  326. let temp;
  327. if((temp = vdoc.find('#info>span.pl:contains("又名:")')[0]) !== undefined){
  328. json.alt_title = json.alt_title.concat(temp.nextSibling.nodeValue.trim().split(' / '))
  329. }
  330. json.imdb_id = null;
  331. return Promise.resolve(doubanRealIMDb(doc)).then(
  332. id=>{
  333. json.imdb_id = id;
  334. return json;
  335. }
  336. )
  337. }
  338. )
  339. function doubanRealIMDb(res){
  340. let e = douban1stSeason(res);
  341. if(typeof e !== 'undefined'){
  342. return this.doubanPage(e).then((e)=>doubanIMDb(e));
  343. }
  344. return doubanIMDb(res);
  345. function douban1stSeason(res){
  346. const vd = document.implementation.createHTMLDocument('virtual');
  347. const list = $(res, vd).find('#season');
  348. if(typeof list === 'undefined' || list.find(':selected').text() === '1'){
  349. return undefined
  350. }
  351. return list.children(':first').val();
  352. }
  353. function doubanIMDb(res){
  354. const vd = document.implementation.createHTMLDocument('virtual');
  355. const link = $(res, vd).find('#info>a[href*="imdb"]')[0];
  356. let id;
  357. if(typeof link === 'undefined' || typeof link.href === 'undefined' || typeof (id = link.href.match(/\d+$/)) === 'undefined'){
  358. return undefined;
  359. }
  360. return id[0];
  361. }
  362. }
  363. }
  364. //IMDb
  365. imdbRating(id){
  366. return new Promise((resolve, reject)=>{
  367. let request = Object.assign({
  368. method: 'GET',
  369. url: `https://p.media-imdb.com/static-content/documents/v1/title/tt${id}/ratings%3Fjsonp=imdb.rating.run:imdb.api.title.ratings/data.json`,
  370. timeout: this.timeOut,
  371. onload: (res)=>{
  372. if(res.status >= 200 && res.status < 300){
  373. resolve(JSON.parse(res.response.slice(16, -1)))
  374. }
  375. else{
  376. reject({
  377. status: res.status,
  378. statusText: res.statusText,
  379. });
  380. }
  381. }
  382. }, this.errorStatus(reject));
  383. GM_xmlhttpRequest(request);
  384. });
  385. }
  386. imdbSuggest(content){
  387. return new Promise((resolve, reject)=>{
  388. let request = Object.assign({
  389. method: 'GET',
  390. url: `https://v2.sg.media-imdb.com/suggestion/${content[0].toLowerCase()}/${encodeURIComponent(content).replace(/%20/g, '+')}.json`,
  391. timeout: this.timeOut,
  392. responseType: 'json',
  393. onload: (res)=>{
  394. if(res.status >= 200 && res.status < 300){
  395. resolve(res.response)
  396. }
  397. else{
  398. reject({
  399. status: res.status,
  400. statusText: res.statusText,
  401. });
  402. }
  403. }
  404. }, this.errorStatus(reject));
  405. GM_xmlhttpRequest(request);
  406. });
  407. }
  408. //MTime
  409. mtimeStatistic(id){
  410. return new Promise((resolve, reject)=>{
  411. let request = Object.assign({
  412. method: 'GET',
  413. url: 'http://service.library.mtime.com/Movie.api'
  414. + '?Ajax_CallBack=true'
  415. + '&Ajax_CallBackType=Mtime.Library.Services'
  416. + '&Ajax_CallBackMethod=GetMovieOverviewRating'
  417. + `&Ajax_CallBackArgument0=${id}`,
  418. timeout: this.timeOut,
  419. onload: (res)=>{
  420. if(res.status >= 200 && res.status < 300){
  421. let m = res.response.match(/^[^{]+({.*});\s*$/);
  422. let val;
  423. if(m && (val = JSON.parse(m[1]).value)){
  424. resolve(val);
  425. }
  426. }
  427. else{
  428. reject({
  429. status: res.status,
  430. statusText: res.statusText,
  431. });
  432. }
  433. }
  434. }, this.errorStatus(reject));
  435. GM_xmlhttpRequest(request);
  436. });
  437. }
  438. mtimeRating(id_array){
  439. if((typeof id_array === 'string' && id_array.match(/^\d+$/)) || Number.isInteger(id_array)){
  440. id_array = [id_array];
  441. }
  442. else if(!(id_array instanceof Array)){
  443. return Promise.reject({
  444. status: 446,
  445. statusText: 'Mtime Rating Argument Wrong Format'
  446. })
  447. }
  448. return new Promise((resolve, reject)=>{
  449. let request = Object.assign({
  450. method: 'GET',
  451. url: 'http://service.mtime.com/Service/Movie.msi'
  452. + '?Ajax_CallBack=true'
  453. + '&Ajax_CallBackType=Mtime.MemberCenter.Pages.MovieService'
  454. + '&Ajax_CallBackMethod=GetRatingsByMovieIds'
  455. + `&Ajax_CallBackArgument0=${id_array.join('|')}`,
  456. timeout: this.timeOut,
  457. onload: (res)=>{
  458. if(res.status >= 200 && res.status < 300){
  459. let m = res.response.match(/^[^{]+({.*});\s*$/);
  460. let val;
  461. if(m && (val = JSON.parse(m[1]).value)){
  462. resolve(val);
  463. }
  464. }
  465. else{
  466. reject({
  467. status: res.status,
  468. statusText: res.statusText,
  469. });
  470. }
  471. }
  472. }, this.errorStatus(reject));
  473. GM_xmlhttpRequest(request);
  474. });
  475. }
  476. mtimeQuery(content, count = 20){
  477. return new Promise((resolve, reject)=>{
  478. let request = Object.assign({
  479. method: 'GET',
  480. url: 'http://my.mtime.com/Service/Movie.mc'
  481. + '?Ajax_CallBack=true'
  482. + '&Ajax_CallBackType=Mtime.MemberCenter.Pages.MovieService'
  483. + '&Ajax_CallBackMethod=GetSearchMoviesByTitle'
  484. + `&Ajax_CallBackArgument0=${encodeURIComponent(content)}`
  485. + `&Ajax_CallBackArgument1=${count}`,
  486. timeout: 10000,
  487. onload: (res)=>{
  488. if(res.status >= 200 && res.status < 300){
  489. let val;
  490. if((val = JSON.parse(res.response).value)){
  491. resolve(val);
  492. }
  493. resolve(JSON.parse(res.response));
  494. }
  495. else{
  496. reject({
  497. status: res.status,
  498. statusText: res.statusText,
  499. });
  500. }
  501. }
  502. }, this.errorStatus(reject));
  503. GM_xmlhttpRequest(request);
  504. });
  505. }
  506. mtimeSearch(content){
  507. return new Promise((resolve, reject)=>{
  508. let request = Object.assign({
  509. method: 'GET',
  510. url: 'http://service-channel.mtime.com/Search.api'
  511. + '?Ajax_CallBack=true'
  512. + '&Ajax_CallBackType=Mtime.Channel.Services'
  513. + '&Ajax_CallBackMethod=GetSearchResult'
  514. + `&Ajax_CallBackArgument0=${encodeURIComponent(content)}`
  515. + '&Ajax_CallBackArgument2=1'
  516. + '&Ajax_CallBackArgument4=10000',
  517. timeout: this.timeOut,
  518. onload: (res)=>{
  519. if(res.status >= 200 && res.status < 300){
  520. let m = res.response.match(/^[^{]+({.*});\s*$/);
  521. let val;
  522. if(m && (val = JSON.parse(m[1]).value)){
  523. resolve(val.movieResult);
  524. }
  525. else{
  526. reject({
  527. status: 445,
  528. statusText: 'Mtime Error',
  529. });
  530. }
  531. }
  532. else{
  533. reject({
  534. status: res.status,
  535. statusText: res.statusText,
  536. });
  537. }
  538. }
  539. }, this.errorStatus(reject));
  540. GM_xmlhttpRequest(request);
  541. });
  542. }
  543. mtimeBehindTheScene(id){
  544. //revealed_list
  545. //revealed_lines
  546. //revealed_news
  547. //revealed_other
  548. //revealed_album
  549. let result = [];
  550. return new Promise((resolve, reject)=>{
  551. let request = Object.assign({
  552. method: 'GET',
  553. url: `http://movie.mtime.com/${id}/behind_the_scene.html`,
  554. timeout: 10000,
  555. onload: (res)=>{
  556. if(res.status >= 200 && res.status < 300){
  557. const vd = document.implementation.createHTMLDocument('virtual');
  558. const bts = $(res.response, vd).find('.revealed_modle');
  559. bts.map((i, e)=>{
  560. result[i] = {};
  561. result[i].preferred_title = $(e).find('h3').text().trim();
  562. result[i].alternative_title = $(e).find('h4').text().trim();
  563. switch($(e).children().filter('[class^="revealed_"]').attr('class')){
  564. case 'revealed_list':
  565. result[i].content = $(e).find('dl.revealed_list>dd').toArray()
  566. .map((e, i)=>e.innerText.trim().replace(new RegExp(`^${i+1}`), ''));
  567. result[i].type = 'list';
  568. break;
  569. case 'revealed_lines':
  570. result[i].content = $(e).find('div.revealed_lines>dl>dd').toArray()
  571. .map((e, i)=>{$(e).find('br').before(document.createTextNode('\n')).remove(); return e.innerText.trim()});
  572. result[i].type = 'lines';
  573. if (result[i].content.length === 0){
  574. result[i].content = $(e).find('div.revealed_album>div.revealed_album_list li').toArray()
  575. .map((e, i)=>`${$(e).attr('title')}:${$(e).attr('imgabstract')}`);
  576. result[i].type = 'album';
  577. }
  578. break;
  579. case 'revealed_other':
  580. $(e).find('div.revealed_other>p:empty').before(document.createTextNode('\x1995')).remove();
  581. $(e).find('div.revealed_other>p>br').before(document.createTextNode('\n')).remove();
  582. 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());
  583. result[i].type = 'other';
  584. break;
  585. case 'revealed_album':
  586. result[i].content = $(e).find('div.revealed_album>div.revealed_album_list li').toArray()
  587. .map((e, i)=>`${$(e).attr('title')}:${$(e).attr('imgabstract')}`);
  588. result[i].type = 'album';
  589. break;
  590. case 'revealed_news':
  591. result[i].content = $(e).find('h3>a').attr('href');
  592. result[i].type = 'news';
  593. break;
  594. }
  595. });
  596. resolve(result);
  597. }
  598. else{
  599. reject({
  600. status: res.status,
  601. statusText: res.statusText,
  602. });
  603. }
  604. }
  605. }, this.errorStatus(reject));
  606. GM_xmlhttpRequest(request);
  607. });
  608. }
  609. //MetaCritic
  610. metacriticSuggest(content, type = 'movie'){
  611. return new Promise((resolve, reject)=>{
  612. let request = Object.assign({
  613. method: 'POST',
  614. url: 'https://www.metacritic.com/autosearch',
  615. data: `search_term=${encodeURIComponent(content).replace(/%20/g, '+')}`,
  616. headers: {
  617. 'content-type': 'application/x-www-form-urlencoded',
  618. 'x-requested-with': 'XMLHttpRequest',
  619. 'referer': `//www.metacritic.com/${type}`
  620. },
  621. responseType: 'json',
  622. timeout: this.timeOut,
  623. onload: (res)=>{
  624. if(res.status >= 200 && res.status < 300){
  625. resolve(res.response);
  626. }
  627. else{
  628. reject({
  629. status: res.status,
  630. statusText: res.statusText,
  631. });
  632. }
  633. }
  634. }, this.errorStatus(reject));
  635. GM_xmlhttpRequest(request);
  636. });
  637. }
  638. //RottenTomatoes
  639. rottentomatoesSuggest(content, limit = 10000){
  640. return new Promise((resolve, reject)=>{
  641. let request = Object.assign({
  642. method: 'GET',
  643. url: `https://www.rottentomatoes.com/napi/search/?query=${encodeURIComponent(content).replace(/%20/g, '+')}&limit=${limit}`,
  644. responseType: 'json',
  645. timeout: this.timeOut,
  646. onload: (res)=>{
  647. if(res.status >= 200 && res.status < 300){
  648. resolve(res.response);
  649. }
  650. else{
  651. reject({
  652. status: res.status,
  653. statusText: res.statusText,
  654. });
  655. }
  656. }
  657. }, this.errorStatus(reject));
  658. GM_xmlhttpRequest(request);
  659. });
  660. }
  661. rottentomatoesSearch(content, limit = 10000){
  662. return new Promise((resolve, reject)=>{
  663. let request = Object.assign({
  664. method: 'GET',
  665. url: `https://www.rottentomatoes.com/api/private/v2.0/search?q=${encodeURIComponent(content).replace(/%20/g, '+')}&limit=${limit}`,
  666. responseType: 'json',
  667. timeout: this.timeOut,
  668. onload: (res)=>{
  669. if(res.status >= 200 && res.status < 300){
  670. resolve(res.response);
  671. }
  672. else{
  673. reject({
  674. status: res.status,
  675. statusText: res.statusText,
  676. });
  677. }
  678. }
  679. }, this.errorStatus(reject));
  680. GM_xmlhttpRequest(request);
  681. });
  682. }
  683. rottentomatoesId(url){
  684. return new Promise((resolve, reject)=>{
  685. let request = Object.assign({
  686. method: 'GET',
  687. url: url,
  688. timeout: this.timeOut,
  689. onload: (res)=>{
  690. if(res.status >= 200 && res.status < 300){
  691. const vd = document.implementation.createHTMLDocument('virtual');
  692. const id = $(res.response, vd).find('#rating-root').attr('data-movie-id');
  693. const emsId = (res.response.match(/emsId":"([^"]+)"/)||[undefined])[1];
  694. resolve({id: id, emsId: emsId});
  695. }
  696. else{
  697. reject({
  698. status: res.status,
  699. statusText: res.statusText,
  700. });
  701. }
  702. }
  703. }, this.errorStatus(reject));
  704. GM_xmlhttpRequest(request);
  705. });
  706. }
  707. rottentomatoesDetail(id){
  708. return new Promise((resolve, reject)=>{
  709. let request = Object.assign({
  710. method: 'GET',
  711. url: `https://www.rottentomatoes.com/api/private/v1.0/movies/${id}`,
  712. responseType: 'json',
  713. timeout: this.timeOut,
  714. onload: (res)=>{
  715. if(res.status >= 200 && res.status < 300){
  716. resolve(res.response);
  717. }
  718. else{
  719. reject({
  720. status: res.status,
  721. statusText: res.statusText,
  722. });
  723. }
  724. }
  725. }, this.errorStatus(reject));
  726. GM_xmlhttpRequest(request);
  727. });
  728. }
  729. rottentomatoesAudienceScore(emsId){
  730. return new Promise((resolve, reject)=>{
  731. let request = Object.assign({
  732. method: 'GET',
  733. url: `https://www.rottentomatoes.com/napi/audienceScore/${emsId}`,
  734. responseType: 'json',
  735. timeout: this.timeOut,
  736. onload: (res)=>{
  737. if(res.status >= 200 && res.status < 300){
  738. resolve(res.response);
  739. }
  740. else{
  741. reject({
  742. status: res.status,
  743. statusText: res.statusText,
  744. });
  745. }
  746. }
  747. }, this.errorStatus(reject));
  748. GM_xmlhttpRequest(request);
  749. });
  750. }
  751. //Flixster
  752. flixsterDetail(id){
  753. return new Promise((resolve, reject)=>{
  754. let request = Object.assign({
  755. method: 'GET',
  756. url: `https://api.flixster.com/android/api/v2/movies/${id}`,
  757. responseType: 'json',
  758. timeout: this.timeOut,
  759. onload: (res)=>{
  760. if(res.status >= 200 && res.status < 300){
  761. resolve(res.response);
  762. }
  763. else{
  764. reject({
  765. status: res.status,
  766. statusText: res.statusText,
  767. });
  768. }
  769. }
  770. }, this.errorStatus(reject));
  771. GM_xmlhttpRequest(request);
  772. });
  773. }
  774. //
  775. parseTorrentName(name){
  776. name = name.trim();
  777. let regexp_array = [
  778. {
  779. name: 'season',//S01E05 Season01episode05
  780. pattern: /\b(?:season[\s.]?|s)(\d{1,3})(?:\b|(?:episode[\s.]?|ep?)\d{1,3}\b)/gi,
  781. fun: (temp)=>Number.parseInt(temp[1])
  782. },
  783. {
  784. name: 'year',//2018
  785. pattern: /\b([12]\d{3})\b/g,
  786. fun: (temp)=>Number.parseInt(temp[1])
  787. },
  788. {
  789. name: 'resp',//1080p
  790. pattern: /\b(\d{3,4})p\b/gi,
  791. fun: (temp)=>Number.parseInt(temp[1])
  792. },
  793. {
  794. name: 'resi',//1080i
  795. pattern: /\b(\d{3,4})i\b/gi,
  796. fun: (temp)=>Number.parseInt(temp[1])
  797. },
  798. {
  799. name: 'resk',//4k
  800. pattern: /\b(\d)k\b/gi,
  801. fun: (temp)=>Number.parseInt(temp[1])
  802. },
  803. {
  804. name: 'resd', //SD
  805. pattern: /\b((?:s|h|uh)d)\b/gi,
  806. fun: (temp)=>temp[1]
  807. },
  808. {
  809. name: 'source',//bluray web-dl webrip web hdtv hddvd dvd
  810. 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,
  811. fun: (temp)=>Object.keys(temp.groups).find(k=>temp.groups[k] !== undefined)
  812. }
  813. ];
  814. let result = {};
  815. let temp = null, start_index = -1, end_index = -1;
  816. let start_index_log = [];
  817. for(let [index, regexp] of regexp_array.entries()){
  818. while ((temp = regexp.pattern.exec(name)) !== null) {
  819. result[regexp.name] = regexp.fun(temp);
  820. start_index = temp.index;
  821. end_index = regexp.pattern.lastIndex;
  822. }
  823. if(start_index !== -1){
  824. start_index_log.push(start_index);
  825. start_index = -1;
  826. end_index = -1;
  827. }
  828. }
  829. if(start_index_log.length === 0){
  830. result.name = name;
  831. }
  832. else{
  833. result.name = name.slice(0, Math.min(...start_index_log)).trim().replace(/^\.|\.$/, '');
  834. }
  835. return result;
  836. }
  837. }
  838. window.MediaFetchAPI = MediaFetchAPI;
  839. })();