Greasy Fork is available in English.

番组迷你

bangumini↗↗

var BanguminiUserscript = function() {
  "use strict";/*!
// ==UserScript==
// @name              番组迷你
// @version           1.0.1
// @description       bangumini↗↗
// @author            Vick Scarlet(vick@syaro.io)
// @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;
}();