番组迷你

bangumini↗↗

目前為 2023-11-13 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

var BanguminiUserscript = function() {
  "use strict";/*!
// ==UserScript==
// @name              番组迷你
// @version           1.0.1
// @description       bangumini↗↗
// @author            Vick Scarlet([email protected])
// @namespace         https://b38.dev
// @require           https://unpkg.com/echarts
// @match             *://bgm.tv/*
// @match             *://bangumi.tv/*
// @match             *://chii.in/*
// @grant             GM_addStyle
// @license           MIT
// ==/UserScript==
*/

  GM_addStyle("");
  const FormerName = {
    name: "former_name",
    indexes: [
      {
        name: "_user_tml",
        keyPath: ["user", "tml"],
        options: { unique: true }
      }
    ]
  };
  const ProgressActivity = {
    name: "progress_activity",
    indexes: [
      {
        name: "_user_time",
        keyPath: ["user", "time"],
        options: { unique: true }
      }
    ]
  };
  const models = [FormerName, ProgressActivity];
  let instance = null;
  async function initDb(name, version) {
    if (instance)
      return instance;
    return new Promise((resolve, reject) => {
      const req = indexedDB.open(name, version);
      req.addEventListener("error", () => reject(req.error));
      req.addEventListener("success", () => {
        instance = req.result;
        resolve(req.result);
      });
      req.addEventListener("upgradeneeded", () => {
        for (const model of models) {
          if (req.result.objectStoreNames.contains(model.name))
            continue;
          const store = req.result.createObjectStore(model.name);
          for (const index of model.indexes)
            store.createIndex(index.name, index.keyPath, index.options);
        }
      });
    });
  }
  function lexer(str) {
    var tokens = [];
    var i = 0;
    while (i < str.length) {
      var char = str[i];
      if (char === "*" || char === "+" || char === "?") {
        tokens.push({ type: "MODIFIER", index: i, value: str[i++] });
        continue;
      }
      if (char === "\\") {
        tokens.push({ type: "ESCAPED_CHAR", index: i++, value: str[i++] });
        continue;
      }
      if (char === "{") {
        tokens.push({ type: "OPEN", index: i, value: str[i++] });
        continue;
      }
      if (char === "}") {
        tokens.push({ type: "CLOSE", index: i, value: str[i++] });
        continue;
      }
      if (char === ":") {
        var name = "";
        var j = i + 1;
        while (j < str.length) {
          var code = str.charCodeAt(j);
          if (
            // `0-9`
            code >= 48 && code <= 57 || // `A-Z`
            code >= 65 && code <= 90 || // `a-z`
            code >= 97 && code <= 122 || // `_`
            code === 95
          ) {
            name += str[j++];
            continue;
          }
          break;
        }
        if (!name)
          throw new TypeError("Missing parameter name at ".concat(i));
        tokens.push({ type: "NAME", index: i, value: name });
        i = j;
        continue;
      }
      if (char === "(") {
        var count = 1;
        var pattern = "";
        var j = i + 1;
        if (str[j] === "?") {
          throw new TypeError('Pattern cannot start with "?" at '.concat(j));
        }
        while (j < str.length) {
          if (str[j] === "\\") {
            pattern += str[j++] + str[j++];
            continue;
          }
          if (str[j] === ")") {
            count--;
            if (count === 0) {
              j++;
              break;
            }
          } else if (str[j] === "(") {
            count++;
            if (str[j + 1] !== "?") {
              throw new TypeError("Capturing groups are not allowed at ".concat(j));
            }
          }
          pattern += str[j++];
        }
        if (count)
          throw new TypeError("Unbalanced pattern at ".concat(i));
        if (!pattern)
          throw new TypeError("Missing pattern at ".concat(i));
        tokens.push({ type: "PATTERN", index: i, value: pattern });
        i = j;
        continue;
      }
      tokens.push({ type: "CHAR", index: i, value: str[i++] });
    }
    tokens.push({ type: "END", index: i, value: "" });
    return tokens;
  }
  function parse(str, options) {
    if (options === void 0) {
      options = {};
    }
    var tokens = lexer(str);
    var _a = options.prefixes, prefixes = _a === void 0 ? "./" : _a;
    var defaultPattern = "[^".concat(escapeString(options.delimiter || "/#?"), "]+?");
    var result = [];
    var key = 0;
    var i = 0;
    var path = "";
    var tryConsume = function(type) {
      if (i < tokens.length && tokens[i].type === type)
        return tokens[i++].value;
    };
    var mustConsume = function(type) {
      var value2 = tryConsume(type);
      if (value2 !== void 0)
        return value2;
      var _a2 = tokens[i], nextType = _a2.type, index = _a2.index;
      throw new TypeError("Unexpected ".concat(nextType, " at ").concat(index, ", expected ").concat(type));
    };
    var consumeText = function() {
      var result2 = "";
      var value2;
      while (value2 = tryConsume("CHAR") || tryConsume("ESCAPED_CHAR")) {
        result2 += value2;
      }
      return result2;
    };
    while (i < tokens.length) {
      var char = tryConsume("CHAR");
      var name = tryConsume("NAME");
      var pattern = tryConsume("PATTERN");
      if (name || pattern) {
        var prefix = char || "";
        if (prefixes.indexOf(prefix) === -1) {
          path += prefix;
          prefix = "";
        }
        if (path) {
          result.push(path);
          path = "";
        }
        result.push({
          name: name || key++,
          prefix,
          suffix: "",
          pattern: pattern || defaultPattern,
          modifier: tryConsume("MODIFIER") || ""
        });
        continue;
      }
      var value = char || tryConsume("ESCAPED_CHAR");
      if (value) {
        path += value;
        continue;
      }
      if (path) {
        result.push(path);
        path = "";
      }
      var open = tryConsume("OPEN");
      if (open) {
        var prefix = consumeText();
        var name_1 = tryConsume("NAME") || "";
        var pattern_1 = tryConsume("PATTERN") || "";
        var suffix = consumeText();
        mustConsume("CLOSE");
        result.push({
          name: name_1 || (pattern_1 ? key++ : ""),
          pattern: name_1 && !pattern_1 ? defaultPattern : pattern_1,
          prefix,
          suffix,
          modifier: tryConsume("MODIFIER") || ""
        });
        continue;
      }
      mustConsume("END");
    }
    return result;
  }
  function match(str, options) {
    var keys = [];
    var re = pathToRegexp(str, keys, options);
    return regexpToFunction(re, keys, options);
  }
  function regexpToFunction(re, keys, options) {
    if (options === void 0) {
      options = {};
    }
    var _a = options.decode, decode = _a === void 0 ? function(x) {
      return x;
    } : _a;
    return function(pathname) {
      var m2 = re.exec(pathname);
      if (!m2)
        return false;
      var path = m2[0], index = m2.index;
      var params = /* @__PURE__ */ Object.create(null);
      var _loop_1 = function(i2) {
        if (m2[i2] === void 0)
          return "continue";
        var key = keys[i2 - 1];
        if (key.modifier === "*" || key.modifier === "+") {
          params[key.name] = m2[i2].split(key.prefix + key.suffix).map(function(value) {
            return decode(value, key);
          });
        } else {
          params[key.name] = decode(m2[i2], key);
        }
      };
      for (var i = 1; i < m2.length; i++) {
        _loop_1(i);
      }
      return { path, index, params };
    };
  }
  function escapeString(str) {
    return str.replace(/([.+*?=^!:${}()[\]|/\\])/g, "\\$1");
  }
  function flags(options) {
    return options && options.sensitive ? "" : "i";
  }
  function regexpToRegexp(path, keys) {
    if (!keys)
      return path;
    var groupsRegex = /\((?:\?<(.*?)>)?(?!\?)/g;
    var index = 0;
    var execResult = groupsRegex.exec(path.source);
    while (execResult) {
      keys.push({
        // Use parenthesized substring match if available, index otherwise
        name: execResult[1] || index++,
        prefix: "",
        suffix: "",
        modifier: "",
        pattern: ""
      });
      execResult = groupsRegex.exec(path.source);
    }
    return path;
  }
  function arrayToRegexp(paths, keys, options) {
    var parts = paths.map(function(path) {
      return pathToRegexp(path, keys, options).source;
    });
    return new RegExp("(?:".concat(parts.join("|"), ")"), flags(options));
  }
  function stringToRegexp(path, keys, options) {
    return tokensToRegexp(parse(path, options), keys, options);
  }
  function tokensToRegexp(tokens, keys, options) {
    if (options === void 0) {
      options = {};
    }
    var _a = options.strict, strict = _a === void 0 ? false : _a, _b = options.start, start = _b === void 0 ? true : _b, _c = options.end, end = _c === void 0 ? true : _c, _d = options.encode, encode = _d === void 0 ? function(x) {
      return x;
    } : _d, _e = options.delimiter, delimiter = _e === void 0 ? "/#?" : _e, _f = options.endsWith, endsWith = _f === void 0 ? "" : _f;
    var endsWithRe = "[".concat(escapeString(endsWith), "]|$");
    var delimiterRe = "[".concat(escapeString(delimiter), "]");
    var route = start ? "^" : "";
    for (var _i = 0, tokens_1 = tokens; _i < tokens_1.length; _i++) {
      var token = tokens_1[_i];
      if (typeof token === "string") {
        route += escapeString(encode(token));
      } else {
        var prefix = escapeString(encode(token.prefix));
        var suffix = escapeString(encode(token.suffix));
        if (token.pattern) {
          if (keys)
            keys.push(token);
          if (prefix || suffix) {
            if (token.modifier === "+" || token.modifier === "*") {
              var mod = token.modifier === "*" ? "?" : "";
              route += "(?:".concat(prefix, "((?:").concat(token.pattern, ")(?:").concat(suffix).concat(prefix, "(?:").concat(token.pattern, "))*)").concat(suffix, ")").concat(mod);
            } else {
              route += "(?:".concat(prefix, "(").concat(token.pattern, ")").concat(suffix, ")").concat(token.modifier);
            }
          } else {
            if (token.modifier === "+" || token.modifier === "*") {
              route += "((?:".concat(token.pattern, ")").concat(token.modifier, ")");
            } else {
              route += "(".concat(token.pattern, ")").concat(token.modifier);
            }
          }
        } else {
          route += "(?:".concat(prefix).concat(suffix, ")").concat(token.modifier);
        }
      }
    }
    if (end) {
      if (!strict)
        route += "".concat(delimiterRe, "?");
      route += !options.endsWith ? "$" : "(?=".concat(endsWithRe, ")");
    } else {
      var endToken = tokens[tokens.length - 1];
      var isEndDelimited = typeof endToken === "string" ? delimiterRe.indexOf(endToken[endToken.length - 1]) > -1 : endToken === void 0;
      if (!strict) {
        route += "(?:".concat(delimiterRe, "(?=").concat(endsWithRe, "))?");
      }
      if (!isEndDelimited) {
        route += "(?=".concat(delimiterRe, "|").concat(endsWithRe, ")");
      }
    }
    return new RegExp(route, flags(options));
  }
  function pathToRegexp(path, keys, options) {
    if (path instanceof RegExp)
      return regexpToRegexp(path, keys);
    if (Array.isArray(path))
      return arrayToRegexp(path, keys, options);
    return stringToRegexp(path, keys, options);
  }
  var o = {}, a = {};
  Object.defineProperty(a, "__esModule", { value: true });
  class p {
    constructor() {
      this.pullQueue = [], this.pushQueue = [], this.eventHandlers = {}, this.isPaused = false, this.isStopped = false;
    }
    push(t) {
      if (this.isStopped)
        return;
      const e = { value: t, done: false };
      if (this.pullQueue.length) {
        const r = this.pullQueue.shift();
        r && r.resolve(e);
      } else
        this.pushQueue.push(Promise.resolve(e)), this.highWaterMark !== void 0 && this.pushQueue.length >= this.highWaterMark && !this.isPaused && (this.isPaused = true, this.eventHandlers.highWater ? this.eventHandlers.highWater() : console && console.warn(`EventIterator queue reached ${this.pushQueue.length} items`));
    }
    stop() {
      if (!this.isStopped) {
        this.isStopped = true, this.remove();
        for (const t of this.pullQueue)
          t.resolve({ value: void 0, done: true });
        this.pullQueue.length = 0;
      }
    }
    fail(t) {
      if (!this.isStopped)
        if (this.isStopped = true, this.remove(), this.pullQueue.length) {
          for (const e of this.pullQueue)
            e.reject(t);
          this.pullQueue.length = 0;
        } else {
          const e = Promise.reject(t);
          e.catch(() => {
          }), this.pushQueue.push(e);
        }
    }
    remove() {
      Promise.resolve().then(() => {
        this.removeCallback && this.removeCallback();
      });
    }
    [Symbol.asyncIterator]() {
      return {
        next: (t) => {
          const e = this.pushQueue.shift();
          return e ? (this.lowWaterMark !== void 0 && this.pushQueue.length <= this.lowWaterMark && this.isPaused && (this.isPaused = false, this.eventHandlers.lowWater && this.eventHandlers.lowWater()), e) : this.isStopped ? Promise.resolve({ value: void 0, done: true }) : new Promise((r, s) => {
            this.pullQueue.push({ resolve: r, reject: s });
          });
        },
        return: () => (this.isStopped = true, this.pushQueue.length = 0, this.remove(), Promise.resolve({ value: void 0, done: true }))
      };
    }
  }
  class d {
    constructor(t, { highWaterMark: e = 100, lowWaterMark: r = 1 } = {}) {
      const s = new p();
      s.highWaterMark = e, s.lowWaterMark = r, s.removeCallback = t({
        push: (i) => s.push(i),
        stop: () => s.stop(),
        fail: (i) => s.fail(i),
        on: (i, u) => {
          s.eventHandlers[i] = u;
        }
      }) || (() => {
      }), this[Symbol.asyncIterator] = () => s[Symbol.asyncIterator](), Object.freeze(this);
    }
  }
  a.EventIterator = d;
  a.default = d;
  Object.defineProperty(o, "__esModule", { value: true });
  const h = a;
  o.EventIterator = h.EventIterator;
  function v(n, t, e) {
    return new h.EventIterator(({ push: r }) => (this.addEventListener(n, r, t), () => this.removeEventListener(n, r, t)), e);
  }
  o.subscribe = v;
  var m = o.default = h.EventIterator;
  const l = /* @__PURE__ */ new Map();
  async function* w(n) {
    const t = (() => {
      let e = null, r = new Promise((i) => e = i);
      const s = new EventSource(n);
      return s.addEventListener("message", (i) => {
        const u = e, c = JSON.parse(i.data);
        c.done ? (s.close(), r = null) : r = new Promise((f) => e = f), u == null || u(c);
      }), {
        next: () => r
      };
    })();
    for (; ; ) {
      const e = await t.next();
      if (!e || (yield e, e != null && e.done))
        break;
    }
  }
  function Q(n) {
    let t = l.get(n), e = !t;
    e && (t = /* @__PURE__ */ new Set(), l.set(n, t));
    const r = new m(({ push: s, stop: i }) => {
      t.add({ push: s, stop: i });
    });
    return e && Promise.resolve(w(n)).then(async (s) => {
      for await (const i of s)
        for (const u of t)
          u.push(i);
      for (const i of t)
        i.stop();
      l.delete(n);
    }), r;
  }
  const apiPath = (api) => `https://api.b38.dev/${api}`;
  const getProgressActivity = (user2) => Q(
    apiPath(`user/${user2}/progress_activity`)
  );
  function drawActivity(element) {
    const chart = echarts.init(element);
    const resize = () => chart.resize();
    const update = (raw, start, end) => {
      const date = [];
      const data = [];
      let time = new Date(end);
      while (time >= start) {
        const t = time.toISOString().slice(0, 10);
        date.push(t);
        data.push(raw.get(t) ?? 0);
        time.setDate(time.getDate() - 1);
      }
      chart.setOption({
        tooltip: { trigger: "axis" },
        xAxis: { type: "category", data: date },
        yAxis: { type: "value" },
        toolbox: { feature: { restore: {}, saveAsImage: {} } },
        dataZoom: [
          { type: "inside", start: 0, end: 100 },
          { start: 0, end: 10 }
        ],
        grid: { left: 35, right: 35, top: 10 },
        series: [
          { data, type: "bar", name: "格子数", itemStyle: { color: "#e19699" } }
        ]
      });
    };
    return { update, resize };
  }
  const style = `
.bangumini.progress_activity {
    position: relative;
    min-height: 60px;
    background-color: #444444;
    border-radius: 5px;
}
.bangumini.progress_activity h3 {
    margin: 0;
    padding: 10px;
    font-size: 14px;
    color: #ffffff;
}
.bangumini.canvas {
    width: 100%;
    height: 300px;
    margin: 10px 0;
}
.bangumini.button {
    position: absolute;
    right: 50%;
    top: 50%;
    transform: translate(50%, -50%);
    cursor: pointer;
    font-size: 12px;
    color: #ffffff;
    padding: 2px 10px;
    background-color: #e19699;
    border-radius: 100px;
}
`;
  function user$1(user2) {
    console.debug("this is", user2, "user page");
    $("head").append(`<style>${style}</style>`);
    const element = $(`<div class="bangumini progress_activity"><h3>格子活跃</h3></div>`);
    const canvas = $(`<div class="bangumini canvas"></div>`).hide();
    const button = $(`<div class="bangumini button chiiBtn">让我看看</div>`);
    element.append(canvas);
    element.append(button);
    $("#user_home .user_box").prepend(element);
    const { update, resize } = drawActivity(canvas[0]);
    button.on("click", async () => {
      button.hide();
      canvas.show();
      resize();
      const raw = /* @__PURE__ */ new Map();
      let start = /* @__PURE__ */ new Date("3000-1-1");
      let end = /* @__PURE__ */ new Date(0);
      const source = getProgressActivity(user2);
      for await (const { data } of source)
        if (data) {
          for (const { time, activity } of data) {
            const old = raw.get(time) ?? 0;
            raw.set(time, old + activity);
            const t = new Date(time);
            if (t < start)
              start = t;
            if (t > end)
              end = t;
          }
          update(raw, start, end);
        }
    });
  }
  const user = () => new Router().add("/:user", async (ctx) => {
    user$1(ctx.params.user);
  }).routes();
  class Router {
    constructor() {
      this._routes = /* @__PURE__ */ new Map();
    }
    routes() {
      const routes = (path, ctx, from = "") => {
        if (!ctx) {
          const re = /(https?:\/\/[^\/?]+)(\/[^?]*)?(\?.*)?/.exec(location.href);
          if (!re)
            throw new Error("Invalid location");
          const req = { origin: re[1], path, query: re[3] ?? "" };
          const query = Object.fromEntries(new URLSearchParams(req.query).entries());
          ctx = { req, path: re[2] ?? "/", params: {}, query };
        }
        return this.dispatch(path, ctx, from);
      };
      return routes;
    }
    add(path, callback) {
      this._routes.set(path, {
        sub: false,
        path,
        callback
      });
      return this;
    }
    use(path, router2) {
      this._routes.set(path, {
        sub: true,
        path,
        callback: router2
      });
      return this;
    }
    dispatch(path, ctx, from) {
      for (const route of this._routes.values()) {
        const m2 = from + route.path;
        const r = match(m2, { end: !route.sub })(path);
        if (!r)
          continue;
        if (route.sub) {
          const ret = route.callback(path, ctx, m2);
          if (ret)
            return true;
          continue;
        }
        Object.assign(ctx.params, r.params);
        route.callback(ctx);
        return true;
      }
      return false;
    }
  }
  const router = () => new Router().use("/user", user()).routes();
  async function main(config) {
    const { name, version } = config.database;
    await initDb(name, version);
    const routes = router();
    routes(location.pathname);
  }
  main({
    database: {
      name: "bangumini",
      version: 1
    }
  });
  return main;
}();