Greasy Fork

Greasy Fork is available in English.

番组迷你

bangumini↗↗

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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;
}();