Greasy Fork is available in English.

「今晚看啥123」显示电影评分解说观看链接

在豆瓣、IMDb、烂番茄、MetaCritic、B站和油管聚合显示电影评分、解说、观看链接信息。

// ==UserScript==
// @name       jwks123
// @name:zh-CN 「今晚看啥123」显示电影评分解说观看链接
// @description       Seamlessly integrates movie ratings, narrations and watch links on douban, IMDb, RottenTomatoes, MetaCritic, Bilibili and YouTube sites.
// @description:zh-CN 在豆瓣、IMDb、烂番茄、MetaCritic、B站和油管聚合显示电影评分、解说、观看链接信息。
// @namespace https://jwks123.com
// @connect   jwks123.cn
// @connect   jwks123.com
// @version   2021.9.1
// @homepage  https://jwks123.cn
// @author    jwks123.com
// @license   all copyrights reserved
// @match     *://www.netflix.com/*/*
// @match     *://www.bilibili.com/video/*
// @match     *://www.youtube.com/watch*
// @match     *://www.metacritic.com/tv/*
// @match     *://www.metacritic.com/movie/*
// @match     *://www.rottentomatoes.com/tv/*
// @match     *://www.rottentomatoes.com/m/*
// @match     *://www.imdb.com/title/*
// @match     *://movie.mtime.com/*
// @match     *://movie.douban.com/subject/*
// @grant     GM.xmlHttpRequest
// ==/UserScript==
(function () {
  'use strict';

  function _classCallCheck(instance, Constructor) {
    if (!(instance instanceof Constructor)) {
      throw new TypeError("Cannot call a class as a function");
    }
  }

  function _defineProperties(target, props) {
    for (var i = 0; i < props.length; i++) {
      var descriptor = props[i];
      descriptor.enumerable = descriptor.enumerable || false;
      descriptor.configurable = true;
      if ("value" in descriptor) descriptor.writable = true;
      Object.defineProperty(target, descriptor.key, descriptor);
    }
  }

  function _createClass(Constructor, protoProps, staticProps) {
    if (protoProps) _defineProperties(Constructor.prototype, protoProps);
    if (staticProps) _defineProperties(Constructor, staticProps);
    return Constructor;
  }

  function _defineProperty(obj, key, value) {
    if (key in obj) {
      Object.defineProperty(obj, key, {
        value: value,
        enumerable: true,
        configurable: true,
        writable: true
      });
    } else {
      obj[key] = value;
    }

    return obj;
  }

  function _inherits(subClass, superClass) {
    if (typeof superClass !== "function" && superClass !== null) {
      throw new TypeError("Super expression must either be null or a function");
    }

    subClass.prototype = Object.create(superClass && superClass.prototype, {
      constructor: {
        value: subClass,
        writable: true,
        configurable: true
      }
    });
    if (superClass) _setPrototypeOf(subClass, superClass);
  }

  function _getPrototypeOf(o) {
    _getPrototypeOf = Object.setPrototypeOf ? Object.getPrototypeOf : function _getPrototypeOf(o) {
      return o.__proto__ || Object.getPrototypeOf(o);
    };
    return _getPrototypeOf(o);
  }

  function _setPrototypeOf(o, p) {
    _setPrototypeOf = Object.setPrototypeOf || function _setPrototypeOf(o, p) {
      o.__proto__ = p;
      return o;
    };

    return _setPrototypeOf(o, p);
  }

  function _isNativeReflectConstruct() {
    if (typeof Reflect === "undefined" || !Reflect.construct) return false;
    if (Reflect.construct.sham) return false;
    if (typeof Proxy === "function") return true;

    try {
      Boolean.prototype.valueOf.call(Reflect.construct(Boolean, [], function () {}));
      return true;
    } catch (e) {
      return false;
    }
  }

  function _assertThisInitialized(self) {
    if (self === void 0) {
      throw new ReferenceError("this hasn't been initialised - super() hasn't been called");
    }

    return self;
  }

  function _possibleConstructorReturn(self, call) {
    if (call && (typeof call === "object" || typeof call === "function")) {
      return call;
    }

    return _assertThisInitialized(self);
  }

  function _createSuper(Derived) {
    var hasNativeReflectConstruct = _isNativeReflectConstruct();

    return function _createSuperInternal() {
      var Super = _getPrototypeOf(Derived),
          result;

      if (hasNativeReflectConstruct) {
        var NewTarget = _getPrototypeOf(this).constructor;

        result = Reflect.construct(Super, arguments, NewTarget);
      } else {
        result = Super.apply(this, arguments);
      }

      return _possibleConstructorReturn(this, result);
    };
  }

  function _superPropBase(object, property) {
    while (!Object.prototype.hasOwnProperty.call(object, property)) {
      object = _getPrototypeOf(object);
      if (object === null) break;
    }

    return object;
  }

  function _get(target, property, receiver) {
    if (typeof Reflect !== "undefined" && Reflect.get) {
      _get = Reflect.get;
    } else {
      _get = function _get(target, property, receiver) {
        var base = _superPropBase(target, property);

        if (!base) return;
        var desc = Object.getOwnPropertyDescriptor(base, property);

        if (desc.get) {
          return desc.get.call(receiver);
        }

        return desc.value;
      };
    }

    return _get(target, property, receiver || target);
  }

  function _unsupportedIterableToArray(o, minLen) {
    if (!o) return;
    if (typeof o === "string") return _arrayLikeToArray(o, minLen);
    var n = Object.prototype.toString.call(o).slice(8, -1);
    if (n === "Object" && o.constructor) n = o.constructor.name;
    if (n === "Map" || n === "Set") return Array.from(o);
    if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen);
  }

  function _arrayLikeToArray(arr, len) {
    if (len == null || len > arr.length) len = arr.length;

    for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i];

    return arr2;
  }

  function _createForOfIteratorHelper(o, allowArrayLike) {
    var it = typeof Symbol !== "undefined" && o[Symbol.iterator] || o["@@iterator"];

    if (!it) {
      if (Array.isArray(o) || (it = _unsupportedIterableToArray(o)) || allowArrayLike && o && typeof o.length === "number") {
        if (it) o = it;
        var i = 0;

        var F = function () {};

        return {
          s: F,
          n: function () {
            if (i >= o.length) return {
              done: true
            };
            return {
              done: false,
              value: o[i++]
            };
          },
          e: function (e) {
            throw e;
          },
          f: F
        };
      }

      throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
    }

    var normalCompletion = true,
        didErr = false,
        err;
    return {
      s: function () {
        it = it.call(o);
      },
      n: function () {
        var step = it.next();
        normalCompletion = step.done;
        return step;
      },
      e: function (e) {
        didErr = true;
        err = e;
      },
      f: function () {
        try {
          if (!normalCompletion && it.return != null) it.return();
        } finally {
          if (didErr) throw err;
        }
      }
    };
  }

  var UtilObj = /*#__PURE__*/function () {
    function UtilObj() {
      _classCallCheck(this, UtilObj);
    }

    _createClass(UtilObj, null, [{
      key: "isEmpty",
      value: // ref:https://stackoverflow.com/questions/679915/how-do-i-test-for-an-empty-javascript-object
      function isEmpty(obj) {
        return !obj || obj && Object.keys(obj).length === 0 && obj.constructor === Object;
      } // ref:https://gist.github.com/schornio/9763477

    }, {
      key: "getType",
      value: function getType(obj) {
        var rs = '';

        if (obj === undefined) {
          rs = 'Undefined';
        } else if (obj === null) {
          rs = 'Null';
        } else {
          var typeString = Object.prototype.toString.call(obj);
          rs = typeString.slice(typeString.indexOf(' ') + 1, -1);
        }

        return rs;
      }
    }]);

    return UtilObj;
  }();

  var UtilStr = /*#__PURE__*/function () {
    function UtilStr() {
      _classCallCheck(this, UtilStr);
    }

    _createClass(UtilStr, null, [{
      key: "getValueFromReGroup",
      value: // parse group value from string in regular expression
      function getValueFromReGroup(rePattern, s) {
        var rs = '';
        var m = rePattern.exec(s);

        if (m) {
          rs = m[1];
        }

        return rs;
      }
    }]);

    return UtilStr;
  }();

  var UtilXpath = /*#__PURE__*/function () {
    function UtilXpath() {
      _classCallCheck(this, UtilXpath);
    }

    _createClass(UtilXpath, null, [{
      key: "getValueFromXPathString",
      value: function getValueFromXPathString(doc, path) {
        // walk around undefined
        var XPathResult = {};
        XPathResult.ANY_TYPE = 0;
        var s = '';
        var node = doc.evaluate(path, doc, null, XPathResult.ANY_TYPE, null).iterateNext();

        if (node) {
          s = node.textContent.trim();
        }

        return s;
      }
    }]);

    return UtilXpath;
  }();

  var UtilJson = /*#__PURE__*/function () {
    function UtilJson() {
      _classCallCheck(this, UtilJson);
    }

    _createClass(UtilJson, null, [{
      key: "escape",
      value: // fix parse JSON failed if contains newline
      function escape(data) {
        //
        // DONT change following that complain by `vue-cli-service build`
        //   error  Unnecessary escape character: \"  no-useless-escape
        //
        var escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g;
        var meta = {
          // table of character substitutions
          '\b': '\\b',
          '\t': '\\t',
          '\n': '\\n',
          '\f': '\\f',
          '\r': '\\r',
          '"': '\\"',
          '\\': '\\\\'
        };
        escapable.lastIndex = 0;
        return escapable.test(data) ? '"' + data.replace(escapable, function (a) {
          var c = meta[a];
          return typeof c === 'string' ? c : "\\u" + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
        }) + '"' : '"' + data + '"';
      }
    }, {
      key: "parse",
      value: function parse(s) {
        try {
          return JSON.parse(s);
        } catch (SyntaxError) {
          return JSON.parse(UtilJson.escape(s));
        }
      }
    }]);

    return UtilJson;
  }();

  var UtilUrl = /*#__PURE__*/function () {
    function UtilUrl() {
      _classCallCheck(this, UtilUrl);
    }

    _createClass(UtilUrl, null, [{
      key: "params2querystring",
      value: function params2querystring(params) {
        if (!params) {
          return '';
        }

        return Object.keys(params).map(function (key) {
          return encodeURIComponent(key) + '=' + encodeURIComponent(params[key]);
        }).join('&');
      }
    }]);

    return UtilUrl;
  }();

  var UtilDom = /*#__PURE__*/function () {
    function UtilDom() {
      _classCallCheck(this, UtilDom);
    }

    _createClass(UtilDom, null, [{
      key: "insertAfter",
      value: function insertAfter(newNode, refNode) {
        refNode.parentNode.insertBefore(newNode, refNode.nextSibling);
      }
    }, {
      key: "insertBefore",
      value: function insertBefore(refNode, newNode) {
        refNode.parentNode.insertBefore(newNode, refNode);
      }
    }, {
      key: "addStyle",
      value: function addStyle(doc, styleString) {
        var style = doc.createElement('style');
        style.textContent = styleString;
        doc.head.append(style);
      }
    }, {
      key: "getElementBySelectors",
      value: function getElementBySelectors(doc, selectors) {
        for (var i = 0; i < selectors.length; i++) {
          var found = doc.querySelector(selectors[i]);

          if (found) {
            return found;
          }
        }
      }
    }]);

    return UtilDom;
  }();

  var cfgs = {
    apiUa: "us" ,
    apiToken: 'ext',
    env: "production" ,
    apiPrefix: 'https://jwks123.cn',
    urlPathSettings: '/api/ymd/v20200815/settings',
    urlPathPreviewBox: '/api/ymd/v20200815/db/2131459',
    keyExtTabMsgType: 'jwks123ext.tabs.onUpdated',
    keySettings: 'jwks123ext.settings'
  };

  var BoxSettings = /*#__PURE__*/function () {
    function BoxSettings() {
      _classCallCheck(this, BoxSettings);
    }

    _createClass(BoxSettings, null, [{
      key: "loadSettingsLocal",
      value: function loadSettingsLocal() {
        var rs = null;
        var value = localStorage.getItem(cfgs.keySettings);

        if (value) {
          rs = JSON.parse(value);
        }

        return rs;
      }
    }, {
      key: "saveSettingsLocal",
      value: function saveSettingsLocal(settings) {
        if (settings) {
          localStorage.setItem(cfgs.keySettings, JSON.stringify(settings));
        }
      }
    }, {
      key: "generateParamsFromSettings",
      value: function generateParamsFromSettings(settings) {
        var params = {// noratings: false,
          // nowatches: false,
          // nonarrs: false,
          // notools: false,
          // nods: '',
          // lang: ''
        };

        if (!settings) {
          return;
        }

        var nods = {};
        params.lang = settings.language === 'auto' ? '' : settings.language;
        settings.features.forEach(function (feature) {
          params['no' + feature.id] = !feature.enabled;

          if (feature.items) {
            feature.items.forEach(function (item) {
              if (!item.checked) {
                nods[item.id] = 1;
              }
            });
          }
        });
        params.nods = Object.keys(nods).join(",");
        return params;
      }
    }, {
      key: "getByFeatureSection",
      value: function getByFeatureSection(settingsLocal, _id) {
        if (!settingsLocal || !settingsLocal.features) {
          return;
        }

        for (var i = 0; i < settingsLocal.features.length; i++) {
          if (settingsLocal.features[i].id === _id) {
            return settingsLocal.features[i];
          }
        }
      }
    }, {
      key: "mergeSettings",
      value: function mergeSettings(settingsOnline, settingsLocal) {
        var merged = {};
        merged.languages = settingsOnline.languages;
        merged.language = settingsLocal && settingsLocal.language ? settingsLocal.language : settingsOnline.language;
        merged.features = [];
        var dsListLocal = {};
        settingsOnline.features.forEach(function (feature) {
          var featureLocal = BoxSettings.getByFeatureSection(settingsLocal, feature.id);

          if (featureLocal) {
            feature.enabled = featureLocal.enabled !== undefined ? featureLocal.enabled : feature.enabled;

            if (featureLocal.items) {
              featureLocal.items.forEach(function (item) {
                dsListLocal[item.id] = item.checked;
              });

              if (feature.items) {
                feature.items.forEach(function (item) {
                  item.checked = dsListLocal[item.id] !== undefined ? dsListLocal[item.id] : item.checked;
                });
              }
            }
          }

          merged.features.push(feature);
        });
        return merged;
      }
    }]);

    return BoxSettings;
  }();

  var NotImplementedError = new Error('not implemented');

  var SiteParser$6 = /*#__PURE__*/function () {
    function SiteParser(url, doc) {
      _classCallCheck(this, SiteParser);

      this.url = url;
      this.doc = doc;
      this.meta = {
        ds: '',
        pk: '',
        titlezhcn: '',
        titleenus: '',
        year: 0
      };
    } // subclass must overwrite followings


    _createClass(SiteParser, [{
      key: "parseMeta",
      value: function parseMeta() {
        this.meta.ds = this.__proto__.constructor.DataSource;
        this.meta.pk = SiteParser.parsePkFromUrl(this.__proto__.constructor.PatternParsePk, this.url);
        return this;
      } // DEPRECATED. use function injectTo instead.
      // inject HTML content into target web page

    }, {
      key: "injectToDom",
      value: function injectToDom(widgetBox) {
        throw NotImplementedError;
      } // inject HTML content into target web page by specify CSS selectors

    }, {
      key: "injectTo",
      value: function injectTo(cssSelectors, widget) {
        var _iterator = _createForOfIteratorHelper(cssSelectors),
            _step;

        try {
          for (_iterator.s(); !(_step = _iterator.n()).done;) {
            var cssSelector = _step.value;
            var nextTo = this.doc.querySelector(cssSelector);

            if (!nextTo) {
              console.warn('element cssSelector=' + cssSelector + ' not found');
              continue;
            }

            UtilDom.insertAfter(widget, nextTo);
            break;
          }
        } catch (err) {
          _iterator.e(err);
        } finally {
          _iterator.f();
        }
      }
    }], [{
      key: "isSupported",
      value: // RegExp or Array
      function isSupported(url) {
        // return boolean;
        throw NotImplementedError;
      }
    }, {
      key: "parsePkFromUrl",
      value: function parsePkFromUrl(pattern, url) {
        var rs = '';
        var typeName = UtilObj.getType(pattern);

        if (typeName === "RegExp") {
          rs = UtilStr.getValueFromReGroup(pattern, url);
        } else if (typeName === "Array") {
          var _iterator2 = _createForOfIteratorHelper(pattern),
              _step2;

          try {
            for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
              var re = _step2.value;
              rs = UtilStr.getValueFromReGroup(re, url);

              if (rs) {
                break;
              }
            }
          } catch (err) {
            _iterator2.e(err);
          } finally {
            _iterator2.f();
          }
        }

        return rs;
      }
    }]);

    return SiteParser;
  }();

  _defineProperty(SiteParser$6, "DataSource", "ymd");

  _defineProperty(SiteParser$6, "PatternParsePk", /foo/);

  var SiteParserDouban = /*#__PURE__*/function (_siteparser$SiteParse) {
    _inherits(SiteParserDouban, _siteparser$SiteParse);

    var _super = _createSuper(SiteParserDouban);

    function SiteParserDouban() {
      _classCallCheck(this, SiteParserDouban);

      return _super.apply(this, arguments);
    }

    _createClass(SiteParserDouban, [{
      key: "injectToDom",
      value: // DEPRECATED. use function injectTo instead.
      // inject HTML content into target web page
      function injectToDom(widget) {
        var nextTo = this.doc.getElementById('interest_sectl');

        if (!nextTo) {
          console.warn('#interest_sectl not found'); // exception for https://movie.douban.com/subject/3543690/

          var found = this.doc.getElementsByClassName('related-info');
          nextTo = found[0];

          if (!nextTo) {
            console.error('.related-info not found');
            return;
          }

          UtilDom.insertBefore(nextTo, widget);
        } else {
          UtilDom.insertAfter(widget, nextTo);
        }
      } // inject HTML content into target web page by specify CSS selectors

    }, {
      key: "injectTo",
      value: function injectTo(_, widget) {
        var nextTo = this.doc.getElementById('interest_sectl');

        if (!nextTo) {
          console.warn('#interest_sectl not found'); // exception for https://movie.douban.com/subject/3543690/

          var found = this.doc.getElementsByClassName('related-info');
          nextTo = found[0];

          if (!nextTo) {
            console.error('.related-info not found');
            return;
          }

          UtilDom.insertBefore(nextTo, widget);
        } else {
          UtilDom.insertAfter(widget, nextTo);
        }
      }
    }, {
      key: "parseMeta",
      value: function parseMeta() {
        _get(_getPrototypeOf(SiteParserDouban.prototype), "parseMeta", this).call(this);

        this.meta.titlezhcn = this.doc.title.split('(')[0].trim();
        var ldJson = UtilJson.parse(this.doc.querySelector('script[type="application/ld+json"]').text);

        if (ldJson.datePublished) {
          this.meta.year = parseInt(ldJson.datePublished.slice(0, 4));
        } else {
          var rs = UtilXpath.getValueFromXPathString(this.doc, '//span[@class="year"]/text()');

          if (rs) {
            // '(2008)' => 2008
            this.meta.year = parseInt(rs.slice(1, 5));
          }
        }

        return this;
      }
    }], [{
      key: "isSupported",
      value: function isSupported(url) {
        return SiteParserDouban.parsePkFromUrl(SiteParserDouban.PatternParsePk, url).length > 0;
      }
    }, {
      key: "isSupportedMeta",
      value: function isSupportedMeta(url, doc) {
        return new Promise(function (resolve, reject) {
          resolve(true);
        });
      }
    }]);

    return SiteParserDouban;
  }(SiteParser$6);

  _defineProperty(SiteParserDouban, "DataSource", "db");

  _defineProperty(SiteParserDouban, "PatternParsePk", /\/\/movie.douban.com\/subject\/(\d+)\//);

  var DataSource$5 = SiteParserDouban.DataSource;
  var SiteParser$5 = SiteParserDouban;

  var SiteParserIMDb = /*#__PURE__*/function (_siteparser$SiteParse) {
    _inherits(SiteParserIMDb, _siteparser$SiteParse);

    var _super = _createSuper(SiteParserIMDb);

    function SiteParserIMDb() {
      _classCallCheck(this, SiteParserIMDb);

      return _super.apply(this, arguments);
    }

    _createClass(SiteParserIMDb, [{
      key: "injectToDom",
      value: function injectToDom(widget) {
        var nextTo = this.doc.getElementsByClassName('title_block')[0];

        if (!nextTo) {
          nextTo = this.doc.querySelector('ul[data-testid=reviewContent-all-reviews]');
        }

        if (!nextTo) {
          console.warn('element nextTo not found');
          return;
        }

        UtilDom.insertAfter(widget, nextTo);
      }
    }, {
      key: "parseMeta",
      value: function parseMeta() {
        _get(_getPrototypeOf(SiteParserIMDb.prototype), "parseMeta", this).call(this);

        var ldJson = UtilJson.parse(this.doc.querySelector('script[type="application/ld+json"]').text);
        this.meta.titleenus = ldJson.name;

        if (ldJson.datePublished) {
          this.meta.year = parseInt(ldJson.datePublished.slice(0, 4));
        } else {
          var rs = UtilXpath.getValueFromXPathString(this.doc, '//*[@id="titleYear"]/a/text()');

          if (rs) {
            this.meta.year = parseInt(rs);
          }
        }

        return this;
      }
    }], [{
      key: "isSupported",
      value: function isSupported(url) {
        return SiteParserIMDb.parsePkFromUrl(SiteParserIMDb.PatternParsePk, url).length > 0;
      }
    }, {
      key: "isSupportedMeta",
      value: function isSupportedMeta(url, doc) {
        return new Promise(function (resolve, reject) {
          resolve(true);
        });
      }
    }]);

    return SiteParserIMDb;
  }(SiteParser$6);

  _defineProperty(SiteParserIMDb, "DataSource", "im");

  _defineProperty(SiteParserIMDb, "PatternParsePk", /\/\/www.imdb.com\/title\/(tt\d+)\//);

  var DataSource$4 = SiteParserIMDb.DataSource;
  var SiteParser$4 = SiteParserIMDb;

  var SiteParserRottenTomatoes = /*#__PURE__*/function (_siteparser$SiteParse) {
    _inherits(SiteParserRottenTomatoes, _siteparser$SiteParse);

    var _super = _createSuper(SiteParserRottenTomatoes);

    function SiteParserRottenTomatoes() {
      _classCallCheck(this, SiteParserRottenTomatoes);

      return _super.apply(this, arguments);
    }

    _createClass(SiteParserRottenTomatoes, [{
      key: "injectToDom",
      value: // DEPRECATED. use function injectTo instead.
      // inject HTML content into target web page
      function injectToDom(widget) {
        var _this = this;

        var retries = 0;
        var maxRetry = 20;
        var checker = setInterval(function () {
          var nextTo = UtilDom.getElementBySelectors(_this.doc, ['score-board', '.thumbnail-scoreboard-wrap']);

          if (nextTo) {
            UtilDom.insertAfter(widget, nextTo);
            clearInterval(checker);
          }

          retries += 1;

          if (retries > maxRetry) {
            clearInterval(checker);
          }
        }, 200);
      } // inject HTML content into target web page by specify CSS selectors

    }, {
      key: "injectTo",
      value: function injectTo(cssSelectors, widget) {
        var _this2 = this;

        var retries = 0;
        var maxRetry = 20;
        var checker = setInterval(function () {
          var nextTo = UtilDom.getElementBySelectors(_this2.doc, cssSelectors);

          if (nextTo) {
            UtilDom.insertAfter(widget, nextTo);
            clearInterval(checker);
          }

          retries += 1;

          if (retries > maxRetry) {
            clearInterval(checker);
          }
        }, 200);
      }
    }, {
      key: "parseMeta",
      value: function parseMeta() {
        _get(_getPrototypeOf(SiteParserRottenTomatoes.prototype), "parseMeta", this).call(this);

        var patternTitleRt = /([A-Za-z0-9\xC0-\xD6\xD8-\xf6\xf8:'?·,&\.\-–\/\+ ]*?) \(([A-Za-z0-9\xC0-\xD6\xD8-\xf6\xf8:'?·,&\.\-–\/\+ ]*?)\)/;
        var ldJson = UtilJson.parse(this.doc.querySelector('script[type="application/ld+json"]').text);
        var year = 0;
        var title = '';

        if (ldJson['@type'] === 'Movie') {
          var reTitleRt = new RegExp(patternTitleRt);
          var m = reTitleRt.exec(ldJson['name']);

          if (m) {
            var titleEn = m[1];
            var titleOrigin = m[2];

            if (titleOrigin.toLowerCase().indexOf('the') !== -1) {
              // case https://www.rottentomatoes.com/m/jian_guo_da_ye
              // 'Jian guo da ye (The Founding of a Republic)' => 'The Founding of a Republic'
              title = titleOrigin;
            } else {
              title = titleEn;
            }
          } else {
            title = ldJson['name'].trim();
          }

          var s = UtilXpath.getValueFromXPathString(this.doc, '//meta[@property="og:title"]/@content');
          var reYear = new RegExp(/\((\d{4})\)/);
          year = parseInt(UtilStr.getValueFromReGroup(reYear, s));
        } else if (ldJson['@type'] === 'TVSeries') {
          // https://www.rottentomatoes.com/tv/breaking_bad
          title = ldJson['name'].trim() + ' Season 1';
          year = new Date(Date.parse(ldJson['startDate'])).getFullYear(); // auto append season info `s01`

          if (this.meta.pk.indexOf('/') === -1) {
            this.meta.pk += '/s01';
          }
        } else if (ldJson['@type'] === 'TVSeason') {
          // case https://www.rottentomatoes.com/tv/breaking_bad/s01
          title = ldJson['name'].trim(); // case https://www.rottentomatoes.com/tv/chernobyl/s01
          // "Chernobyl: Miniseries"

          var tokenMiniSeries = ': Miniseries'; // case https://www.rottentomatoes.com/tv/bodyguard/s01
          // "Bodyguard: Series 1"

          var tokenSeries = ': Series '; // case https://www.rottentomatoes.com/tv/westworld/s03
          // "Westworld: Season 3"

          var tokenSeason = ': Season ';

          if (title.indexOf(tokenMiniSeries) !== -1) {
            // case https://www.rottentomatoes.com/tv/chernobyl/s01
            title = title.replace(': Miniseries', ' Season 1');
          } else if (title.indexOf(tokenSeries) !== -1) {
            title = title.replace(tokenSeries, ' Season ');
          } else if (title.indexOf(tokenSeason) !== -1) {
            title = title.replace(': ', ' ');
          } else {
            if (ldJson['seasonNumber'] && ldJson['seasonNumber'] > 0) {
              var splits = title.split(': ');
              title = splits[0].trim() + ' Season ' + ldJson['seasonNumber'];
            } else {
              title = title.replace(': ', ' ');
            }
          }

          year = new Date(Date.parse(ldJson['datePublished'])).getFullYear();
        } else if (ldJson['@type'] === 'TVEpisode') {
          // case https://www.rottentomatoes.com/tv/breaking_bad/s01/e01
          var pses = ldJson['partOfSeries'];
          var pson = ldJson['partOfSeason'];

          if (pses && pses.name && pson && pson.name) {
            title = pses.name + ' ' + pson.name;
          }

          year = new Date(Date.parse(ldJson['startDate'])).getFullYear();
        }

        this.meta.year = year;
        this.meta.titleenus = title;
        return this;
      }
    }], [{
      key: "isSupported",
      value: function isSupported(url) {
        return SiteParserRottenTomatoes.parsePkFromUrl(SiteParserRottenTomatoes.PatternParsePk, url).length > 0;
      }
    }, {
      key: "isSupportedMeta",
      value: function isSupportedMeta(url, doc) {
        return new Promise(function (resolve, reject) {
          resolve(true);
        });
      }
    }]);

    return SiteParserRottenTomatoes;
  }(SiteParser$6);

  _defineProperty(SiteParserRottenTomatoes, "DataSource", "rt");

  _defineProperty(SiteParserRottenTomatoes, "PatternParsePk", [/\/\/www.rottentomatoes.com\/m\/([\w\-_\/]+)/, /\/\/www.rottentomatoes.com\/tv\/([\w\-_\/]+)/]);

  var DataSource$3 = SiteParserRottenTomatoes.DataSource;
  var SiteParser$3 = SiteParserRottenTomatoes;

  var SiteParserMetaCritic = /*#__PURE__*/function (_siteparser$SiteParse) {
    _inherits(SiteParserMetaCritic, _siteparser$SiteParse);

    var _super = _createSuper(SiteParserMetaCritic);

    function SiteParserMetaCritic() {
      _classCallCheck(this, SiteParserMetaCritic);

      return _super.apply(this, arguments);
    }

    _createClass(SiteParserMetaCritic, [{
      key: "injectToDom",
      value: function injectToDom(widget) {
        var clsNames = ['ms_wrapper', 'us_wrapper'];

        for (var _i = 0, _clsNames = clsNames; _i < _clsNames.length; _i++) {
          var clsName = _clsNames[_i];
          var rs = this.doc.getElementsByClassName(clsName);

          if (rs && rs.length > 0) {
            var nextTo = rs[0];
            UtilDom.insertAfter(widget, nextTo);
            return;
          }
        }
      }
    }, {
      key: "parseMeta",
      value: function parseMeta() {
        _get(_getPrototypeOf(SiteParserMetaCritic.prototype), "parseMeta", this).call(this);

        var ldJson = UtilJson.parse(this.doc.querySelector('script[type="application/ld+json"]').text);
        var title = '';
        var year = 0;

        if (ldJson['@type'] === 'Movie') {
          // https://www.metacritic.com/movie/the-godfather-part-iii
          title = ldJson['name'].trim();
          year = new Date(Date.parse(ldJson['datePublished'])).getFullYear();
        } else if (ldJson['@type'] === 'TVSeries') {
          title = ldJson['name'].trim() + ' Season 1';
          year = new Date(Date.parse(ldJson['datePublished'])).getFullYear(); // auto append season info `s01`

          if (this.meta.pk.indexOf('/') === -1) {
            this.meta.pk += '/season-1';
          }
        } else if (ldJson['@type'] === 'TVSeason') {
          title = ldJson['name'].trim().replace(': ', ' ');
          year = new Date(Date.parse(ldJson['datePublished'])).getFullYear();
        } else if (ldJson['@type'] === 'TVEpisode') {
          title = UtilXpath.getValueFromXPathString(this.doc, '//title/text()').split(' Episode ')[0].trim();
          title = title.replace(' - ', ' ');
          year = new Date(Date.parse(ldJson['datePublished'])).getFullYear();
        }

        this.meta.year = year;
        this.meta.titleenus = title;
        return this;
      }
    }], [{
      key: "isSupported",
      value: function isSupported(url) {
        return SiteParserMetaCritic.parsePkFromUrl(SiteParserMetaCritic.PatternParsePk, url).length > 0;
      }
    }, {
      key: "isSupportedMeta",
      value: function isSupportedMeta(url, doc) {
        return new Promise(function (resolve, reject) {
          resolve(true);
        });
      }
    }]);

    return SiteParserMetaCritic;
  }(SiteParser$6);

  _defineProperty(SiteParserMetaCritic, "DataSource", "mc");

  _defineProperty(SiteParserMetaCritic, "PatternParsePk", [/\/\/www.metacritic.com\/movie\/([\w\-_\/]+)/, /\/\/www.metacritic.com\/tv\/([\w\-_\/]+)/]);

  var DataSource$2 = SiteParserMetaCritic.DataSource;
  var SiteParser$2 = SiteParserMetaCritic;

  var SiteParserBilibili = /*#__PURE__*/function (_siteparser$SiteParse) {
    _inherits(SiteParserBilibili, _siteparser$SiteParse);

    var _super = _createSuper(SiteParserBilibili);

    function SiteParserBilibili() {
      _classCallCheck(this, SiteParserBilibili);

      return _super.apply(this, arguments);
    }

    _createClass(SiteParserBilibili, [{
      key: "injectToDom",
      value: // DEPRECATED. use function injectTo instead.
      // inject HTML content into target web page
      function injectToDom(widget) {
        var _this = this;

        // there is a bug where raise `Uncaught DOMException: Failed to execute 'appendChild' on 'Node': This node type does not support this method.`
        var retries = 0;
        var maxRetry = 5;
        var checker = setInterval(function () {
          var nextTo = _this.doc.querySelector('#arc_toolbar_report');

          if (nextTo && _this.doc.querySelector('#reco_list')) {
            UtilDom.insertAfter(widget, nextTo);
            clearInterval(checker);
          }

          retries += 1;

          if (retries > maxRetry) {
            clearInterval(checker);
          }
        }, 3000);
      } // inject HTML content into target web page by specify CSS selectors

    }, {
      key: "injectTo",
      value: function injectTo(_, widget) {
        var _this2 = this;

        // there is a bug where raise `Uncaught DOMException: Failed to execute 'appendChild' on 'Node': This node type does not support this method.`
        var retries = 0;
        var maxRetry = 5;
        var checker = setInterval(function () {
          var nextTo = _this2.doc.querySelector('#arc_toolbar_report');

          if (nextTo && _this2.doc.querySelector('#reco_list')) {
            UtilDom.insertAfter(widget, nextTo);
            clearInterval(checker);
          }

          retries += 1;

          if (retries > maxRetry) {
            clearInterval(checker);
          }
        }, 3000);
      }
    }, {
      key: "parseMeta",
      value: function parseMeta() {
        _get(_getPrototypeOf(SiteParserBilibili.prototype), "parseMeta", this).call(this); // const ldJson = utils.UtilJson.parse(this.doc.querySelector('script[type="application/ld+json"]').text);
        // TODO: upload ldJson by authorizted user?


        return this;
      }
    }], [{
      key: "parseTags",
      value: function parseTags(url, doc) {
        return new Promise(function (resolve, reject) {
          var retries = 0;
          var maxRetries = 15;
          var checker = setInterval(function () {
            if (retries > maxRetries) {
              clearInterval(checker);
              reject(new Error('max retry'));
              return;
            }

            var eles = doc.querySelectorAll('.tag');

            if (eles.length === 0) {
              retries += 1;
              return;
            }

            clearInterval(checker);

            var _iterator = _createForOfIteratorHelper(eles),
                _step;

            try {
              for (_iterator.s(); !(_step = _iterator.n()).done;) {
                var e = _step.value;

                if (SiteParserBilibili.AcceptTags.indexOf(e.textContent) !== -1) {
                  resolve(true);
                  return;
                }
              }
            } catch (err) {
              _iterator.e(err);
            } finally {
              _iterator.f();
            }

            resolve(false);
          }, 200);
        });
      }
    }, {
      key: "isSupported",
      value: function isSupported(url, doc) {
        return SiteParserBilibili.parsePkFromUrl(SiteParserBilibili.PatternParsePk, url).length > 0;
      }
    }, {
      key: "isSupportedMeta",
      value: function isSupportedMeta(url, doc) {
        return new Promise(function (resolve, reject) {
          SiteParserBilibili.parseTags(url, doc).then(function (isSupported) {
            resolve(isSupported);
          }).catch(function (err) {
            reject(err);
          });
        });
      }
    }]);

    return SiteParserBilibili;
  }(SiteParser$6);

  _defineProperty(SiteParserBilibili, "DataSource", "bi");

  _defineProperty(SiteParserBilibili, "PatternParsePk", [/\/\/www.bilibili.com\/video\/(\w+)/]);

  _defineProperty(SiteParserBilibili, "AcceptTags", ["影视杂谈", "电影解说", "电视剧解说"]);

  var DataSource$1 = SiteParserBilibili.DataSource;
  var SiteParser$1 = SiteParserBilibili;

  var SiteParserYoutube = /*#__PURE__*/function (_siteparser$SiteParse) {
    _inherits(SiteParserYoutube, _siteparser$SiteParse);

    var _super = _createSuper(SiteParserYoutube);

    function SiteParserYoutube() {
      _classCallCheck(this, SiteParserYoutube);

      return _super.apply(this, arguments);
    }

    _createClass(SiteParserYoutube, [{
      key: "injectToDom",
      value: function injectToDom(widget) {
        var _this = this;

        var retries = 0;
        var maxRetry = 20;
        var checker = setInterval(function () {
          var nextTo = _this.doc.querySelector('#info-contents');

          if (nextTo) {
            UtilDom.insertAfter(widget, nextTo);
            clearInterval(checker);
          }

          retries += 1;

          if (retries > maxRetry) {
            clearInterval(checker);
          }
        }, 200);
      }
    }, {
      key: "parseMeta",
      value: function parseMeta() {
        _get(_getPrototypeOf(SiteParserYoutube.prototype), "parseMeta", this).call(this); // const ldJson = utils.UtilJson.parse(this.doc.querySelector('script[type="application/ld+json"]').text);
        // TODO: upload ldJson by authorizted user?


        return this;
      }
    }], [{
      key: "isSupported",
      value: function isSupported(url) {
        return SiteParserYoutube.parsePkFromUrl(SiteParserYoutube.PatternParsePk, url).length > 0;
      }
    }, {
      key: "isSupportedMeta",
      value: function isSupportedMeta(url, doc) {
        return new Promise(function (resolve, reject) {
          resolve(true);
        });
      }
    }]);

    return SiteParserYoutube;
  }(SiteParser$6);

  _defineProperty(SiteParserYoutube, "DataSource", "yt");

  _defineProperty(SiteParserYoutube, "PatternParsePk", [/\/\/www.youtube.com\/watch\?v=([0-9a-z-_]+)/i]);

  var DataSource = SiteParserYoutube.DataSource;
  var SiteParser = SiteParserYoutube;

  var SiteParserNetflix = /*#__PURE__*/function (_siteparser$SiteParse) {
    _inherits(SiteParserNetflix, _siteparser$SiteParse);

    var _super = _createSuper(SiteParserNetflix);

    function SiteParserNetflix() {
      _classCallCheck(this, SiteParserNetflix);

      return _super.apply(this, arguments);
    }

    _createClass(SiteParserNetflix, [{
      key: "injectToDom",
      value: function injectToDom(widget) {
        var nextTo = this.doc.querySelector('.buttonControls--container');

        if (!nextTo) {
          return;
        }

        UtilDom.insertAfter(widget, nextTo);
      }
    }, {
      key: "parseMeta",
      value: function parseMeta() {
        _get(_getPrototypeOf(SiteParserNetflix.prototype), "parseMeta", this).call(this);

        this.meta.pk = SiteParserNetflix.parsePkFromUrl(SiteParserNetflix.PatternParsePk, this.url);

        if (!this.doc) {
          return this;
        }

        var hasLdJson = this.doc.querySelector('script[type="application/ld+json"]');

        if (!hasLdJson) {
          return this;
        }

        var ldJson = UtilJson.parse(hasLdJson.text);
        var title = '';
        var year = 0;

        if (ldJson['@type'] === 'Movie') {
          title = ldJson['name'].trim();
          year = new Date(ldJson['dateCreated']).getFullYear();
        } else if (ldJson['@type'] === 'TVSeries') {
          title = ldJson['name'].trim();
          year = new Date(ldJson['startDate']).getFullYear();
        }

        this.meta.year = year;
        this.meta.titleenus = title;
        return this;
      }
    }], [{
      key: "isSupported",
      value: function isSupported(url) {
        var _iterator = _createForOfIteratorHelper(SiteParserNetflix.PatternParsePk),
            _step;

        try {
          for (_iterator.s(); !(_step = _iterator.n()).done;) {
            var pattern = _step.value;

            if (pattern.test(url)) {
              return true;
            }
          }
        } catch (err) {
          _iterator.e(err);
        } finally {
          _iterator.f();
        }

        return false;
      }
    }, {
      key: "isSupportedMeta",
      value: function isSupportedMeta(url, doc) {
        return new Promise(function (resolve, reject) {
          resolve(true);
        });
      }
    }, {
      key: "parsePkFromUrl",
      value: function parsePkFromUrl(pattern, url) {
        var rs = '';

        var _iterator2 = _createForOfIteratorHelper(SiteParserNetflix.PatternParsePk),
            _step2;

        try {
          for (_iterator2.s(); !(_step2 = _iterator2.n()).done;) {
            var _pattern = _step2.value;
            rs = UtilStr.getValueFromReGroup(_pattern, url);

            if (rs) {
              break;
            } else if (_pattern.test(url)) {
              var qsParams = new URLSearchParams(url.split('?')[1]);
              var jbv = qsParams.get('jbv');

              if (jbv) {
                rs = jbv;
                break;
              }
            }
          }
        } catch (err) {
          _iterator2.e(err);
        } finally {
          _iterator2.f();
        }

        return rs;
      }
    }]);

    return SiteParserNetflix;
  }(SiteParser$6);

  _defineProperty(SiteParserNetflix, "DataSource", "nx");

  _defineProperty(SiteParserNetflix, "PatternParsePk", [/\/\/www.netflix.com\/.+/, /\/\/www.netflix.com\/.+\/title\/([\w\-_\/]+)/, /\/\/www.netflix.com\/title\/([\w\-_\/]+)/]);

  var Box = /*#__PURE__*/function () {
    function Box(settings) {
      _classCallCheck(this, Box);

      this.settings = settings;
    }

    _createClass(Box, [{
      key: "getOrNewWidget",
      value: function getOrNewWidget(doc) {
        var elementId = 'ymd-boxes';
        var widget = doc.getElementById(elementId);

        if (widget) {
          widget.innerHTML = '';
          return widget;
        }

        widget = doc.createElement('span');
        widget.setAttribute('id', elementId);
        return widget;
      }
    }, {
      key: "getBox",
      value: function getBox(meta) {
        var _this = this;

        return new Promise(function (resolve, reject) {
          var ds = meta.ds;
          var pk = meta.pk; // auto escape it for RottenTomatoes and MetaCritic data sources

          pk = pk.replace('/', '|');
          var url = _this.getApiPrefix() + "/api/ymd/v20200815/".concat(ds, "/").concat(pk);
          var qsParams = {};

          if (_this.settings) {
            qsParams = BoxSettings.generateParamsFromSettings(_this.settings);
          }

          if (meta.titlezhcn) {
            qsParams['titlezhcn'] = meta.titlezhcn;
          }

          if (meta.titleenus) {
            qsParams['titleenus'] = meta.titleenus;
          }

          if (meta.year > 0 && meta.year !== 1970) {
            qsParams['year'] = meta.year;
          }

          qsParams['ua'] = cfgs.apiUa;
          var qs = UtilUrl.params2querystring(qsParams);

          if (qs && qs.length > 0) {
            url += '?' + qs;
          }

          var headers = {};

          var apiToken = _this.getApiToken();

          if (apiToken) {
            headers['authorization'] = 'Bearer ' + apiToken;
          }

          if (window.GM && window.GM.xmlHttpRequest) {
            var options = {
              method: 'GET',
              url: url,
              headers: headers,
              onload: function onload(xhr) {
                try {
                  resolve(JSON.parse(xhr.responseText));
                } catch (err) {
                  console.error(err);
                  reject(err);
                }
              },
              onerror: function onerror(xhr) {
                console.error(xhr);
                reject(new Error('GM.xmlHttpRequest failed'));
              }
            };
            window.GM.xmlHttpRequest(options);
          } else {
            var _headers = {};

            var _apiToken = _this.getApiToken();

            if (_apiToken) {
              _headers['authorization'] = 'Bearer ' + _apiToken;
            }

            chrome.runtime.sendMessage({
              contentScriptQuery: "getJson",
              fullUrl: url,
              headers: _headers
            }, function (obj) {
              if (obj.err) {
                reject(obj.err);
              } else {
                resolve(obj.rs);
              }
            });
          }
        });
      }
    }, {
      key: "getApiToken",
      value: function getApiToken() {
        return this.settings && this.settings.apiToken ? this.settings.apiToken : cfgs.apiToken;
      }
    }, {
      key: "getApiPrefix",
      value: function getApiPrefix() {
        var apiPrefix = this.settings && this.settings.apiPrefix ? this.settings.apiPrefix : cfgs.apiPrefix;

        if (apiPrefix.indexOf('http') === -1) {
          if (apiPrefix[0] >= 'a' && apiPrefix[0] <= 'z') {
            // host is a domain
            apiPrefix = "https://" + apiPrefix;
          } else {
            // host is a ip address
            apiPrefix = "http://" + apiPrefix;
          }
        }

        return apiPrefix;
      }
    }], [{
      key: "getSupportedSites",
      value: function getSupportedSites() {
        var rs = {};
        rs[DataSource$5] = SiteParser$5;
        rs[DataSource$4] = SiteParser$4;
        rs[DataSource$3] = SiteParser$3;
        rs[DataSource$2] = SiteParser$2;
        rs[DataSource$1] = SiteParser$1;
        rs[DataSource] = SiteParser; // rs[siteparsernx.DataSource] = siteparsernx.SiteParser;

        return rs;
      }
    }]);

    return Box;
  }();
  var getSupportedSites = Box.getSupportedSites;

  function updateDomForUrlChanged(url, doc, settings) {
    var supported = getSupportedSites();
    var mybox = new Box(settings);

    var _loop = function _loop() {
      var Parser = _Object$values[_i];

      if (!Parser.isSupported(url, doc)) {
        return "continue";
      }

      Parser.isSupportedMeta(url, doc).then(function (isSupported) {
        if (!isSupported) {
          return;
        }

        var parser = new Parser(url, doc);
        var meta = parser.parseMeta().meta;
        var widget = mybox.getOrNewWidget(doc);
        mybox.getBox(meta).then(function (rsInJson) {
          var injectSelectors;

          if (parseInt(rsInJson.code / 100) === 2) {
            var movMeta = rsInJson.data;
            widget.innerHTML = movMeta.content;
            UtilDom.addStyle(doc, movMeta.style);
            injectSelectors = movMeta.injectSelectors;
          } else if (rsInJson.code === 404) {
            //
            // TODO: show rediret link to submit data by manual
            //
            // const fullUrl = window.location.href;
            // widget.innerHTML = `<p>数据暂缺,<a href="https://jwks123.cn/submit/?url=${fullUrl}" target="_blank">我补</a></p>`;
            return;
          } else {
            console.warn('rs ' + JSON.stringify(rsInJson));
            widget.innerHTML = "<p>\u63A5\u53E3\u8FD4\u56DE\u5F02\u5E38\uFF0C\u7A0D\u540E\u91CD\u8BD5\u3002</p>";
          }

          if (injectSelectors) {
            parser.injectTo(injectSelectors, widget);
          } else {
            parser.injectToDom(widget);
          }
        }).catch(function (err) {
          console.error(err); //
          // TODO: migrate sentry service
          //
          // widget.innerHTML = `<p>内部错误,稍后重试。</p>`;
          // parser.injectToDom(widget);
        });
      }).catch(function (err) {
        console.error(err);
      });
    };

    for (var _i = 0, _Object$values = Object.values(supported); _i < _Object$values.length; _i++) {
      var _ret = _loop();

      if (_ret === "continue") continue;
    }
  }

  function main() {
    if (window.chrome && window.chrome.runtime && window.chrome.runtime.onMessage) {
      window.chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) {
        if (message.msgType === cfgs.keyExtTabMsgType) {
          updateDomForUrlChanged(window.location.toString(), window.document, message[cfgs.keySettings]);
        }
      });
    } else {
      updateDomForUrlChanged(window.location.toString(), window.document);
    }
  }

  main(); // watch url change via click right column video
  // https://stackoverflow.com/questions/6390341/how-to-detect-if-url-has-changed-after-hash-in-javascript

  if (document.location.href.indexOf('bilibili.com') !== -1) {
    history.pushState = function (f) {
      return function pushState() {
        var ret = f.apply(this, arguments);
        window.dispatchEvent(new Event('pushstate'));
        window.dispatchEvent(new Event('locationchange'));
        return ret;
      };
    }(history.pushState);

    history.replaceState = function (f) {
      return function replaceState() {
        var ret = f.apply(this, arguments);
        window.dispatchEvent(new Event('replacestate'));
        window.dispatchEvent(new Event('locationchange'));
        return ret;
      };
    }(history.replaceState);

    window.addEventListener('popstate', function () {
      window.dispatchEvent(new Event('locationchange'));
    });
    window.addEventListener('locationchange', function () {
      main();
    });
  }

}());