Halcyon for Discord

A restrained, iOS-styled plugin layer for the Discord web client.

이 스크립트를 설치하려면 Tampermonkey, Greasemonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

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

이 스크립트를 설치하려면 Tampermonkey 또는 Violentmonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey 또는 Userscripts와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 Tampermonkey와 같은 확장 프로그램이 필요합니다.

이 스크립트를 설치하려면 유저 스크립트 관리자 확장 프로그램이 필요합니다.

(이미 유저 스크립트 관리자가 설치되어 있습니다. 설치를 진행합니다!)

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 Stylus와 같은 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

이 스타일을 설치하려면 유저 스타일 관리자 확장 프로그램이 필요합니다.

(이미 유저 스타일 관리자가 설치되어 있습니다. 설치를 진행합니다!)

// ==UserScript==
// @name         Halcyon for Discord
// @namespace    halcyon
// @version      0.6.1
// @description  A restrained, iOS-styled plugin layer for the Discord web client.
// @author       caitemm (mzrodyu)
// @match        *://*.discord.com/*
// @run-at       document-start
// @grant        none
// @license      GPL-3.0-or-later
// ==/UserScript==

"use strict";
var Halcyon = (() => {
  // src/core/logger/index.ts
  var WEIGHT = {
    debug: 10,
    info: 20,
    warn: 30,
    error: 40
  };
  var BADGE = {
    debug: "#8E8E93",
    info: "#0A84FF",
    warn: "#FF9F0A",
    error: "#FF453A"
  };
  var RING_CAPACITY = 500;
  var ring = [];
  var subscribers = /* @__PURE__ */ new Set();
  var threshold = false ? WEIGHT.debug : WEIGHT.info;
  function record(level, scope, parts) {
    const entry = { time: Date.now(), level, scope, parts };
    ring.push(entry);
    if (ring.length > RING_CAPACITY) ring.shift();
    for (const fn of subscribers) {
      try {
        fn(entry);
      } catch {
      }
    }
    if (WEIGHT[level] < threshold) return;
    const badge = `background:${BADGE[level]};color:#fff;border-radius:4px;padding:0 6px;font-weight:600`;
    const sink = level === "error" ? console.error : level === "warn" ? console.warn : console.log;
    sink(`%cHalcyon%c ${scope}`, badge, "color:inherit;font-weight:600", ...parts);
  }
  function logger(scope) {
    return {
      debug: (...p) => record("debug", scope, p),
      info: (...p) => record("info", scope, p),
      warn: (...p) => record("warn", scope, p),
      error: (...p) => record("error", scope, p),
      child: (childScope) => logger(`${scope}:${childScope}`)
    };
  }
  function getLogHistory() {
    return ring.slice();
  }
  function onLog(fn) {
    subscribers.add(fn);
    return () => subscribers.delete(fn);
  }

  // src/core/modules/webpack.ts
  var log = logger("modules");
  var CHUNK_KEY = "webpackChunkdiscord_app";
  var wpRequire;
  var ready = false;
  var interceptorInstalled = false;
  var waiters = /* @__PURE__ */ new Set();
  var sourcePatches = [];
  var selfResolver = () => void 0;
  function setSelfResolver(fn) {
    selfResolver = fn;
    globalThis.__halcyon_self__ = (id) => selfResolver(id);
  }
  function registerSourcePatch(patch) {
    sourcePatches.push({ ...patch, applied: false, hits: 0 });
  }
  function getSourcePatchReport() {
    return sourcePatches.map(({ pluginId, label, applied, hits }) => ({ pluginId, label, applied, hits }));
  }
  function installChunkInterceptor() {
    if (interceptorInstalled) return;
    interceptorInstalled = true;
    const target = globalThis;
    const existing = target[CHUNK_KEY] ?? [];
    const wrapPush = (underlying) => function(...args) {
      try {
        instrumentChunk(args[0]);
      } catch (err) {
        log.error("failed to instrument chunk", err);
      }
      return underlying.apply(this ?? existing, args);
    };
    const prior = existing.push;
    let currentPush = typeof prior === "function" && prior !== Array.prototype.push ? wrapPush(prior.bind(existing)) : Array.prototype.push.bind(existing);
    try {
      Object.defineProperty(existing, "push", {
        configurable: true,
        get: () => currentPush,
        set: (assigned) => {
          currentPush = wrapPush(assigned);
        }
      });
    } catch (err) {
      log.error("could not install chunk interceptor", err);
      return;
    }
    target[CHUNK_KEY] = existing;
    for (const chunk of existing) {
      try {
        instrumentChunk(chunk);
      } catch {
      }
    }
    existing.push([
      [Symbol("halcyon.require")],
      {},
      (req) => {
        wpRequire = req;
        try {
          wrapPendingFactories(req);
        } catch (err) {
          log.error("failed to wrap pre-existing factories", err);
        }
      }
    ]);
  }
  function wrapPendingFactories(req) {
    const factories = req?.m;
    if (!factories || typeof factories !== "object") return;
    let wrapped = 0;
    let alreadyRun = 0;
    for (const id of Object.keys(factories)) {
      const original = factories[id];
      if (typeof original !== "function" || original.__halcyon__) continue;
      if (req.c && req.c[id]) {
        alreadyRun++;
        continue;
      }
      factories[id] = wrapFactory(id, original);
      wrapped++;
    }
    if (wrapped || alreadyRun) {
      log.info(
        `swept pre-existing factories: wrapped ${wrapped}, skipped ${alreadyRun} already-executed`
      );
    }
  }
  function awaitCoreReady() {
    return new Promise((resolveReady) => {
      installChunkInterceptor();
      waitFor(
        (exp) => isFluxDispatcher(exp),
        () => {
          if (ready) return;
          ready = true;
          log.info("core runtime detected");
          resolveReady();
        }
      );
      setTimeout(() => {
        if (ready) return;
        log.warn("core module not seen within grace period; continuing degraded");
        ready = true;
        resolveReady();
      }, 15e3);
    });
  }
  function instrumentChunk(chunk) {
    const factories = chunk?.[1];
    if (!factories || typeof factories !== "object") return;
    for (const id of Object.keys(factories)) {
      const original = factories[id];
      if (typeof original !== "function" || original.__halcyon__) continue;
      factories[id] = wrapFactory(id, original);
    }
  }
  function wrapFactory(id, original) {
    let effective;
    const wrapped = function(module, exports, require2) {
      if (!effective) {
        const applicable = sourcePatches.filter((p) => sourceMatches(p.find, original));
        effective = applicable.length ? applyPatches(id, original, applicable) : original;
      }
      effective.call(this, module, exports, require2);
      try {
        dispatchToWaiters(module);
      } catch (err) {
        log.error("module observer threw for", id, err);
      }
    };
    wrapped.toString = () => original.toString();
    wrapped.__halcyon__ = true;
    return wrapped;
  }
  function applyPatches(id, original, patches) {
    let code = String(original);
    for (const patch of patches) {
      const before = code;
      const replacement = bindSelf(patch.replace, patch.pluginId);
      code = patch.all ? code.replace(new RegExp(patch.match.source, ensureGlobal(patch.match.flags)), replacement) : code.replace(patch.match, replacement);
      if (code === before) {
        log.warn(`patch "${patch.label}" (${patch.pluginId}) matched module ${id} but changed nothing`);
        continue;
      }
      patch.applied = true;
      patch.hits++;
      log.debug(`applied patch "${patch.label}" (${patch.pluginId}) to module ${id}`);
    }
    try {
      const rebuilt = (0, eval)(`(${toFunctionExpression(code)})`);
      return rebuilt;
    } catch (err) {
      log.error(`patched module ${id} failed to compile; using original`, err);
      return original;
    }
  }
  function toFunctionExpression(src) {
    const s = src.trimStart();
    if (/^(async\s+)?function[\s*(]/.test(s)) return s;
    if (/^(async\s+)?(\([^)]*\)|[\w$]+)\s*=>/.test(s)) return s;
    const head = s.match(/^(async\s+)?(\*\s*)?(?:\[[^\]]*\]|[\w$]+)\s*\(/);
    if (head) {
      const asyncKw = head[1] ? "async " : "";
      const star = head[2] ? "*" : "";
      return `${asyncKw}function${star}${s.slice(head[0].length - 1)}`;
    }
    return s;
  }
  function ensureGlobal(flags) {
    return flags.includes("g") ? flags : flags + "g";
  }
  function bindSelf(replace, pluginId) {
    const token = `__halcyon_self__(${JSON.stringify(pluginId)})`;
    if (typeof replace === "string") {
      return replace.split("$self").join(token);
    }
    return (...args) => replace(...args).split("$self").join(token);
  }
  function sourceMatches(find2, factory) {
    const src = factory.toString();
    return typeof find2 === "string" ? src.includes(find2) : find2.test(src);
  }
  var NESTED_SCAN_MAX_KEYS = 40;
  function matchExport(exp, filter, meta) {
    try {
      if (filter(exp, meta)) return exp;
    } catch {
    }
    if (typeof exp !== "object" && typeof exp !== "function") return void 0;
    let keys;
    try {
      keys = Object.keys(exp);
    } catch {
      return void 0;
    }
    if (keys.length > NESTED_SCAN_MAX_KEYS) return void 0;
    for (const key of keys) {
      let value;
      try {
        value = exp[key];
      } catch {
        continue;
      }
      if (value == null || typeof value !== "object" && typeof value !== "function") continue;
      try {
        if (filter(value, meta)) return value;
      } catch {
      }
    }
    return void 0;
  }
  function dispatchToWaiters(module) {
    if (!waiters.size) return;
    const exp = module.exports;
    if (exp == null) return;
    for (const waiter of waiters) {
      const hit = matchExport(exp, waiter.filter, { id: module.id, module });
      if (hit !== void 0) {
        waiters.delete(waiter);
        waiter.resolve(hit);
      }
    }
  }
  function find(filter) {
    if (!wpRequire) return void 0;
    for (const id of Object.keys(wpRequire.c)) {
      const module = wpRequire.c[id];
      const exp = module?.exports;
      if (exp == null || exp === globalThis) continue;
      const hit = matchExport(exp, filter, { id, module });
      if (hit !== void 0) return hit;
    }
    return void 0;
  }
  function findAll(filter) {
    const out = [];
    if (!wpRequire) return out;
    for (const id of Object.keys(wpRequire.c)) {
      const module = wpRequire.c[id];
      const exp = module?.exports;
      if (exp == null || exp === globalThis) continue;
      const hit = matchExport(exp, filter, { id, module });
      if (hit !== void 0) out.push(hit);
    }
    return out;
  }
  function findByProps(...props) {
    return find((exp) => props.every((p) => exp[p] !== void 0));
  }
  function findByCode(...needles) {
    return find((exp) => {
      if (typeof exp !== "function") return false;
      let src;
      try {
        src = Function.prototype.toString.call(exp);
      } catch {
        return false;
      }
      return needles.every((n) => src.includes(n));
    });
  }
  function findStore(name) {
    return find((exp) => exp?.getName?.() === name || exp?.constructor?.displayName === name);
  }
  function getAllStores() {
    const fluxModule = find(
      (exp) => typeof exp?.Store === "function" && typeof exp.Store.getAll === "function"
    );
    if (fluxModule) {
      try {
        const all = fluxModule.Store.getAll();
        if (Array.isArray(all) && all.length > 0) return all;
      } catch {
      }
    }
    return findAll(
      (exp) => typeof exp?.getName === "function" && typeof exp?.addChangeListener === "function" && // Reject Discord's answer-everything intl proxy (see UserStore).
      typeof exp?.__halcyon_probe__ === "undefined"
    );
  }
  function storeNames() {
    const names = /* @__PURE__ */ new Set();
    for (const store of getAllStores()) {
      try {
        const name = store?.getName?.();
        if (typeof name === "string" && name) names.add(name);
      } catch {
      }
    }
    return [...names].sort();
  }
  function findStoreByName(name) {
    const direct = findStore(name);
    if (direct) return direct;
    for (const store of getAllStores()) {
      try {
        if (store?.getName?.() === name || store?.constructor?.displayName === name) return store;
      } catch {
      }
    }
    return void 0;
  }
  function findStoreWithMethods(...methods) {
    for (const store of getAllStores()) {
      try {
        if (methods.every((m) => typeof store?.[m] === "function")) return store;
      } catch {
      }
    }
    return void 0;
  }
  function waitFor(filter, callback) {
    const existing = find(filter);
    if (existing !== void 0) {
      callback(existing);
      return;
    }
    waiters.add({ filter, resolve: callback });
  }
  function lazy(filter) {
    let resolved;
    const get = () => resolved ??= find(filter);
    return new Proxy(
      {},
      {
        get(_t, key) {
          const mod = get();
          if (mod == null) return void 0;
          const value = mod[key];
          return typeof value === "function" ? value.bind(mod) : value;
        },
        has(_t, key) {
          const mod = get();
          return mod != null && key in mod;
        }
      }
    );
  }
  function isReady() {
    return ready;
  }
  function isFluxDispatcher(exp) {
    return exp != null && typeof exp.dispatch === "function" && typeof exp.subscribe === "function" && (typeof exp._actionHandlers !== "undefined" || typeof exp._subscriptions !== "undefined" || typeof exp._waitQueue !== "undefined" || typeof exp.isDispatching === "function" || typeof exp.wait === "function");
  }
  function dumpFactorySource(needle, radius = 300) {
    const factories = wpRequire?.m;
    if (!factories) return "<webpack require not ready \u2014 open the target UI first>";
    const blocks = [];
    for (const id of Object.keys(factories)) {
      let src;
      try {
        src = String(factories[id]);
      } catch {
        continue;
      }
      if (!src.includes(needle)) continue;
      const slices = [];
      let idx = src.indexOf(needle);
      let hits = 0;
      while (idx >= 0 && hits < 4) {
        slices.push(src.slice(Math.max(0, idx - radius), idx + needle.length + radius));
        idx = src.indexOf(needle, idx + needle.length);
        hits++;
      }
      blocks.push(`===== module ${id} (${hits} hit${hits === 1 ? "" : "s"}) =====
${slices.join("\n  ...  \n")}`);
    }
    return blocks.length ? blocks.join("\n\n") : `<no loaded factory contains "${needle}">`;
  }
  function diagnoseSettings() {
    const patches = getSourcePatchReport();
    const dom = {
      // The wrapper EmbeddedView renders — proof the section is on screen.
      embedRendered: typeof document !== "undefined" && !!document.querySelector(".hc-embed"),
      // Any Halcyon surface mounted at all (embed or overlay).
      halcyonMounted: typeof document !== "undefined" && !!document.querySelector(".halcyon")
    };
    try {
      let start = null;
      const els = document.querySelectorAll("*");
      for (let i = 0; i < els.length && !start; i++) {
        const el = els[i];
        const key = Object.keys(el).find((k) => k.startsWith("__reactFiber$"));
        if (key) start = el[key];
      }
      if (!start) return JSON.stringify({ error: "no React fiber found in DOM", patches, dom }, null, 2);
      let root = start;
      while (root.return) root = root.return;
      const srcOf = (t) => {
        try {
          if (typeof t === "function") return Function.prototype.toString.call(t);
          if (t && typeof t === "object") {
            const inner = t.type || t.render;
            if (typeof inner === "function") return Function.prototype.toString.call(inner);
          }
        } catch {
        }
        return "";
      };
      const nameOf = (t) => t && (t.displayName || t.name) || t && t.type && (t.type.displayName || t.type.name) || "";
      const queue = [root];
      let walked = 0;
      const buildLayoutHits = [];
      const gpsHits = [];
      const sidebarComps = /* @__PURE__ */ new Set();
      const namedSettings = /* @__PURE__ */ new Set();
      while (queue.length && walked < 4e4) {
        const f = queue.shift();
        walked++;
        const t = f.type;
        if (t && (typeof t === "function" || typeof t === "object")) {
          const s = srcOf(t);
          const n = nameOf(t) || "anon";
          const patched = s.includes("__halcyon_self__");
          if (s.includes("buildLayout")) buildLayoutHits.push({ name: n, patched });
          if (s.includes("getPredicateSections")) gpsHits.push({ name: n, patched });
          if (s.includes("renderSidebar") || s.includes("SETTINGS_SIDEBAR")) sidebarComps.add(n);
          if (/settings/i.test(n)) namedSettings.add(n);
        }
        if (f.child) queue.push(f.child);
        if (f.sibling) queue.push(f.sibling);
      }
      const layoutPatch = patches.find((p) => p.label === "user-settings-layout");
      const sidebarPatch = patches.find((p) => p.label === "user-settings-sidebar");
      const verdict = dom.embedRendered ? "embed rendered \u2014 Halcyon section is on screen" : layoutPatch?.applied || sidebarPatch?.applied ? "patch applied at load but section not seen \u2014 open user settings, then re-run" : "no settings patch matched this build \u2014 run dumpSource('buildLayout') and share the output";
      return JSON.stringify(
        {
          verdict,
          dom,
          patches,
          walked,
          buildLayoutHits,
          gpsHits,
          sidebarComps: [...sidebarComps].slice(0, 25),
          settingsNamed: [...namedSettings].slice(0, 40)
        },
        null,
        2
      );
    } catch (err) {
      return JSON.stringify({ error: String(err), patches, dom }, null, 2);
    }
  }

  // src/core/common/react.ts
  function lazyProxy(resolve) {
    let cached2;
    const get = () => cached2 ??= resolve();
    return new Proxy(function() {
    }, {
      get: (_t, key) => get()?.[key],
      set: (_t, key, value) => {
        const mod = get();
        if (mod) mod[key] = value;
        return true;
      },
      has: (_t, key) => {
        const mod = get();
        return mod != null && key in mod;
      },
      ownKeys: () => Reflect.ownKeys(get() ?? {}),
      getOwnPropertyDescriptor: (_t, key) => Reflect.getOwnPropertyDescriptor(get() ?? {}, key),
      apply: (_t, thisArg, args) => get().apply(thisArg, args),
      construct: (_t, args) => new (get())(...args)
    });
  }
  function byFunctionProps(...props) {
    return (exp) => props.every((p) => typeof exp[p] === "function") && typeof exp.__halcyon_probe__ === "undefined";
  }
  var React = lazyProxy(
    () => find(byFunctionProps("createElement", "useState", "useEffect", "useMemo"))
  );
  var ReactDOM = lazyProxy(
    () => find(byFunctionProps("createPortal", "flushSync")) ?? find(byFunctionProps("createPortal"))
  );
  function getCreateRoot() {
    const mod = find(byFunctionProps("createRoot", "hydrateRoot")) ?? find(byFunctionProps("createRoot"));
    return mod?.createRoot?.bind(mod);
  }
  function mountDetached(element, container) {
    const createRoot = getCreateRoot();
    if (createRoot) {
      const root = createRoot(container);
      root.render(element);
      return () => {
        try {
          root.unmount();
        } catch {
        }
      };
    }
    ReactDOM.render(element, container);
    return () => {
      try {
        ReactDOM.unmountComponentAtNode(container);
      } catch {
      }
    };
  }
  function getFiber(node) {
    if (node == null || typeof node !== "object") return null;
    try {
      for (const key of Object.getOwnPropertyNames(node)) {
        if (key.startsWith("__reactFiber$") || key.startsWith("__reactInternalInstance$")) {
          return node[key];
        }
      }
    } catch {
    }
    return null;
  }
  function getFiberPropsChain(node, maxDepth = 30) {
    const out = [];
    let fiber = getFiber(node);
    for (let depth = 0; fiber != null && depth < maxDepth; depth++) {
      try {
        const props = fiber.memoizedProps ?? fiber.pendingProps;
        if (props != null && typeof props === "object") out.push(props);
        fiber = fiber.return;
      } catch {
        break;
      }
    }
    return out;
  }
  var useState = (...a) => React.useState(...a);
  var useEffect = (...a) => React.useEffect(...a);
  var useMemo = (...a) => React.useMemo(...a);
  var useRef = (...a) => React.useRef(...a);

  // src/userscript/install-storage.ts
  var g = globalThis;
  try {
    const ls = g.localStorage;
    if (ls) {
      const native = g.HalcyonNative ??= {};
      native.storage = {
        read: (key) => ls.getItem(key),
        write: (key, value) => ls.setItem(key, value),
        remove: (key) => ls.removeItem(key)
      };
    }
  } catch {
  }

  // src/core/settings/storage.ts
  var log2 = logger("settings");
  var PREFIX = "halcyon:";
  function selectBackend() {
    const native = globalThis.HalcyonNative?.storage;
    if (native && typeof native.read === "function" && typeof native.write === "function") {
      return native;
    }
    try {
      const ls = globalThis.localStorage;
      if (ls) {
        return {
          read: (k) => ls.getItem(k),
          write: (k, v) => ls.setItem(k, v),
          remove: (k) => ls.removeItem(k)
        };
      }
    } catch {
    }
    log2.warn("no persistent storage backend; settings will not survive a restart");
    const memory = /* @__PURE__ */ new Map();
    return {
      read: (k) => memory.get(k) ?? null,
      write: (k, v) => void memory.set(k, v),
      remove: (k) => void memory.delete(k)
    };
  }
  var backend = selectBackend();
  function loadNamespace(id) {
    const raw = backend.read(PREFIX + id);
    if (!raw) return {};
    try {
      const parsed = JSON.parse(raw);
      return parsed && typeof parsed === "object" ? parsed : {};
    } catch {
      const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
      try {
        backend.write(`${PREFIX}${id}.corrupt-${stamp}`, raw);
      } catch {
      }
      log2.warn(`stored settings for "${id}" were unreadable; reset to defaults (backup kept)`);
      return {};
    }
  }
  function saveNamespace(id, values) {
    try {
      backend.write(PREFIX + id, JSON.stringify(values));
    } catch (err) {
      log2.error(`could not persist settings for "${id}"`, err);
    }
  }
  var hintStore;
  try {
    hintStore = globalThis.localStorage;
  } catch {
    hintStore = void 0;
  }
  var HINT_PREFIX = "halcyon:hint:";
  function readSyncHint(id) {
    try {
      if (!hintStore) return void 0;
      const raw = hintStore.getItem(HINT_PREFIX + id);
      if (!raw) return void 0;
      const parsed = JSON.parse(raw);
      return parsed && typeof parsed === "object" ? parsed : void 0;
    } catch {
      return void 0;
    }
  }
  function writeSyncHint(id, values) {
    try {
      if (!hintStore) return;
      hintStore.setItem(HINT_PREFIX + id, JSON.stringify(values));
    } catch {
    }
  }

  // src/core/runtime.ts
  var log3 = logger("runtime");
  var ENABLED_NS = "core.enabled";
  var Runtime = class {
    records = /* @__PURE__ */ new Map();
    enabledMap = {};
    bootPatched = /* @__PURE__ */ new Set();
    listeners = /* @__PURE__ */ new Set();
    prepared = false;
    booted = false;
    register(plugin) {
      if (this.records.has(plugin.id)) {
        log3.warn(`duplicate plugin id "${plugin.id}" ignored`);
        return;
      }
      this.records.set(plugin.id, { plugin, state: "disabled" });
      plugin.settings?.__bind(plugin.id);
    }
    registerAll(plugins2) {
      for (const p of plugins2) this.register(p);
    }
    /**
     * Synchronous, storage-independent preparation. Wires up `$self` resolution,
     * registers source patches for everything that will run, and takes over
     * Webpack — all of which must be in place before Discord pushes its first
     * chunk, or a module can be cached in its unpatched form.
     *
     * The extension entry calls this ahead of its async storage handshake for
     * exactly that reason; the desktop entry reaches it synchronously through
     * boot(). Idempotent either way.
     */
    prepare() {
      if (this.prepared) return;
      this.prepared = true;
      setSelfResolver((id) => this.records.get(id)?.plugin);
      const hint = readSyncHint(ENABLED_NS) ?? {};
      const stored = loadNamespace(ENABLED_NS) ?? {};
      this.enabledMap = { ...hint, ...stored };
      this.registerBootPatches();
      installChunkInterceptor();
    }
    /** Boot sequence. Call once, as early as the renderer allows. */
    async boot() {
      if (this.booted) return;
      this.booted = true;
      this.prepare();
      this.enabledMap = loadNamespace(ENABLED_NS) ?? {};
      writeSyncHint(ENABLED_NS, this.enabledMap);
      for (const { plugin } of this.records.values()) {
        plugin.settings?.__bind(plugin.id);
      }
      this.registerBootPatches();
      await awaitCoreReady();
      for (const id of this.startOrder()) {
        if (this.shouldRun(id)) this.startPlugin(id);
      }
      this.emit();
      const build = true ? "2026-07-28 06:19:35" : "dev";
      log3.info(`runtime up \u2014 ${this.runningCount()} plugin(s) active (build ${build})`);
    }
    isEnabled(id) {
      const rec = this.records.get(id);
      if (!rec) return false;
      if (rec.plugin.required) return true;
      return this.enabledMap[id] === true;
    }
    enable(id) {
      const rec = this.records.get(id);
      if (!rec) return;
      for (const dep of rec.plugin.dependencies ?? []) {
        if (!this.isEnabled(dep)) this.enable(dep);
      }
      this.enabledMap[id] = true;
      this.persistEnabledState();
      if (this.booted && isReady()) this.startPlugin(id);
      this.emit();
    }
    disable(id) {
      const rec = this.records.get(id);
      if (!rec) return;
      if (rec.plugin.required) {
        log3.warn(`"${id}" is required and cannot be disabled`);
        return;
      }
      for (const [otherId, other] of this.records) {
        if (other.plugin.dependencies?.includes(id) && this.isEnabled(otherId)) {
          this.disable(otherId);
        }
      }
      this.enabledMap[id] = false;
      this.persistEnabledState();
      this.stopPlugin(id);
      this.emit();
    }
    toggle(id) {
      if (this.isEnabled(id)) {
        this.disable(id);
        return false;
      }
      this.enable(id);
      return true;
    }
    /**
     * Whether a restart is needed for a plugin's source patches to match its
     * current enable-state. Source patches only apply at module load, so toggling
     * a patch-bearing plugin after boot cannot take full effect until relaunch.
     */
    needsRestart(id) {
      const rec = this.records.get(id);
      if (!rec?.plugin.patches?.length) return false;
      return this.isEnabled(id) !== this.bootPatched.has(id);
    }
    getPlugin(id) {
      return this.records.get(id)?.plugin;
    }
    list() {
      return [...this.records.values()].map(({ plugin, state, error }) => ({
        id: plugin.id,
        name: plugin.name,
        description: plugin.description,
        category: plugin.category,
        authors: plugin.authors,
        required: plugin.required ?? false,
        hidden: plugin.hidden ?? false,
        enabled: this.isEnabled(plugin.id),
        state,
        error,
        hasSettings: plugin.settings != null,
        hasPage: plugin.page != null,
        needsRestart: this.needsRestart(plugin.id)
      }));
    }
    /** Subscribe to any registry change (enable/disable/state). */
    onChange(fn) {
      this.listeners.add(fn);
      return () => this.listeners.delete(fn);
    }
    // --- internals -----------------------------------------------------------
    shouldRun(id) {
      if (!this.isEnabled(id)) return false;
      const rec = this.records.get(id);
      if (!rec) return false;
      return (rec.plugin.dependencies ?? []).every((dep) => this.isEnabled(dep));
    }
    /**
     * Register source patches for every plugin that should run and hasn't been
     * patched yet. Safe to call more than once: the bootPatched guard keeps a
     * plugin from being registered twice across prepare() and boot().
     */
    registerBootPatches() {
      for (const { plugin } of this.records.values()) {
        if (this.shouldRun(plugin.id) && plugin.patches?.length && !this.bootPatched.has(plugin.id)) {
          this.registerPatches(plugin);
          this.bootPatched.add(plugin.id);
        }
      }
    }
    registerPatches(plugin) {
      for (const spec of plugin.patches ?? []) {
        const replacements = Array.isArray(spec.replacement) ? spec.replacement : [spec.replacement];
        for (const r of replacements) {
          registerSourcePatch({
            pluginId: plugin.id,
            label: spec.label,
            find: spec.find,
            match: r.match,
            replace: r.replace,
            all: spec.all ?? false
          });
        }
      }
    }
    startPlugin(id) {
      const rec = this.records.get(id);
      if (!rec || rec.state === "running" || rec.state === "starting") return;
      rec.state = "starting";
      try {
        rec.plugin.start?.();
        rec.state = "running";
        rec.error = void 0;
        log3.debug(`started "${id}"`);
      } catch (err) {
        rec.state = "errored";
        rec.error = err;
        this.enabledMap[id] = false;
        this.persistEnabledState();
        log3.error(`plugin "${id}" threw during start; it has been disabled`, err);
      }
      this.emit();
    }
    stopPlugin(id) {
      const rec = this.records.get(id);
      if (!rec || rec.state !== "running" && rec.state !== "errored") return;
      rec.state = "stopping";
      try {
        rec.plugin.stop?.();
        log3.debug(`stopped "${id}"`);
      } catch (err) {
        log3.error(`plugin "${id}" threw during stop; state may be inconsistent`, err);
      } finally {
        rec.state = "disabled";
        this.emit();
      }
    }
    /** Topological order over dependencies so a plugin starts after its deps. */
    startOrder() {
      const ordered = [];
      const seen = /* @__PURE__ */ new Set();
      const visit = (id, trail) => {
        if (seen.has(id)) return;
        if (trail.has(id)) {
          log3.error(`dependency cycle involving "${id}"; breaking it`);
          return;
        }
        trail.add(id);
        const rec = this.records.get(id);
        for (const dep of rec?.plugin.dependencies ?? []) {
          if (this.records.has(dep)) visit(dep, trail);
        }
        trail.delete(id);
        seen.add(id);
        ordered.push(id);
      };
      for (const id of this.records.keys()) visit(id, /* @__PURE__ */ new Set());
      return ordered;
    }
    runningCount() {
      let n = 0;
      for (const rec of this.records.values()) if (rec.state === "running") n++;
      return n;
    }
    persistEnabledState() {
      saveNamespace(ENABLED_NS, this.enabledMap);
      writeSyncHint(ENABLED_NS, this.enabledMap);
    }
    emit() {
      for (const fn of this.listeners) {
        try {
          fn();
        } catch {
        }
      }
    }
  };
  var runtime = new Runtime();

  // src/core/plugin.ts
  var BRAND = Symbol.for("halcyon.plugin");
  var ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
  function definePlugin(definition) {
    if (!ID_PATTERN.test(definition.id)) {
      throw new Error(
        `Halcyon: invalid plugin id "${definition.id}" \u2014 use lowercase words separated by single dashes.`
      );
    }
    if (!definition.authors?.length) {
      throw new Error(`Halcyon: plugin "${definition.id}" must list at least one author.`);
    }
    return Object.assign(definition, { [BRAND]: true });
  }

  // src/ui/tokens.css
  var tokens_default = '/*\n * Design tokens.\n *\n * Every color, size, radius, and duration used anywhere in Halcyon resolves to\n * one of these variables. Components never hardcode raw values. The palette is\n * flat by design: solid fills only, no gradients.\n *\n * Values mirror docs/ui-design-guide.md. If the two ever disagree, the guide\n * is the source of truth and this file is the bug.\n */\n\n.halcyon {\n  /* Accent */\n  --hc-accent: #0a84ff;\n  --hc-accent-pressed: #0768cc;\n\n  /* Semantic */\n  --hc-red: #ff453a;\n  --hc-orange: #ff9f0a;\n  --hc-yellow: #ffd60a;\n  --hc-green: #30d158;\n  --hc-teal: #64d2ff;\n  --hc-indigo: #5e5ce6;\n  --hc-pink: #ff375f;\n\n  /* Neutral surfaces */\n  --hc-bg-primary: #000000;\n  --hc-bg-secondary: #1c1c1e;\n  --hc-bg-tertiary: #2c2c2e;\n  --hc-bg-elevated: #2c2c2e;\n\n  /* Fills */\n  --hc-fill-primary: rgba(120, 120, 128, 0.36);\n  --hc-fill-secondary: rgba(120, 120, 128, 0.24);\n\n  /* Separators */\n  --hc-separator: rgba(84, 84, 88, 0.65);\n  --hc-separator-opaque: #38383a;\n\n  /* Labels */\n  --hc-label-primary: #ffffff;\n  --hc-label-secondary: rgba(235, 235, 245, 0.6);\n  --hc-label-tertiary: rgba(235, 235, 245, 0.3);\n  --hc-label-quaternary: rgba(235, 235, 245, 0.16);\n\n  /* Spacing (8pt grid) */\n  --hc-space-1: 4px;\n  --hc-space-2: 8px;\n  --hc-space-3: 12px;\n  --hc-space-4: 16px;\n  --hc-space-5: 20px;\n  --hc-space-6: 24px;\n  --hc-space-8: 32px;\n  --hc-space-10: 40px;\n\n  /* Radii */\n  --hc-radius-xs: 4px;\n  --hc-radius-sm: 6px;\n  --hc-radius-md: 10px;\n  --hc-radius-lg: 12px;\n  --hc-radius-xl: 16px;\n  --hc-radius-2xl: 22px;\n  --hc-radius-pill: 999px;\n\n  /* Elevation */\n  --hc-elev-1: 0 1px 2px rgba(0, 0, 0, 0.24);\n  --hc-elev-2: 0 4px 12px rgba(0, 0, 0, 0.32);\n  --hc-elev-3: 0 12px 32px rgba(0, 0, 0, 0.44);\n\n  /* Type scale \u2014 sizes paired with absolute line heights */\n  --hc-text-title1: 28px;\n  --hc-lh-title1: 34px;\n  --hc-text-title2: 22px;\n  --hc-lh-title2: 28px;\n  --hc-text-title3: 20px;\n  --hc-lh-title3: 25px;\n  --hc-text-headline: 17px;\n  --hc-lh-headline: 22px;\n  --hc-text-body: 17px;\n  --hc-lh-body: 22px;\n  --hc-text-callout: 16px;\n  --hc-lh-callout: 21px;\n  --hc-text-subhead: 15px;\n  --hc-lh-subhead: 20px;\n  --hc-text-footnote: 13px;\n  --hc-lh-footnote: 18px;\n  --hc-text-caption1: 12px;\n  --hc-lh-caption1: 16px;\n  --hc-text-caption2: 11px;\n  --hc-lh-caption2: 13px;\n\n  /* Motion */\n  --hc-ease: cubic-bezier(0.32, 0.72, 0, 1);\n  --hc-duration-fast: 200ms;\n  --hc-duration-slow: 300ms;\n\n  /* Font stack */\n  --hc-font: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display",\n    "PingFang SC", "Microsoft YaHei", "Segoe UI", Roboto, sans-serif;\n  --hc-font-mono: "SF Mono", ui-monospace, "JetBrains Mono", "Cascadia Code",\n    Menlo, Consolas, monospace;\n}\n';

  // src/ui/components.css
  var components_default = `/*
 * Component styles.
 *
 * Class-based, scoped under \`.halcyon\`. All values reference tokens.css; there
 * are no raw colors or sizes here. Interaction states use flat fills and
 * opacity, never gradients.
 */

.halcyon,
.halcyon * {
  box-sizing: border-box;
}

.halcyon {
  font-family: var(--hc-font);
  color: var(--hc-label-primary);
  -webkit-font-smoothing: antialiased;
}

/* --- Typographic helpers ------------------------------------------------- */

.hc-title2 {
  font-size: var(--hc-text-title2);
  line-height: var(--hc-lh-title2);
  font-weight: 700;
}

.hc-title3 {
  font-size: var(--hc-text-title3);
  line-height: var(--hc-lh-title3);
  font-weight: 600;
}

.hc-headline {
  font-size: var(--hc-text-headline);
  line-height: var(--hc-lh-headline);
  font-weight: 600;
}

.hc-body {
  font-size: var(--hc-text-body);
  line-height: var(--hc-lh-body);
  font-weight: 400;
}

.hc-callout {
  font-size: var(--hc-text-callout);
  line-height: var(--hc-lh-callout);
}

.hc-footnote {
  font-size: var(--hc-text-footnote);
  line-height: var(--hc-lh-footnote);
  color: var(--hc-label-secondary);
}

.hc-muted {
  color: var(--hc-label-secondary);
}

/* --- Button -------------------------------------------------------------- */

.hc-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: var(--hc-space-2);
  border: none;
  cursor: pointer;
  font-family: inherit;
  font-size: var(--hc-text-body);
  line-height: var(--hc-lh-body);
  font-weight: 600;
  border-radius: var(--hc-radius-md);
  padding: 0 var(--hc-space-4);
  height: 40px;
  transition: background-color var(--hc-duration-fast) var(--hc-ease),
    opacity var(--hc-duration-fast) var(--hc-ease),
    transform var(--hc-duration-fast) var(--hc-ease);
  user-select: none;
  white-space: nowrap;
}

.hc-btn:active {
  transform: scale(0.98);
}

.hc-btn:disabled {
  opacity: 0.4;
  cursor: default;
  transform: none;
}

.hc-btn--sm {
  height: 32px;
  font-size: var(--hc-text-subhead);
  padding: 0 var(--hc-space-3);
}

.hc-btn--lg {
  height: 50px;
  border-radius: var(--hc-radius-lg);
}

.hc-btn--primary {
  background: var(--hc-accent);
  color: #ffffff;
}

.hc-btn--primary:hover:not(:disabled) {
  background: var(--hc-accent-pressed);
}

.hc-btn--secondary {
  background: var(--hc-fill-primary);
  color: var(--hc-label-primary);
}

.hc-btn--secondary:hover:not(:disabled) {
  background: var(--hc-fill-secondary);
}

.hc-btn--plain {
  background: transparent;
  color: var(--hc-accent);
  padding-left: var(--hc-space-2);
  padding-right: var(--hc-space-2);
}

.hc-btn--plain:hover:not(:disabled) {
  background: var(--hc-fill-secondary);
}

.hc-btn--destructive {
  background: transparent;
  color: var(--hc-red);
}

.hc-btn--destructive:hover:not(:disabled) {
  background: rgba(255, 69, 58, 0.16);
}

/* --- Toggle -------------------------------------------------------------- */

.hc-toggle {
  position: relative;
  flex: none;
  width: 51px;
  height: 31px;
  border-radius: var(--hc-radius-pill);
  background: var(--hc-fill-secondary);
  border: none;
  cursor: pointer;
  padding: 0;
  transition: background-color var(--hc-duration-fast) var(--hc-ease);
}

.hc-toggle[data-on="true"] {
  background: var(--hc-green);
}

.hc-toggle:disabled {
  opacity: 0.4;
  cursor: default;
}

.hc-toggle__knob {
  position: absolute;
  top: 2px;
  left: 2px;
  width: 27px;
  height: 27px;
  border-radius: 50%;
  background: #ffffff;
  box-shadow: var(--hc-elev-1);
  transition: transform var(--hc-duration-fast) var(--hc-ease);
}

.hc-toggle[data-on="true"] .hc-toggle__knob {
  transform: translateX(20px);
}

/* --- Section ------------------------------------------------------------- */

.hc-section {
  margin-top: var(--hc-space-6);
}

.hc-section:first-child {
  margin-top: 0;
}

.hc-section__title {
  font-size: var(--hc-text-subhead);
  line-height: var(--hc-lh-subhead);
  color: var(--hc-label-secondary);
  text-transform: uppercase;
  letter-spacing: 0.04em;
  padding: 0 var(--hc-space-4);
  margin-bottom: var(--hc-space-2);
}

.hc-section__body {
  background: var(--hc-bg-secondary);
  border-radius: var(--hc-radius-lg);
  overflow: hidden;
}

.hc-section__note {
  font-size: var(--hc-text-footnote);
  line-height: var(--hc-lh-footnote);
  color: var(--hc-label-secondary);
  padding: var(--hc-space-2) var(--hc-space-4) 0;
}

/* --- List row ------------------------------------------------------------ */

.hc-row {
  display: flex;
  align-items: center;
  gap: var(--hc-space-3);
  min-height: 44px;
  padding: var(--hc-space-2) var(--hc-space-4);
  position: relative;
}

.hc-row + .hc-row::before {
  content: "";
  position: absolute;
  top: 0;
  left: 56px;
  right: 0;
  height: 1px;
  background: var(--hc-separator);
  transform: scaleY(0.5);
}

.hc-row--button {
  cursor: pointer;
  transition: background-color var(--hc-duration-fast) var(--hc-ease);
}

.hc-row--button:hover {
  background: var(--hc-fill-secondary);
}

.hc-row--button:active {
  background: var(--hc-fill-primary);
}

.hc-row__icon {
  flex: none;
  width: 28px;
  height: 28px;
  border-radius: var(--hc-radius-sm);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ffffff;
}

.hc-row__text {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.hc-row__title {
  font-size: var(--hc-text-body);
  line-height: var(--hc-lh-body);
  color: var(--hc-label-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hc-row__subtitle {
  font-size: var(--hc-text-footnote);
  line-height: var(--hc-lh-footnote);
  color: var(--hc-label-secondary);
}

.hc-row__accessory {
  flex: none;
  display: flex;
  align-items: center;
  gap: var(--hc-space-2);
  color: var(--hc-label-secondary);
}

.hc-row__chevron {
  color: var(--hc-label-tertiary);
}

/* --- Text input ---------------------------------------------------------- */

.hc-input {
  display: block;
  width: 100%;
  height: 40px;
  background: var(--hc-fill-primary);
  border: 2px solid transparent;
  border-radius: var(--hc-radius-md);
  padding: 0 var(--hc-space-3);
  color: var(--hc-label-primary);
  font-family: inherit;
  font-size: var(--hc-text-callout);
  line-height: var(--hc-lh-callout);
  outline: none;
  transition: border-color var(--hc-duration-fast) var(--hc-ease);
}

.hc-input::placeholder {
  color: var(--hc-label-tertiary);
}

.hc-input:focus {
  border-color: var(--hc-accent);
}

/* --- Number stepper ------------------------------------------------------ */

.hc-stepper {
  display: inline-flex;
  align-items: center;
  background: var(--hc-fill-primary);
  border-radius: var(--hc-radius-md);
  overflow: hidden;
}

.hc-stepper__btn {
  width: 36px;
  height: 32px;
  border: none;
  background: transparent;
  color: var(--hc-label-primary);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color var(--hc-duration-fast) var(--hc-ease);
}

.hc-stepper__btn:hover:not(:disabled) {
  background: var(--hc-fill-secondary);
}

.hc-stepper__btn:disabled {
  color: var(--hc-label-quaternary);
  cursor: default;
}

.hc-stepper__value {
  min-width: 44px;
  text-align: center;
  font-size: var(--hc-text-callout);
  font-variant-numeric: tabular-nums;
  color: var(--hc-label-primary);
}

/* --- Select -------------------------------------------------------------- */

/* Self-drawn dropdown: pill button + floating iOS-style menu sheet. */
.hc-select {
  position: relative;
  display: inline-block;
}

.hc-select__button {
  display: inline-flex;
  align-items: center;
  gap: var(--hc-space-2);
  height: 32px;
  background: var(--hc-fill-primary);
  border: none;
  border-radius: var(--hc-radius-md);
  color: var(--hc-label-primary);
  font-family: inherit;
  font-size: var(--hc-text-callout);
  padding: 0 var(--hc-space-3);
  cursor: pointer;
  outline: none;
  white-space: nowrap;
}

.hc-select__button:hover {
  background: var(--hc-fill-secondary);
}

.hc-select__button:focus-visible {
  box-shadow: 0 0 0 2px var(--hc-accent);
}

.hc-select__chevron {
  color: var(--hc-label-tertiary);
  transition: transform 0.15s ease;
}

.hc-select__chevron[data-open="true"] {
  transform: rotate(180deg);
}

.hc-select__menu {
  /* Positioned by its portal wrapper (fixed, anchored to the button). */
  max-height: 280px;
  overflow-y: auto;
  padding: var(--hc-space-1);
  background: var(--hc-bg-elevated, #2c2c2e);
  border-radius: var(--hc-radius-lg, 12px);
  box-shadow:
    0 0 0 0.5px rgba(255, 255, 255, 0.08),
    0 10px 32px rgba(0, 0, 0, 0.45);
  animation: hc-select-pop 0.14s ease;
}

@keyframes hc-select-pop {
  from {
    opacity: 0;
    transform: translateY(-4px) scale(0.98);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

.hc-select__option {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--hc-space-3);
  width: 100%;
  border: none;
  background: none;
  border-radius: var(--hc-radius-md);
  color: var(--hc-label-primary);
  font-family: inherit;
  font-size: var(--hc-text-callout);
  text-align: left;
  padding: 7px var(--hc-space-3);
  cursor: pointer;
  white-space: nowrap;
}

.hc-select__option[data-active="true"] {
  background: var(--hc-fill-primary);
}

.hc-select__option[data-selected="true"] {
  color: var(--hc-accent);
}

.hc-select__check {
  flex: none;
  color: var(--hc-accent);
}

/* --- String list --------------------------------------------------------- */

.hc-strlist {
  display: flex;
  flex-direction: column;
  gap: var(--hc-space-2);
  padding: var(--hc-space-2) var(--hc-space-4) var(--hc-space-3);
}

.hc-strlist__item {
  display: flex;
  align-items: center;
  gap: var(--hc-space-2);
}

.hc-strlist__add {
  display: flex;
  gap: var(--hc-space-2);
}

.hc-iconbtn {
  flex: none;
  width: 32px;
  height: 32px;
  border-radius: var(--hc-radius-md);
  border: none;
  background: var(--hc-fill-primary);
  color: var(--hc-label-secondary);
  cursor: pointer;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color var(--hc-duration-fast) var(--hc-ease),
    color var(--hc-duration-fast) var(--hc-ease);
}

.hc-iconbtn:hover {
  background: var(--hc-fill-secondary);
}

.hc-iconbtn--danger:hover {
  color: var(--hc-red);
}

/* --- Badge --------------------------------------------------------------- */

.hc-badge {
  display: inline-flex;
  align-items: center;
  height: 20px;
  padding: 0 var(--hc-space-2);
  border-radius: var(--hc-radius-pill);
  font-size: var(--hc-text-caption1);
  line-height: var(--hc-lh-caption1);
  font-weight: 600;
}

.hc-badge[data-tone="neutral"] {
  background: var(--hc-fill-secondary);
  color: var(--hc-label-secondary);
}

.hc-badge[data-tone="accent"] {
  background: rgba(10, 132, 255, 0.2);
  color: var(--hc-accent);
}

.hc-badge[data-tone="green"] {
  background: rgba(48, 209, 88, 0.2);
  color: var(--hc-green);
}

.hc-badge[data-tone="red"] {
  background: rgba(255, 69, 58, 0.2);
  color: var(--hc-red);
}

.hc-badge[data-tone="orange"] {
  background: rgba(255, 159, 10, 0.2);
  color: var(--hc-orange);
}

/* --- Empty state --------------------------------------------------------- */

.hc-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: var(--hc-space-10) var(--hc-space-6);
  color: var(--hc-label-tertiary);
}

.hc-empty__title {
  font-size: var(--hc-text-headline);
  line-height: var(--hc-lh-headline);
  font-weight: 600;
  color: var(--hc-label-secondary);
  margin-top: var(--hc-space-4);
}

.hc-empty__subtitle {
  font-size: var(--hc-text-callout);
  line-height: var(--hc-lh-callout);
  color: var(--hc-label-tertiary);
  margin-top: var(--hc-space-2);
  max-width: 320px;
}

/* --- Overlay + panel (fallback entry point) ------------------------------ */

.hc-overlay {
  position: fixed;
  inset: 0;
  z-index: 10000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.6);
  animation: hc-fade var(--hc-duration-slow) var(--hc-ease);
}

.hc-panel {
  width: min(900px, 92vw);
  height: min(720px, 88vh);
  background: var(--hc-bg-primary);
  border-radius: var(--hc-radius-xl);
  box-shadow: var(--hc-elev-3);
  display: flex;
  overflow: hidden;
  animation: hc-rise var(--hc-duration-slow) var(--hc-ease);
}

.hc-panel__sidebar {
  width: 220px;
  flex: none;
  background: var(--hc-bg-secondary);
  border-right: 1px solid var(--hc-separator-opaque);
  padding: var(--hc-space-4) var(--hc-space-2);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.hc-panel__brand {
  display: flex;
  align-items: center;
  gap: var(--hc-space-2);
  padding: var(--hc-space-2) var(--hc-space-3) var(--hc-space-4);
  color: var(--hc-label-primary);
}

.hc-panel__brand-name {
  font-size: var(--hc-text-headline);
  font-weight: 700;
}

.hc-navitem {
  display: flex;
  align-items: center;
  gap: var(--hc-space-2);
  padding: var(--hc-space-2) var(--hc-space-3);
  border-radius: var(--hc-radius-md);
  color: var(--hc-label-secondary);
  cursor: pointer;
  font-size: var(--hc-text-callout);
  border: none;
  background: transparent;
  text-align: left;
  width: 100%;
  transition: background-color var(--hc-duration-fast) var(--hc-ease),
    color var(--hc-duration-fast) var(--hc-ease);
}

.hc-navitem:hover {
  background: var(--hc-fill-secondary);
  color: var(--hc-label-primary);
}

.hc-navitem[data-active="true"] {
  background: var(--hc-fill-primary);
  color: var(--hc-label-primary);
}

.hc-panel__content {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

.hc-panel__header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: var(--hc-space-5) var(--hc-space-6) var(--hc-space-4);
  border-bottom: 1px solid var(--hc-separator-opaque);
}

.hc-panel__scroll {
  flex: 1;
  overflow-y: auto;
  padding: var(--hc-space-5) var(--hc-space-6) var(--hc-space-8);
}

.hc-embed {
  /* When embedded in Discord's own settings pane rather than the overlay. */
  padding: var(--hc-space-2) 0 var(--hc-space-8);
}

@keyframes hc-fade {
  from {
    opacity: 0;
  }
  to {
    opacity: 1;
  }
}

@keyframes hc-rise {
  from {
    opacity: 0;
    transform: translateY(8px) scale(0.99);
  }
  to {
    opacity: 1;
    transform: none;
  }
}

/* Respect the OS "reduce motion" preference. */
@media (prefers-reduced-motion: reduce) {
  .hc-overlay,
  .hc-panel,
  .hc-btn,
  .hc-toggle__knob {
    animation: none;
    transition: none;
  }
}

/* --- Setting cells (schema-driven form) ---------------------------------- */

.hc-cell {
  padding: var(--hc-space-2) var(--hc-space-4);
  position: relative;
}

.hc-cell + .hc-cell::before {
  content: "";
  position: absolute;
  top: 0;
  left: var(--hc-space-4);
  right: 0;
  height: 1px;
  background: var(--hc-separator);
  transform: scaleY(0.5);
}

.hc-cell--row {
  display: flex;
  align-items: center;
  gap: var(--hc-space-3);
  min-height: 44px;
}

.hc-cell__main {
  flex: 1;
  min-width: 0;
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.hc-cell__label {
  font-size: var(--hc-text-body);
  line-height: var(--hc-lh-body);
  color: var(--hc-label-primary);
}

.hc-cell__desc {
  font-size: var(--hc-text-footnote);
  line-height: var(--hc-lh-footnote);
  color: var(--hc-label-secondary);
}

.hc-cell__control {
  flex: none;
}

.hc-cell__stacked {
  padding-top: var(--hc-space-2);
}

/* --- Toolbar (search + actions) ------------------------------------------ */

.hc-toolbar {
  display: flex;
  align-items: center;
  gap: var(--hc-space-3);
  margin-bottom: var(--hc-space-4);
}

/* --- Server-rail button (injected under Discord's home/DM button) -------- */
/* Styled to read as a native rail icon: a 48px rounded square (not a circle)
   like Discord's own home button, on the same graphite fill, with a muted grey
   glyph. On hover it snaps to the brand color and squares off a touch \u2014 exactly
   how Discord's guild pills animate \u2014 so it belongs in the rail instead of
   standing out as a bright foreign blob. */
.hc-rail-item {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 100%;
  padding: 4px 0;
}

.hc-rail-btn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 48px;
  height: 48px;
  padding: 0;
  border: none;
  background: #000;
  color: var(--interactive-normal, #b5bac1);
  cursor: pointer;
  border-radius: 16px;
  transition: border-radius var(--hc-duration-fast) var(--hc-ease),
    background-color var(--hc-duration-fast) var(--hc-ease),
    color var(--hc-duration-fast) var(--hc-ease);
}

.hc-rail-btn:hover {
  border-radius: 14px;
  background: var(--brand-experiment, var(--hc-accent, #5865f2));
  color: #fff;
}

.hc-rail-btn:active {
  border-radius: 12px;
}

.hc-search {
  flex: 1;
  display: flex;
  align-items: center;
  gap: var(--hc-space-2);
  height: 36px;
  padding: 0 var(--hc-space-3);
  background: var(--hc-fill-primary);
  border-radius: var(--hc-radius-md);
  color: var(--hc-label-tertiary);
}

.hc-search input {
  flex: 1;
  border: none;
  background: transparent;
  outline: none;
  color: var(--hc-label-primary);
  font-family: inherit;
  font-size: var(--hc-text-callout);
}

.hc-search input::placeholder {
  color: var(--hc-label-tertiary);
}

/* --- Plugin detail header ------------------------------------------------ */

.hc-back {
  display: inline-flex;
  align-items: center;
  gap: var(--hc-space-1);
  background: transparent;
  border: none;
  color: var(--hc-accent);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--hc-text-callout);
  padding: var(--hc-space-1) var(--hc-space-1) var(--hc-space-1) 0;
  margin-bottom: var(--hc-space-4);
}

.hc-detail-head {
  display: flex;
  align-items: flex-start;
  gap: var(--hc-space-3);
  margin-bottom: var(--hc-space-5);
}

.hc-detail-head__icon {
  flex: none;
  width: 44px;
  height: 44px;
  border-radius: var(--hc-radius-lg);
  display: flex;
  align-items: center;
  justify-content: center;
  color: #ffffff;
}

.hc-detail-head__text {
  flex: 1;
  min-width: 0;
}

.hc-detail-head__name {
  font-size: var(--hc-text-title3);
  line-height: var(--hc-lh-title3);
  font-weight: 600;
}

.hc-detail-head__desc {
  font-size: var(--hc-text-callout);
  line-height: var(--hc-lh-callout);
  color: var(--hc-label-secondary);
  margin-top: 2px;
}

.hc-detail-head__meta {
  font-size: var(--hc-text-footnote);
  color: var(--hc-label-tertiary);
  margin-top: var(--hc-space-2);
}

/* --- Log viewer ---------------------------------------------------------- */

.hc-logs {
  font-family: var(--hc-font-mono);
  font-size: var(--hc-text-footnote);
  line-height: 1.7;
  background: var(--hc-bg-secondary);
  border-radius: var(--hc-radius-lg);
  padding: var(--hc-space-3);
  overflow-x: auto;
}

.hc-logline {
  display: flex;
  gap: var(--hc-space-2);
  white-space: pre;
  padding: 1px 0;
}

.hc-logline__time {
  color: var(--hc-label-tertiary);
  flex: none;
}

.hc-logline__scope {
  color: var(--hc-label-secondary);
  flex: none;
}

.hc-logline__msg {
  color: var(--hc-label-primary);
  white-space: pre-wrap;
  word-break: break-word;
}

.hc-logline[data-level="warn"] .hc-logline__msg {
  color: var(--hc-orange);
}

.hc-logline[data-level="error"] .hc-logline__msg {
  color: var(--hc-red);
}

.hc-logline[data-level="debug"] .hc-logline__msg {
  color: var(--hc-label-secondary);
}

/* --- About --------------------------------------------------------------- */

.hc-about__row {
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.hc-about__value {
  color: var(--hc-label-secondary);
  font-variant-numeric: tabular-nums;
}

/* --- Generic vertical rhythm --------------------------------------------- */

.hc-stack > * + * {
  margin-top: var(--hc-space-4);
}

.hc-inline-note {
  display: flex;
  align-items: center;
  gap: var(--hc-space-2);
  color: var(--hc-orange);
  font-size: var(--hc-text-footnote);
}

.hc-inline-note--danger {
  color: var(--hc-red);
}

/* --- Detail head toggle stays top-aligned with the icon ------------------ */

.hc-detail-head > span {
  flex: none;
  padding-top: var(--hc-space-1);
}

/* --- About hero ---------------------------------------------------------- */

.hc-about-hero {
  display: flex;
  align-items: center;
  gap: var(--hc-space-3);
  padding: var(--hc-space-2) 0 var(--hc-space-4);
  color: var(--hc-label-primary);
}

.hc-about-hero__name {
  font-size: var(--hc-text-title2);
  line-height: var(--hc-lh-title2);
  font-weight: 700;
}

.hc-about-hero__ver {
  font-size: var(--hc-text-footnote);
  line-height: var(--hc-lh-footnote);
  color: var(--hc-label-secondary);
}

/* --- Tabs (used by plugin pages) ----------------------------------------- */

.hc-tabs {
  display: flex;
  align-items: center;
  gap: var(--hc-space-2);
  margin-bottom: var(--hc-space-4);
}

.hc-tabs__spacer {
  flex: 1;
}

.hc-tab {
  display: inline-flex;
  align-items: center;
  gap: var(--hc-space-2);
  height: 32px;
  padding: 0 var(--hc-space-3);
  border: none;
  border-radius: var(--hc-radius-md);
  background: transparent;
  color: var(--hc-label-secondary);
  cursor: pointer;
  font-family: inherit;
  font-size: var(--hc-text-subhead);
  font-weight: 600;
  transition: background-color var(--hc-duration-fast) var(--hc-ease),
    color var(--hc-duration-fast) var(--hc-ease);
}

.hc-tab:hover {
  color: var(--hc-label-primary);
}

.hc-tab[data-active="true"] {
  background: var(--hc-fill-primary);
  color: var(--hc-label-primary);
}

/* --- Save bar --------------------------------------------------------------- */

.hc-savebar {
  position: sticky;
  bottom: var(--hc-space-3);
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--hc-space-4);
  margin-top: var(--hc-space-4);
  padding: var(--hc-space-2) var(--hc-space-2) var(--hc-space-2) var(--hc-space-4);
  background: var(--hc-bg-elevated, #2c2c2e);
  border-radius: var(--hc-radius-lg);
  box-shadow:
    0 0 0 0.5px rgba(255, 255, 255, 0.08),
    0 8px 24px rgba(0, 0, 0, 0.35);
  animation: hc-select-pop 0.14s ease;
}

.hc-savebar__label {
  font-size: var(--hc-text-subhead);
  color: var(--hc-label-secondary);
}

.hc-savebar__actions {
  display: flex;
  gap: var(--hc-space-2);
  flex: none;
}

/* --- Segmented control ------------------------------------------------------ */

.hc-segment {
  display: flex;
  gap: 2px;
  padding: 2px;
  margin-bottom: var(--hc-space-4);
  background: var(--hc-fill-primary);
  border-radius: var(--hc-radius-md);
  width: fit-content;
}

.hc-segment__item {
  border: none;
  background: transparent;
  color: var(--hc-label-secondary);
  font-family: inherit;
  font-size: var(--hc-text-subhead);
  font-weight: 600;
  height: 28px;
  padding: 0 var(--hc-space-4);
  border-radius: calc(var(--hc-radius-md) - 2px);
  cursor: pointer;
  transition: background-color var(--hc-duration-fast) var(--hc-ease),
    color var(--hc-duration-fast) var(--hc-ease);
}

.hc-segment__item:hover {
  color: var(--hc-label-primary);
}

.hc-segment__item[data-active="true"] {
  background: var(--hc-bg-elevated, #2c2c2e);
  color: var(--hc-label-primary);
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.25);
}

/* --- Pager ----------------------------------------------------------------- */

.hc-pager {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: var(--hc-space-3);
  margin-top: var(--hc-space-4);
}

.hc-pager__label {
  font-size: var(--hc-text-footnote);
  color: var(--hc-label-secondary);
  font-variant-numeric: tabular-nums;
  min-width: 96px;
  text-align: center;
}

.hc-pager .hc-tab:disabled {
  opacity: 0.4;
  cursor: default;
}

/* --- Captured message entries -------------------------------------------- */

.hc-msglist {
  display: flex;
  flex-direction: column;
  gap: var(--hc-space-2);
}

.hc-msg {
  background: var(--hc-bg-secondary);
  border-radius: var(--hc-radius-lg);
  padding: var(--hc-space-3) var(--hc-space-4);
  border-left: 2px solid var(--hc-red);
}

.hc-msg__head {
  display: flex;
  align-items: center;
  gap: var(--hc-space-2);
  margin-bottom: var(--hc-space-1);
}

.hc-msg__author {
  font-size: var(--hc-text-subhead);
  font-weight: 600;
  color: var(--hc-label-primary);
}

.hc-msg__where {
  font-size: var(--hc-text-footnote);
  color: var(--hc-label-secondary);
}

.hc-msg__guild {
  color: var(--hc-label-secondary);
  font-weight: 600;
}

.hc-msg__sep {
  color: var(--hc-label-tertiary);
  margin: 0 4px;
}

.hc-msg__time {
  margin-left: auto;
  font-size: var(--hc-text-caption1);
  color: var(--hc-label-tertiary);
  font-variant-numeric: tabular-nums;
}

/* Jump-to-message action, pinned to the right of each row's header. Keeps the
 * header on one line and doesn't steal the space the time claims via
 * margin-left:auto (which already pushes both to the right edge). */
.hc-msg__jump {
  flex: none;
  margin-left: var(--hc-space-2);
}

.hc-msg__body {
  font-size: var(--hc-text-callout);
  line-height: var(--hc-lh-callout);
  color: var(--hc-label-primary);
  white-space: pre-wrap;
  word-break: break-word;
}

.hc-msg__empty {
  color: var(--hc-label-tertiary);
  font-style: italic;
}

.hc-msg__meta {
  margin-top: var(--hc-space-1);
  font-size: var(--hc-text-footnote);
  color: var(--hc-label-secondary);
}

/* Attachment thumbnails. Constrained so wide/tall media never spills past the
 * message card \u2014 a single image caps at the content width, and the row wraps
 * when there are several. */
.hc-msg__media {
  display: flex;
  flex-wrap: wrap;
  gap: var(--hc-space-2);
  margin-top: var(--hc-space-2);
  min-width: 0;
}

.hc-msg__media a {
  color: var(--hc-accent);
  font-size: var(--hc-text-footnote);
  word-break: break-all;
}

.hc-msg__thumb {
  max-width: 100%;
  max-height: 240px;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: var(--hc-radius-md);
  background: var(--hc-fill-secondary);
}

/* Inline custom emoji, sized to the surrounding text like Discord's own. */
.hc-emoji {
  display: inline-block;
  width: 1.375em;
  height: 1.375em;
  margin: 0 1px;
  object-fit: contain;
  vertical-align: bottom;
}

.hc-msg__versions {
  display: flex;
  flex-direction: column;
  gap: var(--hc-space-1);
}

.hc-msg__version {
  display: flex;
  gap: var(--hc-space-2);
  font-size: var(--hc-text-callout);
  line-height: var(--hc-lh-callout);
}

.hc-msg__vtag {
  flex: none;
  color: var(--hc-label-tertiary);
  font-variant-numeric: tabular-nums;
  font-size: var(--hc-text-footnote);
  padding-top: 2px;
}

.hc-msg__vbody {
  color: var(--hc-label-primary);
  white-space: pre-wrap;
  word-break: break-word;
}

/* The \`edited\` tone reuses the orange rule via a modifier. */
.hc-msg--edited {
  border-left-color: var(--hc-orange);
}

/* --- message-logger status banner ---------------------------------------- *
 * A compact warning on the log page, shown only when at least one of the
 * plugin's source patches failed to match the running Discord build. Inside
 * the .halcyon overlay/embed, so tokens are used throughout. Amber tone: the
 * feature isn't broken \u2014 records still land in the list below \u2014 but the
 * in-chat red row is off, and this is the only place a non-console user will
 * see that. */
.hc-mlog-warn {
  border: 1px solid rgba(224, 165, 63, 0.35);
  background: rgba(224, 165, 63, 0.08);
  border-radius: var(--hc-radius-md);
  padding: var(--hc-space-3) var(--hc-space-4);
  margin: var(--hc-space-3) 0;
  display: flex;
  flex-direction: column;
  gap: 4px;
}
.hc-mlog-warn__title {
  font-size: var(--hc-text-subhead);
  font-weight: 600;
  color: #e0a53f;
}
.hc-mlog-warn__detail {
  font-size: var(--hc-text-footnote);
  color: var(--hc-label-secondary);
  line-height: var(--hc-lh-footnote);
}
.hc-mlog-warn__list {
  margin: 2px 0 0;
  padding-left: 18px;
  font-size: var(--hc-text-footnote);
  color: var(--hc-label-secondary);
  font-variant-numeric: tabular-nums;
}

/* --- Deleted message (in-chat) ------------------------------------------- */

/*
 * Applied to Discord's own message row when a deleted message is kept in place.
 * These live outside the .halcyon scope on purpose \u2014 they decorate Discord
 * elements \u2014 so literal values, no tokens.
 *
 * The row itself only carries the stable .hc-deleted hook; the chosen style is
 * a class on <html> (hc-mlog-<style>). Splitting them lets a style change take
 * effect immediately \u2014 swap the root class and every kept message updates \u2014
 * instead of the pick only landing on rows Discord repaints after the change.
 */

/* Style: red tint (default) \u2014 flat red wash + left bar. */
.hc-mlog-tint .hc-deleted {
  background-color: rgba(255, 69, 58, 0.1);
  box-shadow: inset 2px 0 0 #ff453a;
}

/* Style: red text \u2014 content turns red, no background. */
.hc-mlog-text .hc-deleted [class*="messageContent"],
.hc-mlog-text .hc-deleted [class*="contents"] > div:not([class*="header"]) {
  color: #f04747 !important;
}
.hc-mlog-text .hc-deleted [class*="messageContent"] a {
  color: #ff6b6b !important;
}

/* Style: ghost \u2014 the whole row fades. */
.hc-mlog-ghost .hc-deleted {
  opacity: 0.45;
  filter: saturate(0.6);
}

/* Style: strike \u2014 red strikethrough over the text. */
.hc-mlog-strike .hc-deleted [class*="messageContent"] {
  text-decoration: line-through;
  text-decoration-color: rgba(255, 69, 58, 0.7);
  text-decoration-thickness: 1.5px;
}
.hc-mlog-strike .hc-deleted {
  box-shadow: inset 2px 0 0 rgba(255, 69, 58, 0.5);
}

/* "This message was deleted (\u2026)": marker row under the content. One base
 * class plus a look modifier chosen in settings. */
.hc-deleted-marker {
  display: flex;
  align-items: center;
  gap: 6px;
  margin-top: 2px;
  font-size: 0.8125rem;
  line-height: 1.2;
  color: #f04747;
  user-select: none;
}
.hc-deleted-marker__icon {
  flex: none;
}

/* Look: badge \u2014 pill-shaped chip on its OWN line. It used \`display: inline-flex\`,
 * which let the pill run inline with the message text so the two never wrapped
 * ("\u4E0D\u4F1A\u6362\u884C"). Inheriting the base \`display: flex\` makes it block-level (its own
 * line); \`width: fit-content\` keeps the pill only as wide as its label, and
 * \`max-width: 100%\` stops a long label from overflowing the row. */
.hc-deleted-marker--badge {
  width: fit-content;
  max-width: 100%;
  background: rgba(255, 69, 58, 0.12);
  border-radius: 9999px;
  padding: 2px 10px;
  margin-top: 4px;
}

/* Look: quote \u2014 indented behind a red bar, like a blockquote. */
.hc-deleted-marker--quote {
  border-left: 3px solid rgba(255, 69, 58, 0.7);
  padding-left: 8px;
  margin-top: 4px;
  color: rgba(240, 71, 71, 0.85);
}

/* Tone: edited \u2014 same marker layout, calmer amber so an edit doesn't read as a
 * deletion. Overrides the red the delete marker uses. */
.hc-deleted-marker--edited {
  color: #e0a53f;
}
.hc-deleted-marker--edited.hc-deleted-marker--badge {
  background: rgba(224, 165, 63, 0.14);
}
.hc-deleted-marker--edited.hc-deleted-marker--quote {
  border-left-color: rgba(224, 165, 63, 0.7);
  color: rgba(224, 165, 63, 0.9);
}

/* --- Username next to nickname (show-username plugin) --------------------- */

/*
 * Appended inside Discord's message header, so literal values, no tokens.
 * One base class plus a per-style modifier chosen in the plugin's settings.
 */
.hc-username {
  font-size: 0.75rem;
  font-weight: 500;
  vertical-align: baseline;
}

.hc-username--muted {
  color: var(--text-muted, #949ba4);
}

.hc-username--pill {
  color: var(--text-muted, #949ba4);
  background: rgba(128, 132, 142, 0.16);
  border-radius: 9999px;
  padding: 0 6px;
  line-height: 1.35;
  display: inline-block;
}

.hc-username--at {
  color: #949cf7;
}

.hc-username--paren {
  color: var(--text-muted, #949ba4);
  font-weight: 400;
}

/* --- Inline edit history (in-chat) ---------------------------------------- */

/*
 * Old versions of an edited message, rendered above the current content by the
 * message-logger content patch. Like .hc-deleted this decorates Discord's own
 * DOM, so literal values, no tokens. The base class only handles wrapping; a
 * per-style modifier (chosen in settings) sets the look. MessageExtras re-reads
 * the modifier on every render, so changing the style applies live.
 */
.hc-edit-history__version {
  word-break: break-word;
  white-space: pre-wrap;
}

/* Per-version edit time, shown inline at the end of each old-version line.
 * Muted and compact; opacity keeps it tied to whatever the version style is,
 * and text-decoration:none stops the strike style from striking the time. */
.hc-edit-history__time {
  margin-left: 6px;
  font-size: 0.72em;
  opacity: 0.55;
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
  text-decoration: none;
  vertical-align: baseline;
}

/* The old-version line mirrors the deleted-message style (tint/text/ghost/
 * strike) so both share one setting; strike stays its natural default look. */

/* Style: red strikethrough \u2014 struck out in red, like removed text. */
.hc-edit-history__version--strike {
  color: rgba(255, 69, 58, 0.75);
  text-decoration: line-through;
  text-decoration-color: rgba(255, 69, 58, 0.4);
}

/* Style: red text \u2014 red, no strikethrough. */
.hc-edit-history__version--text {
  color: rgba(255, 69, 58, 0.85);
}

/* Style: ghost \u2014 faded out, keeps the normal text color. */
.hc-edit-history__version--ghost {
  opacity: 0.45;
  filter: saturate(0.6);
}

/* Style: tint \u2014 red wash + left bar, as a quote-like block on the line. */
.hc-edit-history__version--tint {
  background-color: rgba(255, 69, 58, 0.1);
  box-shadow: inset 2px 0 0 #ff453a;
  padding: 1px 6px 1px 8px;
  border-radius: 3px;
}

/* \u2500\u2500 message-cleaner page \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
 * The self-message cleaner's operate surface. Scope/confirm reuse .hc-section
 * and .hc-cell; these rules cover the action bar, the live status line, the
 * preview list, and the stat readout. Decorates Halcyon's own panel, so every
 * value is a token. */
.hc-cleaner__actions {
  display: flex;
  gap: var(--hc-space-3);
  margin: var(--hc-space-4) 0;
}
.hc-cleaner__actions .hc-btn {
  flex: 1;
}
.hc-cleaner__status {
  margin: var(--hc-space-3) 0;
  padding: var(--hc-space-3) var(--hc-space-4);
  background: var(--hc-fill-secondary);
  border-radius: var(--hc-radius-md);
}
.hc-cleaner__status-state {
  font-size: var(--hc-text-subhead);
  font-weight: 600;
  color: var(--hc-label-primary);
}
.hc-cleaner__status-detail {
  margin-top: 2px;
  font-size: var(--hc-text-footnote);
  color: var(--hc-label-secondary);
  word-break: break-word;
}
.hc-cleaner__list {
  display: flex;
  flex-direction: column;
}
.hc-cleaner__item {
  display: flex;
  gap: var(--hc-space-3);
  padding: var(--hc-space-2) var(--hc-space-4);
  font-size: var(--hc-text-footnote);
  border-bottom: 1px solid var(--hc-separator);
}
.hc-cleaner__item:last-child {
  border-bottom: none;
}
.hc-cleaner__item-time {
  flex-shrink: 0;
  color: var(--hc-accent);
  white-space: nowrap;
  font-variant-numeric: tabular-nums;
}
.hc-cleaner__item-text {
  color: var(--hc-label-primary);
  word-break: break-word;
}
.hc-cleaner__more {
  padding: var(--hc-space-2) var(--hc-space-4);
  font-size: var(--hc-text-caption1);
  color: var(--hc-label-tertiary);
}
.hc-cleaner__stat {
  display: flex;
  justify-content: center;
  align-items: baseline;
  gap: var(--hc-space-2);
}
.hc-cleaner__stat-num {
  font-size: var(--hc-text-title1);
  font-weight: 700;
  color: var(--hc-accent);
  font-variant-numeric: tabular-nums;
}
.hc-cleaner__stat-unit {
  font-size: var(--hc-text-footnote);
  color: var(--hc-label-secondary);
}

/* \u2500\u2500 message-cleaner picker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
.hc-cleaner__picker-head {
  display: flex;
  align-items: center;
  gap: var(--hc-space-3);
  padding: var(--hc-space-3) var(--hc-space-4);
  border-bottom: 1px solid var(--hc-separator);
}
.hc-cleaner__picker-title {
  flex: 1;
  text-align: center;
  font-weight: 700;
  font-size: var(--hc-text-subhead);
  color: var(--hc-label-primary);
}
.hc-cleaner__picker-list {
  display: flex;
  flex-direction: column;
  overflow-y: auto;
  max-height: 360px;
  padding: var(--hc-space-2);
}
.hc-cleaner__picker-item {
  display: flex;
  align-items: center;
  gap: var(--hc-space-3);
  padding: var(--hc-space-2) var(--hc-space-3);
  border-radius: var(--hc-radius-md);
  cursor: pointer;
  color: var(--hc-label-primary);
  transition: background var(--hc-duration-fast) var(--hc-ease);
}
.hc-cleaner__picker-item:hover {
  background: var(--hc-fill-secondary);
}
.hc-cleaner__picker-icon {
  width: 36px;
  height: 36px;
  border-radius: 50%;
  background: var(--hc-fill-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  flex-shrink: 0;
  font-size: var(--hc-text-subhead);
  color: var(--hc-label-secondary);
}
.hc-cleaner__picker-icon img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}
.hc-cleaner__picker-name {
  font-size: var(--hc-text-subhead);
  font-weight: 600;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}
.hc-cleaner__picker-empty {
  padding: var(--hc-space-5);
  text-align: center;
  font-size: var(--hc-text-footnote);
  color: var(--hc-label-tertiary);
}

/* \u2500\u2500 emote-cloner server picker \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
 * A floating modal (mounted in its own .halcyon host over Discord) shown when
 * "\u590D\u5236\u8868\u60C5/\u8D34\u7EB8\u5230\u670D\u52A1\u5668" is clicked. Sits on the shared .hc-overlay backdrop;
 * the panel is compact, with a search box and a scrollable, icon-bearing list
 * of the servers the account can add expressions to. Decorates Halcyon's own
 * surface, so every value is a token. */
.hc-emote-picker {
  width: min(440px, 92vw);
  max-height: min(560px, 82vh);
  background: var(--hc-bg-primary);
  border-radius: var(--hc-radius-xl);
  box-shadow: var(--hc-elev-3);
  display: flex;
  flex-direction: column;
  overflow: hidden;
  animation: hc-rise var(--hc-duration-slow) var(--hc-ease);
}

.hc-emote-picker__head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--hc-space-3);
  padding: var(--hc-space-4) var(--hc-space-4) var(--hc-space-3);
  border-bottom: 1px solid var(--hc-separator-opaque);
}

.hc-emote-picker__title {
  font-size: var(--hc-text-headline);
  line-height: var(--hc-lh-headline);
  font-weight: 600;
  color: var(--hc-label-primary);
}

.hc-emote-picker__close {
  flex: none;
  width: 28px;
  height: 28px;
  border: none;
  background: transparent;
  color: var(--hc-label-secondary);
  border-radius: var(--hc-radius-md);
  cursor: pointer;
  font-size: 15px;
  line-height: 1;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: background-color var(--hc-duration-fast) var(--hc-ease),
    color var(--hc-duration-fast) var(--hc-ease);
}

.hc-emote-picker__close:hover {
  background: var(--hc-fill-secondary);
  color: var(--hc-label-primary);
}

.hc-emote-picker__search {
  padding: var(--hc-space-3) var(--hc-space-4) var(--hc-space-2);
}

.hc-emote-picker__list {
  flex: 1;
  min-height: 0;
  overflow-y: auto;
  padding: var(--hc-space-2);
  display: flex;
  flex-direction: column;
  gap: 2px;
}

.hc-emote-picker__item {
  display: flex;
  align-items: center;
  gap: var(--hc-space-3);
  padding: var(--hc-space-2) var(--hc-space-3);
  border-radius: var(--hc-radius-md);
  cursor: pointer;
  transition: background-color var(--hc-duration-fast) var(--hc-ease);
}

.hc-emote-picker__item:hover {
  background: var(--hc-fill-secondary);
}

.hc-emote-picker__icon {
  flex: none;
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: var(--hc-fill-primary);
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  font-size: var(--hc-text-subhead);
  font-weight: 600;
  color: var(--hc-label-secondary);
}

.hc-emote-picker__icon img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.hc-emote-picker__name {
  flex: 1;
  min-width: 0;
  font-size: var(--hc-text-body);
  font-weight: 500;
  color: var(--hc-label-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.hc-emote-picker__empty {
  padding: var(--hc-space-8) var(--hc-space-6);
  text-align: center;
  color: var(--hc-label-tertiary);
  font-size: var(--hc-text-footnote);
}

/* Thin, subtle scrollbar for the picker list. Our overlay mounts in its own
 * .halcyon host, which Discord's global scrollbar styling doesn't reach, so
 * without this the list falls back to the chunky default OS scrollbar. */
.hc-emote-picker__list::-webkit-scrollbar {
  width: 8px;
}

.hc-emote-picker__list::-webkit-scrollbar-track {
  background: transparent;
}

.hc-emote-picker__list::-webkit-scrollbar-thumb {
  background: var(--hc-fill-secondary);
  border-radius: 9999px;
  border: 2px solid transparent;
  background-clip: padding-box;
}

.hc-emote-picker__list::-webkit-scrollbar-thumb:hover {
  background: var(--hc-label-tertiary);
  background-clip: padding-box;
}

/* Post-pick status view (copying / done / error), shown in place of the list
 * so a clone never looks like "nothing happened" even when the toast module
 * isn't present on this build. */
.hc-emote-picker__status {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  gap: var(--hc-space-3);
  padding: var(--hc-space-8) var(--hc-space-6);
}

.hc-emote-picker__status-icon {
  font-size: 32px;
  line-height: 1;
}

.hc-emote-picker__status[data-state="done"] .hc-emote-picker__status-icon {
  color: var(--hc-green);
}

.hc-emote-picker__status[data-state="error"] .hc-emote-picker__status-icon {
  color: var(--hc-red);
}

.hc-emote-picker__status-title {
  font-size: var(--hc-text-headline);
  line-height: var(--hc-lh-headline);
  font-weight: 600;
  color: var(--hc-label-primary);
}

.hc-emote-picker__status-detail {
  font-size: var(--hc-text-footnote);
  line-height: var(--hc-lh-footnote);
  color: var(--hc-label-secondary);
  max-width: 340px;
  word-break: break-word;
}

/* --- Quest indicator badge ------------------------------------------------ */
/* Small count badge on the quest rail button. Positioned at top-right, styled
   to match Discord's own notification badges. */
.hc-quest-btn {
  position: relative;
}

.hc-quest-badge {
  position: absolute;
  top: -4px;
  right: -4px;
  min-width: 18px;
  height: 18px;
  padding: 0 5px;
  border-radius: 9px;
  background: #ed4245;
  color: #fff;
  font-size: 11px;
  font-weight: 700;
  line-height: 18px;
  text-align: center;
  box-shadow: 0 0 0 3px var(--background-tertiary, #1e1f22);
}

/* --- Member count chip (member-count plugin) ------------------------------ */
/*
 * Inserted into Discord's channel header toolbar or above its member list, so
 * literal values and Discord's own CSS variables \u2014 the \`--hc-*\` tokens are
 * scoped to \`.halcyon\` and do not reach this far into the client's tree.
 * The host is inert: an empty chip (a DM, or a guild with no numbers yet)
 * occupies nothing.
 */
.hc-membercount-host {
  display: contents;
}
.hc-membercount {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  height: 24px;
  padding: 0 8px;
  margin-right: 8px;
  border-radius: 8px;
  background: rgba(128, 132, 142, 0.12);
  color: var(--interactive-normal, #b5bac1);
  font-size: 13px;
  font-weight: 500;
  line-height: 24px;
  white-space: nowrap;
  cursor: default;
  user-select: none;
}
.hc-membercount__icon {
  flex: 0 0 auto;
  opacity: 0.75;
}
.hc-membercount__part {
  display: inline-flex;
  align-items: center;
  gap: 4px;
}
.hc-membercount__dot {
  width: 8px;
  height: 8px;
  border-radius: 50%;
  background: #23a55a;
}
.hc-membercount__label {
  color: var(--text-muted, #949ba4);
  font-weight: 400;
}
.hc-membercount__value {
  font-variant-numeric: tabular-nums;
}
.hc-membercount__sep {
  color: var(--text-muted, #949ba4);
  opacity: 0.6;
}
/* Above the member list, the chip sits inside the scroller (before the first
 * group header), so it flows as a natural roster line rather than floating in
 * empty space above everything. Layout is the same pill as the header variant;
 * only the outer margin changes so it doesn't hug the aside's edge. */
.hc-membercount--list {
  margin: 8px 12px 4px;
}

/* --- Reactor list card (who-reacted plugin) ------------------------------- */
/*
 * Our own floating surface on document.body, hosted inside a \`.halcyon\`
 * element, so this one does use the design tokens. Non-interactive by design:
 * pointer-events stay off so the card can never eat the click that toggles a
 * reaction.
 */
.hc-whoreacted-host {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 4000;
  pointer-events: none;
}
.hc-whoreacted {
  min-width: 180px;
  max-width: 280px;
  padding: var(--hc-space-2) 0;
  border-radius: var(--hc-radius-md);
  background: var(--hc-bg-elevated);
  box-shadow: var(--hc-elev-2);
  font-size: var(--hc-text-footnote);
  line-height: var(--hc-lh-footnote);
}
.hc-whoreacted__head {
  display: flex;
  align-items: center;
  gap: var(--hc-space-2);
  padding: var(--hc-space-1) var(--hc-space-3) var(--hc-space-2);
  border-bottom: 1px solid var(--hc-separator);
  margin-bottom: var(--hc-space-2);
}
.hc-whoreacted__emoji-img {
  flex: 0 0 auto;
  width: 18px;
  height: 18px;
  object-fit: contain;
}
.hc-whoreacted__emoji-char {
  flex: 0 0 auto;
  font-size: 16px;
  line-height: 18px;
}
.hc-whoreacted__title {
  flex: 1;
  min-width: 0;
  font-weight: 600;
  color: var(--hc-label-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hc-whoreacted__count {
  flex: 0 0 auto;
  padding: 0 6px;
  border-radius: var(--hc-radius-pill);
  background: var(--hc-fill-secondary);
  color: var(--hc-label-secondary);
  font-size: var(--hc-text-caption2);
  font-variant-numeric: tabular-nums;
}
.hc-whoreacted__hint {
  padding: var(--hc-space-1) var(--hc-space-3) var(--hc-space-2);
  color: var(--hc-label-secondary);
}
.hc-whoreacted__hint--error {
  color: var(--hc-red);
}
.hc-whoreacted__list {
  max-height: 260px;
  overflow: hidden;
}
.hc-whoreacted__row {
  display: flex;
  align-items: center;
  gap: var(--hc-space-2);
  padding: 3px var(--hc-space-3);
}
.hc-whoreacted__avatar {
  flex: 0 0 auto;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  object-fit: cover;
}
.hc-whoreacted__name {
  flex: 1;
  min-width: 0;
  color: var(--hc-label-primary);
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}
.hc-whoreacted__tag {
  flex: 0 0 auto;
  padding: 0 4px;
  border-radius: var(--hc-radius-xs);
  background: var(--hc-accent);
  color: #fff;
  font-size: var(--hc-text-caption2);
  font-weight: 600;
  line-height: 14px;
}
.hc-whoreacted__id {
  flex: 0 0 auto;
  color: var(--hc-label-tertiary);
  font-family: var(--hc-font-mono);
  font-size: var(--hc-text-caption2);
}
.hc-whoreacted__more {
  padding: var(--hc-space-1) var(--hc-space-3) 0;
  color: var(--hc-label-tertiary);
}

/* --- Platform indicators (platform-indicators plugin) --------------------- */
/*
 * Inline glyphs appended inside Discord's message header and member rows, so
 * literal values, no tokens. \`vertical-align: middle\` keeps them on the name's
 * baseline; the status colors match Discord's own presence dots.
 */
.hc-platform-host {
  display: inline;
}
.hc-platform {
  display: inline-flex;
  align-items: center;
  gap: 3px;
  margin-left: 5px;
  vertical-align: middle;
}
.hc-platform__item {
  display: inline-flex;
  align-items: center;
}
.hc-platform__item--online {
  color: #23a55a;
}
.hc-platform__item--idle {
  color: #f0b232;
}
.hc-platform__item--dnd {
  color: #f23f43;
}
.hc-platform__item--offline,
.hc-platform__item--muted {
  color: var(--text-muted, #949ba4);
}




/* --- Inline reactor avatars (who-reacted plugin) -------------------------- */
/*
 * Reactor faces inside every reaction pill \u2014 the primary surface. Meant to
 * blend with Discord's own count layout: same vertical center, small enough
 * that a pill with 3 avatars is only a little wider than one without, and no
 * background of our own so the pill's own tint (blue for reactionMe, grey
 * otherwise) shows through.
 *
 * Attached inside \`.reactionInner__\u2026\` as its last child. Sits after the count
 * with a small margin, so it reads as an appended detail rather than a
 * standalone widget.
 */
.hc-inline-reactors {
  display: inline-flex;
  align-items: center;
  margin-left: 4px;
  gap: 0;
  line-height: 1;
  /* Not interactive: this must never eat the click that toggles your own
   * reaction on the pill it's inside. */
  pointer-events: none;
}
.hc-inline-reactors__avatar {
  width: 14px;
  height: 14px;
  border-radius: 50%;
  object-fit: cover;
  /* A slim rim in the pill's background color visually separates overlapping
   * avatars from each other without adding a foreign block color. */
  border: 1.5px solid var(--background-secondary, #2b2d31);
  background: var(--background-tertiary, #1e1f22);
  /* Overlap each next avatar over the previous one; the first stands alone. */
  margin-left: -4px;
}
.hc-inline-reactors__avatar:first-child {
  margin-left: 0;
}
.hc-inline-reactors__more {
  margin-left: 4px;
  font-size: 11px;
  font-weight: 600;
  color: var(--interactive-normal, #b5bac1);
  font-variant-numeric: tabular-nums;
}
/* When the pill is the "I reacted" variant Discord tints the whole pill blue,
 * so switch the avatar rim to that darker inner tone (approximation \u2014 no exact
 * token exists for the "reactionMe" background) so avatars don't rim in a
 * conflicting color. Falls back to the default rim on builds without that
 * class. */
[class*="reactionMe"] .hc-inline-reactors__avatar {
  border-color: rgba(88, 101, 242, 0.35);
}


/* --- Recovered media on a deleted message (message-logger, in-chat) ------- */
/*
 * Discord strips a deleted message's attachments/embeds from its render, so we
 * paint the recovered thumbnails back in beneath the "\u6B64\u6D88\u606F\u5DF2\u5220\u9664" marker. Sits
 * inside Discord's own message row, so literal values, no tokens.
 */
.hc-deleted-media {
  display: flex;
  flex-wrap: wrap;
  gap: 6px;
  margin-top: 4px;
}
.hc-deleted-media__thumb {
  max-width: 240px;
  max-height: 200px;
  width: auto;
  height: auto;
  object-fit: contain;
  border-radius: 8px;
  background: rgba(0, 0, 0, 0.2);
}
.hc-deleted-media__file {
  color: #00a8fc;
  font-size: 0.8125rem;
  word-break: break-all;
}


/* --- Message-log button in the channel header toolbar -------------------- */
/*
 * Sits among Discord's own header icons (pin, members, \u2026), so it must read as
 * one of them: same 24px hit target, muted normal color, brighter on hover.
 * Decorates Discord's toolbar, so literal values + Discord CSS variables.
 */
.hc-mlog-toolbtn-host {
  display: inline-flex;
  align-items: center;
}
.hc-mlog-toolbtn {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  margin: 0 8px;
  padding: 0;
  border: none;
  background: transparent;
  color: var(--interactive-normal, #b5bac1);
  cursor: pointer;
  transition: color 0.15s ease;
}
.hc-mlog-toolbtn:hover {
  color: var(--interactive-hover, #dbdee1);
}
.hc-mlog-toolbtn:active {
  color: var(--interactive-active, #fff);
}


/* --- Message-log search box ---------------------------------------------- */
/* Inside the .halcyon panel, so design tokens throughout. Mirrors the plugin
 * browser's search field but on its own row above the list. */
.hc-mlog-search {
  display: flex;
  align-items: center;
  gap: var(--hc-space-2);
  height: 36px;
  margin: var(--hc-space-2) 0 var(--hc-space-3);
  padding: 0 var(--hc-space-3);
  border-radius: var(--hc-radius-md);
  background: var(--hc-fill-secondary);
  color: var(--hc-label-secondary);
}
.hc-mlog-search input {
  flex: 1;
  min-width: 0;
  border: none;
  background: transparent;
  outline: none;
  color: var(--hc-label-primary);
  font-size: var(--hc-text-callout);
  font-family: var(--hc-font);
}
.hc-mlog-search input::placeholder {
  color: var(--hc-label-tertiary);
}
.hc-mlog-search__clear {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 20px;
  height: 20px;
  border: none;
  border-radius: var(--hc-radius-pill);
  background: var(--hc-fill-primary);
  color: var(--hc-label-secondary);
  font-size: 15px;
  line-height: 1;
  cursor: pointer;
}
.hc-mlog-search__clear:hover {
  color: var(--hc-label-primary);
}

/* --- Thin scrollbars for Halcyon's own scroll areas ---------------------- */
/*
 * The settings panel and embedded views scroll with the OS default scrollbar,
 * which is a chunky light bar that reads as foreign inside the dark iOS-styled
 * panel. Give those containers the same slim, self-colored bar the emote picker
 * uses. Our surfaces mount in their own .halcyon host, outside Discord's global
 * scrollbar styling, so these rules are needed here.
 */
.hc-panel__scroll,
.hc-embed,
.hc-msglist {
  scrollbar-width: thin;
  scrollbar-color: var(--hc-fill-primary) transparent;
}
.hc-panel__scroll::-webkit-scrollbar,
.hc-embed::-webkit-scrollbar,
.hc-msglist::-webkit-scrollbar {
  width: 8px;
  height: 8px;
}
.hc-panel__scroll::-webkit-scrollbar-track,
.hc-embed::-webkit-scrollbar-track,
.hc-msglist::-webkit-scrollbar-track {
  background: transparent;
}
.hc-panel__scroll::-webkit-scrollbar-thumb,
.hc-embed::-webkit-scrollbar-thumb,
.hc-msglist::-webkit-scrollbar-thumb {
  background: var(--hc-fill-secondary);
  border-radius: 9999px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
.hc-panel__scroll::-webkit-scrollbar-thumb:hover,
.hc-embed::-webkit-scrollbar-thumb:hover,
.hc-msglist::-webkit-scrollbar-thumb:hover {
  background: var(--hc-label-tertiary);
  background-clip: padding-box;
}
`;

  // src/ui/inject-styles.ts
  var STYLE_ID = "halcyon-styles";
  var mounted = false;
  function injectStyles() {
    if (mounted) return;
    const existing = document.getElementById(STYLE_ID);
    const style = existing instanceof HTMLStyleElement ? existing : document.createElement("style");
    style.id = STYLE_ID;
    style.textContent = `${tokens_default}
${components_default}`;
    if (!existing) {
      document.head.appendChild(style);
    }
    mounted = true;
  }

  // src/icons/index.tsx
  function Glyph({ size = 20, className, filled, children, ...rest }) {
    const label = rest["aria-label"];
    if (typeof size !== "number" || !Number.isFinite(size)) size = 20;
    return /* @__PURE__ */ React.createElement(
      "svg",
      {
        className,
        width: size,
        height: size,
        viewBox: "0 0 24 24",
        fill: filled ? "currentColor" : "none",
        stroke: filled ? "none" : "currentColor",
        strokeWidth: 1.5,
        strokeLinecap: "round",
        strokeLinejoin: "round",
        role: label ? "img" : void 0,
        "aria-label": label,
        "aria-hidden": label ? void 0 : true
      },
      children
    );
  }
  function HalcyonMark(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("rect", { x: "3.25", y: "3.25", width: "17.5", height: "17.5", rx: "5" }), /* @__PURE__ */ React.createElement("path", { d: "M6.5 13.2c1.4-2.5 2.9-2.5 4.3 0s2.9 2.5 4.3 0 2.9-2.5 2.9-2.5" }));
  }
  function ChevronRightIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M9 6l6 6-6 6" }));
  }
  function ClockIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "12", r: "8.25" }), /* @__PURE__ */ React.createElement("path", { d: "M12 7.5V12l3 2" }));
  }
  function TrashIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M4.5 7h15" }), /* @__PURE__ */ React.createElement("path", { d: "M9.25 7V5.5A1.5 1.5 0 0110.75 4h2.5A1.5 1.5 0 0114.75 5.5V7" }), /* @__PURE__ */ React.createElement("path", { d: "M6.5 7l.85 11.1A2 2 0 009.34 20h5.32a2 2 0 001.99-1.9L17.5 7" }), /* @__PURE__ */ React.createElement("path", { d: "M10 11v5.5M14 11v5.5" }));
  }
  function PencilIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M13.5 6.5l4 4" }), /* @__PURE__ */ React.createElement("path", { d: "M4.5 19.5l1-4L15.5 5.5a2 2 0 013 3L8.5 18.5l-4 1z" }));
  }
  function ShieldIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M12 3.5l7 2.6v5c0 4.4-3 7.3-7 8.9-4-1.6-7-4.5-7-8.9v-5l7-2.6z" }), /* @__PURE__ */ React.createElement("path", { d: "M9 12l2 2 4-4" }));
  }
  function MessageIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M5 5.5h14a1.5 1.5 0 011.5 1.5v8a1.5 1.5 0 01-1.5 1.5H9.5L5.5 20v-3H5A1.5 1.5 0 013.5 15.5V7A1.5 1.5 0 015 5.5z" }));
  }
  function SearchIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("circle", { cx: "11", cy: "11", r: "6.25" }), /* @__PURE__ */ React.createElement("path", { d: "M20 20l-3.8-3.8" }));
  }
  function XmarkIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M6.5 6.5l11 11M17.5 6.5l-11 11" }));
  }
  function MessageCheckIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M5 5.5h14a1.5 1.5 0 011.5 1.5v8a1.5 1.5 0 01-1.5 1.5H9.5L5.5 20v-3H5A1.5 1.5 0 013.5 15.5V7A1.5 1.5 0 015 5.5z" }), /* @__PURE__ */ React.createElement("path", { d: "M8.5 11l2.25 2.25L15.5 8.5" }));
  }
  function SlidersIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M4.5 8h9M17 8h2.5M4.5 16h2.5M10.5 16h9" }), /* @__PURE__ */ React.createElement("circle", { cx: "15", cy: "8", r: "2.25" }), /* @__PURE__ */ React.createElement("circle", { cx: "9", cy: "16", r: "2.25" }));
  }
  function SpeakerIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M4.5 9.5v5H7l4.5 3.5V6L7 9.5H4.5z" }), /* @__PURE__ */ React.createElement("path", { d: "M15 9a4 4 0 010 6" }), /* @__PURE__ */ React.createElement("path", { d: "M17.5 6.5a7.5 7.5 0 010 11" }));
  }
  function AppearanceIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "12", r: "8.25" }), /* @__PURE__ */ React.createElement("path", { d: "M12 3.75a8.25 8.25 0 010 16.5z", fill: "currentColor", stroke: "none" }));
  }
  function CodeIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M8.5 8L4.5 12l4 4" }), /* @__PURE__ */ React.createElement("path", { d: "M15.5 8l4 4-4 4" }), /* @__PURE__ */ React.createElement("path", { d: "M13.5 5.5l-3 13" }));
  }
  function EllipsisIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props, filled: true }, /* @__PURE__ */ React.createElement("circle", { cx: "5.5", cy: "12", r: "1.6" }), /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "12", r: "1.6" }), /* @__PURE__ */ React.createElement("circle", { cx: "18.5", cy: "12", r: "1.6" }));
  }
  function DownloadIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M12 4v10" }), /* @__PURE__ */ React.createElement("path", { d: "M8 10.5l4 4 4-4" }), /* @__PURE__ */ React.createElement("path", { d: "M5 19.5h14" }));
  }
  function PlusIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M12 5v14M5 12h14" }));
  }
  function InfoIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "12", r: "8.25" }), /* @__PURE__ */ React.createElement("path", { d: "M12 11v5" }), /* @__PURE__ */ React.createElement("path", { d: "M12 7.75h.01" }));
  }
  function WarningIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M12 4.5L3.5 19h17L12 4.5z" }), /* @__PURE__ */ React.createElement("path", { d: "M12 10v4" }), /* @__PURE__ */ React.createElement("path", { d: "M12 16.75h.01" }));
  }
  function ListIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M8.5 7h11M8.5 12h11M8.5 17h11" }), /* @__PURE__ */ React.createElement("path", { d: "M4.5 7h.01M4.5 12h.01M4.5 17h.01" }));
  }
  function MinusIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M5 12h14" }));
  }
  function RefreshIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M19 8.5a7.5 7.5 0 10.9 6" }), /* @__PURE__ */ React.createElement("path", { d: "M19 4v4.5h-4.5" }));
  }
  function ChevronLeftIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M15 6l-6 6 6 6" }));
  }
  function ServerIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("rect", { x: "4", y: "4", width: "16", height: "6", rx: "2" }), /* @__PURE__ */ React.createElement("rect", { x: "4", y: "14", width: "16", height: "6", rx: "2" }), /* @__PURE__ */ React.createElement("path", { d: "M8 7h.01M8 17h.01" }));
  }
  function BroadcastIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "12", r: "2" }), /* @__PURE__ */ React.createElement("path", { d: "M8.5 8.5a5 5 0 000 7M15.5 8.5a5 5 0 010 7" }), /* @__PURE__ */ React.createElement("path", { d: "M6 6a9 9 0 000 12M18 6a9 9 0 010 12" }));
  }
  function QuestIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props, filled: true }, /* @__PURE__ */ React.createElement("path", { d: "M7.5 21.7a8.95 8.95 0 0 1 9 0 1 1 0 0 0 1-1.73c-.6-.35-1.24-.64-1.9-.87.54-.3 1.05-.65 1.52-1.07a3.98 3.98 0 0 0 5.49-1.8.77.77 0 0 0-.24-.95 3.98 3.98 0 0 0-2.02-.76A4 4 0 0 0 23 10.47a.76.76 0 0 0-.71-.71 4.06 4.06 0 0 0-1.6.22 3.99 3.99 0 0 0 .54-5.35.77.77 0 0 0-.95-.24c-.75.36-1.37.95-1.77 1.67V6a4 4 0 0 0-4.9-3.9.77.77 0 0 0-.6.72 4 4 0 0 0 3.7 4.17c.89 1.3 1.3 2.95 1.3 4.51 0 3.66-2.75 6.5-6 6.5s-6-2.84-6-6.5c0-1.56.41-3.21 1.3-4.51A4 4 0 0 0 11 2.82a.77.77 0 0 0-.6-.72 4.01 4.01 0 0 0-4.9 3.96A4.02 4.02 0 0 0 3.73 4.4a.77.77 0 0 0-.95.24 3.98 3.98 0 0 0 .55 5.35 4 4 0 0 0-1.6-.22.76.76 0 0 0-.72.71l-.01.28a4 4 0 0 0 2.65 3.77c-.75.06-1.45.33-2.02.76-.3.22-.4.62-.24.95a4 4 0 0 0 5.49 1.8c.47.42.98.78 1.53 1.07-.67.23-1.3.52-1.91.87a1 1 0 1 0 1 1.73Z" }));
  }
  function PeopleIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("circle", { cx: "9", cy: "8.25", r: "3.25" }), /* @__PURE__ */ React.createElement("path", { d: "M3.5 19.5c0-2.9 2.46-5.25 5.5-5.25s5.5 2.35 5.5 5.25" }), /* @__PURE__ */ React.createElement("path", { d: "M16 5.4a3.25 3.25 0 010 6.2" }), /* @__PURE__ */ React.createElement("path", { d: "M17.2 14.6c2.03.6 3.3 2.4 3.3 4.9" }));
  }
  function DesktopIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("rect", { x: "3", y: "4.5", width: "18", height: "11.5", rx: "2" }), /* @__PURE__ */ React.createElement("path", { d: "M9 19.5h6M12 16v3.5" }));
  }
  function MobileIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("rect", { x: "7", y: "2.75", width: "10", height: "18.5", rx: "2.5" }), /* @__PURE__ */ React.createElement("path", { d: "M10.75 18.25h2.5" }));
  }
  function GlobeIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("circle", { cx: "12", cy: "12", r: "8.25" }), /* @__PURE__ */ React.createElement("path", { d: "M3.75 12h16.5" }), /* @__PURE__ */ React.createElement("path", { d: "M12 3.75c2.2 2.3 3.3 5.05 3.3 8.25S14.2 17.95 12 20.25c-2.2-2.3-3.3-5.05-3.3-8.25S9.8 6.05 12 3.75z" }));
  }
  function GamepadIcon(props) {
    return /* @__PURE__ */ React.createElement(Glyph, { ...props }, /* @__PURE__ */ React.createElement("path", { d: "M7.5 7.5h9a5 5 0 014.9 6l-.5 2.6A2.5 2.5 0 0118.45 18c-.9 0-1.73-.48-2.17-1.26L15.5 15.5h-7l-.78 1.24A2.5 2.5 0 015.55 18a2.5 2.5 0 01-2.45-1.9l-.5-2.6a5 5 0 014.9-6z" }), /* @__PURE__ */ React.createElement("path", { d: "M8.25 10.5v2.25M7.12 11.6h2.26" }), /* @__PURE__ */ React.createElement("path", { d: "M15.25 11h.01M17 12.75h.01" }));
  }

  // src/ui/components/Toggle.tsx
  function Toggle({ checked, onChange, disabled, ...rest }) {
    return /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        role: "switch",
        "aria-checked": checked,
        "aria-label": rest["aria-label"],
        className: "hc-toggle",
        "data-on": checked,
        disabled,
        onClick: () => {
          if (!disabled) onChange(!checked);
        }
      },
      /* @__PURE__ */ React.createElement("span", { className: "hc-toggle__knob" })
    );
  }

  // src/ui/components/ListRow.tsx
  function ListRow({
    icon,
    iconBackground,
    title,
    subtitle,
    accessory,
    onClick,
    showChevron
  }) {
    const interactive = typeof onClick === "function";
    return /* @__PURE__ */ React.createElement(
      "div",
      {
        className: interactive ? "hc-row hc-row--button" : "hc-row",
        onClick,
        role: interactive ? "button" : void 0,
        tabIndex: interactive ? 0 : void 0,
        onKeyDown: interactive ? (e) => {
          if (e.key === "Enter" || e.key === " ") {
            e.preventDefault();
            onClick?.();
          }
        } : void 0
      },
      icon && /* @__PURE__ */ React.createElement(
        "div",
        {
          className: "hc-row__icon",
          style: iconBackground ? { background: iconBackground } : void 0
        },
        icon
      ),
      /* @__PURE__ */ React.createElement("div", { className: "hc-row__text" }, /* @__PURE__ */ React.createElement("div", { className: "hc-row__title" }, title), subtitle != null && subtitle !== false && /* @__PURE__ */ React.createElement("div", { className: "hc-row__subtitle" }, subtitle)),
      accessory != null && accessory !== false && /* @__PURE__ */ React.createElement("div", { className: "hc-row__accessory" }, accessory),
      showChevron && /* @__PURE__ */ React.createElement(ChevronRightIcon, { size: 20, className: "hc-row__chevron" })
    );
  }

  // src/ui/components/Badge.tsx
  function Badge({ tone = "neutral", children }) {
    return /* @__PURE__ */ React.createElement("span", { className: "hc-badge", "data-tone": tone }, children);
  }

  // src/ui/components/EmptyState.tsx
  function EmptyState({ icon, title, subtitle, action }) {
    return /* @__PURE__ */ React.createElement("div", { className: "hc-empty" }, icon, /* @__PURE__ */ React.createElement("div", { className: "hc-empty__title" }, title), subtitle && /* @__PURE__ */ React.createElement("div", { className: "hc-empty__subtitle" }, subtitle), action && /* @__PURE__ */ React.createElement("div", { style: { marginTop: "var(--hc-space-5)" } }, action));
  }

  // src/ui/components/NumberStepper.tsx
  function clamp(value, min, max) {
    if (min != null && value < min) return min;
    if (max != null && value > max) return max;
    return value;
  }
  function NumberStepper({ value, onChange, min, max, step = 1 }) {
    const atMin = min != null && value <= min;
    const atMax = max != null && value >= max;
    return /* @__PURE__ */ React.createElement("div", { className: "hc-stepper" }, /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-stepper__btn",
        onClick: () => onChange(clamp(value - step, min, max)),
        disabled: atMin,
        "aria-label": "\u51CF\u5C11"
      },
      /* @__PURE__ */ React.createElement(MinusIcon, { size: 16 })
    ), /* @__PURE__ */ React.createElement("span", { className: "hc-stepper__value" }, value), /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-stepper__btn",
        onClick: () => onChange(clamp(value + step, min, max)),
        disabled: atMax,
        "aria-label": "\u589E\u52A0"
      },
      /* @__PURE__ */ React.createElement(PlusIcon, { size: 16 })
    ));
  }

  // src/ui/components/TextInput.tsx
  function TextInput({ value, onChange, className, ...rest }) {
    return /* @__PURE__ */ React.createElement(
      "input",
      {
        className: className ? `hc-input ${className}` : "hc-input",
        value,
        onChange: (e) => onChange(e.currentTarget.value),
        ...rest
      }
    );
  }

  // src/ui/components/Select.tsx
  function Select({ value, options, onChange, ...rest }) {
    const [open, setOpen] = useState(false);
    const [active2, setActive] = useState(-1);
    const rootRef = useRef(null);
    const menuRef = useRef(null);
    const [menuPos, setMenuPos] = useState(null);
    const current = options.find((o) => o.value === value);
    useEffect(() => {
      if (!open) return;
      const onPress = (e) => {
        const t = e.target;
        if (rootRef.current?.contains(t)) return;
        if (menuRef.current?.contains(t)) return;
        setOpen(false);
      };
      document.addEventListener("pointerdown", onPress, true);
      return () => document.removeEventListener("pointerdown", onPress, true);
    }, [open]);
    useEffect(() => {
      if (!open) return;
      const onMove = (e) => {
        if (menuRef.current && e.target instanceof Node && menuRef.current.contains(e.target)) return;
        setOpen(false);
      };
      window.addEventListener("scroll", onMove, true);
      window.addEventListener("resize", onMove);
      return () => {
        window.removeEventListener("scroll", onMove, true);
        window.removeEventListener("resize", onMove);
      };
    }, [open]);
    const openMenu = () => {
      const rect = rootRef.current?.getBoundingClientRect();
      if (rect) {
        const estimated = Math.min(280, options.length * 36 + 10);
        const below = rect.bottom + 6;
        const top = below + estimated > window.innerHeight - 8 ? Math.max(8, rect.top - 6 - estimated) : below;
        setMenuPos({
          top,
          right: Math.max(8, window.innerWidth - rect.right),
          width: rect.width
        });
      }
      setActive(Math.max(0, options.findIndex((o) => o.value === value)));
      setOpen(true);
    };
    const pick = (next) => {
      setOpen(false);
      if (next !== value) onChange(next);
    };
    const onKeyDown3 = (e) => {
      if (!open) {
        if (e.key === "Enter" || e.key === " " || e.key === "ArrowDown") {
          e.preventDefault();
          openMenu();
        }
        return;
      }
      if (e.key === "Escape") {
        e.preventDefault();
        setOpen(false);
      } else if (e.key === "ArrowDown") {
        e.preventDefault();
        setActive((i) => Math.min(options.length - 1, i + 1));
      } else if (e.key === "ArrowUp") {
        e.preventDefault();
        setActive((i) => Math.max(0, i - 1));
      } else if (e.key === "Enter" || e.key === " ") {
        e.preventDefault();
        if (active2 >= 0 && active2 < options.length) pick(options[active2].value);
      } else if (e.key === "Tab") {
        setOpen(false);
      }
    };
    return /* @__PURE__ */ React.createElement("div", { className: "hc-select", ref: rootRef, onKeyDown: onKeyDown3 }, /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-select__button",
        "aria-haspopup": "listbox",
        "aria-expanded": open,
        "aria-label": rest["aria-label"],
        onClick: () => open ? setOpen(false) : openMenu()
      },
      /* @__PURE__ */ React.createElement("span", { className: "hc-select__value" }, current?.label ?? value),
      /* @__PURE__ */ React.createElement(
        "svg",
        {
          className: "hc-select__chevron",
          width: "12",
          height: "12",
          viewBox: "0 0 24 24",
          fill: "none",
          stroke: "currentColor",
          strokeWidth: 2.5,
          strokeLinecap: "round",
          strokeLinejoin: "round",
          "aria-hidden": true,
          "data-open": open
        },
        /* @__PURE__ */ React.createElement("path", { d: "M6 9l6 6 6-6" })
      )
    ), open && menuPos && ReactDOM.createPortal(
      /* @__PURE__ */ React.createElement(
        "div",
        {
          className: "halcyon",
          ref: menuRef,
          style: { position: "fixed", top: menuPos.top, right: menuPos.right, zIndex: 1e4 },
          onKeyDown: onKeyDown3
        },
        /* @__PURE__ */ React.createElement("div", { className: "hc-select__menu", role: "listbox", style: { minWidth: menuPos.width } }, options.map((opt, index) => /* @__PURE__ */ React.createElement(
          "button",
          {
            type: "button",
            key: opt.value,
            role: "option",
            "aria-selected": opt.value === value,
            className: "hc-select__option",
            "data-active": index === active2,
            "data-selected": opt.value === value,
            onPointerEnter: () => setActive(index),
            onClick: () => pick(opt.value)
          },
          /* @__PURE__ */ React.createElement("span", { className: "hc-select__optlabel" }, opt.label),
          opt.value === value && /* @__PURE__ */ React.createElement(
            "svg",
            {
              className: "hc-select__check",
              width: "14",
              height: "14",
              viewBox: "0 0 24 24",
              fill: "none",
              stroke: "currentColor",
              strokeWidth: 2.5,
              strokeLinecap: "round",
              strokeLinejoin: "round",
              "aria-hidden": true
            },
            /* @__PURE__ */ React.createElement("path", { d: "M5 12.5l4.5 4.5L19 7" })
          )
        )))
      ),
      document.body
    ));
  }

  // src/ui/components/StringListEditor.tsx
  function StringListEditor({ value, onChange, itemPlaceholder }) {
    const [draft, setDraft] = useState("");
    const commit = () => {
      const next = draft.trim();
      if (!next || value.includes(next)) {
        setDraft("");
        return;
      }
      onChange([...value, next]);
      setDraft("");
    };
    const removeAt = (index) => {
      onChange(value.filter((_, i) => i !== index));
    };
    return /* @__PURE__ */ React.createElement("div", { className: "hc-strlist" }, value.map((item, index) => /* @__PURE__ */ React.createElement("div", { className: "hc-strlist__item", key: item }, /* @__PURE__ */ React.createElement(TextInput, { value: item, onChange: () => void 0, readOnly: true }), /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-iconbtn hc-iconbtn--danger",
        onClick: () => removeAt(index),
        "aria-label": "\u79FB\u9664"
      },
      /* @__PURE__ */ React.createElement(TrashIcon, { size: 18 })
    ))), /* @__PURE__ */ React.createElement("div", { className: "hc-strlist__add" }, /* @__PURE__ */ React.createElement(
      TextInput,
      {
        value: draft,
        onChange: setDraft,
        placeholder: itemPlaceholder ?? "\u6DFB\u52A0\u4E00\u9879",
        onKeyDown: (e) => {
          if (e.key === "Enter") {
            e.preventDefault();
            commit();
          }
        }
      }
    ), /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-iconbtn",
        onClick: commit,
        "aria-label": "\u6DFB\u52A0",
        disabled: !draft.trim()
      },
      /* @__PURE__ */ React.createElement(PlusIcon, { size: 18 })
    )));
  }

  // src/ui/components/Button.tsx
  function Button({
    variant = "secondary",
    size = "md",
    icon,
    className,
    children,
    type = "button",
    ...rest
  }) {
    const classes = ["hc-btn", `hc-btn--${variant}`];
    if (size !== "md") classes.push(`hc-btn--${size}`);
    if (className) classes.push(className);
    return /* @__PURE__ */ React.createElement("button", { type, className: classes.join(" "), ...rest }, icon, children != null && children !== false && /* @__PURE__ */ React.createElement("span", null, children));
  }

  // src/ui/settings/hooks.ts
  function useRuntimeList() {
    const [list, setList] = useState(() => runtime.list());
    useEffect(() => {
      const refresh = () => setList(runtime.list());
      refresh();
      return runtime.onChange(refresh);
    }, []);
    return list;
  }
  function useSettingsSnapshot(settings11) {
    const [, bump] = useState(0);
    useEffect(() => {
      const unsubscribes3 = Object.keys(settings11.schema).map(
        (key) => settings11.subscribe(key, () => bump((n) => n + 1))
      );
      return () => {
        for (const off of unsubscribes3) off();
      };
    }, [settings11]);
    return settings11.store;
  }

  // src/ui/settings/SettingsForm.tsx
  function clone(value) {
    if (value === null || typeof value !== "object") return value;
    return JSON.parse(JSON.stringify(value));
  }
  function equal(a, b) {
    if (a === b) return true;
    try {
      return JSON.stringify(a) === JSON.stringify(b);
    } catch {
      return false;
    }
  }
  function SettingsForm({ settings: settings11 }) {
    const store = useSettingsSnapshot(settings11);
    const keys = useMemo(
      () => Object.keys(settings11.schema).filter((key) => !settings11.schema[key].hidden),
      [settings11]
    );
    const [draft, setDraft] = useState(() => seed(store, keys));
    useEffect(() => {
      setDraft(seed(store, keys));
    }, [settings11]);
    if (keys.length === 0) return null;
    const dirty = keys.filter((key) => !equal(draft[key], store[key]));
    const save = () => {
      for (const key of dirty) store[key] = clone(draft[key]);
    };
    const discard = () => setDraft(seed(store, keys));
    const sections = [];
    for (const key of keys) {
      const title = settings11.schema[key].group ?? "\u8BBE\u7F6E";
      const last = sections[sections.length - 1];
      if (last && last.title === title) last.keys.push(key);
      else sections.push({ title, keys: [key] });
    }
    return /* @__PURE__ */ React.createElement(React.Fragment, null, sections.map((section, index) => /* @__PURE__ */ React.createElement("div", { className: "hc-section", key: `${section.title}-${index}` }, /* @__PURE__ */ React.createElement("div", { className: "hc-section__title" }, section.title), /* @__PURE__ */ React.createElement("div", { className: "hc-section__body" }, section.keys.map((key) => /* @__PURE__ */ React.createElement(
      SettingField,
      {
        key,
        def: settings11.schema[key],
        value: draft[key],
        onChange: (next) => setDraft((prev) => ({ ...prev, [key]: next }))
      }
    ))))), dirty.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "hc-savebar" }, /* @__PURE__ */ React.createElement("span", { className: "hc-savebar__label" }, "\u6709 ", dirty.length, " \u9879\u672A\u4FDD\u5B58\u7684\u4FEE\u6539"), /* @__PURE__ */ React.createElement("div", { className: "hc-savebar__actions" }, /* @__PURE__ */ React.createElement(Button, { size: "sm", variant: "plain", onClick: discard }, "\u653E\u5F03"), /* @__PURE__ */ React.createElement(Button, { size: "sm", variant: "primary", onClick: save }, "\u4FDD\u5B58"))));
  }
  function seed(store, keys) {
    const out = {};
    for (const key of keys) out[key] = clone(store[key]);
    return out;
  }
  function SettingField({ def, value, onChange }) {
    const label = /* @__PURE__ */ React.createElement("div", { className: "hc-cell__main" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__label" }, def.label), def.description && /* @__PURE__ */ React.createElement("div", { className: "hc-cell__desc" }, def.description));
    switch (def.type) {
      case "boolean":
        return /* @__PURE__ */ React.createElement("div", { className: "hc-cell hc-cell--row" }, label, /* @__PURE__ */ React.createElement(
          Toggle,
          {
            checked: value === true,
            onChange: (next) => onChange(next),
            disabled: def.disabled?.(),
            "aria-label": def.label
          }
        ));
      case "number":
        return /* @__PURE__ */ React.createElement("div", { className: "hc-cell hc-cell--row" }, label, /* @__PURE__ */ React.createElement(
          NumberStepper,
          {
            value: typeof value === "number" ? value : def.default,
            onChange: (next) => onChange(next),
            min: def.min,
            max: def.max,
            step: def.step
          }
        ));
      case "select":
        return /* @__PURE__ */ React.createElement("div", { className: "hc-cell hc-cell--row" }, label, /* @__PURE__ */ React.createElement(
          Select,
          {
            value: typeof value === "string" ? value : def.default,
            onChange: (next) => onChange(next),
            options: def.options
          }
        ));
      case "string":
        return /* @__PURE__ */ React.createElement("div", { className: "hc-cell" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell--row" }, label), /* @__PURE__ */ React.createElement("div", { className: "hc-cell__control" }, /* @__PURE__ */ React.createElement(
          TextInput,
          {
            value: typeof value === "string" ? value : "",
            onChange: (next) => onChange(next),
            placeholder: def.placeholder,
            maxLength: def.maxLength
          }
        )));
      case "string-list":
        return /* @__PURE__ */ React.createElement("div", { className: "hc-cell" }, label, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__control" }, /* @__PURE__ */ React.createElement(
          StringListEditor,
          {
            value: Array.isArray(value) ? value : [],
            onChange: (next) => onChange(next),
            itemPlaceholder: def.itemPlaceholder
          }
        )));
      case "custom": {
        const Custom = def.component;
        return /* @__PURE__ */ React.createElement("div", { className: "hc-cell" }, label, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__control" }, /* @__PURE__ */ React.createElement(Custom, { value, onChange })));
      }
      default:
        return null;
    }
  }

  // src/ui/settings/categories.ts
  var CATEGORIES = {
    utility: { label: "\u5B9E\u7528\u5DE5\u5177", color: "var(--hc-accent)", Icon: SlidersIcon },
    chat: { label: "\u804A\u5929", color: "var(--hc-green)", Icon: MessageIcon },
    voice: { label: "\u8BED\u97F3", color: "var(--hc-indigo)", Icon: SpeakerIcon },
    appearance: { label: "\u5916\u89C2", color: "var(--hc-pink)", Icon: AppearanceIcon },
    privacy: { label: "\u9690\u79C1", color: "var(--hc-teal)", Icon: ShieldIcon },
    developer: { label: "\u5F00\u53D1\u8005", color: "var(--hc-orange)", Icon: CodeIcon },
    misc: { label: "\u5176\u4ED6", color: "var(--hc-fill-primary)", Icon: EllipsisIcon }
  };
  var CATEGORY_ORDER = [
    "utility",
    "chat",
    "voice",
    "appearance",
    "privacy",
    "developer",
    "misc"
  ];

  // src/ui/settings/PluginsView.tsx
  function PluginsView({
    initialSelectedId
  } = {}) {
    const plugins2 = useRuntimeList().filter((p) => !p.hidden);
    const [selectedId, setSelectedId] = useState(initialSelectedId ?? null);
    const [query, setQuery] = useState("");
    const selected = selectedId ? plugins2.find((p) => p.id === selectedId) : void 0;
    if (selected) {
      return /* @__PURE__ */ React.createElement(PluginDetail, { view: selected, onBack: () => setSelectedId(null) });
    }
    const needle = query.trim().toLowerCase();
    const filtered = needle ? plugins2.filter(
      (p) => p.name.toLowerCase().includes(needle) || p.description.toLowerCase().includes(needle)
    ) : plugins2;
    return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "hc-toolbar" }, /* @__PURE__ */ React.createElement("div", { className: "hc-search" }, /* @__PURE__ */ React.createElement(SearchIcon, { size: 20 }), /* @__PURE__ */ React.createElement(
      "input",
      {
        value: query,
        onChange: (event) => setQuery(event.currentTarget.value),
        placeholder: "\u641C\u7D22\u63D2\u4EF6",
        "aria-label": "\u641C\u7D22\u63D2\u4EF6"
      }
    ))), filtered.length === 0 ? /* @__PURE__ */ React.createElement(
      EmptyState,
      {
        icon: /* @__PURE__ */ React.createElement(SearchIcon, { size: 48 }),
        title: "\u6CA1\u6709\u5339\u914D\u7684\u63D2\u4EF6",
        subtitle: "\u6362\u4E2A\u5173\u952E\u8BCD\u518D\u8BD5\u8BD5\u3002"
      }
    ) : CATEGORY_ORDER.map((category) => {
      const inCategory = filtered.filter((p) => p.category === category);
      if (inCategory.length === 0) return null;
      const meta = CATEGORIES[category];
      return /* @__PURE__ */ React.createElement("div", { className: "hc-section", key: category }, /* @__PURE__ */ React.createElement("div", { className: "hc-section__title" }, meta.label), /* @__PURE__ */ React.createElement("div", { className: "hc-section__body" }, inCategory.map((view) => /* @__PURE__ */ React.createElement(
        PluginRow,
        {
          key: view.id,
          view,
          onOpen: () => setSelectedId(view.id)
        }
      ))));
    }));
  }
  function PluginRow({ view, onOpen }) {
    const meta = CATEGORIES[view.category];
    const Icon = meta.Icon;
    const openable = view.hasSettings || view.hasPage;
    return /* @__PURE__ */ React.createElement(
      ListRow,
      {
        icon: /* @__PURE__ */ React.createElement(Icon, { size: 18 }),
        iconBackground: meta.color,
        title: view.name,
        subtitle: view.description,
        onClick: openable ? onOpen : void 0,
        showChevron: openable,
        accessory: /* @__PURE__ */ React.createElement(React.Fragment, null, view.needsRestart && /* @__PURE__ */ React.createElement(Badge, { tone: "orange" }, /* @__PURE__ */ React.createElement(RefreshIcon, { size: 12 }), " \u5F85\u91CD\u542F"), view.state === "errored" && /* @__PURE__ */ React.createElement(Badge, { tone: "red" }, /* @__PURE__ */ React.createElement(WarningIcon, { size: 12 }), " \u51FA\u9519"), /* @__PURE__ */ React.createElement(
          "span",
          {
            onClick: (event) => event.stopPropagation(),
            onKeyDown: (event) => event.stopPropagation()
          },
          /* @__PURE__ */ React.createElement(
            Toggle,
            {
              checked: view.enabled,
              disabled: view.required,
              onChange: () => runtime.toggle(view.id),
              "aria-label": `\u542F\u7528 ${view.name}`
            }
          )
        ))
      }
    );
  }
  function PluginDetail({ view, onBack }) {
    const plugin = runtime.getPlugin(view.id);
    const meta = CATEGORIES[view.category];
    const Icon = meta.Icon;
    const hasVisibleSettings = Boolean(
      plugin?.settings && Object.values(plugin.settings.schema).some((def) => !def.hidden)
    );
    const hasBoth = Boolean(plugin?.page) && hasVisibleSettings;
    const [section, setSection] = useState("page");
    return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("button", { type: "button", className: "hc-back", onClick: onBack }, /* @__PURE__ */ React.createElement(ChevronLeftIcon, { size: 20 }), "\u63D2\u4EF6"), /* @__PURE__ */ React.createElement("div", { className: "hc-detail-head" }, /* @__PURE__ */ React.createElement("div", { className: "hc-detail-head__icon", style: { background: meta.color } }, /* @__PURE__ */ React.createElement(Icon, { size: 26 })), /* @__PURE__ */ React.createElement("div", { className: "hc-detail-head__text" }, /* @__PURE__ */ React.createElement("div", { className: "hc-detail-head__name" }, view.name), /* @__PURE__ */ React.createElement("div", { className: "hc-detail-head__desc" }, view.description), /* @__PURE__ */ React.createElement("div", { className: "hc-detail-head__meta" }, view.authors.map((a) => a.name).join("\u3001"))), /* @__PURE__ */ React.createElement(
      "span",
      {
        onClick: (event) => event.stopPropagation(),
        onKeyDown: (event) => event.stopPropagation()
      },
      /* @__PURE__ */ React.createElement(
        Toggle,
        {
          checked: view.enabled,
          disabled: view.required,
          onChange: () => runtime.toggle(view.id),
          "aria-label": `\u542F\u7528 ${view.name}`
        }
      )
    )), view.needsRestart && /* @__PURE__ */ React.createElement("div", { className: "hc-inline-note" }, /* @__PURE__ */ React.createElement(RefreshIcon, { size: 18 }), /* @__PURE__ */ React.createElement("span", null, "\u8FD9\u4E2A\u63D2\u4EF6\u5305\u542B\u52A0\u8F7D\u671F\u8865\u4E01\uFF0C\u9700\u8981\u91CD\u542F Discord \u624D\u80FD\u5B8C\u5168\u751F\u6548\u3002")), view.state === "errored" && /* @__PURE__ */ React.createElement("div", { className: "hc-inline-note hc-inline-note--danger" }, /* @__PURE__ */ React.createElement(WarningIcon, { size: 18 }), /* @__PURE__ */ React.createElement("span", null, "\u63D2\u4EF6\u542F\u52A8\u65F6\u629B\u51FA\u5F02\u5E38\uFF0C\u5DF2\u88AB\u81EA\u52A8\u505C\u7528\uFF0C\u8BE6\u60C5\u89C1\u65E5\u5FD7\u3002")), hasBoth && /* @__PURE__ */ React.createElement("div", { className: "hc-segment" }, /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-segment__item",
        "data-active": section === "page",
        onClick: () => setSection("page")
      },
      plugin.page.title || "\u8BB0\u5F55"
    ), /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-segment__item",
        "data-active": section === "settings",
        onClick: () => setSection("settings")
      },
      "\u8BBE\u7F6E"
    )), plugin?.page && (!hasBoth || section === "page") ? /* @__PURE__ */ React.createElement(plugin.page.component, null) : plugin?.settings ? /* @__PURE__ */ React.createElement(SettingsForm, { settings: plugin.settings }) : /* @__PURE__ */ React.createElement(EmptyState, { title: "\u6CA1\u6709\u53EF\u914D\u7F6E\u9879", subtitle: "\u8FD9\u4E2A\u63D2\u4EF6\u5F00\u7BB1\u5373\u7528\uFF0C\u65E0\u9700\u8BBE\u7F6E\u3002" }));
  }

  // src/ui/settings/LogsView.tsx
  var MAX_VISIBLE = 500;
  var PAGE_SIZE = 100;
  function LogsView() {
    const [entries, setEntries] = useState(() => getLogHistory().slice());
    const [page, setPage] = useState(0);
    const scrollRef = useRef(null);
    useEffect(() => {
      setEntries(getLogHistory().slice());
      return onLog((entry) => {
        setEntries((prev) => {
          const next = prev.concat(entry);
          return next.length > MAX_VISIBLE ? next.slice(next.length - MAX_VISIBLE) : next;
        });
      });
    }, []);
    const pageCount = Math.max(1, Math.ceil(entries.length / PAGE_SIZE));
    const clamped = Math.min(page, pageCount - 1);
    const end = entries.length - clamped * PAGE_SIZE;
    const visible = entries.slice(Math.max(0, end - PAGE_SIZE), end);
    useEffect(() => {
      if (clamped !== 0) return;
      const el = scrollRef.current;
      if (el) el.scrollTop = el.scrollHeight;
    }, [entries, clamped]);
    if (entries.length === 0) {
      return /* @__PURE__ */ React.createElement(
        EmptyState,
        {
          icon: /* @__PURE__ */ React.createElement(ListIcon, { size: 48 }),
          title: "\u6682\u65E0\u65E5\u5FD7",
          subtitle: "\u8FD0\u884C\u65F6\u548C\u63D2\u4EF6\u7684\u8F93\u51FA\u4F1A\u5B9E\u65F6\u51FA\u73B0\u5728\u8FD9\u91CC\u3002"
        }
      );
    }
    return /* @__PURE__ */ React.createElement("div", { className: "hc-stack" }, /* @__PURE__ */ React.createElement("div", { className: "hc-logs", ref: scrollRef }, visible.map((entry, index) => /* @__PURE__ */ React.createElement("div", { className: "hc-logline", "data-level": entry.level, key: `${entry.time}-${index}` }, /* @__PURE__ */ React.createElement("span", { className: "hc-logline__time" }, formatTime(entry.time)), /* @__PURE__ */ React.createElement("span", { className: "hc-logline__scope" }, entry.scope), /* @__PURE__ */ React.createElement("span", { className: "hc-logline__msg" }, entry.parts.map(stringify).join(" "))))), pageCount > 1 && /* @__PURE__ */ React.createElement("div", { className: "hc-pager" }, /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-tab",
        disabled: clamped >= pageCount - 1,
        onClick: () => setPage(Math.min(pageCount - 1, clamped + 1))
      },
      "\u2190 \u66F4\u65E9"
    ), /* @__PURE__ */ React.createElement("span", { className: "hc-pager__label" }, clamped === 0 ? "\u5B9E\u65F6" : `\u7B2C ${pageCount - clamped} / ${pageCount} \u9875`), /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-tab",
        disabled: clamped === 0,
        onClick: () => setPage(Math.max(0, clamped - 1))
      },
      "\u66F4\u65B0 \u2192"
    )));
  }
  function formatTime(time) {
    const date = new Date(time);
    const clock = date.toLocaleTimeString(void 0, { hour12: false });
    return `${clock}.${String(date.getMilliseconds()).padStart(3, "0")}`;
  }
  function stringify(part) {
    if (typeof part === "string") return part;
    if (part instanceof Error) return part.stack ?? part.message;
    try {
      return JSON.stringify(part);
    } catch {
      return String(part);
    }
  }

  // src/ui/components/Section.tsx
  function Section({ title, note, children }) {
    return /* @__PURE__ */ React.createElement("div", { className: "hc-section" }, title && /* @__PURE__ */ React.createElement("div", { className: "hc-section__title" }, title), /* @__PURE__ */ React.createElement("div", { className: "hc-section__body" }, children), note && /* @__PURE__ */ React.createElement("div", { className: "hc-section__note" }, note));
  }

  // src/core/update.ts
  var log4 = logger("update");
  var REPO = "mzrodyu/CatieDiscordTools";
  var VERSION_URL = `https://raw.githubusercontent.com/${REPO}/main/package.json`;
  var PROJECT_URL = `https://github.com/${REPO}`;
  var cached = null;
  var inflight = null;
  function currentVersion() {
    return true ? "0.6.1" : "dev";
  }
  function getCachedUpdate() {
    return cached;
  }
  function parseVersion(v) {
    return String(v).trim().replace(/^v/i, "").split(/[.+-]/).map((p) => parseInt(p, 10)).filter((n) => Number.isFinite(n));
  }
  function isNewer(remote, local) {
    const a = parseVersion(remote);
    const b = parseVersion(local);
    const len = Math.max(a.length, b.length);
    for (let i = 0; i < len; i++) {
      const x = a[i] ?? 0;
      const y = b[i] ?? 0;
      if (x !== y) return x > y;
    }
    return false;
  }
  async function fetchText(url) {
    const native = globalThis.HalcyonNative;
    if (native && typeof native.fetchText === "function") {
      try {
        const text = await native.fetchText(url);
        if (typeof text === "string") return text;
      } catch {
      }
    }
    try {
      const res = await fetch(url, { cache: "no-store" });
      if (res.ok) return await res.text();
    } catch {
    }
    return null;
  }
  async function checkForUpdate(force = false) {
    if (!force && cached && cached.status !== "unknown") return cached;
    if (inflight) return inflight;
    inflight = (async () => {
      const current = currentVersion();
      const raw = await fetchText(VERSION_URL);
      let state;
      if (raw == null) {
        state = { status: "unknown", current, latest: null };
      } else {
        let latest = null;
        try {
          const parsed = JSON.parse(raw);
          latest = typeof parsed?.version === "string" && parsed.version ? parsed.version : null;
        } catch {
          latest = null;
        }
        if (!latest) {
          state = { status: "unknown", current, latest: null };
        } else if (current === "dev") {
          state = { status: "current", current, latest };
        } else {
          state = { status: isNewer(latest, current) ? "outdated" : "current", current, latest };
        }
      }
      if (state.status === "outdated") {
        log4.info(`update available: ${state.current} \u2192 ${state.latest}`);
      } else if (state.status === "unknown") {
        log4.info("could not determine the latest version (CSP or offline) \u2014 skipping notice");
      } else {
        log4.info(`up to date (${state.current})`);
      }
      cached = state;
      inflight = null;
      return state;
    })();
    return inflight;
  }

  // src/ui/settings/AboutView.tsx
  function AboutView() {
    const plugins2 = useRuntimeList().filter((p) => !p.hidden);
    const enabled = plugins2.filter((p) => p.enabled).length;
    const version2 = true ? "0.6.1" : "dev";
    const [update, setUpdate] = React.useState(getCachedUpdate);
    React.useEffect(() => {
      let alive = true;
      void checkForUpdate().then((state) => {
        if (alive) setUpdate(state);
      });
      return () => {
        alive = false;
      };
    }, []);
    return /* @__PURE__ */ React.createElement("div", { className: "hc-stack" }, /* @__PURE__ */ React.createElement("div", { className: "hc-about-hero" }, /* @__PURE__ */ React.createElement(HalcyonMark, { size: 32 }), /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement("div", { className: "hc-about-hero__name" }, "Halcyon"), /* @__PURE__ */ React.createElement("div", { className: "hc-about-hero__ver" }, "\u7248\u672C ", version2, update?.status === "outdated" && "\uFF0C\u6709\u65B0\u7248\u672C\u53EF\u7528"))), update?.status === "outdated" && /* @__PURE__ */ React.createElement(Section, { title: "\u66F4\u65B0" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell hc-cell--row" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__main" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__label" }, "\u53D1\u73B0\u65B0\u7248\u672C ", update.latest)), /* @__PURE__ */ React.createElement(
      Button,
      {
        variant: "primary",
        size: "sm",
        onClick: () => window.open(PROJECT_URL, "_blank", "noopener,noreferrer")
      },
      "\u524D\u5F80\u4E0B\u8F7D"
    ))), /* @__PURE__ */ React.createElement(Section, { title: "\u6982\u89C8" }, /* @__PURE__ */ React.createElement(AboutRow, { label: "\u63D2\u4EF6\u603B\u6570", value: String(plugins2.length) }), /* @__PURE__ */ React.createElement(AboutRow, { label: "\u5DF2\u542F\u7528", value: String(enabled) })), /* @__PURE__ */ React.createElement(
      Section,
      {
        title: "\u9879\u76EE",
        note: "\u4FEE\u6539 Discord \u5BA2\u6237\u7AEF\u8FDD\u53CD\u5176\u670D\u52A1\u6761\u6B3E\uFF0C\u7531\u6B64\u4EA7\u751F\u7684\u4EFB\u4F55\u540E\u679C\u7531\u4F7F\u7528\u8005\u81EA\u884C\u627F\u62C5\u3002\u672C\u9879\u76EE\u4EC5\u4F9B\u6280\u672F\u7814\u7A76\u4E0E\u4E2A\u4EBA\u4F7F\u7528\u3002"
      },
      /* @__PURE__ */ React.createElement(AboutRow, { label: "\u4F5C\u8005", value: "caitemm (mzrodyu)" }),
      /* @__PURE__ */ React.createElement(AboutRow, { label: "\u8BB8\u53EF\u534F\u8BAE", value: "GPL-3.0-or-later" })
    ));
  }
  function AboutRow({ label, value }) {
    return /* @__PURE__ */ React.createElement("div", { className: "hc-cell hc-cell--row" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__main" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__label" }, label)), /* @__PURE__ */ React.createElement("span", { className: "hc-about__value" }, value));
  }

  // src/ui/settings/SettingsRoot.tsx
  var TABS = [
    { id: "plugins", label: "\u63D2\u4EF6", title: "\u63D2\u4EF6", Icon: SlidersIcon },
    { id: "logs", label: "\u65E5\u5FD7", title: "\u65E5\u5FD7", Icon: ListIcon },
    { id: "about", label: "\u5173\u4E8E", title: "\u5173\u4E8E Halcyon", Icon: InfoIcon }
  ];
  function renderView(tab, initialPluginId) {
    switch (tab) {
      case "plugins":
        return /* @__PURE__ */ React.createElement(PluginsView, { initialSelectedId: initialPluginId });
      case "logs":
        return /* @__PURE__ */ React.createElement(LogsView, null);
      case "about":
        return /* @__PURE__ */ React.createElement(AboutView, null);
    }
  }
  function SettingsRoot({
    onClose,
    initial
  }) {
    const [tab, setTab] = useState(initial?.tab ?? "plugins");
    const [initialPluginId] = useState(initial?.pluginId);
    const active2 = TABS.find((t) => t.id === tab) ?? TABS[0];
    return /* @__PURE__ */ React.createElement("div", { className: "halcyon hc-panel" }, /* @__PURE__ */ React.createElement("nav", { className: "hc-panel__sidebar" }, /* @__PURE__ */ React.createElement("div", { className: "hc-panel__brand" }, /* @__PURE__ */ React.createElement(HalcyonMark, { size: 24 }), /* @__PURE__ */ React.createElement("span", { className: "hc-panel__brand-name" }, "Halcyon")), TABS.map((t) => /* @__PURE__ */ React.createElement(
      "button",
      {
        key: t.id,
        type: "button",
        className: "hc-navitem",
        "data-active": t.id === tab,
        onClick: () => setTab(t.id)
      },
      /* @__PURE__ */ React.createElement(t.Icon, { size: 18 }),
      t.label
    ))), /* @__PURE__ */ React.createElement("section", { className: "hc-panel__content" }, /* @__PURE__ */ React.createElement("header", { className: "hc-panel__header" }, /* @__PURE__ */ React.createElement("span", { className: "hc-title2" }, active2.title), onClose && /* @__PURE__ */ React.createElement("button", { type: "button", className: "hc-iconbtn", onClick: onClose, "aria-label": "\u5173\u95ED" }, /* @__PURE__ */ React.createElement(XmarkIcon, { size: 20 }))), /* @__PURE__ */ React.createElement("div", { className: "hc-panel__scroll" }, renderView(tab, tab === "plugins" ? initialPluginId : void 0))));
  }
  function EmbeddedView({ tab }) {
    return /* @__PURE__ */ React.createElement("div", { className: "halcyon hc-embed" }, renderView(tab));
  }

  // src/ui/settings/overlay.tsx
  var log5 = logger("settings");
  var host = null;
  var unmount = null;
  var keyHandler = null;
  function openSettings(target) {
    injectStyles();
    if (host) return;
    host = document.createElement("div");
    host.className = "halcyon";
    document.body.appendChild(host);
    keyHandler = (event) => {
      if (event.key === "Escape") closeSettings();
    };
    document.addEventListener("keydown", keyHandler);
    try {
      unmount = mountDetached(
        React.createElement(Overlay, { onClose: closeSettings, target }),
        host
      );
    } catch (err) {
      log5.error("could not open settings overlay", err);
      closeSettings();
    }
  }
  function closeSettings() {
    if (keyHandler) {
      document.removeEventListener("keydown", keyHandler);
      keyHandler = null;
    }
    if (unmount) {
      unmount();
      unmount = null;
    }
    if (host) {
      host.remove();
      host = null;
    }
  }
  function Overlay({
    onClose,
    target
  }) {
    return /* @__PURE__ */ React.createElement(
      "div",
      {
        className: "hc-overlay",
        role: "dialog",
        "aria-modal": "true",
        "aria-label": "Halcyon \u8BBE\u7F6E",
        onMouseDown: (event) => {
          if (event.target === event.currentTarget) onClose();
        }
      },
      /* @__PURE__ */ React.createElement(SettingsRoot, { onClose, initial: target })
    );
  }

  // src/plugins/settings-host/index.tsx
  var log6 = logger("settings-host");
  function PluginsSection() {
    return /* @__PURE__ */ React.createElement(EmbeddedView, { tab: "plugins" });
  }
  function LogsSection() {
    return /* @__PURE__ */ React.createElement(EmbeddedView, { tab: "logs" });
  }
  function AboutSection() {
    return /* @__PURE__ */ React.createElement(EmbeddedView, { tab: "about" });
  }
  function sidebarIcon(Icon) {
    return function HalcyonSidebarIcon() {
      return /* @__PURE__ */ React.createElement(Icon, { size: 20 });
    };
  }
  var HALCYON_SECTION_KEY = "halcyon-section";
  var ENTRIES = [
    { key: "halcyon-plugins", title: "\u63D2\u4EF6", Component: PluginsSection, Icon: SlidersIcon },
    { key: "halcyon-logs", title: "\u65E5\u5FD7", Component: LogsSection, Icon: ListIcon },
    { key: "halcyon-about", title: "\u5173\u4E8E", Component: AboutSection, Icon: InfoIcon }
  ];
  var diagLogged = false;
  var INJECT_SECTION = true;
  var FALLBACK_LAYOUT_TYPES = {
    SECTION: 1,
    SIDEBAR_ITEM: 2,
    PANEL: 3,
    CATEGORY: 5,
    CUSTOM: 20
  };
  var layoutTypes = null;
  function getLayoutTypes() {
    if (layoutTypes) return layoutTypes;
    try {
      const found = findByProps("SECTION", "SIDEBAR_ITEM", "PANEL", "CUSTOM");
      if (found && typeof found.SECTION === "number") {
        layoutTypes = {
          SECTION: found.SECTION,
          SIDEBAR_ITEM: found.SIDEBAR_ITEM,
          PANEL: found.PANEL,
          CATEGORY: typeof found.CATEGORY === "number" ? found.CATEGORY : FALLBACK_LAYOUT_TYPES.CATEGORY,
          CUSTOM: found.CUSTOM
        };
        return layoutTypes;
      }
    } catch (err) {
      log6.warn("could not resolve settings layout types; using fallback values", err);
    }
    return FALLBACK_LAYOUT_TYPES;
  }
  function safeChildren(node) {
    try {
      if (node && typeof node.buildLayout === "function") {
        const kids = node.buildLayout();
        if (Array.isArray(kids)) return kids;
      }
    } catch {
    }
    return [];
  }
  function resolveTypesFromLayout(layout) {
    const types = { ...FALLBACK_LAYOUT_TYPES };
    try {
      const first = Array.isArray(layout) ? layout[0] : void 0;
      if (first && typeof first.type === "number") types.SECTION = first.type;
      for (const section of layout) {
        for (const item of safeChildren(section)) {
          if (typeof item?.type !== "number") continue;
          types.SIDEBAR_ITEM = item.type;
          for (const panel of safeChildren(item)) {
            if (typeof panel?.type !== "number") continue;
            types.PANEL = panel.type;
            for (const category of safeChildren(panel)) {
              if (typeof category?.type !== "number") continue;
              types.CATEGORY = category.type;
              for (const leaf of safeChildren(category)) {
                if (leaf && typeof leaf.type === "number" && "Component" in leaf) {
                  types.CUSTOM = leaf.type;
                  return types;
                }
              }
            }
          }
        }
      }
    } catch (err) {
      log6.warn("could not read layout types from the live tree; using fallbacks", err);
    }
    return types;
  }
  function buildEntry(types, entry) {
    const panel = {
      key: `${entry.key}-panel`,
      type: types.PANEL,
      useTitle: () => entry.title,
      buildLayout: () => [
        {
          key: `${entry.key}-category`,
          type: types.CATEGORY,
          buildLayout: () => [
            {
              key: `${entry.key}-custom`,
              type: types.CUSTOM,
              Component: entry.Component,
              useSearchTerms: () => [entry.title]
            }
          ]
        }
      ]
    };
    return {
      key: entry.key,
      type: types.SIDEBAR_ITEM,
      useTitle: () => entry.title,
      icon: sidebarIcon(entry.Icon),
      buildLayout: () => [panel]
    };
  }
  function fnSources(node) {
    const out = {};
    if (node && typeof node === "object") {
      for (const k of Object.keys(node)) {
        const v = node[k];
        if (typeof v === "function") out[k] = String(v).replace(/\s+/g, " ").slice(0, 400);
      }
    }
    return out;
  }
  function describeNode(node, depth) {
    if (!node || typeof node !== "object") return { raw: typeof node };
    const info = {
      key: node.key,
      type: node.type,
      fields: Object.keys(node)
    };
    if (depth > 0 && typeof node.buildLayout === "function") {
      try {
        const kids = node.buildLayout();
        if (Array.isArray(kids)) {
          info.children = kids.slice(0, 6).map((k) => describeNode(k, depth - 1));
        }
      } catch (err) {
        info.childrenError = String(err);
      }
    }
    return info;
  }
  function probeLayoutOnce(layout) {
    if (diagLogged) return;
    diagLogged = true;
    try {
      const s0 = layout[0];
      const s1 = safeChildren(s0)[0];
      const s2 = safeChildren(s1)[0];
      const s3 = safeChildren(s2)[0];
      const s4 = safeChildren(s3)[0];
      const payload = {
        resolvedTypesFromEnum: getLayoutTypes(),
        resolvedTypesFromLive: resolveTypesFromLayout(layout),
        topLevelCount: layout.length,
        // The real function bodies Discord ships for one full branch: section ->
        // sidebar item -> panel -> category -> leaf. This is the ground truth for
        // what our own nodes must return (e.g. does useTitle yield a plain string
        // or an intl message object {locale, ast}?).
        sampleSources: {
          section: fnSources(s0),
          sidebarItem: fnSources(s1),
          panel: fnSources(s2),
          category: fnSources(s3),
          leaf: fnSources(s4)
        },
        layout: layout.slice(0, 12).map((n) => describeNode(n, 2))
      };
      globalThis.__halcyonLayoutProbe = JSON.stringify(payload, null, 2);
      log6.info(
        "[embed-probe] captured Discord's settings layout shape. In the console run  copy(__halcyonLayoutProbe)  and paste the result back."
      );
    } catch (err) {
      log6.warn("[embed-probe] failed to capture layout shape", err);
    }
  }
  function buildLegacySections() {
    return [
      { section: "HEADER", label: "HALCYON" },
      { section: "halcyon-plugins", label: "\u63D2\u4EF6", element: PluginsSection },
      { section: "halcyon-logs", label: "\u65E5\u5FD7", element: LogsSection },
      { section: "halcyon-about", label: "\u5173\u4E8E", element: AboutSection }
    ];
  }
  var onKeyDown = null;
  var settings_host_default = definePlugin({
    id: "halcyon-settings",
    name: "Halcyon \u8BBE\u7F6E",
    description: "Halcyon \u81EA\u8EAB\u7684\u8BBE\u7F6E\u754C\u9762\u5BBF\u4E3B\u3002",
    authors: [{ name: "caitemm" }],
    category: "misc",
    required: true,
    hidden: true,
    patches: [
      {
        // Current builds. The sidebar renderer maps over a layout array produced
        // by `<builder>.buildLayout()`; we hand that call to `buildLayout` below,
        // which calls the original and splices Halcyon's SECTION in. `.map` in
        // the lookahead pins us to the render call site (not the many nested
        // `buildLayout()` calls the tree makes internally).
        label: "user-settings-layout",
        find: ".buildLayout().map",
        replacement: {
          match: /([A-Za-z_$][\w$]*)\.buildLayout\(\)(?=\.map)/,
          replace: "$self.buildLayout($1)"
        }
      },
      {
        // Older builds. `getPredicateSections(){return <expr>}` gates the whole
        // sidebar; we wrap its return value. The captured body may itself contain
        // braces (arrow predicates, object literals), so it is matched as a
        // balanced block and replayed inside an arrow IIFE — `this` stays lexical,
        // so `this.props...` inside still resolves.
        label: "user-settings-sidebar",
        find: "getPredicateSections",
        replacement: {
          match: /getPredicateSections\(\)(\{(?:[^{}]|\{(?:[^{}]|\{[^{}]*\})*\})*\})/,
          replace: (_full, body) => `getPredicateSections(){return $self.injectSections((()=>${body})())}`
        }
      }
    ],
    /**
     * Wrap the settings layout builder. Called from the patch with the object the
     * renderer was about to call `.buildLayout()` on; we call it ourselves, then
     * splice Halcyon's SECTION into the result.
     *
     * Defensive throughout: this feeds the entire settings pane, so any failure
     * falls through to the untouched layout rather than blanking it. Only the
     * root builder ("$Root") owns the sidebar — every panel and category runs its
     * own builder through here too, and those are left exactly as they were.
     */
    buildLayout(builder) {
      const layout = builder.buildLayout();
      try {
        if (!builder || builder.key !== "$Root") return layout;
        if (!Array.isArray(layout)) return layout;
        probeLayoutOnce(layout);
        if (!INJECT_SECTION) return layout;
        if (layout.some((n) => n?.key === HALCYON_SECTION_KEY)) return layout;
        const types = resolveTypesFromLayout(layout);
        const section = {
          key: HALCYON_SECTION_KEY,
          type: types.SECTION,
          useTitle: () => "HALCYON",
          buildLayout: () => ENTRIES.map((entry) => buildEntry(types, entry))
        };
        let index = layout.findIndex((n) => n?.key === "billing_section");
        if (index < 0) index = layout.findIndex((n) => n?.key === "user_section");
        if (index < 0) index = Math.min(2, layout.length);
        layout.splice(index, 0, section);
        log6.info(`native settings embed active \u2014 section inserted at index ${index}/${layout.length}`);
        return layout;
      } catch (err) {
        log6.error("failed to inject settings section into layout", err);
        return layout;
      }
    },
    /**
     * Splice Halcyon's group into the legacy settings sidebar array and return
     * it. Called from the older-build patch with that build's section list.
     *
     * Placement: right after the first divider, the seam between the account
     * block and the rest. Defensive: a throw here would blank the whole pane, so
     * any failure returns the original array untouched.
     */
    injectSections(sections) {
      try {
        if (!Array.isArray(sections)) return sections;
        if (sections.some((s) => s?.section === "halcyon-plugins")) return sections;
        const mine = buildLegacySections();
        const out = sections.slice();
        const firstDivider = out.findIndex((s) => s && s.section === "DIVIDER");
        if (firstDivider >= 0) {
          out.splice(firstDivider + 1, 0, ...mine);
        } else {
          out.push({ section: "DIVIDER" }, ...mine);
        }
        if (!diagLogged) {
          diagLogged = true;
          log6.info(`native settings embed active (legacy) \u2014 ${sections.length} base sections`);
        }
        return out;
      } catch (err) {
        log6.error("failed to inject settings sections", err);
        return sections;
      }
    },
    start() {
      injectStyles();
      onKeyDown = (event) => {
        const combo = (event.ctrlKey || event.metaKey) && event.shiftKey && event.code === "KeyH";
        if (!combo) return;
        event.preventDefault();
        openSettings();
      };
      window.addEventListener("keydown", onKeyDown);
      log6.info("settings host ready \u2014 open with Ctrl/Cmd+Shift+H");
    },
    stop() {
      if (onKeyDown) {
        window.removeEventListener("keydown", onKeyDown);
        onKeyDown = null;
      }
      closeSettings();
    }
  });

  // src/core/common/context-menu.ts
  var log7 = logger("context-menu");
  var navPatches = /* @__PURE__ */ new Map();
  var lastTarget = null;
  var trackingInstalled = false;
  function installTargetTracking() {
    if (trackingInstalled || typeof document === "undefined") return;
    trackingInstalled = true;
    document.addEventListener(
      "contextmenu",
      (e) => {
        lastTarget = e.target ?? null;
      },
      true
    );
  }
  function getContextMenuTarget() {
    return lastTarget;
  }
  var menuItemComponent = null;
  function getMenuItemComponent() {
    return menuItemComponent;
  }
  function findMenuItemType(children) {
    for (const child of children) {
      if (child == null) continue;
      if (Array.isArray(child)) {
        const found = findMenuItemType(child);
        if (found) return found;
      }
      const props = child.props;
      if (child.type && props && typeof props.id === "string" && (props.action != null || props.label != null || props.render != null || props.onClick != null || props.subtext != null)) {
        return child.type;
      }
      const sub = props?.children;
      if (sub) {
        const found = findMenuItemType(Array.isArray(sub) ? sub : [sub]);
        if (found) return found;
      }
    }
    return null;
  }
  function addContextMenuPatch(navId, callback) {
    installTargetTracking();
    const ids = Array.isArray(navId) ? navId : [navId];
    for (const id of ids) {
      let set = navPatches.get(id);
      if (!set) {
        set = /* @__PURE__ */ new Set();
        navPatches.set(id, set);
      }
      set.add(callback);
    }
    return () => {
      for (const id of ids) navPatches.get(id)?.delete(callback);
    };
  }
  function removeContextMenuPatch(navId, callback) {
    const ids = Array.isArray(navId) ? navId : [navId];
    for (const id of ids) navPatches.get(id)?.delete(callback);
  }
  function cloneChildren(children) {
    if (Array.isArray(children)) return children.slice();
    return children == null ? [] : [children];
  }
  function usePatchContextMenu(props) {
    try {
      if (!props || typeof props.navId !== "string") return props;
      if (!menuItemComponent && props.children != null) {
        menuItemComponent = findMenuItemType(cloneChildren(props.children));
      }
      const set = navPatches.get(props.navId);
      if (!set || set.size === 0) return props;
      const next = { ...props, children: cloneChildren(props.children) };
      for (const cb of set) {
        try {
          cb(next.children);
        } catch (err) {
          log7.error(`context-menu patch for "${props.navId}" threw`, err);
        }
      }
      return next;
    } catch (err) {
      log7.error("failed to apply context-menu patches", err);
      return props;
    }
  }

  // src/plugins/context-menu-api/index.ts
  var context_menu_api_default = definePlugin({
    id: "context-menu-api",
    name: "\u53F3\u952E\u83DC\u5355 API",
    description: "\u4E3A\u5176\u4ED6\u63D2\u4EF6\u63D0\u4F9B\u5411 Discord \u53F3\u952E\u83DC\u5355\u6CE8\u5165\u83DC\u5355\u9879\u7684\u80FD\u529B\u3002",
    authors: [{ name: "Vencord" }, { name: "caitemm" }],
    category: "misc",
    required: true,
    hidden: true,
    patches: [
      {
        // The central menu component. Inject our hook at the very top of the
        // function body, right before it destructures navId out of its props.
        label: "context-menu central handler",
        find: "Menu API only allows Items",
        replacement: {
          match: /(?=let\{navId:)(?<=function [A-Za-z_$][\w$]*\(([A-Za-z_$][\w$]*)\).+?)/,
          replace: "$1=$self._usePatchContextMenu($1);"
        }
      }
    ],
    /** Called from the patched menu component with its props. */
    _usePatchContextMenu(props) {
      return usePatchContextMenu(props);
    }
  });

  // src/core/patcher/index.ts
  var log8 = logger("patcher");
  var INSTALLED = Symbol("halcyon.patch");
  function ensureInstalled(target, method) {
    const current = target[method];
    if (current && current[INSTALLED]) {
      return current[INSTALLED];
    }
    if (typeof current !== "function") {
      throw new TypeError(`cannot patch "${method}": not a function`);
    }
    const hooks = {
      before: /* @__PURE__ */ new Set(),
      instead: /* @__PURE__ */ new Set(),
      after: /* @__PURE__ */ new Set(),
      original: current
    };
    const wrapper = function(...args) {
      const ctx = {
        args,
        result: void 0,
        self: this,
        callOriginal: () => hooks.original.apply(this, ctx.args)
      };
      for (const hook of hooks.before) {
        try {
          hook(ctx);
        } catch (err) {
          log8.error(`before-hook on "${method}" threw`, err);
        }
      }
      if (hooks.instead.size) {
        let outcome;
        let ran = false;
        for (const hook of hooks.instead) {
          try {
            outcome = hook(ctx);
            ran = true;
          } catch (err) {
            log8.error(`instead-hook on "${method}" threw; falling back to original`, err);
            outcome = ctx.callOriginal();
            ran = true;
          }
        }
        ctx.result = ran ? outcome : ctx.callOriginal();
      } else {
        try {
          ctx.result = hooks.original.apply(this, ctx.args);
        } catch (err) {
          throw err;
        }
      }
      for (const hook of hooks.after) {
        try {
          hook(ctx);
        } catch (err) {
          log8.error(`after-hook on "${method}" threw`, err);
        }
      }
      return ctx.result;
    };
    Object.defineProperty(wrapper, "name", { value: current.name, configurable: true });
    Object.defineProperty(wrapper, "length", { value: current.length, configurable: true });
    wrapper.toString = () => hooks.original.toString();
    wrapper[INSTALLED] = hooks;
    Object.assign(wrapper, current);
    target[method] = wrapper;
    return hooks;
  }
  function maybeRestore(target, method, hooks) {
    if (hooks.before.size || hooks.instead.size || hooks.after.size) return;
    if (target[method] && target[method][INSTALLED] === hooks) {
      target[method] = hooks.original;
    }
  }
  function attach(kind, target, method, hook) {
    if (target == null) {
      log8.error(`refusing to patch "${method}" on a null target`);
      return () => {
      };
    }
    let hooks;
    try {
      hooks = ensureInstalled(target, method);
    } catch (err) {
      log8.error(err);
      return () => {
      };
    }
    hooks[kind].add(hook);
    let live = true;
    return () => {
      if (!live) return;
      live = false;
      hooks[kind].delete(hook);
      maybeRestore(target, method, hooks);
    };
  }
  var patcher = {
    /** Run before the original. Edit `ctx.args` to change what it receives. */
    before(target, method, hook) {
      return attach("before", target, method, hook);
    },
    /** Run after the original. Edit `ctx.result` to change what callers see. */
    after(target, method, hook) {
      return attach("after", target, method, hook);
    },
    /** Replace the original. Call `ctx.callOriginal()` to defer to it. */
    instead(target, method, hook) {
      return attach("instead", target, method, hook);
    }
  };

  // src/core/common/discord.ts
  var Dispatcher = lazy(isFluxDispatcher);
  function getDispatcher() {
    for (const store of [GuildStore, ChannelStore, ReadStateStore]) {
      try {
        const viaStore = store?._dispatcher;
        if (isFluxDispatcher(viaStore)) return viaStore;
      } catch {
      }
    }
    return find(isFluxDispatcher);
  }
  var MessageStore = lazy(
    (m) => m?.getName?.() === "MessageStore" || typeof m?.getMessage === "function" && typeof m?.getMessages === "function" && typeof m?.__halcyon_probe__ === "undefined"
  );
  var MessageActions = lazy(
    (m) => typeof m?.editMessage === "function" && typeof m?.deleteMessage === "function"
  );
  var UserStore = lazy(
    (m) => m?.getName?.() === "UserStore" || typeof m?.getCurrentUser === "function" && typeof m?.getUser === "function" && typeof m?.__halcyon_probe__ === "undefined"
  );
  var ChannelStore = lazy(
    (m) => m?.getName?.() === "ChannelStore" || m?.constructor?.displayName === "ChannelStore"
  );
  var SelectedChannelStore = lazy(
    (m) => m?.getName?.() === "SelectedChannelStore" || typeof m?.getChannelId === "function" && typeof m?.getLastSelectedChannelId === "function" && typeof m?.__halcyon_probe__ === "undefined"
  );
  var GuildStore = lazy(
    (m) => m?.getName?.() === "GuildStore" || m?.constructor?.displayName === "GuildStore"
  );
  var GuildChannelStore = lazy(
    (m) => (
      // Name-only, exactly like Vencord's findStoreLazy. A shape probe
      // (getChannels/getDefaultChannel "look like" functions) also matches
      // Discord's intl `t` proxy — which answers every property — so getChannels()
      // returned {locale, ast, deleted} instead of real channels, and the scan
      // collected zero. The proxy's getName() is a message object, never the
      // string, so a name check rejects it.
      m?.getName?.() === "GuildChannelStore"
    )
  );
  var GuildSubscriptions = lazy(
    (m) => typeof m?.subscribeToGuild === "function" || typeof m?.subscribeToChannel === "function"
  );
  var moment = lazy((m) => typeof m === "function" && typeof m?.locale === "function" && typeof m?.utc === "function");
  var NavigationRouter = lazy(
    (m) => typeof m?.transitionTo === "function" && // One companion method to avoid matching a bare transitionTo-only helper.
    // NOT the full {replaceWith AND transitionToGuild} triple: on the current
    // build the real router doesn't expose transitionToGuild, so requiring it
    // made this resolve to NOTHING — which sent NavigationRouter callers down
    // their fallback path (quest-indicator to `location.href`, i.e. a full page
    // reload — the "任务中心变成刷新了" report — and the log-page jump to a warn).
    // The intl-proxy is rejected by the __halcyon_probe__ guard alone, so the
    // companion check only needs to be specific enough, not exhaustive.
    (typeof m?.replaceWith === "function" || typeof m?.transitionToGuild === "function" || typeof m?.back === "function") && // Reject Discord's answer-everything intl `t` proxy, which reports EVERY
    // property as callable — so it satisfies any method probe and would win.
    typeof m?.__halcyon_probe__ === "undefined"
  );
  function navigate(path) {
    try {
      const router = NavigationRouter;
      if (typeof router?.transitionTo === "function") {
        router.transitionTo(path);
        return true;
      }
    } catch {
    }
    let scanned;
    try {
      scanned = find(
        (x) => typeof x?.transitionTo === "function" && typeof x?.__halcyon_probe__ === "undefined"
      );
      if (typeof scanned?.transitionTo === "function") {
        scanned.transitionTo(path);
        return true;
      }
    } catch {
    }
    try {
      const candidates = [NavigationRouter, scanned];
      try {
        candidates.push(
          find((x) => typeof x?.getHistory === "function" && typeof x?.__halcyon_probe__ === "undefined")
        );
      } catch {
      }
      for (const c of candidates) {
        try {
          const history2 = c?.getHistory?.();
          if (history2 && typeof history2.push === "function") {
            history2.push(path);
            return true;
          }
        } catch {
        }
      }
    } catch {
    }
    return false;
  }
  var AppLayers = lazy(
    (m) => typeof m?.popLayer === "function" && typeof m?.pushLayer === "function" && // Same intl-proxy rejection as NavigationRouter above.
    typeof m?.__halcyon_probe__ === "undefined"
  );
  var JumpActions = lazy(
    (m) => typeof m?.jumpToMessage === "function" && typeof m?.__halcyon_probe__ === "undefined"
  );
  var RestAPI = lazy(
    (m) => (
      // EXACTLY Vencord's discriminator: an *object* carrying `del` AND `put`.
      // This is what reliably picks Discord's real authenticated API client.
      // Every earlier attempt failed on the wrong signal: requiring
      // `getAPIBaseURL` matched nothing (this build doesn't expose it where our
      // scan looks), and requiring get/post/put/del-as-functions matched a
      // generic no-op HTTP client that answered 200 with an empty body and
      // created nothing (the silent sticker-upload failure).
      typeof m === "object" && typeof m?.del === "function" && typeof m?.put === "function" && // Reject Discord's intl `t` proxy, which answers EVERY property access with
      // a message value — so del/put "look like" functions and it wins the probe.
      // A real module returns undefined for a name it doesn't export; the
      // answer-everything proxy returns a (truthy) message, failing this guard.
      typeof m?.__halcyon_probe__ === "undefined"
    )
  );
  var PermissionStore = lazy(
    (m) => m?.getName?.() === "PermissionStore" && typeof m?.can === "function"
  );
  var EmojiStore = lazy((m) => m?.getName?.() === "EmojiStore");
  var Constants = lazy(
    (m) => typeof m?.Endpoints?.GUILD_STICKER_PACKS === "function"
  );
  var StickersStore = lazy((m) => m?.getName?.() === "StickersStore");
  var QuestsStore = lazy((m) => m?.getName?.() === "QuestsStore");
  var ReadStateStore = lazy(
    (m) => (
      // Name-only (see GuildChannelStore): the method-shape fallback also matched
      // Discord's answer-everything intl proxy, so hasUnread() returned a truthy
      // message object for every channel. The store's registered name is stable.
      m?.getName?.() === "ReadStateStore"
    )
  );
  var ActiveJoinedThreadsStore = lazy(
    (m) => m?.getName?.() === "ActiveJoinedThreadsStore"
  );
  var Toasts = lazy(
    (m) => typeof m?.showToast === "function" && typeof m?.createToast === "function" && // Reject Discord's intl `t` proxy, which answers EVERY property as a
    // callable — so showToast/createToast "look like" functions and it wins the
    // probe (which is why toasts silently never appeared). A real module returns
    // undefined for a name it doesn't export; the answer-everything proxy does not.
    typeof m?.__halcyon_probe__ === "undefined"
  );
  function showToast(message, type = "info") {
    try {
      const T = Toasts;
      const typeEnum = T?.Type ?? {};
      const resolved = type === "success" ? typeEnum.SUCCESS ?? 1 : type === "failure" ? typeEnum.FAILURE ?? 2 : typeEnum.MESSAGE ?? typeEnum.INFO ?? 0;
      if (typeof T?.showToast === "function" && typeof T?.createToast === "function") {
        T.showToast(T.createToast(message, resolved));
      }
    } catch {
    }
  }

  // src/core/settings/index.ts
  var log9 = logger("settings");
  function deepClone(value) {
    if (value === null || typeof value !== "object") return value;
    return JSON.parse(JSON.stringify(value));
  }
  function defineSettings(schema) {
    const listeners = /* @__PURE__ */ new Map();
    let boundId = null;
    const values = {};
    for (const key of Object.keys(schema)) {
      values[key] = deepClone(schema[key].default);
    }
    const persist = () => {
      if (boundId) saveNamespace(boundId, values);
    };
    const notify = (key, next, prev) => {
      const set = listeners.get(key);
      if (!set) return;
      for (const listener of set) {
        try {
          listener(next, prev);
        } catch (err) {
          log9.error(`settings listener for "${key}" threw`, err);
        }
      }
    };
    const store = new Proxy(values, {
      get: (target, key) => target[key],
      set: (target, key, value) => {
        if (!(key in schema)) {
          log9.warn(`ignoring write to unknown setting "${key}"`);
          return true;
        }
        const prev = target[key];
        if (Object.is(prev, value)) return true;
        target[key] = value;
        persist();
        notify(key, value, prev);
        return true;
      }
    });
    return {
      schema,
      store,
      subscribe(key, listener) {
        const k = key;
        let set = listeners.get(k);
        if (!set) {
          set = /* @__PURE__ */ new Set();
          listeners.set(k, set);
        }
        set.add(listener);
        return () => void set.delete(listener);
      },
      reset(key) {
        if (key != null) {
          store[key] = deepClone(schema[key].default);
          return;
        }
        for (const k of Object.keys(schema)) {
          store[k] = deepClone(schema[k].default);
        }
      },
      __bind(pluginId) {
        boundId = pluginId;
        const saved = loadNamespace(pluginId);
        for (const k of Object.keys(schema)) {
          if (Object.prototype.hasOwnProperty.call(saved, k)) {
            values[k] = saved[k];
          }
        }
      }
    };
  }

  // src/plugins/message-logger/settings.ts
  var settings = defineSettings({
    // --- 记录 -----------------------------------------------------------------
    keepDeletedInChat: {
      group: "\u8BB0\u5F55",
      type: "boolean",
      default: true,
      label: "\u5728\u804A\u5929\u4E2D\u4FDD\u7559\u88AB\u5220\u6D88\u606F",
      description: "\u88AB\u5220\u9664\u7684\u6D88\u606F\u4E0D\u518D\u6D88\u5931\uFF0C\u800C\u662F\u6807\u8BB0\u4FDD\u7559\u5728\u539F\u4F4D\u3002\u9700\u8981\u5BA2\u6237\u7AEF\u8865\u4E01\u751F\u6548\u3002"
    },
    toolbarButton: {
      group: "\u8BB0\u5F55",
      type: "boolean",
      default: true,
      label: "\u9891\u9053\u9876\u680F\u52A0\u300C\u6D88\u606F\u8BB0\u5F55\u300D\u6309\u94AE",
      description: "\u5728\u9891\u9053\u53F3\u4E0A\u89D2\u5DE5\u5177\u6761\u653E\u4E00\u4E2A\u56FE\u6807\uFF0C\u70B9\u4E00\u4E0B\u76F4\u63A5\u6253\u5F00\u6D88\u606F\u8BB0\u5F55\u9875\uFF0C\u4E0D\u7528\u7FFB\u8BBE\u7F6E\u3002"
    },
    logEdits: {
      group: "\u8BB0\u5F55",
      type: "boolean",
      default: true,
      label: "\u8BB0\u5F55\u7F16\u8F91\u5386\u53F2",
      description: "\u4FDD\u5B58\u6BCF\u6761\u6D88\u606F\u88AB\u7F16\u8F91\u524D\u7684\u5185\u5BB9\u3002"
    },
    retention: {
      group: "\u8BB0\u5F55",
      type: "number",
      default: 50,
      label: "\u6BCF\u9891\u9053\u4FDD\u7559\u6761\u6570",
      description: "0 \u8868\u793A\u4E0D\u9650\u5236\u3002\u4E0A\u9650 500\u3002",
      min: 0,
      max: 500,
      step: 10
    },
    // --- 外观 -----------------------------------------------------------------
    deleteStyle: {
      group: "\u5916\u89C2",
      type: "select",
      default: "tint",
      label: "\u5220\u9664 / \u7F16\u8F91\u6837\u5F0F",
      description: "\u88AB\u5220\u6D88\u606F\u3001\u4EE5\u53CA\u7F16\u8F91\u6D88\u606F\u4E0A\u65B9\u65E7\u7248\u672C\u5185\u5BB9\u5728\u804A\u5929\u4E2D\u7684\u5448\u73B0\u65B9\u5F0F\u3002",
      options: [
        { value: "tint", label: "\u7EA2\u8272\u5E95\u7EB9 + \u5DE6\u4FA7\u7EA2\u6761" },
        { value: "text", label: "\u6B63\u6587\u53D8\u7EA2" },
        { value: "ghost", label: "\u534A\u900F\u660E\u6DE1\u51FA" },
        { value: "strike", label: "\u7EA2\u8272\u5220\u9664\u7EBF" }
      ]
    },
    showDeletedMarker: {
      group: "\u5916\u89C2",
      type: "boolean",
      default: true,
      label: "\u663E\u793A\u5220\u9664\u6807\u8BB0\u884C",
      description: "\u5728\u88AB\u5220\u6D88\u606F\u4E0B\u65B9\u663E\u793A\u201C\u6B64\u6D88\u606F\u5DF2\u5220\u9664\u201D\u4E0E\u5220\u9664\u65F6\u95F4\u3002"
    },
    showEditedMarker: {
      group: "\u5916\u89C2",
      type: "boolean",
      default: true,
      label: "\u663E\u793A\u7F16\u8F91\u6807\u8BB0\u884C",
      description: "\u5728\u7F16\u8F91\u8FC7\u7684\u6D88\u606F\u65C1\u663E\u793A\u201C\u6B64\u6D88\u606F\u5DF2\u7F16\u8F91\u201D\u4E0E\u7F16\u8F91\u65F6\u95F4\uFF08\u6CBF\u7528\u4E0B\u65B9\u6807\u8BB0\u7684\u56FE\u6807 / \u5916\u89C2 / \u65F6\u95F4\u8BBE\u7F6E\uFF09\u3002"
    },
    markerIcon: {
      group: "\u5916\u89C2",
      type: "select",
      default: "trash",
      label: "\u6807\u8BB0\u56FE\u6807",
      description: "\u6807\u8BB0\u884C\u524D\u7684\u56FE\u6807\uFF08\u5220\u9664 / \u7F16\u8F91\u901A\u7528\uFF09\u3002",
      options: [
        { value: "trash", label: "\u{1F5D1} \u5783\u573E\u6876" },
        { value: "shield", label: "\u{1F6E1} \u76FE\u724C" },
        { value: "warning", label: "\u26A0 \u8B66\u544A\u4E09\u89D2" },
        { value: "none", label: "\u65E0\u56FE\u6807" }
      ]
    },
    markerLook: {
      group: "\u5916\u89C2",
      type: "select",
      default: "plain",
      label: "\u6807\u8BB0\u5916\u89C2",
      description: "\u6807\u8BB0\u884C\u7684\u5448\u73B0\u65B9\u5F0F\uFF08\u5220\u9664 / \u7F16\u8F91\u901A\u7528\uFF09\u3002",
      options: [
        { value: "plain", label: "\u7EAF\u6587\u5B57" },
        { value: "badge", label: "\u5706\u89D2\u5FBD\u7AE0" },
        { value: "quote", label: "\u5F15\u7528\u5757\uFF08\u5DE6\u4FA7\u7AD6\u6761\uFF09" }
      ]
    },
    markerTime: {
      group: "\u5916\u89C2",
      type: "select",
      default: "time",
      label: "\u6807\u8BB0\u65F6\u95F4\u683C\u5F0F",
      description: "\u6807\u8BB0\u884C\u91CC\u65F6\u95F4\u7684\u663E\u793A\u65B9\u5F0F\u3002",
      options: [
        { value: "time", label: "\u4EC5\u65F6\u95F4\uFF0803:19:42\uFF09" },
        { value: "datetime", label: "\u65E5\u671F + \u65F6\u95F4" },
        { value: "none", label: "\u4E0D\u663E\u793A\u65F6\u95F4" }
      ]
    },
    // --- 屏蔽对象 ---------------------------------------------------------------
    // Every rule below gates BOTH capture paths: the recorder (log page) and
    // the in-chat red retention, via isIgnored().
    ignoreBots: {
      group: "\u5C4F\u853D\u5BF9\u8C61",
      type: "boolean",
      default: false,
      label: "\u5C4F\u853D\u673A\u5668\u4EBA",
      description: "\u673A\u5668\u4EBA\u7684\u6D88\u606F\u4E0D\u8BB0\u5F55\u3001\u4E0D\u5728\u804A\u5929\u4E2D\u4FDD\u7559\u3002"
    },
    ignoreSelf: {
      group: "\u5C4F\u853D\u5BF9\u8C61",
      type: "boolean",
      default: false,
      label: "\u5C4F\u853D\u81EA\u5DF1",
      description: "\u4F60\u81EA\u5DF1\u5220\u9664\u6216\u7F16\u8F91\u7684\u6D88\u606F\u4E0D\u8BB0\u5F55\u3001\u4E0D\u5728\u804A\u5929\u4E2D\u4FDD\u7559\u3002"
    },
    ignoredUsers: {
      group: "\u5C4F\u853D\u5BF9\u8C61",
      type: "string-list",
      default: [],
      label: "\u5C4F\u853D\u7684\u7528\u6237",
      description: "\u8FD9\u4E9B\u7528\u6237\u7684\u6D88\u606F\u4E0D\u8BB0\u5F55\u3001\u4E0D\u5728\u804A\u5929\u4E2D\u4FDD\u7559\u3002",
      itemPlaceholder: "\u7528\u6237 ID"
    },
    ignoredChannels: {
      group: "\u5C4F\u853D\u5BF9\u8C61",
      type: "string-list",
      default: [],
      label: "\u5C4F\u853D\u7684\u9891\u9053",
      description: "\u8FD9\u4E9B\u9891\u9053\u91CC\u7684\u6D88\u606F\u4E0D\u8BB0\u5F55\u3001\u4E0D\u5728\u804A\u5929\u4E2D\u4FDD\u7559\u3002",
      itemPlaceholder: "\u9891\u9053 ID"
    }
  });

  // src/plugins/message-logger/store.ts
  var log10 = logger("message-logger");
  var DATA_NS = "message-logger.log";
  var MessageLogStore = class {
    deleted = [];
    edited = [];
    retention = 50;
    listeners = /* @__PURE__ */ new Set();
    saveTimer;
    /** `${channelId}:${id}` of every deleted entry — for per-render lookups. */
    deletedIndex = /* @__PURE__ */ new Set();
    /** Load persisted history. Safe to call before the first record. */
    load() {
      const raw = loadNamespace(DATA_NS);
      this.deleted = Array.isArray(raw.deleted) ? raw.deleted : [];
      this.edited = Array.isArray(raw.edited) ? raw.edited : [];
      this.trimDeleted();
      this.reindex();
    }
    /** O(1) "was this message deleted" — cheap enough for render paths. */
    isDeleted(channelId, id) {
      return this.deletedIndex.has(`${channelId}:${id}`);
    }
    /** The deleted-entry record for a message, if any. */
    findDeleted(channelId, id) {
      if (!this.isDeleted(channelId, id)) return void 0;
      return this.deleted.find((d) => d.channelId === channelId && d.id === id);
    }
    setRetention(n) {
      this.retention = Math.max(0, n | 0);
      this.trimDeleted();
      this.reindex();
      this.scheduleSave();
      this.emit();
    }
    recordDeleted(entry) {
      if (this.deleted.some((d) => d.id === entry.id)) return;
      this.deleted.unshift(entry);
      this.trimDeleted();
      this.reindex();
      this.scheduleSave();
      this.emit();
    }
    recordEdit(id, channelId, author, previous, guildId) {
      const now = Date.now();
      let entry = this.edited.find((e) => e.id === id);
      if (!entry) {
        entry = { id, channelId, guildId, author, history: [{ content: previous, at: now }], updatedAt: now };
        this.edited.unshift(entry);
      } else {
        const last = entry.history[entry.history.length - 1];
        if (last?.content === previous) return;
        entry.history.push({ content: previous, at: now });
        entry.updatedAt = now;
      }
      if (this.edited.length > 300) this.edited.length = 300;
      this.scheduleSave();
      this.emit();
    }
    getDeleted() {
      return this.deleted;
    }
    getEdited() {
      return this.edited;
    }
    counts() {
      return { deleted: this.deleted.length, edited: this.edited.length };
    }
    clear() {
      this.deleted = [];
      this.edited = [];
      this.reindex();
      this.scheduleSave();
      this.emit();
    }
    toJSON() {
      return JSON.stringify({ deleted: this.deleted, edited: this.edited }, null, 2);
    }
    subscribe(listener) {
      this.listeners.add(listener);
      return () => void this.listeners.delete(listener);
    }
    /** Flush any pending save immediately (used on plugin stop). */
    flush() {
      if (this.saveTimer !== void 0) {
        clearTimeout(this.saveTimer);
        this.saveTimer = void 0;
      }
      this.save();
    }
    // --- internals -----------------------------------------------------------
    trimDeleted() {
      if (this.retention <= 0) return;
      const perChannel = /* @__PURE__ */ new Map();
      this.deleted = this.deleted.filter((d) => {
        const seen = perChannel.get(d.channelId) ?? 0;
        if (seen >= this.retention) return false;
        perChannel.set(d.channelId, seen + 1);
        return true;
      });
    }
    reindex() {
      this.deletedIndex = new Set(this.deleted.map((d) => `${d.channelId}:${d.id}`));
    }
    emit() {
      for (const fn of this.listeners) {
        try {
          fn();
        } catch {
        }
      }
    }
    scheduleSave() {
      if (this.saveTimer !== void 0) clearTimeout(this.saveTimer);
      this.saveTimer = setTimeout(() => this.save(), 500);
    }
    save() {
      try {
        saveNamespace(DATA_NS, { deleted: this.deleted, edited: this.edited });
      } catch (err) {
        log10.error("failed to persist message log", err);
      }
    }
  };
  var messageLog = new MessageLogStore();

  // src/plugins/message-logger/render-content.tsx
  var EMOJI_TOKEN = /<(a)?:([A-Za-z0-9_]+):(\d+)>/g;
  function renderContent(content) {
    const parts = [];
    let cursor = 0;
    let key = 0;
    EMOJI_TOKEN.lastIndex = 0;
    for (let m = EMOJI_TOKEN.exec(content); m; m = EMOJI_TOKEN.exec(content)) {
      if (m.index > cursor) {
        parts.push(/* @__PURE__ */ React.createElement("span", { key: key++ }, content.slice(cursor, m.index)));
      }
      const [, animated, name, id] = m;
      parts.push(
        /* @__PURE__ */ React.createElement(
          "img",
          {
            key: key++,
            className: "hc-emoji",
            src: `https://cdn.discordapp.com/emojis/${id}.${animated ? "gif" : "webp"}`,
            alt: `:${name}:`,
            title: `:${name}:`,
            draggable: false,
            loading: "lazy"
          }
        )
      );
      cursor = m.index + m[0].length;
    }
    if (parts.length === 0) return content;
    if (cursor < content.length) {
      parts.push(/* @__PURE__ */ React.createElement("span", { key: key++ }, content.slice(cursor)));
    }
    return parts;
  }

  // src/plugins/message-logger/ui/LogPage.tsx
  var log11 = logger("message-logger");
  function useLog() {
    const [snapshot, setSnapshot] = useState(() => ({
      deleted: messageLog.getDeleted(),
      edited: messageLog.getEdited()
    }));
    useEffect(() => {
      const update = () => setSnapshot({ deleted: messageLog.getDeleted(), edited: messageLog.getEdited() });
      update();
      return messageLog.subscribe(update);
    }, []);
    return snapshot;
  }
  var PAGE_SIZE2 = 25;
  function InChatStatus() {
    const [snapshot, setSnapshot] = useState(
      () => getSourcePatchReport().filter((p) => p.pluginId === "message-logger")
    );
    useEffect(() => {
      const tick = () => setSnapshot(getSourcePatchReport().filter((p) => p.pluginId === "message-logger"));
      tick();
      const t = setInterval(tick, 3e3);
      return () => clearInterval(t);
    }, []);
    if (snapshot.length === 0) return null;
    const failed = snapshot.filter((p) => !p.applied);
    if (failed.length === 0) return null;
    const critical = failed.find((p) => p.label === "keep deleted message in store");
    const title = critical ? "\u804A\u5929\u4E2D\u7684\u7EA2\u8272\u5360\u4F4D\u672A\u751F\u6548" : "\u90E8\u5206\u804A\u5929\u5185\u8865\u4E01\u672A\u5339\u914D\u5F53\u524D Discord \u7248\u672C";
    const detail = critical ? "\u88AB\u5220\u9664\u7684\u6D88\u606F\u4ECD\u7136\u8BB0\u5F55\u5728\u4E0B\u65B9\u5217\u8868\uFF0C\u4F46\u5728\u804A\u5929\u91CC\u4F1A\u76F4\u63A5\u6D88\u5931\u3002\u6838\u5FC3\u8865\u4E01 keep-deleted \u672A\u5339\u914D\u5F53\u524D Discord \u7248\u672C\u3002" : "\u8BB0\u5F55\u529F\u80FD\u6B63\u5E38\uFF0C\u4F46\u804A\u5929\u4E2D\u7684\u7F16\u8F91\u5386\u53F2 / \u5220\u9664\u6807\u8BB0\u53EF\u80FD\u65E0\u6CD5\u663E\u793A\u3002";
    return /* @__PURE__ */ React.createElement("div", { className: "hc-mlog-warn" }, /* @__PURE__ */ React.createElement("div", { className: "hc-mlog-warn__title" }, title), /* @__PURE__ */ React.createElement("div", { className: "hc-mlog-warn__detail" }, detail), /* @__PURE__ */ React.createElement("ul", { className: "hc-mlog-warn__list" }, failed.map((p) => /* @__PURE__ */ React.createElement("li", { key: p.label }, "\u201C", p.label, "\u201D"))), /* @__PURE__ */ React.createElement("div", { className: "hc-mlog-warn__detail" }, "\u8BF7\u628A\u6B64\u5904\u4EE5\u53CA\u65E5\u5FD7\u9875\u91CC \u201CHalcyon modules\u201D \u76F8\u5173\u7684\u8F93\u51FA\u53D1\u7ED9\u5F00\u53D1\u8005\u5B9A\u4F4D\u3002"));
  }
  function LogPage() {
    const { deleted, edited } = useLog();
    const [tab, setTab] = useState("deleted");
    const [pages, setPages] = useState({ deleted: 0, edited: 0 });
    const [query, setQuery] = useState("");
    const all = tab === "deleted" ? deleted : edited;
    const needle = query.trim().toLowerCase();
    const entries = needle ? all.filter((e) => entryMatches(e, needle)) : all;
    const pageCount = Math.max(1, Math.ceil(entries.length / PAGE_SIZE2));
    const page = Math.min(pages[tab], pageCount - 1);
    const visible = entries.slice(page * PAGE_SIZE2, (page + 1) * PAGE_SIZE2);
    const goTo = (next) => setPages((prev) => ({ ...prev, [tab]: Math.max(0, Math.min(pageCount - 1, next)) }));
    const onQuery = (value) => {
      setQuery(value);
      setPages((prev) => ({ ...prev, [tab]: 0 }));
    };
    return /* @__PURE__ */ React.createElement("div", null, /* @__PURE__ */ React.createElement(InChatStatus, null), /* @__PURE__ */ React.createElement("div", { className: "hc-tabs" }, /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-tab",
        "data-active": tab === "deleted",
        onClick: () => setTab("deleted")
      },
      /* @__PURE__ */ React.createElement(TrashIcon, { size: 16 }),
      " \u5DF2\u5220\u9664",
      deleted.length > 0 && /* @__PURE__ */ React.createElement(Badge, { tone: "red" }, deleted.length)
    ), /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-tab",
        "data-active": tab === "edited",
        onClick: () => setTab("edited")
      },
      /* @__PURE__ */ React.createElement(PencilIcon, { size: 16 }),
      " \u5DF2\u7F16\u8F91",
      edited.length > 0 && /* @__PURE__ */ React.createElement(Badge, { tone: "orange" }, edited.length)
    ), /* @__PURE__ */ React.createElement("div", { className: "hc-tabs__spacer" }), /* @__PURE__ */ React.createElement(Button, { size: "sm", variant: "plain", icon: /* @__PURE__ */ React.createElement(DownloadIcon, { size: 16 }), onClick: exportLog }, "\u5BFC\u51FA"), /* @__PURE__ */ React.createElement(
      Button,
      {
        size: "sm",
        variant: "destructive",
        onClick: () => messageLog.clear(),
        disabled: all.length === 0
      },
      "\u6E05\u7A7A"
    )), /* @__PURE__ */ React.createElement("div", { className: "hc-mlog-search" }, /* @__PURE__ */ React.createElement(SearchIcon, { size: 18 }), /* @__PURE__ */ React.createElement(
      "input",
      {
        value: query,
        onChange: (event) => onQuery(event.currentTarget.value),
        placeholder: "\u641C\u7D22\u4F5C\u8005\u3001\u5185\u5BB9\u3001\u670D\u52A1\u5668 / \u9891\u9053",
        "aria-label": "\u641C\u7D22\u6D88\u606F\u8BB0\u5F55"
      }
    ), query && /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-mlog-search__clear",
        "aria-label": "\u6E05\u9664\u641C\u7D22",
        onClick: () => onQuery("")
      },
      "\xD7"
    )), all.length === 0 ? tab === "deleted" ? /* @__PURE__ */ React.createElement(
      EmptyState,
      {
        icon: /* @__PURE__ */ React.createElement(TrashIcon, { size: 48 }),
        title: "\u8FD8\u6CA1\u6709\u8BB0\u5F55",
        subtitle: "\u88AB\u5220\u9664\u7684\u6D88\u606F\u4F1A\u5728\u8FD9\u91CC\u4FDD\u7559\uFF0C\u542F\u7528\u63D2\u4EF6\u540E\u5373\u65F6\u751F\u6548\u3002"
      }
    ) : /* @__PURE__ */ React.createElement(
      EmptyState,
      {
        icon: /* @__PURE__ */ React.createElement(PencilIcon, { size: 48 }),
        title: "\u8FD8\u6CA1\u6709\u7F16\u8F91\u8BB0\u5F55",
        subtitle: "\u6D88\u606F\u88AB\u7F16\u8F91\u524D\u7684\u5185\u5BB9\u4F1A\u4FDD\u7559\u5728\u8FD9\u91CC\u3002"
      }
    ) : entries.length === 0 ? /* @__PURE__ */ React.createElement(
      EmptyState,
      {
        icon: /* @__PURE__ */ React.createElement(SearchIcon, { size: 48 }),
        title: "\u6CA1\u6709\u5339\u914D\u7684\u8BB0\u5F55",
        subtitle: `\u6CA1\u6709\u5305\u542B\u201C${query.trim()}\u201D\u7684\u8BB0\u5F55\uFF0C\u6362\u4E2A\u5173\u952E\u8BCD\u8BD5\u8BD5\u3002`
      }
    ) : /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "hc-msglist" }, tab === "deleted" ? visible.map((entry) => /* @__PURE__ */ React.createElement(DeletedRow, { key: `${entry.channelId}-${entry.id}`, entry })) : visible.map((entry) => /* @__PURE__ */ React.createElement(EditedRow, { key: `${entry.channelId}-${entry.id}`, entry }))), pageCount > 1 && /* @__PURE__ */ React.createElement(Pager, { page, pageCount, onChange: goTo })));
  }
  function Pager(props) {
    const { page, pageCount, onChange } = props;
    return /* @__PURE__ */ React.createElement("div", { className: "hc-pager" }, /* @__PURE__ */ React.createElement(Button, { size: "sm", variant: "plain", onClick: () => onChange(page - 1), disabled: page === 0 }, "\u4E0A\u4E00\u9875"), /* @__PURE__ */ React.createElement("span", { className: "hc-pager__label" }, "\u7B2C ", page + 1, " / ", pageCount, " \u9875"), /* @__PURE__ */ React.createElement(
      Button,
      {
        size: "sm",
        variant: "plain",
        onClick: () => onChange(page + 1),
        disabled: page >= pageCount - 1
      },
      "\u4E0B\u4E00\u9875"
    ));
  }
  function jumpToMessage(channelId, messageId, guildId) {
    dismissSettingsSurface();
    let gid = guildId;
    if (!gid) {
      try {
        const channel = ChannelStore.getChannel?.(channelId);
        gid = channel?.guild_id ?? channel?.guildId ?? void 0;
      } catch {
      }
    }
    const path = `/channels/${gid ?? "@me"}/${channelId}/${messageId}`;
    const selected = () => {
      try {
        return SelectedChannelStore.getChannelId?.();
      } catch {
        return void 0;
      }
    };
    const doJump = () => {
      const jump = JumpActions;
      if (typeof jump?.jumpToMessage === "function") {
        try {
          jump.jumpToMessage({ channelId, messageId, flash: true });
          if (selected() !== channelId) navigate(path);
          return;
        } catch (err) {
          log11.warn("[jump] jumpToMessage threw; falling back to route", err);
        }
      }
      if (!navigate(path)) {
        log11.warn("[jump] \u8DF3\u8F6C\u5931\u8D25\uFF1AJumpActions \u4E0E NavigationRouter \u5747\u672A\u89E3\u6790\u5230");
      }
    };
    const schedule = [80, 220, 450, 800];
    let i = 0;
    const tick = () => {
      doJump();
      const now = selected();
      const ok = now === channelId;
      log11.info(`[jump] \u7B2C ${i + 1} \u6B21 \xB7 now=${now ?? "?"} wanted=${channelId} ok=${ok}`);
      i++;
      if (!ok && i < schedule.length) {
        setTimeout(tick, schedule[i] - schedule[i - 1]);
      }
    };
    setTimeout(tick, schedule[0]);
  }
  function dismissSettingsSurface() {
    try {
      closeSettings();
    } catch {
    }
    try {
      const opts = { key: "Escape", code: "Escape", keyCode: 27, which: 27, bubbles: true, cancelable: true };
      document.dispatchEvent(new KeyboardEvent("keydown", opts));
      document.dispatchEvent(new KeyboardEvent("keyup", opts));
    } catch (err) {
      log11.error("[jump] escape dispatch failed", err);
    }
    try {
      if (typeof AppLayers.popLayer === "function") {
        AppLayers.popLayer();
      } else {
        getDispatcher()?.dispatch?.({ type: "LAYER_POP" });
      }
    } catch (err) {
      log11.error("[jump] layer pop failed", err);
    }
  }
  function JumpButton({ entry }) {
    return /* @__PURE__ */ React.createElement(
      Button,
      {
        size: "sm",
        variant: "plain",
        className: "hc-msg__jump",
        icon: /* @__PURE__ */ React.createElement(ChevronRightIcon, { size: 16 }),
        title: "\u8DF3\u8F6C\u5230\u8BE5\u6D88\u606F\u6240\u5728\u4F4D\u7F6E",
        onClick: () => jumpToMessage(entry.channelId, entry.id, entry.guildId)
      },
      "\u8DF3\u8F6C"
    );
  }
  function DeletedRow({ entry }) {
    return /* @__PURE__ */ React.createElement("div", { className: "hc-msg" }, /* @__PURE__ */ React.createElement("div", { className: "hc-msg__head" }, /* @__PURE__ */ React.createElement("span", { className: "hc-msg__author" }, entry.author.name), entry.author.bot && /* @__PURE__ */ React.createElement(Badge, { tone: "neutral" }, "BOT"), /* @__PURE__ */ React.createElement(Location, { channelId: entry.channelId, guildId: entry.guildId }), /* @__PURE__ */ React.createElement("span", { className: "hc-msg__time" }, formatTime2(entry.deletedAt)), /* @__PURE__ */ React.createElement(JumpButton, { entry })), /* @__PURE__ */ React.createElement("div", { className: "hc-msg__body" }, entry.content ? renderContent(entry.content) : entry.stickers?.length ? /* @__PURE__ */ React.createElement("span", null, "\u{1F3F7}\uFE0F \u8D34\u7EB8\uFF1A", entry.stickers.map((s) => s.name).join("\u3001")) : entry.attachmentsRich?.length || entry.embeds?.length ? /* @__PURE__ */ React.createElement("span", null, "\u{1F5BC}\uFE0F \u5A92\u4F53\u6D88\u606F") : /* @__PURE__ */ React.createElement("span", { className: "hc-msg__empty" }, "\uFF08\u65E0\u6587\u672C\u5185\u5BB9\uFF09")), (entry.attachmentsRich?.length ?? 0) > 0 && /* @__PURE__ */ React.createElement("div", { className: "hc-msg__media" }, entry.attachmentsRich.map(
      (a, i) => (a.content_type ?? "").startsWith("image/") || (a.content_type ?? "").startsWith("video/") ? /* @__PURE__ */ React.createElement(
        "img",
        {
          key: i,
          className: "hc-msg__thumb",
          src: a.proxy_url ?? a.url,
          alt: a.filename ?? "\u9644\u4EF6",
          loading: "lazy"
        }
      ) : /* @__PURE__ */ React.createElement("a", { key: i, href: a.url, target: "_blank", rel: "noreferrer" }, "\u{1F4CE} ", a.filename ?? "\u9644\u4EF6")
    )), !entry.attachmentsRich?.length && entry.attachments.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "hc-msg__meta" }, "\u9644\u4EF6 ", entry.attachments.length, " \u4E2A"));
  }
  function EditedRow({ entry }) {
    return /* @__PURE__ */ React.createElement("div", { className: "hc-msg" }, /* @__PURE__ */ React.createElement("div", { className: "hc-msg__head" }, /* @__PURE__ */ React.createElement("span", { className: "hc-msg__author" }, entry.author.name), /* @__PURE__ */ React.createElement(Location, { channelId: entry.channelId, guildId: entry.guildId }), /* @__PURE__ */ React.createElement("span", { className: "hc-msg__time" }, formatTime2(entry.updatedAt)), /* @__PURE__ */ React.createElement(JumpButton, { entry })), /* @__PURE__ */ React.createElement("div", { className: "hc-msg__versions" }, entry.history.map((version2, index) => /* @__PURE__ */ React.createElement("div", { className: "hc-msg__version", key: index }, /* @__PURE__ */ React.createElement("span", { className: "hc-msg__vtag" }, "v", index + 1), /* @__PURE__ */ React.createElement("span", { className: "hc-msg__vbody" }, version2.content ? renderContent(version2.content) : "\uFF08\u7A7A\uFF09")))));
  }
  function resolveLocation(channelId, guildId) {
    let channelName;
    let gid = guildId;
    let isDM = false;
    try {
      const channel2 = ChannelStore.getChannel?.(channelId);
      if (channel2) {
        if (channel2.name) channelName = String(channel2.name);
        gid = gid ?? channel2.guild_id ?? channel2.guildId ?? void 0;
        isDM = channel2.type === 1 || channel2.type === 3;
      }
    } catch {
    }
    let guildName;
    try {
      if (gid) {
        const guild = GuildStore.getGuild?.(gid);
        if (guild?.name) guildName = String(guild.name);
      }
    } catch {
    }
    const channel = channelName ? `#${channelName}` : isDM ? "\u79C1\u4FE1" : `#${channelId}`;
    return { guild: guildName, channel };
  }
  function Location({ channelId, guildId }) {
    const loc = resolveLocation(channelId, guildId);
    return /* @__PURE__ */ React.createElement("span", { className: "hc-msg__where" }, loc.guild && /* @__PURE__ */ React.createElement("span", { className: "hc-msg__guild" }, loc.guild), loc.guild && /* @__PURE__ */ React.createElement("span", { className: "hc-msg__sep" }, "\u203A"), /* @__PURE__ */ React.createElement("span", null, loc.channel));
  }
  function entryMatches(entry, needle) {
    try {
      if (entry.author?.name && entry.author.name.toLowerCase().includes(needle)) return true;
      const loc = resolveLocation(entry.channelId, entry.guildId);
      if (loc.guild && loc.guild.toLowerCase().includes(needle)) return true;
      if (loc.channel && loc.channel.toLowerCase().includes(needle)) return true;
      if ("content" in entry && typeof entry.content === "string") {
        if (entry.content.toLowerCase().includes(needle)) return true;
      }
      if ("history" in entry && Array.isArray(entry.history)) {
        for (const v of entry.history) {
          if (v?.content && v.content.toLowerCase().includes(needle)) return true;
        }
      }
    } catch {
    }
    return false;
  }
  function formatTime2(time) {
    const date = new Date(time);
    const pad = (n) => String(n).padStart(2, "0");
    return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
  }
  function exportLog() {
    try {
      const blob = new Blob([messageLog.toJSON()], { type: "application/json" });
      const url = URL.createObjectURL(blob);
      const anchor2 = document.createElement("a");
      anchor2.href = url;
      anchor2.download = `halcyon-message-log-${Date.now()}.json`;
      document.body.appendChild(anchor2);
      anchor2.click();
      anchor2.remove();
      URL.revokeObjectURL(url);
    } catch (err) {
      log11.error("export failed", err);
    }
  }

  // src/plugins/message-logger/toolbar-button.tsx
  var log12 = logger("message-logger");
  var TOOLBAR_ANCHORS = [
    'section[class*="title_"] [class*="toolbar_"]',
    'section[class*="title"] [class*="toolbar"]',
    '[class*="chat_"] [class*="toolbar_"]',
    '[class*="toolbar_"]'
  ];
  var ENSURE_MS = 1e3;
  var host2 = null;
  var unmount2 = null;
  var timer;
  var unsubscribe;
  function LogButton() {
    return /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-mlog-toolbtn",
        "aria-label": "\u6D88\u606F\u8BB0\u5F55",
        title: "\u6D88\u606F\u8BB0\u5F55\uFF08\u88AB\u5220 / \u7F16\u8F91\uFF09",
        onClick: () => openSettings({ pluginId: "message-logger" })
      },
      /* @__PURE__ */ React.createElement(ClockIcon, { size: 24 })
    );
  }
  function findToolbar() {
    for (const selector of TOOLBAR_ANCHORS) {
      try {
        const el = document.querySelector(selector);
        if (el) return el;
      } catch {
      }
    }
    return null;
  }
  function ensureMounted() {
    if (!settings.store.toolbarButton) {
      teardown();
      return;
    }
    if (host2 && document.contains(host2)) return;
    if (host2) teardown();
    const toolbar = findToolbar();
    if (!toolbar) return;
    const el = document.createElement("div");
    el.className = "hc-mlog-toolbtn-host";
    el.setAttribute("data-hc-plugin", "message-logger");
    try {
      toolbar.insertBefore(el, toolbar.firstChild);
    } catch {
      return;
    }
    try {
      const off = mountDetached(React.createElement(LogButton), el);
      host2 = el;
      unmount2 = off;
    } catch (err) {
      el.remove();
      log12.debug("toolbar button mount failed", err);
    }
  }
  function teardown() {
    if (unmount2) {
      try {
        unmount2();
      } catch {
      }
      unmount2 = null;
    }
    if (host2) {
      host2.remove();
      host2 = null;
    }
  }
  function startToolbarButton() {
    injectStyles();
    stopToolbarButton();
    ensureMounted();
    timer = setInterval(ensureMounted, ENSURE_MS);
    unsubscribe = settings.subscribe("toolbarButton", () => ensureMounted());
  }
  function stopToolbarButton() {
    if (timer) {
      clearInterval(timer);
      timer = void 0;
    }
    unsubscribe?.();
    unsubscribe = void 0;
    teardown();
  }

  // src/plugins/message-logger/index.tsx
  var log13 = logger("message-logger");
  var unpatchDispatch;
  var unsubscribeRetention;
  var unsubscribeDeleteStyle;
  function toMillis(value) {
    if (typeof value === "number") return value;
    if (typeof value === "string") {
      const parsed = Date.parse(value);
      return Number.isNaN(parsed) ? Date.now() : parsed;
    }
    if (value && typeof value.valueOf === "function") {
      const raw = value.valueOf();
      if (typeof raw === "number") return raw;
    }
    return Date.now();
  }
  function displayName(author) {
    return author?.globalName || author?.global_name || author?.username || author?.name || "\u672A\u77E5\u7528\u6237";
  }
  function toAuthor(author) {
    return { id: String(author?.id ?? "0"), name: displayName(author), bot: Boolean(author?.bot) };
  }
  function attachmentsOf(message) {
    const list = message?.attachments;
    if (!Array.isArray(list)) return [];
    return list.map((a) => a?.filename || a?.url || "\u9644\u4EF6").slice(0, 20);
  }
  function richAttachmentsOf(message) {
    const list = message?.attachments;
    if (!Array.isArray(list)) return [];
    return list.map((a) => ({
      id: a?.id != null ? String(a.id) : void 0,
      filename: a?.filename ?? a?.fileName ?? void 0,
      url: a?.url ?? void 0,
      proxy_url: a?.proxy_url ?? a?.proxyURL ?? a?.proxyUrl ?? void 0,
      content_type: a?.content_type ?? a?.contentType ?? void 0,
      width: typeof a?.width === "number" ? a.width : void 0,
      height: typeof a?.height === "number" ? a.height : void 0,
      size: typeof a?.size === "number" ? a.size : void 0
    })).filter((a) => a.url || a.proxy_url).slice(0, 10);
  }
  function embedsOf(message) {
    const list = message?.embeds;
    if (!Array.isArray(list) || list.length === 0) return [];
    try {
      return JSON.parse(JSON.stringify(list)).slice(0, 6);
    } catch {
      return [];
    }
  }
  function stickersOf(message) {
    const list = message?.sticker_items ?? message?.stickerItems ?? message?.stickers;
    if (!Array.isArray(list)) return [];
    return list.filter((s) => s?.id != null).map((s) => ({
      id: String(s.id),
      name: String(s.name ?? "\u8D34\u7EB8"),
      format_type: typeof s.format_type === "number" ? s.format_type : s.formatType
    })).slice(0, 4);
  }
  function currentUserId() {
    try {
      return UserStore.getCurrentUser?.()?.id;
    } catch {
      return void 0;
    }
  }
  var selfIgnoreLogged = false;
  function isIgnored(channelId, author) {
    const s = settings.store;
    if (channelId && s.ignoredChannels.includes(channelId)) return true;
    const authorId = author?.id != null ? String(author.id) : "";
    if (authorId && s.ignoredUsers.includes(authorId)) return true;
    if (s.ignoreBots && author?.bot) return true;
    if (s.ignoreSelf) {
      const me = currentUserId();
      if (!selfIgnoreLogged) {
        selfIgnoreLogged = true;
        const hit = Boolean(authorId && me && authorId === String(me));
        log13.info(
          `\u5C4F\u853D\u81EA\u5DF1 \u81EA\u68C0 \u2014 \u5F00\u5173=on\uFF0C\u6D88\u606F\u4F5C\u8005id=${authorId || "(\u7A7A)"}\uFF0C\u5F53\u524D\u7528\u6237id=${me ?? "(\u53D6\u4E0D\u5230)"}\uFF0C\u5224\u5B9A=${hit ? "\u547D\u4E2D\u2192\u4F1A\u5C4F\u853D" : "\u672A\u547D\u4E2D\u2192\u4E0D\u5C4F\u853D"}`
        );
      }
      if (authorId && me && authorId === String(me)) return true;
    }
    return false;
  }
  var shadow = /* @__PURE__ */ new Map();
  var SHADOW_MAX = 4e3;
  function remember(channelId, id, message) {
    const content = message?.content;
    if (!channelId || !id || typeof content !== "string") return;
    const key = `${channelId}:${id}`;
    const prior = shadow.get(key);
    if (prior) shadow.delete(key);
    const stickers = stickersOf(message);
    const rich = richAttachmentsOf(message);
    const embeds = embedsOf(message);
    shadow.set(key, {
      content,
      // Partial payloads (some MESSAGE_UPDATEs) may omit these; keep what we had.
      author: message?.author ?? prior?.author,
      attachments: Array.isArray(message?.attachments) ? attachmentsOf(message) : prior?.attachments,
      attachmentsRich: rich.length ? rich : prior?.attachmentsRich,
      embeds: embeds.length ? embeds : prior?.embeds,
      stickers: stickers.length ? stickers : prior?.stickers,
      sentAt: message?.timestamp != null ? toMillis(message.timestamp) : prior?.sentAt,
      guildId: message?.guild_id ?? message?.guildId ?? prior?.guildId
    });
    if (shadow.size > SHADOW_MAX) {
      const oldest = shadow.keys().next().value;
      if (oldest !== void 0) shadow.delete(oldest);
    }
  }
  function readMessage(channelId, id) {
    try {
      return MessageStore.getMessage(channelId, id);
    } catch {
      return void 0;
    }
  }
  var tintObserver;
  var tintInterval;
  var sweepScheduled = false;
  function sweepDeletedRows() {
    try {
      if (typeof document === "undefined") return;
      const root = document.documentElement;
      const want = `hc-mlog-${settings.store.deleteStyle || "tint"}`;
      if (root && !root.classList.contains(want)) {
        for (const s of DELETE_STYLE_CLASSES) root.classList.remove(`hc-mlog-${s}`);
        root.classList.add(want);
      }
      const rows = document.querySelectorAll('li[id^="chat-messages-"]');
      rows.forEach((el) => {
        if (!el.classList.contains("hc-deleted") && rowIsDeleted(el)) el.classList.add("hc-deleted");
      });
    } catch {
    }
  }
  function scheduleSweep() {
    if (sweepScheduled) return;
    sweepScheduled = true;
    setTimeout(() => {
      sweepScheduled = false;
      sweepDeletedRows();
    }, 60);
  }
  function rowIsDeleted(el) {
    const parts = el.id.split("-");
    const messageId = parts[parts.length - 1];
    const channelId = parts.length >= 4 ? parts[parts.length - 2] : void 0;
    return channelId ? messageLog.isDeleted(channelId, messageId) : messageLog.getDeleted().some((d) => d.id === messageId);
  }
  function startDomTinter() {
    if (typeof MutationObserver === "undefined" || typeof document === "undefined") return;
    tintObserver = new MutationObserver((mutations) => {
      for (const mu of mutations) {
        const t = mu.target;
        if (mu.type === "attributes" && t instanceof Element && t.id && t.id.startsWith("chat-messages-") && !t.classList.contains("hc-deleted") && rowIsDeleted(t)) {
          t.classList.add("hc-deleted");
        }
      }
      scheduleSweep();
    });
    const attach4 = () => {
      const target = document.documentElement ?? document.body;
      if (!target) return false;
      sweepDeletedRows();
      tintObserver?.observe(target, {
        childList: true,
        subtree: true,
        attributes: true,
        attributeFilter: ["class"]
      });
      return true;
    };
    if (!attach4()) {
      let tries = 0;
      const timer3 = setInterval(() => {
        if (attach4() || ++tries > 100) clearInterval(timer3);
      }, 100);
    }
    if (tintInterval) clearInterval(tintInterval);
    tintInterval = setInterval(sweepDeletedRows, 300);
  }
  function stopDomTinter() {
    tintObserver?.disconnect();
    tintObserver = void 0;
    if (tintInterval) {
      clearInterval(tintInterval);
      tintInterval = void 0;
    }
  }
  function tintRowInDom(channelId, messageId) {
    try {
      const el = document.getElementById(`chat-messages-${channelId}-${messageId}`) || document.getElementById(`chat-messages-${messageId}`);
      if (el) el.classList.add("hc-deleted");
    } catch {
    }
    scheduleSweep();
  }
  function rawAuthorFor(authorId, fallbackName, fallbackBot) {
    try {
      const u = UserStore.getUser?.(authorId);
      if (u) {
        const author = {
          id: String(u.id),
          username: u.username ?? fallbackName,
          global_name: u.globalName ?? u.global_name ?? null,
          discriminator: String(u.discriminator ?? "0"),
          bot: Boolean(u.bot),
          public_flags: u.publicFlags ?? u.public_flags ?? 0
        };
        if (u.avatar !== void 0) author.avatar = u.avatar;
        const deco = u.avatarDecorationData ?? u.avatar_decoration_data;
        if (deco !== void 0) author.avatar_decoration_data = deco;
        return author;
      }
    } catch {
    }
    return {
      id: String(authorId || "0"),
      username: fallbackName,
      global_name: fallbackName,
      discriminator: "0",
      bot: fallbackBot
    };
  }
  function liveRepaintPatchesApplied() {
    try {
      const mine = getSourcePatchReport().filter((p) => p.pluginId === "message-logger");
      const needed = ["re-render on deleted flag", "declare deleted field on message record"];
      return needed.every((label) => mine.some((p) => p.label === label && p.applied));
    } catch {
      return false;
    }
  }
  var rerendered = /* @__PURE__ */ new Set();
  function forceRowRerender(channelId, messageId) {
    try {
      const dispatcher2 = getDispatcher();
      if (!dispatcher2 || typeof dispatcher2.dispatch !== "function") return;
      const msg = readMessage(channelId, messageId);
      if (!msg) return;
      const a = msg.author ?? {};
      const iso = (v) => {
        if (v == null) return null;
        if (typeof v?.toISOString === "function") return v.toISOString();
        if (typeof v === "string") return v;
        return new Date(toMillis(v)).toISOString();
      };
      const entry = messageLog.findDeleted(channelId, messageId);
      let embeds = embedsOf(msg);
      if ((!embeds || embeds.length === 0) && entry?.embeds?.length) embeds = entry.embeds;
      let stickerItems = stickersOf(msg);
      if (stickerItems.length === 0 && entry?.stickers?.length) stickerItems = entry.stickers;
      let rich = richAttachmentsOf(msg);
      if (rich.length === 0 && entry?.attachmentsRich?.length) rich = entry.attachmentsRich;
      const content = typeof msg.content === "string" && msg.content !== "" ? msg.content : entry?.content ?? "";
      const raw = {
        id: String(messageId),
        channel_id: String(channelId),
        guild_id: msg.guild_id ?? msg.guildId ?? entry?.guildId ?? null,
        type: typeof msg.type === "number" ? msg.type : 0,
        content,
        // Echo the REAL user (real avatar hash) so this update can't wipe it.
        author: rawAuthorFor(
          String(a.id ?? entry?.author.id ?? "0"),
          a.username ?? a.global_name ?? a.globalName ?? entry?.author.name ?? "user",
          Boolean(a.bot ?? entry?.author.bot)
        ),
        timestamp: iso(msg.timestamp) ?? (/* @__PURE__ */ new Date()).toISOString(),
        edited_timestamp: iso(msg.editedTimestamp ?? msg.edited_timestamp),
        tts: Boolean(msg.tts),
        mention_everyone: Boolean(msg.mentionEveryone ?? msg.mention_everyone),
        mentions: [],
        mention_roles: [],
        attachments: rich.map((x, i) => ({
          id: x.id ?? `${messageId}${i}`,
          filename: x.filename ?? "file",
          url: x.url ?? x.proxy_url,
          proxy_url: x.proxy_url ?? x.url,
          content_type: x.content_type,
          width: x.width,
          height: x.height,
          size: x.size ?? 0
        })),
        embeds,
        sticker_items: stickerItems,
        pinned: Boolean(msg.pinned),
        flags: typeof msg.flags === "number" ? msg.flags : 0,
        // Carried so the rebuilt record keeps the flag where our patch preserves it.
        deleted: true
      };
      dispatcher2.dispatch({ type: "MESSAGE_UPDATE", message: raw });
    } catch (err) {
      log13.debug("force row re-render failed (non-fatal)", err);
    }
  }
  function scheduleRerender(channelId, messageId) {
    if (liveRepaintPatchesApplied()) return;
    const key = `${channelId}:${messageId}`;
    if (rerendered.has(key)) return;
    rerendered.add(key);
    setTimeout(() => {
      forceRowRerender(channelId, messageId);
      setTimeout(() => rerendered.delete(key), 1500);
    }, 0);
  }
  function captureDelete(channelId, id) {
    if (!channelId || !id) return;
    const message = readMessage(channelId, id);
    const snap = shadow.get(`${channelId}:${id}`);
    if (!message && !snap) {
      log13.debug(`delete of ${id} skipped: message not in cache or shadow`);
      return;
    }
    const author = message?.author ?? snap?.author ?? {};
    if (isIgnored(channelId, author)) return;
    const content = typeof message?.content === "string" && message.content !== "" ? message.content : snap?.content ?? "";
    const attachments = message ? attachmentsOf(message) : snap?.attachments ?? [];
    const liveRich = message ? richAttachmentsOf(message) : [];
    const attachmentsRich = liveRich.length ? liveRich : snap?.attachmentsRich ?? [];
    const liveEmbeds = message ? embedsOf(message) : [];
    const embeds = liveEmbeds.length ? liveEmbeds : snap?.embeds ?? [];
    const liveStickers = message ? stickersOf(message) : [];
    const stickers = liveStickers.length ? liveStickers : snap?.stickers ?? [];
    if (!content && attachments.length === 0 && attachmentsRich.length === 0 && embeds.length === 0 && stickers.length === 0) return;
    messageLog.recordDeleted({
      id: String(id),
      channelId: String(channelId),
      guildId: message?.guild_id ?? message?.guildId ?? snap?.guildId ?? void 0,
      author: toAuthor(author),
      content,
      attachments,
      attachmentsRich: attachmentsRich.length ? attachmentsRich : void 0,
      embeds: embeds.length ? embeds : void 0,
      stickers: stickers.length ? stickers : void 0,
      sentAt: message?.timestamp != null ? toMillis(message.timestamp) : snap?.sentAt ?? Date.now(),
      deletedAt: Date.now()
    });
    if (message && settings.store.keepDeletedInChat) {
      try {
        message.deleted = true;
      } catch {
      }
    }
    if (settings.store.keepDeletedInChat) {
      tintRowInDom(String(channelId), String(id));
      scheduleRerender(String(channelId), String(id));
    }
    if (settings.store.keepDeletedInChat && !liveKeepChecked) {
      liveKeepChecked = true;
      const cid = String(channelId);
      const mid = String(id);
      setTimeout(() => {
        const still = readMessage(cid, mid);
        const domEl = typeof document !== "undefined" ? document.getElementById(`chat-messages-${cid}-${mid}`) || document.getElementById(`chat-messages-${mid}`) : null;
        const tinted = !!domEl && domEl.classList.contains("hc-deleted");
        if (still && still.deleted === true) {
          log13.info(
            `live keep-deleted \u81EA\u68C0 OK \u2014 \u88AB\u5220\u6D88\u606F\u4ECD\u7559\u5728 store \u4E14\u5DF2\u6807\u8BB0 deleted\uFF1BDOM \u884C${domEl ? tinted ? "\u5DF2\u76F4\u63A5\u67D3\u7EA2\uFF08\u5B9E\u65F6\u7EA2\u6761\u751F\u6548\uFF09" : "\u627E\u5230\u4F46\u672A\u67D3\u7EA2\uFF0C\u8BF7\u53CD\u9988" : "\u672A\u627E\u5230\uFF08\u53EF\u80FD\u5DF2\u6EDA\u51FA\u89C6\u56FE\uFF09"}`
          );
        } else if (still) {
          log13.warn("live keep-deleted \u81EA\u68C0 PARTIAL \u2014 \u6D88\u606F\u4FDD\u7559\u4F46\u672A\u6807\u8BB0 deleted\uFF0C\u6539\u7528 DOM \u76F4\u63A5\u67D3\u7EA2\u515C\u5E95");
        } else {
          log13.error(
            "live keep-deleted \u81EA\u68C0 FAILED \u2014 MessageStore \u5DF2\u4E22\u5F03\u88AB\u5220\u6D88\u606F\uFF0C\u8BF4\u660E \u201Ckeep deleted message in store\u201D \u8865\u4E01\u672A\u547D\u4E2D\u5F53\u524D\u6784\u5EFA\uFF1B\u88AB\u5220\u6D88\u606F\u53EA\u4F1A\u5728\u91CD\u65B0\u52A0\u8F7D\u9891\u9053\u540E\u7531 revive \u91CD\u65B0\u51FA\u73B0\uFF08\u6B63\u662F\u4F60\u8BF4\u7684\u201C\u5237\u65B0\u624D\u6709\u3001\u5B9E\u65F6\u6CA1\u6709\u201D\uFF09\u3002"
          );
        }
      }, 0);
    }
  }
  function captureEdit(payload) {
    if (!settings.store.logEdits || !payload) return;
    const channelId = payload.channel_id ?? payload.channelId;
    const id = payload.id;
    if (!channelId || !id) return;
    if (typeof payload.content !== "string") return;
    const key = `${channelId}:${id}`;
    const existing = readMessage(channelId, id);
    const snap = shadow.get(key);
    const previous = snap?.content ?? (typeof existing?.content === "string" ? existing.content : void 0);
    remember(channelId, id, payload);
    if (previous === void 0) {
      log13.debug(`edit to ${id} skipped: no prior content known (message predates the recorder)`);
      return;
    }
    if (previous === payload.content) return;
    const author = existing?.author ?? snap?.author ?? payload.author ?? {};
    if (isIgnored(channelId, author)) return;
    const guildId = payload.guild_id ?? payload.guildId ?? existing?.guild_id ?? snap?.guildId;
    messageLog.recordEdit(
      String(id),
      String(channelId),
      toAuthor(author),
      previous,
      guildId != null ? String(guildId) : void 0
    );
  }
  function entryToRaw(entry) {
    const attachments = (entry.attachmentsRich ?? []).map((a, i) => ({
      id: a.id ?? `${entry.id}${i}`,
      filename: a.filename ?? "attachment",
      url: a.url ?? a.proxy_url,
      proxy_url: a.proxy_url ?? a.url,
      content_type: a.content_type,
      width: a.width,
      height: a.height,
      size: a.size ?? 0,
      spoiler: false
    }));
    return {
      id: entry.id,
      type: 0,
      channel_id: entry.channelId,
      guild_id: entry.guildId,
      sticker_items: entry.stickers?.length ? entry.stickers : void 0,
      content: entry.content || (attachments.length === 0 && entry.attachments.length ? `\u{1F4CE} ${entry.attachments.join(", ")}` : ""),
      // Echo the real user so resurrecting a deleted message on reload can't null
      // that user's avatar across the client (the "吞头像" bug).
      author: rawAuthorFor(entry.author.id, entry.author.name, entry.author.bot),
      timestamp: new Date(entry.sentAt).toISOString(),
      attachments,
      embeds: entry.embeds ?? [],
      mentions: [],
      mention_roles: [],
      mention_everyone: false,
      pinned: false,
      tts: false,
      flags: 0
    };
  }
  function compareIds(a, b) {
    try {
      const x = BigInt(a);
      const y = BigInt(b);
      return x < y ? -1 : x > y ? 1 : 0;
    } catch {
      return a < b ? -1 : a > b ? 1 : 0;
    }
  }
  var injectedActions = /* @__PURE__ */ new WeakSet();
  function resurrectIntoLoad(action) {
    if (!settings.store.keepDeletedInChat) return;
    if (injectedActions.has(action)) return;
    injectedActions.add(action);
    const channelId = String(action.channelId ?? action.channel_id ?? "");
    const msgs = action.messages;
    if (!channelId || !Array.isArray(msgs)) return;
    const mine = messageLog.getDeleted().filter((d) => d.channelId === channelId);
    if (!mine.length) return;
    const present = new Set(msgs.map((m) => String(m?.id)));
    let minId;
    for (const m of msgs) {
      const id = m?.id != null ? String(m.id) : void 0;
      if (id && (minId === void 0 || compareIds(id, minId) < 0)) minId = id;
    }
    const revived = mine.filter(
      (d) => !present.has(d.id) && (minId === void 0 || compareIds(d.id, minId) >= 0) && // Respect the ignore rules at revive time too, so toggling "屏蔽机器人"
      // or "屏蔽自己" takes effect for already-recorded messages on reload.
      !isIgnored(channelId, d.author)
    );
    if (!revived.length) return;
    const descending = msgs.length >= 2 ? compareIds(String(msgs[0].id), String(msgs[msgs.length - 1].id)) > 0 : true;
    msgs.push(...revived.map(entryToRaw));
    msgs.sort((a, b) => {
      const c = compareIds(String(a?.id ?? "0"), String(b?.id ?? "0"));
      return descending ? -c : c;
    });
    log13.info(`revived ${revived.length} deleted message(s) into ${channelId}`);
  }
  function reflagLoaded(action) {
    if (!settings.store.keepDeletedInChat) return;
    const channelId = String(action.channelId ?? action.channel_id ?? "");
    if (!channelId) return;
    for (const d of messageLog.getDeleted()) {
      if (d.channelId !== channelId) continue;
      const msg = readMessage(channelId, d.id);
      if (msg && !msg.deleted) {
        try {
          msg.deleted = true;
        } catch {
        }
      }
    }
  }
  function trackContent(action, type) {
    try {
      if (type === "MESSAGE_CREATE") {
        const m = action.message;
        remember(m?.channel_id ?? m?.channelId ?? action.channelId, m?.id, m);
      } else if (type === "LOAD_MESSAGES_SUCCESS") {
        const channelId = action.channelId ?? action.channel_id;
        if (Array.isArray(action.messages)) {
          for (const m of action.messages) remember(m?.channel_id ?? channelId, m?.id, m);
        }
      }
    } catch {
    }
  }
  var firstCaptureLogged = false;
  var actionsSeen = 0;
  var liveKeepChecked = false;
  function onAction(action) {
    const type = action?.type;
    if (typeof type !== "string") return;
    if (WATCHED.includes(type)) actionsSeen++;
    trackContent(action, type);
    if (type === "LOAD_MESSAGES_SUCCESS") {
      try {
        resurrectIntoLoad(action);
        setTimeout(() => reflagLoaded(action), 0);
      } catch (err) {
        log13.error("failed to revive deleted messages on channel load", err);
      }
    }
    try {
      if (type === "MESSAGE_DELETE") {
        captureDelete(action.channelId ?? action.channel_id, action.id ?? action.messageId);
      } else if (type === "MESSAGE_DELETE_BULK") {
        const channelId = action.channelId ?? action.channel_id;
        for (const id of action.ids ?? []) captureDelete(channelId, id);
      } else if (type === "MESSAGE_UPDATE") {
        captureEdit(action.message);
      } else {
        return;
      }
      if (!firstCaptureLogged) {
        firstCaptureLogged = true;
        log13.info(`recorder saw its first ${type}`);
      }
    } catch (err) {
      log13.error("recorder failed for", type, err);
    }
  }
  function onDispatch(ctx) {
    onAction(ctx.args[0]);
  }
  var WATCHED = ["MESSAGE_CREATE", "MESSAGE_UPDATE", "MESSAGE_DELETE", "MESSAGE_DELETE_BULK", "LOAD_MESSAGES_SUCCESS"];
  function attachRecorder(dispatcher2, tag) {
    const undo = [];
    const seams = [];
    if (typeof dispatcher2.addInterceptor === "function") {
      try {
        const interceptor = (action) => {
          onAction(action);
          return false;
        };
        dispatcher2.addInterceptor(interceptor);
        undo.push(() => {
          const list = dispatcher2._interceptors;
          if (Array.isArray(list)) {
            const at = list.indexOf(interceptor);
            if (at >= 0) list.splice(at, 1);
          }
        });
        seams.push("interceptor");
      } catch {
      }
    }
    for (const method of ["dispatch", "_dispatch"]) {
      if (typeof dispatcher2[method] === "function") {
        try {
          undo.push(patcher.before(dispatcher2, method, onDispatch));
          seams.push(method);
        } catch {
        }
        break;
      }
    }
    if (typeof dispatcher2.subscribe === "function") {
      try {
        const handler = (action) => onAction(action);
        for (const type of WATCHED) dispatcher2.subscribe(type, handler);
        undo.push(() => {
          if (typeof dispatcher2.unsubscribe === "function") {
            for (const type of WATCHED) {
              try {
                dispatcher2.unsubscribe(type, handler);
              } catch {
              }
            }
          }
        });
        seams.push("subscribe");
      } catch {
      }
    }
    log13.info(`recorder on dispatcher ${tag}: seams [${seams.join(", ") || "none"}]`);
    return () => undo.forEach((u) => u());
  }
  function attachRecorderEverywhere() {
    const hooked = /* @__PURE__ */ new Set();
    const undo = [];
    const sweep = () => {
      const candidates = [...findAll(isFluxDispatcher), getDispatcher()].filter(Boolean);
      let added = 0;
      for (const d of candidates) {
        if (hooked.has(d)) continue;
        hooked.add(d);
        undo.push(attachRecorder(d, `#${hooked.size}`));
        added++;
      }
      return added;
    };
    const first = sweep();
    log13.info(`recorder attached to ${first} dispatcher instance(s)`);
    const timer3 = setInterval(() => {
      const added = sweep();
      if (added > 0) log13.info(`recorder attached to ${added} late dispatcher instance(s)`);
    }, 5e3);
    const stopTimer = setTimeout(() => clearInterval(timer3), 6e4);
    return () => {
      clearInterval(timer3);
      clearTimeout(stopTimer);
      undo.forEach((u) => u());
    };
  }
  var MARKER_ICON_PATHS = {
    trash: () => /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("path", { d: "M4.5 7h15" }), /* @__PURE__ */ React.createElement("path", { d: "M9.25 7V5.5A1.5 1.5 0 0110.75 4h2.5a1.5 1.5 0 011.5 1.5V7" }), /* @__PURE__ */ React.createElement("path", { d: "M6.5 7l.85 11.1A2 2 0 009.34 20h5.32a2 2 0 001.99-1.9L17.5 7" })),
    shield: () => /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("path", { d: "M12 3.5l7 2.6v5c0 4.4-3 7.3-7 8.9-4-1.6-7-4.5-7-8.9v-5l7-2.6z" }), /* @__PURE__ */ React.createElement("path", { d: "M9.5 12l1.8 1.8 3.2-3.6" })),
    warning: () => /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("path", { d: "M12 4.5L3.5 19h17L12 4.5z" }), /* @__PURE__ */ React.createElement("path", { d: "M12 10v4" }), /* @__PURE__ */ React.createElement("path", { d: "M12 16.75h.01" }))
  };
  function formatDeletedAt(at, mode) {
    if (at == null || mode === "none") return void 0;
    const d = new Date(at);
    if (mode === "datetime") {
      const pad = (n) => String(n).padStart(2, "0");
      return `${pad(d.getMonth() + 1)}-${pad(d.getDate())} ${d.toLocaleTimeString("zh-CN", { hour12: false })}`;
    }
    return d.toLocaleTimeString("zh-CN", { hour12: false });
  }
  function MessageMarker(props) {
    const s = settings.store;
    const icon = MARKER_ICON_PATHS[s.markerIcon]?.();
    const stamp = formatDeletedAt(props.at, s.markerTime);
    const cls = `hc-deleted-marker hc-deleted-marker--${s.markerLook || "plain"}` + (props.edited ? " hc-deleted-marker--edited" : "");
    return /* @__PURE__ */ React.createElement("div", { className: cls }, icon && /* @__PURE__ */ React.createElement(
      "svg",
      {
        className: "hc-deleted-marker__icon",
        width: "14",
        height: "14",
        viewBox: "0 0 24 24",
        fill: "none",
        stroke: "currentColor",
        strokeWidth: 2,
        strokeLinecap: "round",
        strokeLinejoin: "round",
        "aria-hidden": true
      },
      icon
    ), /* @__PURE__ */ React.createElement("span", null, props.text, stamp ? `\uFF08${stamp}\uFF09` : ""));
  }
  var MARKER_SETTING_KEYS = ["logEdits", "deleteStyle", "showDeletedMarker", "showEditedMarker", "markerIcon", "markerLook", "markerTime"];
  function useMlogSettings() {
    const [, bump] = useState(0);
    useEffect(() => {
      const unsubs = MARKER_SETTING_KEYS.map((key) => settings.subscribe(key, () => bump((n) => n + 1)));
      return () => unsubs.forEach((unsub) => unsub());
    }, []);
  }
  function collectDeletedMedia(attachments, embeds) {
    const out = [];
    for (const a of attachments ?? []) {
      const url = a.proxy_url ?? a.url;
      if (!url) continue;
      const ct = a.content_type ?? "";
      out.push({
        url,
        kind: ct.startsWith("video/") ? "video" : ct.startsWith("image/") ? "image" : "file",
        name: a.filename
      });
    }
    for (const e of embeds ?? []) {
      const img = e?.image?.proxy_url ?? e?.image?.url ?? e?.thumbnail?.proxy_url ?? e?.thumbnail?.url;
      if (typeof img === "string" && img) out.push({ url: img, kind: "image" });
    }
    return out.slice(0, 6);
  }
  function MessageExtras(props) {
    useMlogSettings();
    const s = settings.store;
    const nodes = [];
    if (s.logEdits && props.history && props.history.length > 0) {
      nodes.push(
        /* @__PURE__ */ React.createElement("div", { className: "hc-edit-history", key: "hc-edit-history" }, props.history.map((version2, index) => {
          const time = formatDeletedAt(version2.at, "time");
          return /* @__PURE__ */ React.createElement(
            "div",
            {
              className: `hc-edit-history__version hc-edit-history__version--${s.deleteStyle || "tint"}`,
              key: index
            },
            renderContent(version2.content),
            time ? /* @__PURE__ */ React.createElement("span", { className: "hc-edit-history__time" }, time) : null
          );
        }))
      );
    }
    if (s.showEditedMarker && props.isEdited && !props.isDeleted) {
      nodes.push(/* @__PURE__ */ React.createElement(MessageMarker, { key: "hc-edited-marker", text: "\u6B64\u6D88\u606F\u5DF2\u7F16\u8F91", at: props.editedAt, edited: true }));
    }
    if (s.showDeletedMarker && props.isDeleted) {
      nodes.push(/* @__PURE__ */ React.createElement(MessageMarker, { key: "hc-deleted-marker", text: "\u6B64\u6D88\u606F\u5DF2\u5220\u9664", at: props.deletedAt }));
    }
    if (props.isDeleted && props.media && props.media.length > 0) {
      nodes.push(
        /* @__PURE__ */ React.createElement("div", { className: "hc-deleted-media", key: "hc-deleted-media" }, props.media.map(
          (m, i) => m.kind === "file" ? /* @__PURE__ */ React.createElement(
            "a",
            {
              className: "hc-deleted-media__file",
              key: i,
              href: m.url,
              target: "_blank",
              rel: "noreferrer"
            },
            "\u{1F4CE} ",
            m.name ?? "\u9644\u4EF6"
          ) : /* @__PURE__ */ React.createElement(
            "img",
            {
              className: "hc-deleted-media__thumb",
              key: i,
              src: m.url,
              alt: m.name ?? "",
              loading: "lazy",
              referrerPolicy: "no-referrer"
            }
          )
        ))
      );
    }
    return nodes.length ? /* @__PURE__ */ React.createElement(React.Fragment, null, nodes) : null;
  }
  var DELETE_STYLE_CLASSES = ["tint", "text", "ghost", "strike"];
  function syncDeleteStyleClass() {
    try {
      const root = document.documentElement;
      if (!root) return;
      for (const s of DELETE_STYLE_CLASSES) root.classList.remove(`hc-mlog-${s}`);
      root.classList.add(`hc-mlog-${settings.store.deleteStyle || "tint"}`);
    } catch {
    }
  }
  function reportPatches() {
    const mine = getSourcePatchReport().filter((p) => p.pluginId === "message-logger");
    if (!mine.length) return;
    for (const p of mine) {
      if (p.applied) {
        log13.info(`patch OK   \xB7 ${p.label} (${p.hits} hit${p.hits === 1 ? "" : "s"})`);
      } else {
        log13.warn(`patch MISS \xB7 ${p.label} \u2014 \u672A\u5339\u914D\u5F53\u524D Discord \u6784\u5EFA`);
      }
    }
    const missed = mine.filter((p) => !p.applied);
    if (missed.length === 0) {
      log13.info("in-chat patches applied \u2014 \u5168\u90E8\u547D\u4E2D");
    } else {
      log13.warn(
        "\u90E8\u5206 in-chat patch \u672A\u5339\u914D\u5F53\u524D Discord \u6784\u5EFA\uFF1A" + missed.map((p) => `"${p.label}"`).join("\u3001") + "\u3002\u5220\u9664\u6D88\u606F\u4ECD\u4F1A\u8BB0\u5F55\u5728\u63D2\u4EF6\u9875\uFF0C\u4F46\u53EF\u80FD\u65E0\u6CD5\u5728\u804A\u5929\u5185\u4FDD\u7559 / \u53D8\u7EA2\u3002"
      );
    }
    const storeMissed = mine.some((p) => p.label === "keep deleted message in store" && !p.applied);
    const recordMissed = mine.some((p) => p.label === "declare deleted field on message record" && !p.applied);
    if (storeMissed || recordMissed) {
      try {
        const forms = ["MESSAGE_DELETE:function", "MESSAGE_DELETE(", "MESSAGE_DELETE_BULK"];
        const dumps = forms.map((needle) => {
          const out = dumpFactorySource(needle, 220);
          return out.startsWith("<no loaded factory") || out.startsWith("<webpack") ? "" : `\u3010${needle}\u3011${out}`;
        }).filter(Boolean);
        const combined = dumps.join("  ||  ").replace(/\s+/g, " ");
        const slice = combined.length > 3800 ? combined.slice(0, 3800) + " \u2026(\u622A\u65AD)" : combined;
        log13.warn(
          "MESSAGE_DELETE \u5904\u7406\u5668\u771F\u5B9E\u6E90\u7801\u5207\u7247\uFF08\u8865\u4E01\u672A\u547D\u4E2D\uFF0C\u7528\u4E8E\u4FEE\u6B63\uFF0C\u8BF7\u6574\u6BB5\u53D1\u7ED9\u5F00\u53D1\u8005\uFF09\uFF1A" + (slice || "\u672A\u5728\u5DF2\u52A0\u8F7D\u6A21\u5757\u4E2D\u627E\u5230 MESSAGE_DELETE \u5904\u7406\u5668\uFF1B\u8BF7\u5148\u6253\u5F00\u4E00\u4E2A\u9891\u9053\u540E\u518D\u67E5\u770B\u65E5\u5FD7\u3002")
        );
      } catch (err) {
        log13.error("could not dump MESSAGE_DELETE handler shape", err);
      }
    }
  }
  var message_logger_default = definePlugin({
    id: "message-logger",
    name: "\u6D88\u606F\u8BB0\u5F55\u5668",
    description: "\u4FDD\u7559\u88AB\u5220\u9664\u7684\u6D88\u606F\u4E0E\u7F16\u8F91\u5386\u53F2\uFF0C\u53EF\u6309\u7528\u6237\u6216\u9891\u9053\u5FFD\u7565\uFF0C\u652F\u6301\u5BFC\u51FA\u3002",
    authors: [{ name: "caitemm" }],
    category: "utility",
    settings,
    page: {
      title: "\u6D88\u606F\u8BB0\u5F55",
      icon: ClockIcon,
      component: LogPage
    },
    /** Diagnostic snapshot for HalcyonAPI.probe() — jump readiness in particular. */
    probe() {
      const jump = JumpActions;
      const router = NavigationRouter;
      let scanRouterFound = false;
      try {
        scanRouterFound = typeof find(
          (x) => typeof x?.transitionTo === "function" && typeof x?.__halcyon_probe__ === "undefined"
        )?.transitionTo === "function";
      } catch {
        scanRouterFound = false;
      }
      return {
        jumpActionsFound: jump != null,
        jumpToMessageIsFn: typeof jump?.jumpToMessage === "function",
        navigationRouterFound: router != null,
        transitionToIsFn: typeof router?.transitionTo === "function",
        scanRouterFound,
        deletedCount: messageLog.getDeleted().length,
        settingsHostEmbedded: getSourcePatchReport().some(
          (p) => p.pluginId === "halcyon-settings" && p.applied
        )
      };
    },
    patches: [
      {
        // The message store drops records when it handles MESSAGE_DELETE /
        // MESSAGE_DELETE_BULK. Instead of letting it, we rebuild the channel
        // cache ourselves: kept messages are re-committed flagged `deleted:true`,
        // everything else is removed exactly as the original would. Without this
        // the row simply vanishes the instant a message is deleted and only
        // reappears (red) on reload via the revive path — precisely the
        // "刷新才有、实时没有" symptom.
        label: "keep deleted message in store",
        // Ported VERBATIM from Vencord's MessageLogger "MessageStore" patch.
        // The module is selected by the store's registered name — the quoted
        // string "MessageStore" the minifier keeps — and the handler is patched
        // by a ZERO-WIDTH insertion right after `MESSAGE_DELETE:function(e){`:
        // the original body stays byte-for-byte intact and merely becomes
        // unreachable after our early `return`. Vencord tracks the current
        // Discord build, so this is the shape that actually ships; the earlier
        // hand-rolled regexes guessed at method-shorthand / multi-dot shapes this
        // client no longer uses, which is exactly why they missed 4×. Vencord's
        // `\i` token is expanded here to its definition [A-Za-z_$][\w$]*.
        find: '"MessageStore"',
        replacement: [
          {
            // Single delete. $1 = raw action param, $2 = store ref (e.g. `d.A`).
            match: /(?<=MESSAGE_DELETE:function\(([A-Za-z_$][\w$]*)\)\{)(?=let.{0,100}?([A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*)\.getOrCreate)/,
            replace: "let cache=$2.getOrCreate($1.channelId);cache=$self.handleDelete(cache,$1,!1);$2.commit(cache);return;"
          },
          {
            // Bulk delete.
            match: /(?<=MESSAGE_DELETE_BULK:function\(([A-Za-z_$][\w$]*)\)\{)(?=let.{0,100}?([A-Za-z_$][\w$]*\.[A-Za-z_$][\w$]*)\.getOrCreate)/,
            replace: "let cache=$2.getOrCreate($1.channelId);cache=$self.handleDelete(cache,$1,!0);$2.commit(cache);return;"
          }
        ]
      },
      {
        // Base message row: append our class to the "li" so kept messages tint
        // red. The find string is a dev assertion that survives minification.
        // The prefix character before `("li",{` can be `)` (a `(0, X.createElement)`
        // style call preserved by some minifiers) OR a plain identifier
        // (`_jsx("li",` in a jsx-runtime build), so match either — the old
        // pattern only accepted `)` and no-op'd on jsx-runtime builds.
        label: "tint deleted message row (base)",
        find: "Message must not be a thread starter message",
        replacement: {
          match: /([)\w$\]])\("li",\{(.+?),className:/,
          replace: '$1("li",{$2,className:($self.deletedClass(arguments[0])||"")+" "+'
        }
      },
      {
        // The message row builds a className. When the record is flagged deleted,
        // append our modifier so it renders tinted. The argument list may carry
        // nested calls/strings, so parens are matched one level deep instead of
        // `[^)]*` (which used to cut mid-expression and produce code that failed
        // to compile).
        label: "tint deleted message row",
        find: "childrenRepliedMessage",
        replacement: {
          match: /(className:)(\w+\(\)\((?:[^()"']|"[^"]*"|'[^']*'|\([^()]*\))*\))/,
          replace: '$1[$2,$self.deletedClass(arguments[0])].filter(Boolean).join(" ")'
        }
      },
      {
        // Message content renderer: prepend the persisted edit history above the
        // current content, so old versions read top-to-bottom. Located via the
        // ".SEND_FAILED," module; the history comes from OUR persisted store, so
        // it survives client reloads.
        label: "inline edit history",
        find: ".SEND_FAILED,",
        replacement: {
          match: /\]:[\w$]+\.isUnsupported.{0,30}?,children:\[/,
          replace: "$&$self.renderEdits(arguments[0]),"
        }
      },
      {
        // Force a live re-render when a message is flagged deleted.
        //
        // This is the fix for "红条只有刷新后才出现". The store patch above keeps a
        // deleted message with `deleted: true` (confirmed live by the self-check
        // log), but Discord wraps each message row in React.memo with a custom
        // comparator that only re-renders when content / state / flags /
        // editedTimestamp change — it never looks at `deleted`. So flipping the
        // flag updates the store but the row is memoized stale and never repaints;
        // the red styling only shows on reload, when the list mounts fresh.
        //
        // We extend that comparator to also compare `deleted`, so the instant we
        // flag a message the row is considered changed and repaints red. The two
        // message variables are captured straight from the tail of the comparator
        // (`X.editedTimestamp?.toString()===Y.editedTimestamp?.toString()`) so we
        // never hardcode minified names. Same module as the edit-history patch
        // (".SEND_FAILED,").
        label: "re-render on deleted flag",
        find: ".SEND_FAILED,",
        replacement: {
          match: /((\w+)\.editedTimestamp\?\.toString\(\)===(\w+)\.editedTimestamp\?\.toString\(\))/,
          replace: "$1&&$2.deleted===$3.deleted"
        }
      },
      {
        // THE fix for "红条只有刷新后才出现". Discord's Message is an Immutable
        // Record with a FIXED field schema. `deleted` is not one of those fields,
        // so `m.set("deleted",true)` stores a value that is readable (the live
        // self-check sees deleted===true) but is invisible to the record's
        // structural equality — and Discord's message list decides whether to
        // repaint a row via that equality. Old record and new record compare
        // "equal" on the schema fields, so the row is never repainted live; only
        // a reload (fresh mount) shows the red styling.
        //
        // Declaring `deleted` (and editHistory / firstEditTimestamp) as real
        // fields on the record class means `.set("deleted",true)` now yields a
        // record that is genuinely not-equal to the original, so the list
        // repaints the instant we flag it. Ported verbatim from Vencord's
        // "Message domain model" patch, which is what makes deletes show live
        // there. Runs in the record constructor, located by `}addReaction(`.
        label: "declare deleted field on message record",
        find: /\}addReaction\(|addReaction\([\w$]+\)\{/,
        replacement: {
          match: /this\.customRenderedContent=(\w+)\.customRenderedContent,/,
          replace: "this.customRenderedContent=$1.customRenderedContent,this.deleted=$1.deleted||!1,this.editHistory=$1.editHistory||[],this.firstEditTimestamp=$1.firstEditTimestamp||this.editedTimestamp||this.timestamp,"
        }
      },
      {
        // Keep `deleted` / editHistory / firstEditTimestamp alive when Discord
        // rebuilds a message record on MESSAGE_UPDATE (edits, reactions, embed
        // unfurls). Without this, any post-delete update to the same message
        // re-derives the record from the server payload and silently drops our
        // flag, so a deleted message that then gets an embed/reaction update
        // would lose its red row. Ported from Vencord's "updated message
        // transformer" patch, located by ".PREMIUM_REFERRAL&&(".
        label: "carry deleted flag through message updates",
        find: /\.PREMIUM_REFERRAL\s*&&\s*\(/,
        replacement: {
          match: /(?<=null!=[\w$]+\.edited_timestamp\)return )[\w$]+\([\w$]+,\{reactions:([\w$]+)\.reactions[\s\S]{0,60}?\}\)/,
          replace: "Object.assign($&,{deleted:$1.deleted,editHistory:$1.editHistory,firstEditTimestamp:$1.firstEditTimestamp})"
        }
      }
    ],
    start() {
      messageLog.load();
      messageLog.setRetention(settings.store.retention);
      unsubscribeRetention = settings.subscribe("retention", (next) => messageLog.setRetention(next));
      syncDeleteStyleClass();
      unsubscribeDeleteStyle = settings.subscribe("deleteStyle", syncDeleteStyleClass);
      unpatchDispatch = attachRecorderEverywhere();
      startDomTinter();
      startToolbarButton();
      setTimeout(reportPatches, 4e3);
      setTimeout(() => {
        if (actionsSeen > 0) {
          log13.info(`recorder pulse OK \u2014 ${actionsSeen} message action(s) observed so far`);
        } else {
          log13.error(
            "recorder pulse FAILED \u2014 no message actions observed in 30s. The dispatcher hooks are not receiving events on this build. \u8BF7\u628A\u65E5\u5FD7\u9875\u91CC recorder on dispatcher \u5F00\u5934\u7684\u51E0\u884C\u53D1\u7ED9\u5F00\u53D1\u8005\u3002"
          );
        }
      }, 3e4);
    },
    stop() {
      unpatchDispatch?.();
      unpatchDispatch = void 0;
      unsubscribeRetention?.();
      unsubscribeRetention = void 0;
      unsubscribeDeleteStyle?.();
      unsubscribeDeleteStyle = void 0;
      stopDomTinter();
      stopToolbarButton();
      try {
        for (const s of DELETE_STYLE_CLASSES) document.documentElement?.classList.remove(`hc-mlog-${s}`);
      } catch {
      }
      messageLog.flush();
      log13.info("stopped");
    },
    // --- methods the source patches call through `$self` --------------------
    /**
     * Replacement body for the MessageStore's MESSAGE_DELETE(_BULK) handlers.
     * Receives the channel's immutable message cache; returns it with kept
     * messages flagged `deleted: true` (so the row patch tints them) and
     * everything else removed, exactly as the original handler would have.
     * Any surprise falls back to the original handler by returning the cache
     * unchanged only when nothing was touched — a throw here must never take
     * the store down, so the whole body is guarded.
     */
    handleDelete(cache2, action, isBulk) {
      try {
        if (cache2 == null) return cache2;
        if (!isBulk && typeof cache2.has === "function" && !cache2.has(action.id)) return cache2;
        const keepInChat = settings.store.keepDeletedInChat;
        const EPHEMERAL = 64;
        const mutate = (id) => {
          const msg = typeof cache2.get === "function" ? cache2.get(id) : void 0;
          if (!msg) return;
          const keep = keepInChat && !action.mlDeleted && (msg.flags & EPHEMERAL) !== EPHEMERAL && !isIgnored(String(action.channelId ?? action.channel_id ?? msg.channel_id ?? ""), msg.author ?? {});
          if (!keep) {
            cache2 = cache2.remove(id);
          } else {
            cache2 = cache2.update(id, (m) => m.set("deleted", true));
          }
        };
        if (isBulk) {
          for (const id of action.ids ?? []) mutate(id);
        } else {
          mutate(action.id);
        }
      } catch (err) {
        log13.error("handleDelete failed; messages removed normally", err);
      }
      return cache2;
    },
    /**
     * Extra classNames for a message row whose message is deleted. Keyed off the
     * PERSISTED record (not just the in-memory `deleted` flag), so rows stay
     * marked across reloads; the flag remains a fast path for freshly deleted
     * ones. The style modifier comes from settings, so users pick the look.
     * Patched call sites differ in what they pass: the base row patch hands us
     * the component props (message under `.message`), the legacy patch the
     * message itself — accept either.
     */
    deletedClass(propsOrMessage) {
      try {
        const m = propsOrMessage?.message ?? propsOrMessage;
        if (!m) return "";
        const channelId = m.channel_id ?? m.channelId;
        const isDeleted = m.deleted === true || channelId && m.id && messageLog.isDeleted(String(channelId), String(m.id));
        if (!isDeleted) return "";
        return "hc-deleted";
      } catch {
        return "";
      }
    },
    /**
     * Called from the content-renderer patch with the component's props. Renders
     * (a) the persisted edit history above the content and (b) a "deleted at"
     * marker line beneath it, both from the plugin's own store, so both survive
     * reloads. Runs inside Discord's message renderer and must never break it —
     * everything is guarded and returns null on any surprise.
     */
    renderEdits(props) {
      try {
        const message = props?.message;
        const id = message?.id;
        const channelId = message?.channel_id ?? message?.channelId;
        if (!id || !channelId) return null;
        if (isIgnored(String(channelId), message?.author)) return null;
        const entry = messageLog.getEdited().find((e) => e.id === String(id) && e.channelId === String(channelId));
        const record2 = messageLog.findDeleted(String(channelId), String(id));
        const hasHistory = Boolean(entry && entry.history.length > 0);
        const isDeleted = Boolean(record2) || message?.deleted === true;
        const editedTs = message?.edited_timestamp ?? message?.editedTimestamp;
        const isEdited = editedTs != null || hasHistory;
        const editedAt = editedTs != null ? toMillis(editedTs) : entry?.updatedAt;
        if (!hasHistory && !isDeleted && !isEdited) return null;
        return /* @__PURE__ */ React.createElement(
          MessageExtras,
          {
            history: entry?.history,
            deletedAt: record2?.deletedAt,
            editedAt,
            isDeleted,
            isEdited,
            media: isDeleted ? collectDeletedMedia(record2?.attachmentsRich, record2?.embeds) : void 0
          }
        );
      } catch {
        return null;
      }
    }
  });

  // src/plugins/show-username/index.tsx
  var log14 = logger("show-username");
  var settings2 = defineSettings({
    mode: {
      type: "select",
      default: "nick-user",
      label: "\u663E\u793A\u65B9\u5F0F",
      description: "\u6635\u79F0\u4E0E\u7528\u6237\u540D\u7684\u6392\u5217\u3002",
      options: [
        { value: "nick-user", label: "\u6635\u79F0\u5728\u524D\uFF0C\u7528\u6237\u540D\u5728\u540E" },
        { value: "user-nick", label: "\u7528\u6237\u540D\u5728\u524D\uFF0C\u6635\u79F0\u5728\u540E" },
        { value: "user-only", label: "\u53EA\u663E\u793A\u7528\u6237\u540D" }
      ]
    },
    style: {
      type: "select",
      default: "muted",
      label: "\u7528\u6237\u540D\u6837\u5F0F",
      description: "\u9644\u52A0\u7684\u7528\u6237\u540D\u90E8\u5206\u7684\u89C6\u89C9\u6837\u5F0F\u3002",
      options: [
        { value: "muted", label: "\u7070\u8272\u5C0F\u5B57" },
        { value: "pill", label: "\u5706\u89D2\u80F6\u56CA" },
        { value: "at", label: "@ \u524D\u7F00" },
        { value: "paren", label: "\u62EC\u53F7\u5305\u88F9" }
      ]
    },
    hideWhenSame: {
      type: "boolean",
      default: true,
      label: "\u6635\u79F0\u76F8\u540C\u65F6\u9690\u85CF",
      description: "\u6635\u79F0\u4E0E\u7528\u6237\u540D\u4E00\u81F4\u65F6\u4E0D\u91CD\u590D\u663E\u793A\u3002"
    },
    inReplies: {
      type: "boolean",
      default: false,
      label: "\u56DE\u590D\u9884\u89C8\u4E2D\u4E5F\u663E\u793A",
      description: "\u5728\u56DE\u590D\u5F15\u7528\u7684\u5C0F\u5B57\u6761\u4E2D\u4E5F\u9644\u52A0\u7528\u6237\u540D\u3002"
    }
  });
  function Username(props) {
    const { original } = props;
    const s = settings2.store;
    const user = original.userOverride ?? original.message?.author;
    const username = user?.username;
    const nick = original.author?.nick ?? user?.globalName ?? username ?? "";
    const prefix = original.withMentionPrefix ? "@" : "";
    try {
      if (!username) return /* @__PURE__ */ React.createElement(React.Fragment, null, prefix, nick);
      if (original.isRepliedMessage && !s.inReplies) return /* @__PURE__ */ React.createElement(React.Fragment, null, prefix, nick);
      if (s.hideWhenSame && username.toLowerCase() === nick.toLowerCase()) return /* @__PURE__ */ React.createElement(React.Fragment, null, prefix, nick);
      const suffixClass = `hc-username hc-username--${s.style || "muted"}`;
      const decorated = s.style === "at" ? `@${username}` : s.style === "paren" ? `\uFF08${username}\uFF09` : username;
      if (s.mode === "user-only") {
        return /* @__PURE__ */ React.createElement(React.Fragment, null, prefix, username);
      }
      if (s.mode === "user-nick") {
        return /* @__PURE__ */ React.createElement(React.Fragment, null, prefix, username, " ", /* @__PURE__ */ React.createElement("span", { className: suffixClass }, nick));
      }
      return /* @__PURE__ */ React.createElement(React.Fragment, null, prefix, nick, " ", /* @__PURE__ */ React.createElement("span", { className: suffixClass }, decorated));
    } catch (err) {
      log14.error("username render failed; falling back to the nick", err);
      return /* @__PURE__ */ React.createElement(React.Fragment, null, prefix, nick);
    }
  }
  var show_username_default = definePlugin({
    id: "show-username",
    name: "\u663E\u793A\u7528\u6237\u540D",
    description: "\u5728\u6635\u79F0\u65C1\u8FB9\u663E\u793A\u8D26\u53F7\u7528\u6237\u540D\uFF0C\u9632\u6B62\u6539\u540D\u5192\u5145\uFF0C\u652F\u6301\u591A\u79CD\u6837\u5F0F\u3002",
    authors: [{ name: "caitemm" }],
    category: "appearance",
    settings: settings2,
    patches: [
      {
        // The message-header module (find string survives minification). The
        // username hook renders `children: <ternary>`; capturing that whole
        // expression is fragile (nested commas/parens — a truncated capture
        // produced unbalanced code), so instead our render becomes the new
        // `children` and the original expression is parked, syntactically
        // intact, under a dummy `_hcOld` prop.
        label: "message header username",
        find: '="SYSTEM_TAG"',
        replacement: {
          match: /(?<=onContextMenu:[\w$]+,children:)([\w$]+)\?(?=.{0,100}?user[Nn]ame:)/,
          replace: "$self.renderUsername(arguments[0]),_hcOld:$1?"
        }
      }
    ],
    start() {
      log14.info("appending usernames to message headers");
    },
    stop() {
    },
    /** Called from the patch with the header component's props. */
    renderUsername(props) {
      try {
        return /* @__PURE__ */ React.createElement(Username, { original: props });
      } catch {
        return props?.author?.nick ?? null;
      }
    }
  });

  // src/plugins/guild-monitor/settings.ts
  var settings3 = defineSettings({
    // Toggled from the plugin page (with the full risk note), not the generic
    // form, so it's hidden here — but persisted through the store like any value.
    acknowledgedRisk: {
      type: "boolean",
      default: false,
      label: "\u6211\u5DF2\u4E86\u89E3\u5C01\u53F7\u98CE\u9669",
      description: "\u4E3B\u52A8\u8BA2\u9605\u9891\u9053\u5C5E\u4E8E\u81EA\u52A8\u5316\u884C\u4E3A\uFF0C\u53EF\u80FD\u8FDD\u53CD Discord \u670D\u52A1\u6761\u6B3E\u5E76\u5BFC\u81F4\u8D26\u53F7\u88AB\u5C01\u3002\u4EC5\u5728\u4F60\u5B8C\u5168\u7406\u89E3\u5E76\u81EA\u613F\u627F\u62C5\u98CE\u9669\u65F6\u5F00\u542F\u3002",
      hidden: true
    },
    selectedGuilds: {
      type: "string-list",
      default: [],
      label: "\u76D1\u63A7\u7684\u670D\u52A1\u5668",
      description: "\u6309\u670D\u52A1\u5668 ID \u76D1\u63A7\u3002\u5EFA\u8BAE\u4ECE\u4E0B\u65B9\u7684\u670D\u52A1\u5668\u5217\u8868\u52FE\u9009\uFF0C\u800C\u4E0D\u662F\u624B\u586B\u3002",
      itemPlaceholder: "\u670D\u52A1\u5668 ID",
      hidden: true
    }
  });

  // src/plugins/guild-monitor/subscribe.ts
  var log15 = logger("guild-monitor");
  var REFRESH_MS = 5 * 60 * 1e3;
  var timer2;
  var getGuildIds = () => [];
  function textChannelIds(guildId) {
    try {
      const grouped = GuildChannelStore.getChannels(guildId);
      if (!grouped || typeof grouped !== "object") return [];
      const ids = /* @__PURE__ */ new Set();
      for (const value of Object.values(grouped)) {
        if (!Array.isArray(value)) continue;
        for (const item of value) {
          const ch = item?.channel ?? item;
          const id = ch?.id;
          if (id != null && (ch?.type === 0 || ch?.type === 5)) ids.add(String(id));
        }
      }
      return [...ids];
    } catch (err) {
      log15.debug(`could not read channels for guild ${guildId}`, err);
      return [];
    }
  }
  function subscribeGuild(guildId) {
    const api = GuildSubscriptions;
    if (!api) return;
    try {
      if (typeof api.subscribeToChannel === "function") {
        for (const channelId of textChannelIds(guildId)) {
          api.subscribeToChannel(guildId, channelId);
        }
        return;
      }
      if (typeof api.subscribeToGuild === "function") {
        api.subscribeToGuild(guildId);
      }
    } catch (err) {
      log15.warn(`subscribe failed for guild ${guildId}`, err);
    }
  }
  function isSubscriptionSupported() {
    const api = GuildSubscriptions;
    return Boolean(api && (typeof api.subscribeToChannel === "function" || typeof api.subscribeToGuild === "function"));
  }
  function pass() {
    const ids = getGuildIds();
    if (!ids.length) return;
    for (const id of ids) subscribeGuild(id);
    log15.debug(`refreshed subscriptions for ${ids.length} guild(s)`);
  }
  function startSubscribing(resolver) {
    getGuildIds = resolver;
    stopSubscribing();
    if (!isSubscriptionSupported()) {
      log15.warn("this Discord build exposes no guild-subscription action; monitoring is inactive");
      return;
    }
    pass();
    timer2 = setInterval(pass, REFRESH_MS);
  }
  function refreshNow() {
    if (timer2) pass();
  }
  function stopSubscribing() {
    if (timer2) {
      clearInterval(timer2);
      timer2 = void 0;
    }
  }

  // src/plugins/guild-monitor/ui/MonitorPage.tsx
  function readGuilds() {
    try {
      const store = findStore("GuildStore") ?? GuildStore;
      const map = store?.getGuilds?.() ?? {};
      return Object.values(map).map((g2) => ({ id: String(g2?.id ?? ""), name: String(g2?.name ?? g2?.id ?? "\u672A\u77E5\u670D\u52A1\u5668") })).filter((g2) => g2.id).sort((a, b) => a.name.localeCompare(b.name, "zh-CN"));
    } catch {
      return [];
    }
  }
  function MonitorPage() {
    const [guilds, setGuilds] = useState(() => readGuilds());
    const [selected, setSelected] = useState(() => [...settings3.store.selectedGuilds]);
    const [acknowledged, setAcknowledged] = useState(() => settings3.store.acknowledgedRisk === true);
    const supported = isSubscriptionSupported();
    useEffect(() => {
      if (guilds.length === 0) {
        const t = setTimeout(() => setGuilds(readGuilds()), 400);
        return () => clearTimeout(t);
      }
    }, [guilds.length]);
    const persist = (ids) => {
      setSelected(ids);
      settings3.store.selectedGuilds = ids;
      refreshNow();
    };
    const toggleGuild = (id) => {
      persist(selected.includes(id) ? selected.filter((g2) => g2 !== id) : [...selected, id]);
    };
    const setAck = (on) => {
      setAcknowledged(on);
      settings3.store.acknowledgedRisk = on;
      if (!on) persist([]);
    };
    return /* @__PURE__ */ React.createElement("div", { className: "hc-stack" }, /* @__PURE__ */ React.createElement("div", { className: "hc-inline-note hc-inline-note--danger" }, /* @__PURE__ */ React.createElement(WarningIcon, { size: 18 }), /* @__PURE__ */ React.createElement("span", null, "\u4E3B\u52A8\u76D1\u63A7\u4F1A\u8BA2\u9605\u4F60\u5C1A\u672A\u6253\u5F00\u7684\u9891\u9053\uFF0C\u5C5E\u4E8E\u81EA\u52A8\u5316\u884C\u4E3A\uFF0C\u53EF\u80FD\u8FDD\u53CD Discord \u670D\u52A1\u6761\u6B3E\u5E76\u5BFC\u81F4", /* @__PURE__ */ React.createElement("b", null, "\u8D26\u53F7\u88AB\u5C01\u7981"), "\u3002\u8BF7\u81EA\u884C\u627F\u62C5\u98CE\u9669\u3002")), /* @__PURE__ */ React.createElement("div", { className: "hc-section" }, /* @__PURE__ */ React.createElement("div", { className: "hc-section__body" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell hc-cell--row" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__main" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__label" }, "\u542F\u7528\u4E3B\u52A8\u76D1\u63A7"), /* @__PURE__ */ React.createElement("div", { className: "hc-cell__desc" }, "\u5F00\u542F\u540E\u624D\u80FD\u52FE\u9009\u4E0B\u65B9\u7684\u670D\u52A1\u5668\u3002")), /* @__PURE__ */ React.createElement(Toggle, { checked: acknowledged, onChange: setAck, "aria-label": "\u542F\u7528\u4E3B\u52A8\u76D1\u63A7" })))), !supported && /* @__PURE__ */ React.createElement("div", { className: "hc-inline-note" }, /* @__PURE__ */ React.createElement(WarningIcon, { size: 18 }), /* @__PURE__ */ React.createElement("span", null, "\u5F53\u524D Discord \u7248\u672C\u672A\u66B4\u9732\u53EF\u7528\u7684\u8BA2\u9605\u63A5\u53E3\uFF0C\u76D1\u63A7\u6682\u65F6\u65E0\u6CD5\u751F\u6548\u3002")), /* @__PURE__ */ React.createElement("div", { className: "hc-section" }, /* @__PURE__ */ React.createElement("div", { className: "hc-section__title", style: { display: "flex", justifyContent: "space-between" } }, /* @__PURE__ */ React.createElement("span", null, "\u670D\u52A1\u5668\uFF08", guilds.length, "\uFF09"), /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-tab",
        onClick: () => setGuilds(readGuilds()),
        style: { height: 20, padding: "0 8px", textTransform: "none" }
      },
      /* @__PURE__ */ React.createElement(RefreshIcon, { size: 12 }),
      " \u5237\u65B0"
    )), guilds.length === 0 ? /* @__PURE__ */ React.createElement(
      EmptyState,
      {
        icon: /* @__PURE__ */ React.createElement(ServerIcon, { size: 48 }),
        title: "\u6CA1\u6709\u8BFB\u5230\u670D\u52A1\u5668",
        subtitle: "\u7B49 Discord \u52A0\u8F7D\u5B8C\u6210\u540E\u70B9\u4E0A\u9762\u7684\u5237\u65B0\uFF0C\u6216\u7A0D\u540E\u518D\u6765\u3002"
      }
    ) : /* @__PURE__ */ React.createElement("div", { className: "hc-section__body", style: { opacity: acknowledged ? 1 : 0.5, pointerEvents: acknowledged ? "auto" : "none" } }, guilds.map((g2) => /* @__PURE__ */ React.createElement("div", { className: "hc-cell hc-cell--row", key: g2.id }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__main" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__label" }, g2.name), /* @__PURE__ */ React.createElement("div", { className: "hc-cell__desc" }, g2.id)), /* @__PURE__ */ React.createElement(
      Toggle,
      {
        checked: selected.includes(g2.id),
        onChange: () => toggleGuild(g2.id),
        "aria-label": `\u76D1\u63A7 ${g2.name}`
      }
    ))))), selected.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "hc-savebar" }, /* @__PURE__ */ React.createElement("span", { className: "hc-savebar__label" }, "\u6B63\u5728\u76D1\u63A7 ", selected.length, " \u4E2A\u670D\u52A1\u5668"), /* @__PURE__ */ React.createElement("div", { className: "hc-savebar__actions" }, /* @__PURE__ */ React.createElement(Button, { size: "sm", variant: "destructive", onClick: () => persist([]) }, "\u5168\u90E8\u53D6\u6D88"))));
  }

  // src/plugins/guild-monitor/index.tsx
  var log16 = logger("guild-monitor");
  function activeGuildIds() {
    if (settings3.store.acknowledgedRisk !== true) return [];
    const ids = settings3.store.selectedGuilds;
    return Array.isArray(ids) ? ids : [];
  }
  var guild_monitor_default = definePlugin({
    id: "guild-monitor",
    name: "\u670D\u52A1\u5668\u76D1\u63A7",
    description: "\u4E3B\u52A8\u8BA2\u9605\u9009\u5B9A\u670D\u52A1\u5668\u7684\u9891\u9053\uFF0C\u6355\u6349\u672A\u6253\u5F00\u9891\u9053\u91CC\u7684\u6D88\u606F\uFF08\u6709\u5C01\u53F7\u98CE\u9669\uFF0C\u9ED8\u8BA4\u5173\u95ED\uFF09\u3002",
    authors: [{ name: "caitemm" }],
    category: "privacy",
    settings: settings3,
    page: {
      title: "\u76D1\u63A7",
      icon: BroadcastIcon,
      component: MonitorPage
    },
    start() {
      startSubscribing(activeGuildIds);
      const n = activeGuildIds().length;
      if (n > 0) log16.info(`monitoring ${n} guild(s)`);
    },
    stop() {
      stopSubscribing();
    }
  });

  // src/plugins/message-cleaner/settings.ts
  var settings4 = defineSettings({
    order: {
      group: "\u9ED8\u8BA4\u53C2\u6570",
      type: "select",
      default: "desc",
      label: "\u6E05\u7406\u65B9\u5411",
      description: "\u53D7\u6761\u6570\u9650\u5236\u65F6\uFF0C\u4F18\u5148\u4ECE\u54EA\u4E00\u7AEF\u5F00\u59CB\u5220\u3002",
      options: [
        { value: "desc", label: "\u4ECE\u65B0\u5230\u8001" },
        { value: "asc", label: "\u4ECE\u8001\u5230\u65B0" }
      ]
    },
    limit: {
      group: "\u9ED8\u8BA4\u53C2\u6570",
      type: "number",
      default: 100,
      label: "\u6700\u591A\u5904\u7406\u6761\u6570",
      description: "\u5355\u6B21\u9884\u89C8 / \u5220\u9664\u7684\u4E0A\u9650\u3002",
      min: 1,
      max: 5e3,
      step: 50
    },
    delayMs: {
      group: "\u9ED8\u8BA4\u53C2\u6570",
      type: "number",
      default: 1600,
      label: "\u5220\u9664\u95F4\u9694\uFF08\u6BEB\u79D2\uFF09",
      description: "\u4E24\u6B21\u5220\u9664\u4E4B\u95F4\u7684\u7B49\u5F85\uFF0C\u592A\u5FEB\u4F1A\u89E6\u53D1\u9650\u901F\uFF0C\u5EFA\u8BAE\u4E0D\u4F4E\u4E8E 1000\u3002",
      min: 300,
      max: 3e4,
      step: 100
    },
    confirmBeforeDelete: {
      group: "\u9ED8\u8BA4\u53C2\u6570",
      type: "boolean",
      default: true,
      label: "\u5220\u9664\u524D\u4E8C\u6B21\u786E\u8BA4",
      description: "\u70B9\u300C\u5220\u9664\u300D\u540E\u5F39\u51FA\u786E\u8BA4\u6846\uFF0C\u907F\u514D\u8BEF\u5220\u3002"
    }
  });

  // src/plugins/message-cleaner/cleaner.ts
  var log17 = logger("message-cleaner");
  var API_BASE = "https://discord.com/api/v10";
  var skipList = /* @__PURE__ */ new Set();
  var sleep = (ms) => new Promise((r) => setTimeout(r, ms));
  var EPOCH = 1420070400000n;
  var tsToSf = (d) => String(BigInt(d.getTime()) - EPOCH << 22n);
  function extractToken() {
    try {
      const chunks = window.webpackChunkdiscord_app;
      if (Array.isArray(chunks)) {
        let tok = null;
        chunks.push([[Symbol()], {}, (req) => {
          for (const id of Object.keys(req.m || {})) {
            try {
              for (const m of [req(id), req(id)?.default]) {
                if (m && typeof m.getToken === "function") {
                  const t = m.getToken();
                  if (t && t.length > 20) {
                    tok = t;
                    return;
                  }
                }
              }
            } catch {
            }
          }
        }]);
        if (tok) return tok;
      }
    } catch {
    }
    try {
      const t = window.localStorage.getItem("token");
      if (t) return t.replace(/^"|"$/g, "");
    } catch {
    }
    return null;
  }
  async function apiFetch(token, path, opts = {}, attempt = 0) {
    let res;
    try {
      res = await fetch(API_BASE + path, {
        ...opts,
        headers: {
          Authorization: token,
          "Content-Type": "application/json",
          ...opts.headers || {}
        }
      });
    } catch (e) {
      if (attempt < 5) {
        await sleep(3e3);
        return apiFetch(token, path, opts, attempt + 1);
      }
      throw new Error(`\u7F51\u7EDC\u8BF7\u6C42\u5931\u8D25: ${e.message}`);
    }
    if (res.status === 429) {
      const j = await res.json().catch(() => ({}));
      const wait = j.retry_after ? Math.ceil(Number(j.retry_after) * 1e3) : Math.pow(2, attempt) * 1e3;
      if (attempt < 5) {
        await sleep(wait + 500);
        return apiFetch(token, path, opts, attempt + 1);
      }
      throw new Error("\u89E6\u53D1\u9650\u901F\u4E14\u91CD\u8BD5\u6B21\u6570\u8017\u5C3D\u3002");
    }
    if (!res.ok) {
      const b = await res.text().catch(() => "");
      throw new Error(`API ${res.status}: ${b.slice(0, 120)}`);
    }
    return res.status === 204 ? null : res.json();
  }
  async function fetchUserId(token) {
    const me = await apiFetch(token, "/users/@me");
    if (!me?.id) throw new Error("\u65E0\u6CD5\u901A\u8FC7 Token \u83B7\u53D6\u8D26\u53F7\u4FE1\u606F\uFF0C\u8BF7\u68C0\u67E5 Token \u662F\u5426\u6709\u6548\u3002");
    return String(me.id);
  }
  function currentTarget() {
    try {
      const m = location.pathname.match(/\/channels\/(\d{15,25}|@me)\/(\d{15,25})/);
      if (!m) return null;
      return { guildId: m[1], channelId: m[2], serverWide: false };
    } catch {
      return null;
    }
  }
  async function fetchGuilds(token) {
    const guilds = await apiFetch(token, "/users/@me/guilds");
    if (!Array.isArray(guilds)) return [];
    return guilds.map((g2) => ({ id: String(g2.id), name: g2.name ?? "\u672A\u77E5", icon: g2.icon ?? null }));
  }
  async function fetchChannels(token, guildId) {
    if (guildId === "@me") {
      const dms = await apiFetch(token, "/users/@me/channels");
      if (!Array.isArray(dms)) return [];
      return dms.map((c) => {
        const name = c.name || (Array.isArray(c.recipients) ? c.recipients.map((r) => r.global_name || r.username).join("\u3001") : "") || "\u672A\u77E5\u79C1\u804A";
        return { id: String(c.id), name, type: c.type ?? 1 };
      });
    }
    const channels = await apiFetch(token, `/guilds/${guildId}/channels`);
    if (!Array.isArray(channels)) return [];
    return channels.filter((c) => c.type !== 4).map((c) => ({ id: String(c.id), name: c.name ?? "\u672A\u77E5", type: c.type ?? 0 }));
  }
  async function collect(token, opts, meId, onProgress, ctrl) {
    const out = [];
    if (opts.serverWide && opts.guildId && opts.guildId !== "@me") {
      let offset = 0;
      while (out.length < opts.limit) {
        if (ctrl.stopped) break;
        onProgress("\u5168\u670D\u68C0\u7D22\u4E2D", `\u5DF2\u627E\u5230 ${out.length} \u6761\uFF08\u641C\u7D22\u63A5\u53E3\u8F83\u6162\uFF0C\u8BF7\u7A0D\u5019\uFF09`);
        const params = new URLSearchParams({
          author_id: meId,
          offset: String(offset),
          include_nsfw: "true",
          sort_order: opts.order === "asc" ? "asc" : "desc"
        });
        if (opts.after) params.set("min_id", tsToSf(opts.after));
        if (opts.before) params.set("max_id", tsToSf(opts.before));
        let res;
        try {
          res = await apiFetch(token, `/guilds/${opts.guildId}/messages/search?${params}`);
        } catch (e) {
          throw new Error(`\u5168\u670D\u68C0\u7D22\u5931\u8D25\uFF1A${e.message}`);
        }
        if (res?.message === "Indexing") {
          onProgress("\u5EFA\u7ACB\u7D22\u5F15\u4E2D", "Discord \u6B63\u5728\u5EFA\u7ACB\u5168\u670D\u7D22\u5F15\uFF0C10 \u79D2\u540E\u81EA\u52A8\u91CD\u8BD5\u2026");
          await sleep(1e4);
          continue;
        }
        if (!res?.messages || res.messages.length === 0) break;
        for (const group of res.messages) {
          const m = group.find((x) => x?.hit) ?? group.find((x) => x?.author?.id === meId) ?? group[0];
          if (!m || m.author?.id !== meId || skipList.has(m.id)) continue;
          out.push({ id: m.id, channelId: m.channel_id, content: m.content ?? "", timestamp: m.timestamp });
          if (out.length >= opts.limit) break;
        }
        if (res.messages.length < 25) break;
        offset += res.messages.length;
        await sleep(1200);
      }
      return out;
    }
    if (!opts.channelId) throw new Error("\u8BF7\u586B\u5199\u9891\u9053 ID\uFF0C\u6216\u5F00\u542F\u300C\u5168\u670D\u626B\u63CF\u300D\u5E76\u586B\u5199\u670D\u52A1\u5668 ID\u3002");
    let boundary = null;
    if (opts.order === "desc") {
      boundary = opts.before ? tsToSf(opts.before) : null;
    } else {
      boundary = opts.after ? tsToSf(opts.after) : "0";
    }
    while (out.length < opts.limit) {
      if (ctrl.stopped) break;
      const params = new URLSearchParams({ limit: "100" });
      if (boundary) params.set(opts.order === "desc" ? "before" : "after", boundary);
      let batch;
      try {
        batch = await apiFetch(token, `/channels/${opts.channelId}/messages?${params}`);
      } catch (e) {
        throw new Error(`\u8BFB\u53D6\u9891\u9053\u6D88\u606F\u5931\u8D25\uFF1A${e.message}`);
      }
      if (!Array.isArray(batch) || batch.length === 0) break;
      for (const m of batch) {
        const t = new Date(m.timestamp);
        if (opts.order === "desc" && opts.after && t < opts.after) {
          return out;
        }
        if (opts.order === "asc" && opts.before && t > opts.before) {
          return out;
        }
        const inRange = (!opts.after || t >= opts.after) && (!opts.before || t <= opts.before);
        if (m.author?.id === meId && inRange && !skipList.has(m.id)) {
          out.push({ id: m.id, channelId: m.channel_id ?? opts.channelId, content: m.content ?? "", timestamp: m.timestamp });
          if (out.length >= opts.limit) break;
        }
      }
      boundary = batch[batch.length - 1].id;
      onProgress("\u626B\u63CF\u4E2D", `\u5DF2\u627E\u5230 ${out.length} \u6761`);
      await sleep(150);
    }
    return out;
  }
  async function remove(token, messages, opts, onProgress, ctrl) {
    let deleted = 0;
    let skipped = 0;
    for (const m of messages) {
      if (ctrl.stopped) break;
      const t0 = Date.now();
      try {
        await apiFetch(token, `/channels/${m.channelId || opts.channelId}/messages/${m.id}`, { method: "DELETE" });
        deleted++;
      } catch (e) {
        skipped++;
        if (!String(e?.message ?? "").includes("404")) skipList.add(m.id);
        log17.warn(`skip ${m.id}: ${e?.message ?? e}`);
      }
      onProgress("\u5220\u9664\u4E2D", `\u5DF2\u5220\u9664 ${deleted} / ${messages.length}${skipped ? `\uFF08\u8DF3\u8FC7 ${skipped}\uFF09` : ""}`);
      const elapsed = Date.now() - t0;
      if (elapsed < opts.delayMs) await sleep(opts.delayMs - elapsed);
    }
    return { deleted, skipped };
  }
  async function count(token, target, meId) {
    let url;
    const params = new URLSearchParams({ author_id: meId, include_nsfw: "true" });
    if (target.serverWide && target.guildId && target.guildId !== "@me") {
      url = `/guilds/${target.guildId}/messages/search?${params}`;
    } else if (target.channelId) {
      url = `/channels/${target.channelId}/messages/search?${params}`;
    } else if (target.guildId && target.guildId !== "@me") {
      url = `/guilds/${target.guildId}/messages/search?${params}`;
    } else {
      throw new Error("\u8BF7\u586B\u5199\u670D\u52A1\u5668 ID \u6216\u9891\u9053 ID\u3002");
    }
    const res = await apiFetch(token, url);
    if (res?.message === "Indexing") return { total: 0, indexing: true };
    return { total: res?.total_results ?? 0, indexing: false };
  }

  // src/plugins/message-cleaner/ui/CleanerPage.tsx
  var log18 = logger("message-cleaner");
  function formatTs(ts) {
    const date = new Date(ts);
    if (Number.isNaN(date.getTime())) return "";
    const pad = (n) => String(n).padStart(2, "0");
    return `${pad(date.getMonth() + 1)}-${pad(date.getDate())} ${pad(date.getHours())}:${pad(date.getMinutes())}`;
  }
  function CleanerPage() {
    const [token, setToken] = useState("");
    const [guildId, setGuildId] = useState("");
    const [channelId, setChannelId] = useState("");
    const [serverWide, setServerWide] = useState(false);
    const [afterStr, setAfterStr] = useState("");
    const [beforeStr, setBeforeStr] = useState("");
    const [order, setOrder] = useState(settings4.store.order);
    const [disclaimer, setDisclaimer] = useState(false);
    const [mode, setMode] = useState("idle");
    const [previewed, setPreviewed] = useState([]);
    const [state, setState] = useState("\u5F85\u673A");
    const [detail, setDetail] = useState("\u5148\u83B7\u53D6 Token\uFF0C\u9009\u597D\u8303\u56F4\u5E76\u9884\u89C8\uFF0C\u786E\u8BA4\u540E\u518D\u5220\u9664\u3002");
    const [statCount, setStatCount] = useState(null);
    const [pickerOpen, setPickerOpen] = useState(false);
    const [pickerGuilds, setPickerGuilds] = useState([]);
    const [pickerChannels, setPickerChannels] = useState([]);
    const [pickerLevel, setPickerLevel] = useState("guilds");
    const [pickerGuildName, setPickerGuildName] = useState("");
    const [pickerLoading, setPickerLoading] = useState(false);
    const [pickerError, setPickerError] = useState("");
    const ctrlRef = useRef({ stopped: false });
    const running = mode !== "idle";
    useEffect(() => {
      const tok = extractToken();
      if (tok) {
        setToken(tok);
        setState("\u5DF2\u83B7\u53D6 Token");
        setDetail("\u53EF\u70B9\u51FB\u300C\u5217\u8868\u300D\u9009\u62E9\u9891\u9053\uFF0C\u6216\u624B\u52A8\u586B\u5199 ID\u3002");
      }
    }, []);
    const progress = (s, d) => {
      setState(s);
      setDetail(d);
    };
    const requireToken = () => {
      const t = token.trim();
      if (!t) throw new Error("\u8BF7\u5148\u83B7\u53D6\u6216\u586B\u5165 Token\u3002");
      return t;
    };
    const buildOptions = () => ({
      guildId: guildId.trim(),
      channelId: serverWide ? "" : channelId.trim(),
      serverWide,
      order,
      limit: settings4.store.limit,
      delayMs: settings4.store.delayMs,
      after: afterStr ? new Date(afterStr) : null,
      before: beforeStr ? new Date(beforeStr) : null
    });
    const onAutoToken = () => {
      const tok = extractToken();
      if (tok) {
        setToken(tok);
        progress("Token \u5DF2\u83B7\u53D6", "\u53EF\u70B9\u51FB\u300C\u5217\u8868\u300D\u9009\u62E9\u9891\u9053\u3002");
      } else progress("\u83B7\u53D6\u5931\u8D25", "\u8BF7\u624B\u52A8\u7C98\u8D34 Token\u3002");
    };
    const useCurrent = () => {
      const target = currentTarget();
      if (!target) {
        progress("\u65E0\u6CD5\u8BFB\u53D6", "\u5F53\u524D\u4E0D\u5728\u67D0\u4E2A\u9891\u9053/\u79C1\u4FE1\u9875\u9762\u3002");
        return;
      }
      setGuildId(target.guildId);
      setChannelId(target.channelId);
      setServerWide(false);
      progress("\u5DF2\u586B\u5165\u5F53\u524D\u9891\u9053", `\u670D\u52A1\u5668 ${target.guildId} \xB7 \u9891\u9053 ${target.channelId}`);
    };
    const openPicker = async () => {
      let tok;
      try {
        tok = requireToken();
      } catch (e) {
        progress("\u9700\u8981 Token", e.message);
        return;
      }
      setPickerOpen(true);
      setPickerLevel("guilds");
      setPickerChannels([]);
      setPickerError("");
      setPickerLoading(true);
      try {
        const guilds = await fetchGuilds(tok);
        setPickerGuilds([{ id: "@me", name: "\u79C1\u4FE1\u4E0E\u7FA4\u804A (DMs)", icon: null }, ...guilds]);
      } catch (e) {
        setPickerError(e.message ?? String(e));
      } finally {
        setPickerLoading(false);
      }
    };
    const pickGuild = async (g2) => {
      let tok;
      try {
        tok = requireToken();
      } catch (e) {
        progress("\u9700\u8981 Token", e.message);
        return;
      }
      setPickerGuildName(g2.name);
      setPickerLevel("channels");
      setPickerError("");
      setPickerLoading(true);
      try {
        const channels = await fetchChannels(tok, g2.id);
        const withServerWide = g2.id === "@me" ? channels : [{ id: "", name: "\u2500\u2500 \u5168\u670D\u626B\u63CF\uFF08\u4E0D\u9650\u9891\u9053\uFF09\u2500\u2500", type: -1 }, ...channels];
        setPickerChannels(withServerWide);
      } catch (e) {
        setPickerError(e.message ?? String(e));
      } finally {
        setPickerLoading(false);
      }
    };
    const pickChannel = (ch) => {
      if (!ch.id) {
        setServerWide(true);
        setChannelId("");
      } else {
        setServerWide(false);
        setChannelId(ch.id);
      }
      setPickerOpen(false);
      progress("\u5DF2\u9009\u62E9", `${pickerGuildName} \u2192 ${ch.name || "\u5168\u670D"}`);
    };
    const syncNow = () => {
      const now = /* @__PURE__ */ new Date();
      now.setMinutes(now.getMinutes() - now.getTimezoneOffset());
      setBeforeStr(now.toISOString().slice(0, 16));
    };
    const onPreview = async () => {
      let tok;
      try {
        tok = requireToken();
      } catch (e) {
        progress("\u5931\u8D25", e.message);
        return;
      }
      let meId;
      try {
        meId = await fetchUserId(tok);
      } catch (e) {
        progress("\u5931\u8D25", e.message);
        return;
      }
      const opts = buildOptions();
      if (opts.serverWide && (!opts.guildId || opts.guildId === "@me")) {
        progress("\u5931\u8D25", "\u5168\u670D\u626B\u63CF\u9700\u8981\u586B\u5199\u670D\u52A1\u5668 ID\u3002");
        return;
      }
      if (!opts.serverWide && !opts.channelId) {
        progress("\u5931\u8D25", "\u8BF7\u586B\u5199\u9891\u9053 ID\uFF0C\u6216\u6539\u7528\u5168\u670D\u626B\u63CF\u3002");
        return;
      }
      if (opts.after && opts.before && opts.after >= opts.before) {
        progress("\u5931\u8D25", "\u8D77\u59CB\u65F6\u95F4\u5FC5\u987B\u65E9\u4E8E\u7ED3\u675F\u65F6\u95F4\u3002");
        return;
      }
      ctrlRef.current = { stopped: false };
      setMode("previewing");
      setPreviewed([]);
      progress("\u9884\u89C8\u4E2D", "\u6B63\u5728\u626B\u63CF\u4F60\u7684\u6D88\u606F\u2026");
      try {
        const found = await collect(tok, opts, meId, progress, ctrlRef.current);
        setPreviewed(found);
        progress(ctrlRef.current.stopped ? "\u5DF2\u505C\u6B62" : "\u9884\u89C8\u5B8C\u6210", `\u627E\u5230 ${found.length} \u6761\u4F60\u7684\u6D88\u606F\u3002`);
      } catch (err) {
        progress("\u5931\u8D25", err.message ?? String(err));
        log18.error("preview failed", err);
      } finally {
        setMode("idle");
      }
    };
    const onDelete = async () => {
      if (previewed.length === 0) {
        progress("\u8BF7\u5148\u9884\u89C8", "");
        return;
      }
      if (settings4.store.confirmBeforeDelete) {
        const ok = window.confirm(`\u5C06\u5220\u9664 ${previewed.length} \u6761\u6D88\u606F\uFF0C\u5220\u9664\u4E0D\u53EF\u6062\u590D\uFF0C\u786E\u8BA4\u7EE7\u7EED\uFF1F`);
        if (!ok) return;
      }
      let tok;
      try {
        tok = requireToken();
      } catch (e) {
        progress("\u5931\u8D25", e.message);
        return;
      }
      const opts = buildOptions();
      ctrlRef.current = { stopped: false };
      setMode("deleting");
      progress("\u5220\u9664\u4E2D", `0 / ${previewed.length}`);
      try {
        const result = await remove(tok, previewed, opts, progress, ctrlRef.current);
        progress(
          ctrlRef.current.stopped ? "\u5DF2\u505C\u6B62" : "\u5B8C\u6210",
          `\u5DF2\u5220\u9664 ${result.deleted} \u6761${result.skipped ? `\uFF0C\u8DF3\u8FC7 ${result.skipped} \u6761` : ""}\u3002`
        );
        setPreviewed([]);
      } catch (err) {
        progress("\u5931\u8D25", err.message ?? String(err));
        log18.error("delete failed", err);
      } finally {
        setMode("idle");
      }
    };
    const onStop = () => {
      ctrlRef.current.stopped = true;
      progress("\u505C\u6B62\u4E2D", "\u7B49\u5F85\u5F53\u524D\u8BF7\u6C42\u7ED3\u675F\u2026");
    };
    const onCount = async () => {
      let tok;
      try {
        tok = requireToken();
      } catch (e) {
        progress("\u5931\u8D25", e.message);
        return;
      }
      let meId;
      try {
        meId = await fetchUserId(tok);
      } catch (e) {
        progress("\u5931\u8D25", e.message);
        return;
      }
      const target = { guildId: guildId.trim(), channelId: serverWide ? "" : channelId.trim(), serverWide };
      setStatCount(null);
      progress("\u7EDF\u8BA1\u4E2D", "\u8C03\u7528\u641C\u7D22\u63A5\u53E3\u2026");
      try {
        const result = await count(tok, target, meId);
        if (result.indexing) {
          progress("\u5EFA\u7ACB\u7D22\u5F15\u4E2D", "Discord \u6B63\u5728\u5EFA\u7ACB\u7D22\u5F15\uFF0C\u7A0D\u540E\u518D\u8BD5\u3002");
          return;
        }
        setStatCount(result.total);
        progress("\u7EDF\u8BA1\u5B8C\u6210", `\u5171 ${result.total} \u6761\u53D1\u8A00\u3002`);
      } catch (err) {
        progress("\u5931\u8D25", err.message ?? String(err));
      }
    };
    if (pickerOpen) {
      return /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__picker-head" }, pickerLevel === "channels" && /* @__PURE__ */ React.createElement(Button, { size: "sm", variant: "plain", onClick: () => setPickerLevel("guilds") }, "\u2190 \u8FD4\u56DE"), /* @__PURE__ */ React.createElement("span", { className: "hc-cleaner__picker-title" }, pickerLevel === "guilds" ? "\u9009\u62E9\u670D\u52A1\u5668" : pickerGuildName), /* @__PURE__ */ React.createElement(Button, { size: "sm", variant: "plain", onClick: () => setPickerOpen(false) }, "\u2715")), /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__picker-list" }, pickerLoading ? /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__picker-empty" }, "\u6B63\u5728\u52A0\u8F7D\u2026") : pickerError ? /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__picker-empty hc-cleaner__picker-empty--error" }, "\u52A0\u8F7D\u5931\u8D25\uFF1A", pickerError) : pickerLevel === "guilds" ? pickerGuilds.map((g2) => /* @__PURE__ */ React.createElement(
        "div",
        {
          key: g2.id,
          className: "hc-cleaner__picker-item",
          onClick: () => pickGuild(g2),
          role: "button",
          tabIndex: 0,
          onKeyDown: (e) => {
            if (e.key === "Enter") pickGuild(g2);
          }
        },
        /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__picker-icon" }, g2.icon ? /* @__PURE__ */ React.createElement("img", { src: `https://cdn.discordapp.com/icons/${g2.id}/${g2.icon}.png?size=64`, alt: "" }) : g2.name.charAt(0)),
        /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__picker-name" }, g2.name)
      )) : pickerChannels.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__picker-empty" }, "\u6B64\u670D\u52A1\u5668\u6682\u65E0\u9891\u9053\uFF0C\u53EF\u624B\u52A8\u586B\u5199\u9891\u9053 ID\u3002") : pickerChannels.map((ch) => /* @__PURE__ */ React.createElement(
        "div",
        {
          key: ch.id || "server-wide",
          className: "hc-cleaner__picker-item",
          onClick: () => pickChannel(ch),
          role: "button",
          tabIndex: 0,
          onKeyDown: (e) => {
            if (e.key === "Enter") pickChannel(ch);
          }
        },
        /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__picker-icon" }, ch.id ? "#" : "\u{1F310}"),
        /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__picker-name" }, ch.name)
      ))));
    }
    return /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner" }, /* @__PURE__ */ React.createElement("div", { className: "hc-inline-note hc-inline-note--danger" }, /* @__PURE__ */ React.createElement(WarningIcon, { size: 18 }), /* @__PURE__ */ React.createElement("span", null, "\u5220\u9664\u4E0D\u53EF\u6062\u590D\uFF0C\u4E14\u53EA\u4F1A\u5220\u9664", /* @__PURE__ */ React.createElement("strong", null, "\u4F60\u81EA\u5DF1"), "\u53D1\u9001\u7684\u6D88\u606F\u3002\u8BF7\u52A1\u5FC5\u5148\u9884\u89C8\u786E\u8BA4\u3002")), /* @__PURE__ */ React.createElement(Section, { title: "Token" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell--row" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__main" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__label" }, "Discord Token"), /* @__PURE__ */ React.createElement("div", { className: "hc-cell__desc" }, "\u4EE3\u8868\u4F60\u7684\u8D26\u53F7\u6743\u9650\uFF0C\u4E0D\u8981\u6CC4\u9732\u7ED9\u4EFB\u4F55\u4EBA\u3002")), /* @__PURE__ */ React.createElement(Button, { size: "sm", variant: "secondary", icon: /* @__PURE__ */ React.createElement(RefreshIcon, { size: 16 }), onClick: onAutoToken }, "\u81EA\u52A8")), /* @__PURE__ */ React.createElement("div", { className: "hc-cell__control" }, /* @__PURE__ */ React.createElement(TextInput, { value: token, onChange: setToken, placeholder: "\u81EA\u52A8\u586B\u5165\u6216\u624B\u52A8\u7C98\u8D34", type: "password" })))), /* @__PURE__ */ React.createElement(Section, { title: "\u8303\u56F4" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell hc-cell--row" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__main" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__label" }, "\u5168\u670D\u626B\u63CF"), /* @__PURE__ */ React.createElement("div", { className: "hc-cell__desc" }, "\u5FFD\u7565\u9891\u9053\uFF0C\u626B\u63CF\u6574\u4E2A\u670D\u52A1\u5668\uFF08\u8D70\u641C\u7D22\u63A5\u53E3\uFF0C\u8F83\u6162\uFF09\u3002")), /* @__PURE__ */ React.createElement(Toggle, { checked: serverWide, onChange: setServerWide, "aria-label": "\u5168\u670D\u626B\u63CF" })), /* @__PURE__ */ React.createElement("div", { className: "hc-cell" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell--row" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__main" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__label" }, "\u670D\u52A1\u5668 ID"))), /* @__PURE__ */ React.createElement("div", { className: "hc-cell__control" }, /* @__PURE__ */ React.createElement(TextInput, { value: guildId, onChange: setGuildId, placeholder: "\u670D\u52A1\u5668 ID" }))), !serverWide && /* @__PURE__ */ React.createElement("div", { className: "hc-cell" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell--row" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__main" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__label" }, "\u9891\u9053 ID"))), /* @__PURE__ */ React.createElement("div", { className: "hc-cell__control" }, /* @__PURE__ */ React.createElement(TextInput, { value: channelId, onChange: setChannelId, placeholder: "\u9891\u9053 ID" }))), /* @__PURE__ */ React.createElement("div", { className: "hc-cell hc-cell--row", style: { gap: "var(--hc-space-2)" } }, /* @__PURE__ */ React.createElement(Button, { size: "sm", variant: "secondary", icon: /* @__PURE__ */ React.createElement(ServerIcon, { size: 16 }), onClick: openPicker, disabled: running }, "\u5217\u8868"), /* @__PURE__ */ React.createElement(Button, { size: "sm", variant: "secondary", icon: /* @__PURE__ */ React.createElement(ListIcon, { size: 16 }), onClick: useCurrent, disabled: running }, "\u5F53\u524D"))), /* @__PURE__ */ React.createElement(Section, { title: "\u65F6\u95F4\u8303\u56F4", note: "\u53EF\u9009\u3002\u7559\u7A7A\u8868\u793A\u4E0D\u9650\u5236\u8BE5\u65B9\u5411\u3002" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell--row" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__main" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__label" }, "\u8D77\u59CB\u65F6\u95F4"))), /* @__PURE__ */ React.createElement("div", { className: "hc-cell__control" }, /* @__PURE__ */ React.createElement("input", { className: "hc-input", type: "datetime-local", value: afterStr, onChange: (e) => setAfterStr(e.currentTarget.value) }))), /* @__PURE__ */ React.createElement("div", { className: "hc-cell" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell--row" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__main" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__label" }, "\u7ED3\u675F\u65F6\u95F4")), /* @__PURE__ */ React.createElement(Button, { size: "sm", variant: "plain", onClick: syncNow }, "\u540C\u6B65\u6700\u65B0")), /* @__PURE__ */ React.createElement("div", { className: "hc-cell__control" }, /* @__PURE__ */ React.createElement("input", { className: "hc-input", type: "datetime-local", value: beforeStr, onChange: (e) => setBeforeStr(e.currentTarget.value) })))), /* @__PURE__ */ React.createElement(Section, { title: "\u65B9\u5411" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell hc-cell--row" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__main" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__label" }, "\u6E05\u7406\u65B9\u5411")), /* @__PURE__ */ React.createElement(
      Select,
      {
        value: order,
        onChange: setOrder,
        options: [
          { value: "desc", label: "\u4ECE\u65B0\u5230\u8001" },
          { value: "asc", label: "\u4ECE\u8001\u5230\u65B0" }
        ]
      }
    ))), /* @__PURE__ */ React.createElement(Section, { title: "\u786E\u8BA4", note: "\u5220\u9664\u662F\u4E0D\u53EF\u9006\u64CD\u4F5C\uFF0C\u8BF7\u5148\u9884\u89C8\u518D\u5220\u9664\u3002" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell hc-cell--row" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__main" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell__label" }, "\u6211\u786E\u8BA4\u53EA\u5220\u9664\u81EA\u5DF1\u7684\u6D88\u606F\uFF0C\u4E14\u660E\u767D\u4E0D\u53EF\u6062\u590D")), /* @__PURE__ */ React.createElement(Toggle, { checked: disclaimer, onChange: setDisclaimer, "aria-label": "\u786E\u8BA4" }))), /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__actions" }, mode === "previewing" ? /* @__PURE__ */ React.createElement(Button, { variant: "destructive", onClick: onStop }, "\u505C\u6B62\u9884\u89C8") : /* @__PURE__ */ React.createElement(Button, { variant: "primary", icon: /* @__PURE__ */ React.createElement(SearchIcon, { size: 16 }), disabled: running, onClick: onPreview }, "\u9884\u89C8"), mode === "deleting" ? /* @__PURE__ */ React.createElement(Button, { variant: "destructive", onClick: onStop }, "\u505C\u6B62\u5220\u9664") : /* @__PURE__ */ React.createElement(
      Button,
      {
        variant: "destructive",
        icon: /* @__PURE__ */ React.createElement(TrashIcon, { size: 16 }),
        disabled: running || !disclaimer || previewed.length === 0,
        onClick: onDelete
      },
      "\u5220\u9664\u9884\u89C8\uFF08",
      previewed.length,
      "\uFF09"
    )), /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__status" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__status-state" }, state), detail && /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__status-detail" }, detail)), previewed.length > 0 && /* @__PURE__ */ React.createElement(Section, { title: `\u9884\u89C8\u7ED3\u679C\uFF08${previewed.length}\uFF09` }, /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__list" }, previewed.slice(0, 50).map((m) => /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__item", key: m.id }, /* @__PURE__ */ React.createElement("span", { className: "hc-cleaner__item-time" }, formatTs(m.timestamp)), /* @__PURE__ */ React.createElement("span", { className: "hc-cleaner__item-text" }, m.content.trim() || "\uFF08\u65E0\u6587\u672C\u5185\u5BB9\uFF09"))), previewed.length > 50 && /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__more" }, "\u2026\u8FD8\u6709 ", previewed.length - 50, " \u6761\u672A\u5C55\u793A"))), /* @__PURE__ */ React.createElement(Section, { title: "\u7EDF\u8BA1", note: "\u7EDF\u8BA1\u4F60\u5728\u6240\u9009\u8303\u56F4\u5185\u7684\u5386\u53F2\u53D1\u8A00\u603B\u6570\uFF08\u8C03\u7528\u641C\u7D22\u63A5\u53E3\uFF09\u3002" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell" }, /* @__PURE__ */ React.createElement(Button, { size: "sm", variant: "secondary", icon: /* @__PURE__ */ React.createElement(SearchIcon, { size: 16 }), disabled: running, onClick: onCount }, "\u7EDF\u8BA1\u6211\u7684\u53D1\u8A00\u6570")), statCount != null && /* @__PURE__ */ React.createElement("div", { className: "hc-cell hc-cleaner__stat" }, /* @__PURE__ */ React.createElement("span", { className: "hc-cleaner__stat-num" }, statCount), /* @__PURE__ */ React.createElement("span", { className: "hc-cleaner__stat-unit" }, "\u6761"))));
  }

  // src/plugins/message-cleaner/index.tsx
  var log19 = logger("message-cleaner");
  var message_cleaner_default = definePlugin({
    id: "message-cleaner",
    name: "\u6D88\u606F\u6E05\u7406",
    description: "\u6279\u91CF\u5220\u9664\u4F60\u81EA\u5DF1\u5728\u67D0\u4E2A\u9891\u9053\u6216\u6574\u4E2A\u670D\u52A1\u5668\u7684\u5386\u53F2\u6D88\u606F\uFF08\u81EA\u52A9\u51B2\u6C34\u673A\uFF09\u3002\u5148\u9884\u89C8\u518D\u5220\u9664\uFF0C\u4EC5\u9650\u672C\u4EBA\u6D88\u606F\uFF0C\u5220\u9664\u4E0D\u53EF\u6062\u590D\u3002",
    authors: [{ name: "caitemm" }, { name: "catie" }],
    category: "privacy",
    settings: settings4,
    page: {
      title: "\u6E05\u7406",
      icon: TrashIcon,
      component: CleanerPage
    },
    start() {
      log19.info("message-cleaner ready");
    },
    stop() {
    }
  });

  // src/plugins/fake-nitro/index.ts
  var log20 = logger("fake-nitro");
  var settings5 = defineSettings({
    enableEmojiBypass: {
      group: "\u8868\u60C5",
      type: "boolean",
      default: true,
      label: "\u7ED5\u8FC7\u8868\u60C5\u9650\u5236",
      description: "\u53D1\u9001\u4F60\u6CA1\u6709 Nitro \u6743\u9650\u7684\u81EA\u5B9A\u4E49\u8868\u60C5\uFF08\u8DE8\u670D / \u52A8\u6001\u8868\u60C5\uFF09\u65F6\uFF0C\u81EA\u52A8\u6539\u4E3A\u53D1\u9001\u8BE5\u8868\u60C5\u7684\u56FE\u7247\u94FE\u63A5\u3002"
    },
    emojiSize: {
      group: "\u8868\u60C5",
      type: "select",
      default: "48",
      label: "\u8868\u60C5\u56FE\u7247\u5C3A\u5BF8",
      description: "\u5185\u8054\u8868\u60C5\u56FE\u7247\u7684\u8FB9\u957F\uFF08\u50CF\u7D20\uFF09\u3002\u8D8A\u5927\u8D8A\u6E05\u6670\u3001\u5360\u7528\u8D8A\u5927\u3002",
      options: [
        { value: "32", label: "32" },
        { value: "48", label: "48\uFF08\u9ED8\u8BA4\uFF09" },
        { value: "64", label: "64" },
        { value: "128", label: "128" },
        { value: "256", label: "256" },
        { value: "512", label: "512" }
      ]
    },
    enableStickerBypass: {
      group: "\u8D34\u7EB8",
      type: "boolean",
      default: true,
      label: "\u7ED5\u8FC7\u8D34\u7EB8\u9650\u5236",
      description: "\u53D1\u9001\u9501\u5B9A\u7684\u8D34\u7EB8\u65F6\u6539\u4E3A\u53D1\u9001\u8D34\u7EB8\u56FE\u7247\u94FE\u63A5\u3002Lottie\uFF08\u77E2\u91CF\uFF09\u8D34\u7EB8\u65E0\u6CD5\u5185\u8054\uFF0C\u4F1A\u8DF3\u8FC7\u3002"
    },
    stickerSize: {
      group: "\u8D34\u7EB8",
      type: "select",
      default: "160",
      label: "\u8D34\u7EB8\u56FE\u7247\u5C3A\u5BF8",
      description: "\u5185\u8054\u8D34\u7EB8\u56FE\u7247\u7684\u8FB9\u957F\uFF08\u50CF\u7D20\uFF09\u3002",
      options: [
        { value: "32", label: "32" },
        { value: "64", label: "64" },
        { value: "128", label: "128" },
        { value: "160", label: "160\uFF08\u9ED8\u8BA4\uFF09" },
        { value: "256", label: "256" },
        { value: "512", label: "512" }
      ]
    },
    enableStreamQualityBypass: {
      group: "\u76F4\u64AD",
      type: "boolean",
      default: true,
      label: "\u89E3\u9501\u76F4\u64AD\u753B\u8D28",
      description: "\u5141\u8BB8\u4EE5 Nitro \u753B\u8D28\u8FDB\u884C\u5C4F\u5E55\u5171\u4EAB\u76F4\u64AD\uFF08\u9700\u91CD\u542F\u5BA2\u6237\u7AEF\u751F\u6548\uFF0C\u56E0\u4E3A\u8FD9\u662F\u6E90\u7801\u7EA7 patch\uFF09\u3002"
    }
  });
  var EmojiStore2 = lazy((m) => m?.getName?.() === "EmojiStore");
  var StickersStore2 = lazy((m) => m?.getName?.() === "StickersStore");
  var GuildMemberStore = lazy((m) => m?.getName?.() === "GuildMemberStore");
  var PermissionStore2 = lazy((m) => m?.getName?.() === "PermissionStore" && typeof m?.can === "function");
  var PERM = {
    USE_EXTERNAL_EMOJIS: 1n << 18n,
    USE_EXTERNAL_STICKERS: 1n << 37n,
    EMBED_LINKS: 1n << 14n
  };
  var STICKER_LOTTIE = 3;
  var STICKER_GIF = 4;
  var INTENT_CHAT = 3;
  var INTENT_STICKER_EMOJI = 4;
  function currentPremiumType() {
    try {
      return UserStore.getCurrentUser?.()?.premiumType ?? 0;
    } catch {
      return 0;
    }
  }
  var canUseEmotesNatively = () => currentPremiumType() > 0;
  var canUseStickersNatively = () => currentPremiumType() > 1;
  function hasPermission(channelId, bit) {
    try {
      const channel = ChannelStore.getChannel?.(channelId);
      if (!channel || channel.isPrivate?.()) return true;
      return PermissionStore2.can?.(bit, channel) ?? true;
    } catch {
      return true;
    }
  }
  function guildIdOfChannel(channelId) {
    try {
      const channel = ChannelStore.getChannel?.(channelId);
      return channel?.guild_id ?? channel?.getGuildId?.() ?? void 0;
    } catch {
      return void 0;
    }
  }
  function canUseEmote(emoji, channelId, guildId) {
    if (emoji?.type === 0) return true;
    if (emoji?.available === false) return false;
    let usableManaged = false;
    if (emoji?.managed && emoji?.guildId) {
      const myRoles = GuildMemberStore.getSelfMember?.(emoji.guildId)?.roles ?? [];
      usableManaged = Array.isArray(emoji?.roles) && emoji.roles.some((r) => myRoles.includes(r));
    }
    if (canUseEmotesNatively() || usableManaged) {
      return emoji.guildId === guildId || hasPermission(channelId, PERM.USE_EXTERNAL_EMOJIS);
    }
    return !emoji?.animated && emoji?.guildId === guildId;
  }
  function emojiUrl(emoji) {
    const size = Number(settings5.store.emojiSize) || 48;
    const ext = emoji?.animated ? "gif" : "webp";
    const url = new URL(`https://cdn.discordapp.com/emojis/${emoji.id}.${ext}`);
    url.searchParams.set("size", String(size));
    return url.toString();
  }
  function stickerUrl(sticker) {
    const size = Number(settings5.store.stickerSize) || 160;
    const ext = sticker?.format_type === STICKER_GIF ? "gif" : "png";
    const url = new URL(`https://media.discordapp.net/stickers/${sticker.id}.${ext}`);
    url.searchParams.set("size", String(size));
    if (sticker?.name) url.searchParams.set("name", String(sticker.name));
    return url.toString();
  }
  function wordBoundary(str, offset) {
    return !str[offset] || /\s/.test(str[offset]) ? "" : " ";
  }
  function escapeRegExp(s) {
    return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  }
  function findMessageArg(args) {
    const m = args[1];
    if (m && typeof m === "object" && typeof m.content === "string") return m;
    return args.find((a) => a && typeof a === "object" && typeof a.content === "string");
  }
  function findOptionsArg(args) {
    for (let i = 2; i < args.length; i++) {
      const a = args[i];
      if (a && typeof a === "object" && "stickerIds" in a) return a;
    }
    return args[3] && typeof args[3] === "object" ? args[3] : void 0;
  }
  function rewriteStickers(channelId, message, options, guildId) {
    if (!settings5.store.enableStickerBypass) return false;
    const ids = options?.stickerIds;
    if (!Array.isArray(ids) || ids.length === 0) return false;
    const sticker = StickersStore2.getStickerById?.(ids[0]);
    if (!sticker) return false;
    if ("pack_id" in sticker) return false;
    const canUse = canUseStickersNatively() && hasPermission(channelId, PERM.USE_EXTERNAL_STICKERS);
    if (sticker.available !== false && (canUse || sticker.guild_id === guildId)) return false;
    if (sticker.format_type === STICKER_LOTTIE) {
      log20.warn("Lottie \u8D34\u7EB8\u65E0\u6CD5\u4F5C\u4E3A\u56FE\u7247\u5185\u8054\uFF0C\u5DF2\u8DF3\u8FC7\uFF1A", sticker.name);
      return false;
    }
    const url = stickerUrl(sticker);
    message.content = `${message.content ?? ""}${wordBoundary(message.content ?? "", (message.content ?? "").length - 1)}${url}`;
    ids.length = 0;
    return true;
  }
  var EMOJI_TOKEN_RE = /(?<!\\)<(a)?:(\w+):(\d+)>/gi;
  function rewriteEmojis(channelId, message, guildId) {
    if (!settings5.store.enableEmojiBypass) return false;
    let changed = false;
    const emojis = message?.validNonShortcutEmojis;
    if (Array.isArray(emojis) && emojis.length > 0) {
      for (const emoji of emojis) {
        if (canUseEmote(emoji, channelId, guildId)) continue;
        const token = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
        const url = emojiUrl(emoji);
        const re = new RegExp(escapeRegExp(token), "g");
        message.content = String(message.content ?? "").replace(
          re,
          (match, offset, str) => {
            changed = true;
            return `${wordBoundary(str, offset - 1)}${url}${wordBoundary(str, offset + match.length)}`;
          }
        );
      }
    }
    const before = String(message.content ?? "");
    EMOJI_TOKEN_RE.lastIndex = 0;
    if (before.length > 0 && EMOJI_TOKEN_RE.test(before)) {
      EMOJI_TOKEN_RE.lastIndex = 0;
      const rewritten = before.replace(
        EMOJI_TOKEN_RE,
        (tokenStr, animatedFlag, _name, emojiId, offset, str) => {
          const cached2 = EmojiStore2.getCustomEmojiById?.(emojiId);
          if (cached2 && canUseEmote(cached2, channelId, guildId)) return tokenStr;
          changed = true;
          const url = emojiUrlFromParts(emojiId, Boolean(animatedFlag));
          return `${wordBoundary(str, offset - 1)}${url}${wordBoundary(str, offset + tokenStr.length)}`;
        }
      );
      if (rewritten !== before) message.content = rewritten;
    }
    return changed;
  }
  function emojiUrlFromParts(id, animated) {
    const size = Number(settings5.store.emojiSize) || 48;
    const ext = animated ? "gif" : "webp";
    const url = new URL(`https://cdn.discordapp.com/emojis/${id}.${ext}`);
    url.searchParams.set("size", String(size));
    return url.toString();
  }
  var unpatchSend;
  var unpatchEdit;
  function onSendMessage(ctx) {
    try {
      const args = ctx.args;
      const channelId = args[0];
      const message = findMessageArg(args);
      if (!message) return;
      if (message.__fakeNitroRewritten) return;
      if (typeof message.content !== "string") message.content = String(message.content ?? "");
      const options = findOptionsArg(args);
      const guildId = guildIdOfChannel(channelId);
      if (options) rewriteStickers(channelId, message, options, guildId);
      rewriteEmojis(channelId, message, guildId);
    } catch (err) {
      log20.error("send \u6539\u5199\u5931\u8D25\uFF0C\u6D88\u606F\u6309\u539F\u6837\u53D1\u9001", err);
    }
  }
  function onEditMessage(ctx) {
    try {
      if (!settings5.store.enableEmojiBypass) return;
      const args = ctx.args;
      const channelId = args[0];
      const message = findMessageArg(args);
      if (!message || typeof message.content !== "string") return;
      const guildId = guildIdOfChannel(channelId);
      message.content = message.content.replace(
        EMOJI_TOKEN_RE,
        (tokenStr, animatedFlag, _name, emojiId, offset, str) => {
          const cached2 = EmojiStore2.getCustomEmojiById?.(emojiId);
          if (cached2 && canUseEmote(cached2, channelId, guildId)) return tokenStr;
          const url = emojiUrlFromParts(emojiId, Boolean(animatedFlag));
          return `${wordBoundary(str, offset - 1)}${url}${wordBoundary(str, offset + tokenStr.length)}`;
        }
      );
    } catch (err) {
      log20.error("edit \u6539\u5199\u5931\u8D25\uFF0C\u6D88\u606F\u6309\u539F\u6837\u4FDD\u5B58", err);
    }
  }
  function reportPatches2() {
    const mine = getSourcePatchReport().filter((p) => p.pluginId === "fake-nitro");
    if (!mine.length) return;
    const missed = mine.filter((p) => !p.applied);
    if (missed.length === 0) {
      log20.info("\u6240\u6709\u6E90\u7801 patch \u5747\u5DF2\u5728\u5F53\u524D Discord \u7248\u672C\u751F\u6548");
    } else {
      log20.warn(
        "\u90E8\u5206\u6E90\u7801 patch \u672A\u5339\u914D\u5F53\u524D Discord \u7248\u672C\uFF1B\u9009\u62E9\u5668\u89E3\u9501\u6216\u53D1\u9001\u6539\u5199\u53EF\u80FD\u4E0D\u5B8C\u6574\u3002\u672A\u5339\u914D\uFF1A" + missed.map((p) => `\u201C${p.label}\u201D`).join("\u3001")
      );
    }
  }
  var IS_BYPASSEABLE_INTENTION = `[${INTENT_CHAT},${INTENT_STICKER_EMOJI}].includes(fakeNitroIntention)`;
  var fake_nitro_default = definePlugin({
    id: "fake-nitro",
    name: "\u5047 Nitro",
    description: "\u65E0\u9700 Nitro \u4E5F\u80FD\u4F7F\u7528\u9700\u8981 Nitro \u7684\u81EA\u5B9A\u4E49\u8868\u60C5\u4E0E\u8D34\u7EB8\uFF1A\u89E3\u9501\u9009\u62E9\u5668\uFF0C\u5E76\u5728\u53D1\u9001\u65F6\u628A\u9501\u5B9A\u7684\u8868\u60C5 / \u8D34\u7EB8\u81EA\u52A8\u6539\u5199\u4E3A\u56FE\u7247\u94FE\u63A5\uFF0C\u5BF9\u65B9\u770B\u5230\u7684\u5C31\u662F\u5185\u8054\u56FE\u7247\u3002\u4FEE\u6539\u9700\u91CD\u542F\u5BA2\u6237\u7AEF\u624D\u80FD\u5B8C\u5168\u751F\u6548\u3002",
    authors: [{ name: "Vencord" }, { name: "caitemm" }],
    category: "chat",
    settings: settings5,
    patches: [
      // 0. THE send-time fix — pre-send rewrite, same seam as Vencord's
      //    MessageEventsAPI (find: ".handleSendMessage,onResize:"), but the match
      //    is re-anchored to THIS build's `handleSendMessage`, verified against
      //    the dumped module source. Vencord's upstream regex expects
      //    `parse(channel,...).getSendMessageOptions({...});` back-to-back; this
      //    build instead reads:
      //
      //      let w=X.Y.parse(h,t);            // w = parsed draft (validNonShortcutEmojis)
      //      w.tts=...; ...
      //      let I={...Z.getSendMessageOptions({content:t,channelId:h.id,...}),
      //             location:...};            // I = send options (stickerIds)
      //      if(...) <send>
      //
      //    So we capture w (message), h (channel), I (options) and splice our
      //    rewrite in right after `let I={...};` — before the send, and before
      //    the client-side "该表情符号为动态表情" check that otherwise aborts an
      //    animated / cross-server emoji locally. By then the `<a:name:id>` token
      //    is already a CDN URL, so the check sees a plain link and lets it through.
      //
      //    Hooking MessageActions.sendMessage (the old approach) fired AFTER that
      //    block already killed the send — which is why the emoji came back
      //    "无法使用" no matter what.
      {
        label: "message pre-send rewrite",
        find: /handleSendMessage[\s\S]{0,200}onResize|getSendMessageOptions[\s\S]{0,500}handleSendMessage/,
        replacement: {
          match: /let ([\w$]+)=[\w$]+\.[\w$]+\.parse\(([\w$]+),[\w$]+\);.+?let ([\w$]+)=\{\.\.\.[\w$]+\.[\w$]+\.getSendMessageOptions\(\{.+?\}\),location:[^}]*\};/,
          replace: (m, msg, channel, options) => `${m}if($self.handlePreSend(${channel}.id,${msg},${options}))return{shouldClear:false,shouldRefocus:true};`
        }
      },
      // 1. Premium predicate bypass. The `canUse…` helpers under PremiumUtils
      //    gate every "is this a premium feature?" check in the client. Rewrite
      //    each function body to just `return true;` so the picker, sticker
      //    dropdowns, and stream-quality options all show as usable.
      {
        label: "premium predicates return true",
        find: "canUseCustomStickersEverywhere:",
        replacement: [
          {
            match: /(?<=canUseCustomStickersEverywhere:function\([\w$]+\)\{)/,
            replace: "return true;"
          },
          {
            match: /(?<=canUseHighVideoUploadQuality:function\([\w$]+\)\{)/,
            replace: "return true;"
          },
          {
            match: /(?<=canStreamQuality:function\([\w$]+,[\w$]+\)\{)/,
            replace: "return true;"
          },
          {
            match: /(?<=canUseClientThemes:function\([\w$]+\)\{)/,
            replace: "return true;"
          },
          {
            match: /(?<=canUsePremiumAppIcons:function\([\w$]+\)\{)/,
            replace: "return true;"
          }
        ]
      },
      // 2. Voice-call emoji picker keeps its native (server-side) restriction:
      //    fake-nitro shouldn't try to bypass emoji use in voice channels where
      //    the intention isn't CHAT. Vencord swaps the CHAT constant for STATUS
      //    at this exact call site so our picker patches leave voice alone.
      {
        label: "voice call emoji stays native",
        find: '.getByName("fork_and_knife")',
        replacement: {
          match: /\.CHAT/,
          replace: ".STATUS"
        }
      },
      // 3. Emoji picker unlock. The module that produces the "why is this emoji
      //    disabled?" enum has multiple gates: DISALLOW_EXTERNAL,
      //    GUILD_SUBSCRIPTION_UNAVAILABLE, premium-locked, animated-locked.
      //    Widen each so intentions CHAT (3) and GUILD_STICKER_RELATED_EMOJI (4)
      //    pass, giving the picker "usable" for anything we're about to rewrite
      //    into a URL anyway.
      {
        label: "emoji picker unlock",
        find: ".GUILD_SUBSCRIPTION_UNAVAILABLE;",
        replacement: [
          // Introduce `fakeNitroIntention`, bound to Discord's own intention
          // variable, so the widenings below can key off it.
          //
          // Vencord anchors this on the literal `intention:` token — but that
          // token is ABSENT in the current build (verified against the dumped
          // module source): the intention is a bare minified variable, e.g.
          // `d`, compared as `d===X.EmojiIntention.STATUS`. Anchoring on the
          // (present) `.USE_EXTERNAL_EMOJIS,x);` statement boundary and reading
          // the intention variable out of the nearby
          // `isExternalEmojiAllowedForIntention(d)` call is what actually lands.
          //
          // This one MUST match: patches #2–#5 below all reference
          // `fakeNitroIntention`, so if this fails while they apply, the emoji
          // function throws ReferenceError on every call and the picker greys
          // out everything — which is exactly the "全是锁" symptom this fixes.
          {
            match: /(?<=\.USE_EXTERNAL_EMOJIS,[\w$]+\);)(?=.{0,300}?isExternalEmojiAllowedForIntention\)\(([\w$]+)\))/,
            replace: "const fakeNitroIntention=$1;"
          },
          // DISALLOW_EXTERNAL: bypass for our intentions.
          {
            match: /&&![\w$]+&&![\w$]+(?=\)return [\w$]+\.[\w$]+\.DISALLOW_EXTERNAL;)/,
            replace: `$&&&!${IS_BYPASSEABLE_INTENTION}`
          },
          // GUILD_SUBSCRIPTION_UNAVAILABLE: bypass for our intentions.
          {
            match: /![\w$]+\.available(?=\)return [\w$]+\.[\w$]+\.GUILD_SUBSCRIPTION_UNAVAILABLE;)/,
            replace: `$&&&!${IS_BYPASSEABLE_INTENTION}`
          },
          // "You need premium for cross-server emoji": bypass for our intentions.
          {
            match: /!([\w$]+\.[\w$]+\.canUseEmojisEverywhere\([\w$]+\))/,
            replace: `(!$1&&!${IS_BYPASSEABLE_INTENTION})`
          },
          // "You need premium for animated emoji": pretend we can, for our intentions.
          {
            match: /(?<=\|\|)[\w$]+\.[\w$]+\.canUseAnimatedEmojis\([\w$]+\)/,
            replace: `($&||${IS_BYPASSEABLE_INTENTION})`
          }
        ]
      },
      // 4. Subscription-locked (role-benefit) emoji unlock. A guarded predicate
      //    returns false when the current user lacks admin on the role-benefit
      //    guild. Route calls that WE make (with a fakeNitroOriginal=true tail
      //    arg) through the original, and let everyone else's calls (i.e. the
      //    picker's own probe) short-circuit to "usable".
      {
        label: "subscription emoji unlock",
        find: ".getUserIsAdmin(",
        replacement: {
          match: /(function [\w$]+\([\w$]+,[\w$]+)\)\{(.{0,250}\.getUserIsAdmin\(.+?return!1\})/,
          replace: "$1,fakeNitroOriginal){if(!fakeNitroOriginal)return false;$2"
        }
      },
      // 5. Sticker always "SENDABLE". Same trick: rewrite the availability
      //    predicate at the sticker send-affordance site so the picker doesn't
      //    grey out locked stickers before our runtime send hook can rewrite
      //    them into image links.
      {
        label: "stickers always sendable",
        find: '"SENDABLE"',
        replacement: {
          match: /[\w$]+\.available\?/,
          replace: "true?"
        }
      },
      // 6. Stream quality: drop the `guildPremiumTier: TIER_x,` requirements
      //    from the stream FPS / resolution options so all quality tiers are
      //    picker-visible regardless of the server's boost level.
      //
      //    NOTE: Vencord's find is the intl macro `#{intl::STREAM_FPS_OPTION}`,
      //    which its build step rewrites into the real (hashed) runtime lookup.
      //    This runtime has no such transform, so that literal never appears in
      //    Discord's code. We fall back to the bare `STREAM_FPS_OPTION` token as
      //    a best effort; if the build hashes it away this patch simply no-ops
      //    (stream quality is a minor extra — it never blocks emoji/sticker use).
      {
        label: "stream quality tiers removed",
        find: "STREAM_FPS_OPTION",
        all: true,
        replacement: {
          match: /guildPremiumTier:[\w$]+\.[\w$]+\.TIER_\d,?/,
          replace: ""
        }
      },
      // 7. Custom desktop app icons — the picker checks `isPremium(currentUser)`.
      //    Force true. Small quality-of-life patch that comes free with the
      //    premium-bypass mood.
      {
        label: "custom app icons",
        find: "getCurrentDesktopIcon(),",
        replacement: {
          match: /[\w$]+\.[\w$]+\.isPremium\([\w$]+\.[\w$]+\.getCurrentUser\(\)\)/,
          replace: "true"
        }
      },
      // 8. Custom client themes — a `isTier2Above` gate on the custom-theme
      //    editor. Force true so the editor unlocks for the user.
      {
        label: "custom client themes",
        find: '("custom_themes_editor_footer")',
        all: true,
        replacement: {
          match: /\(0,[\w$]+\.[\w$]+\)\([\w$]+\.[\w$]+\.TIER_2\)(?=,|;)/,
          replace: "true"
        }
      },
      // 9. Soundboard sounds — `available` fields arrive as false for locked
      //    sounds on non-Nitro accounts. Force them to true on the ingest
      //    reducers so the picker treats them as usable.
      {
        label: "soundboard sounds available",
        find: 'type:"GUILD_SOUNDBOARD_SOUND_CREATE"',
        all: true,
        replacement: {
          match: /(?<=type:"(?:SOUNDBOARD_SOUNDS_RECEIVED|GUILD_SOUNDBOARD_SOUND_CREATE|GUILD_SOUNDBOARD_SOUND_UPDATE|GUILD_SOUNDBOARD_SOUNDS_UPDATE)".+?available:)[\w$]+\.available/,
          replace: "true"
        }
      }
    ],
    start() {
      const messageActions = findByProps("sendMessage", "editMessage", "deleteMessage");
      if (messageActions) {
        if (typeof messageActions.sendMessage === "function") {
          try {
            unpatchSend = patcher.before(messageActions, "sendMessage", onSendMessage);
          } catch (err) {
            log20.error("\u6302\u63A5 sendMessage \u5931\u8D25", err);
          }
        }
        if (typeof messageActions.editMessage === "function") {
          try {
            unpatchEdit = patcher.before(messageActions, "editMessage", onEditMessage);
          } catch (err) {
            log20.error("\u6302\u63A5 editMessage \u5931\u8D25", err);
          }
        }
        log20.info("MessageActions \u5DF2\u6302\u63A5\uFF08\u53D1\u9001 / \u7F16\u8F91\u6539\u5199\u5C31\u7EEA\uFF1B\u82E5 pre-send \u8865\u4E01\u5DF2\u751F\u6548\u5219\u6B64 hook \u4EC5\u4F5C fallback\uFF09");
      } else {
        log20.warn(
          "\u672A\u627E\u5230 MessageActions \u2014\u2014 \u9009\u62E9\u5668\u89E3\u9501\u5DF2\u901A\u8FC7\u6E90\u7801 patch \u751F\u6548\uFF0C\u4F46\u53D1\u9001\u65F6\u7684 URL \u6539\u5199\u4E0D\u53EF\u7528\u3002\u91CD\u542F\u5BA2\u6237\u7AEF\u540E\u518D\u8BD5\uFF1B\u82E5\u4ECD\u672A\u627E\u5230\uFF0C\u8BF4\u660E\u8BE5 Discord \u7248\u672C\u7684 MessageActions \u5F62\u72B6\u6709\u53D8\u3002"
        );
      }
      setTimeout(reportPatches2, 4e3);
    },
    stop() {
      unpatchSend?.();
      unpatchEdit?.();
      unpatchSend = void 0;
      unpatchEdit = void 0;
    },
    /**
     * Called from the pre-send source patch (#0) with Discord's freshly parsed
     * draft — at the exact point Vencord's MessageEventsAPI fires, after parse
     * and before send. Rewrites locked sticker ids and emoji tokens in
     * `messageObj` IN PLACE into CDN URLs. Returns false so the send is never
     * cancelled: we've already mutated the draft, there's nothing to abort.
     * Guarded end-to-end — a throw here must never break the send box.
     */
    handlePreSend(channelId, messageObj, options) {
      try {
        if (typeof messageObj?.content !== "string") {
          messageObj.content = String(messageObj?.content ?? "");
        }
        const guildId = guildIdOfChannel(channelId);
        if (options) rewriteStickers(channelId, messageObj, options, guildId);
        rewriteEmojis(channelId, messageObj, guildId);
        messageObj.__fakeNitroRewritten = true;
      } catch (err) {
        log20.error("pre-send \u6539\u5199\u5931\u8D25\uFF0C\u6D88\u606F\u6309\u539F\u6837\u53D1\u9001", err);
      }
      return false;
    }
  });

  // src/plugins/console-cleaner/index.ts
  var log21 = logger("console-cleaner");
  var settings6 = defineSettings({
    hideSelfXss: {
      group: "\u5185\u7F6E\u89C4\u5219",
      type: "boolean",
      default: true,
      label: "\u5C4F\u853D\u81EA\u6211 XSS \u8B66\u544A",
      description: "Discord \u90A3\u6761\u6BCF\u79D2\u91CD\u5237\u7684\u7EA2\u8272\u201C\u7B49\u4E00\u4E0B\uFF01/ Stop!\u201D\u7C98\u8D34\u8B66\u544A\u3002"
    },
    hideLocaleSpam: {
      group: "\u5185\u7F6E\u89C4\u5219",
      type: "boolean",
      default: true,
      label: "\u5C4F\u853D\u672C\u5730\u5316\u7F3A\u5931\u5237\u5C4F",
      description: "\u201C\u2026 does not have a value in the requested locale \u2026\u201D\uFF0C\u5BA2\u6237\u7AEF mod \u8BA2\u9605\u4E8B\u4EF6\u65F6\u4F1A\u75AF\u72C2\u5237\u3002"
    },
    hideRiveSpam: {
      group: "\u5185\u7F6E\u89C4\u5219",
      type: "boolean",
      default: true,
      label: "\u5C4F\u853D Rive \u52A8\u753B\u62A5\u9519",
      description: "\u201CCould not find a View Model linked to Artboard \u2026\u201D\uFF0C\u9644\u5E26\u8D85\u957F wasm \u5806\u6808\u3002"
    },
    hidePreloadWarnings: {
      group: "\u5185\u7F6E\u89C4\u5219",
      type: "boolean",
      default: true,
      label: "\u5C4F\u853D\u8D44\u6E90\u9884\u52A0\u8F7D\u8B66\u544A",
      description: "\u201Cresource was preloaded using link preload but not used \u2026\u201D\u3002\u89C1\u4E0B\u65B9\u8BF4\u660E\uFF1A\u90E8\u5206\u6B64\u7C7B\u8B66\u544A\u7531\u6D4F\u89C8\u5668\u76F4\u63A5\u4EA7\u751F\uFF0C\u65E0\u6CD5\u62E6\u622A\u3002"
    },
    customPatterns: {
      group: "\u81EA\u5B9A\u4E49",
      type: "string-list",
      default: [],
      label: "\u81EA\u5B9A\u4E49\u5C4F\u853D\u5173\u952E\u8BCD",
      description: "\u4EFB\u4F55\u4E00\u6761 console \u6D88\u606F\u53EA\u8981\u5305\u542B\u8FD9\u91CC\u7684\u67D0\u4E2A\u5B50\u4E32\uFF0C\u5C31\u4F1A\u88AB\u4E22\u5F03\uFF08\u533A\u5206\u5927\u5C0F\u5199\uFF09\u3002",
      itemPlaceholder: "\u8981\u5C4F\u853D\u7684\u6587\u5B57\u7247\u6BB5"
    }
  });
  var SELF_XSS_NEEDLES = [
    "\u7B49\u4E00\u4E0B",
    "\u5728\u8FD9\u91CC\u7C98\u8D34",
    "\u5982\u679C\u6709\u4EBA\u544A\u8BC9\u60A8",
    "\u8BF7\u5173\u95ED\u6B64\u7A97\u53E3",
    "Stop!",
    "self-XSS",
    "browser feature intended for developers",
    "This is a browser feature",
    "Nicht so schnell",
    "Attends",
    "Alto",
    "\u3061\u3087\u3063\u3068\u5F85\u3063\u3066",
    "\uC7A0\uAE50"
  ];
  var LOCALE_NEEDLES = ["does not have a value in the requested locale"];
  var RIVE_NEEDLES = [
    "Could not find a View Model linked to Artboard",
    "BaseGlowRemapped"
  ];
  var PRELOAD_NEEDLES = [
    "was preloaded using link preload",
    "preloaded intentionally"
  ];
  var METHODS = ["log", "info", "warn", "error", "debug"];
  function textOf(args) {
    let out = "";
    for (const a of args) {
      if (typeof a === "string") out += a + " ";
      else if (typeof a === "number" || typeof a === "boolean") out += String(a) + " ";
    }
    return out;
  }
  function anyNeedle(text, needles) {
    for (const n of needles) if (n && text.includes(n)) return true;
    return false;
  }
  function shouldSuppress(args) {
    if (typeof args[0] === "string" && args[0].startsWith("%cHalcyon")) return false;
    const text = textOf(args);
    if (text === "") return false;
    const s = settings6.store;
    if (s.hideSelfXss && anyNeedle(text, SELF_XSS_NEEDLES)) return true;
    if (s.hideLocaleSpam && anyNeedle(text, LOCALE_NEEDLES)) return true;
    if (s.hideRiveSpam && anyNeedle(text, RIVE_NEEDLES)) return true;
    if (s.hidePreloadWarnings && anyNeedle(text, PRELOAD_NEEDLES)) return true;
    if (s.customPatterns.length && anyNeedle(text, s.customPatterns)) return true;
    return false;
  }
  var unpatchers = [];
  var suppressedCount = 0;
  function makeHook() {
    return (ctx) => {
      try {
        if (shouldSuppress(ctx.args)) {
          suppressedCount++;
          return void 0;
        }
      } catch {
      }
      return ctx.callOriginal();
    };
  }
  var console_cleaner_default = definePlugin({
    id: "console-cleaner",
    name: "\u63A7\u5236\u53F0\u51C0\u5316",
    description: "\u5C4F\u853D Discord \u5728\u5F00\u53D1\u8005\u63A7\u5236\u53F0\u91CC\u5237\u5C4F\u7684\u65E0\u7528\u4FE1\u606F\uFF08\u81EA\u6211 XSS \u8B66\u544A\u3001Rive \u52A8\u753B\u62A5\u9519\u3001\u672C\u5730\u5316\u7F3A\u5931\u3001\u8D44\u6E90\u9884\u52A0\u8F7D\u8B66\u544A\uFF09\uFF0C\u652F\u6301\u81EA\u5B9A\u4E49\u5173\u952E\u8BCD\u3002\u5173\u95ED\u63D2\u4EF6\u5373\u6062\u590D\u539F\u59CB console\u3002",
    authors: [{ name: "caitemm" }, { name: "catie" }],
    category: "utility",
    settings: settings6,
    start() {
      const con = globalThis.console;
      if (!con) {
        log21.warn("\u672A\u627E\u5230 console \u5BF9\u8C61\uFF0C\u63D2\u4EF6\u65E0\u4E8B\u53EF\u505A");
        return;
      }
      suppressedCount = 0;
      const hook = makeHook();
      for (const method of METHODS) {
        if (typeof con[method] === "function") {
          try {
            unpatchers.push(patcher.instead(con, method, hook));
          } catch (err) {
            log21.error(`\u6302\u63A5 console.${method} \u5931\u8D25`, err);
          }
        }
      }
      log21.info(
        `\u5DF2\u51C0\u5316 console\uFF08\u62E6\u622A ${unpatchers.length} \u4E2A\u65B9\u6CD5\uFF09\u3002\u6CE8\u610F\uFF1A\u6D4F\u89C8\u5668\u81EA\u8EAB\u4EA7\u751F\u7684\u8B66\u544A\uFF08\u5982\u67D0\u4E9B preload \u63D0\u793A\uFF09\u65E0\u6CD5\u901A\u8FC7 JS \u62E6\u622A\u3002`
      );
    },
    stop() {
      for (const undo of unpatchers) {
        try {
          undo();
        } catch {
        }
      }
      unpatchers = [];
      log21.info(`\u5DF2\u6062\u590D\u539F\u59CB console\uFF08\u672C\u6B21\u5171\u5C4F\u853D ${suppressedCount} \u6761\u6D88\u606F\uFF09`);
    }
  });

  // src/plugins/emote-cloner/clone.ts
  var log22 = logger("emote-cloner");
  var MAX_EMOJI_SIZE_BYTES = 256 * 1024;
  var MAX_STICKER_SIZE_BYTES = 512 * 1024;
  var uploadEmojiAction = null;
  function getUploadEmoji() {
    if (uploadEmojiAction) return uploadEmojiAction;
    uploadEmojiAction = findByCode(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START") ?? null;
    return uploadEmojiAction;
  }
  function sanitizeEmojiName(name) {
    let n = (name || "emoji").split("~")[0].replace(/[^\w]/g, "_");
    if (n.length < 2) n = `${n}_e`;
    return n.slice(0, 32);
  }
  function stickerExt(formatType) {
    if (formatType === 4) return "gif";
    if (formatType === 3) return "json";
    return "png";
  }
  function emojiUrl2(id, size) {
    return `https://cdn.discordapp.com/emojis/${id}.webp?size=${size}&lossless=true&animated=true`;
  }
  function stickerUrl2(id, ext, size) {
    return `https://media.discordapp.net/stickers/${id}.${ext}?size=${size}&lossless=true&animated=true`;
  }
  async function fetchBlobUnderLimit(makeUrl, maxBytes) {
    for (let size = 4096; size >= 16; size /= 2) {
      const url = makeUrl(size);
      const res = await fetch(url);
      if (!res.ok) throw new Error(`\u4E0B\u8F7D\u56FE\u7247\u5931\u8D25\uFF1AHTTP ${res.status}`);
      const blob = await res.blob();
      if (blob.size <= maxBytes) return blob;
    }
    throw new Error(`\u56FE\u7247\u8D85\u51FA\u5927\u5C0F\u9650\u5236\uFF08${Math.round(maxBytes / 1024)}KB\uFF09`);
  }
  function blobToDataUri(blob) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onload = () => resolve(String(reader.result));
      reader.onerror = () => reject(reader.error ?? new Error("\u8BFB\u53D6\u56FE\u7247\u5931\u8D25"));
      reader.readAsDataURL(blob);
    });
  }
  function resPayload(res) {
    if (res == null) return null;
    if (res.body != null && !(typeof res.body === "object" && Object.keys(res.body).length === 0)) {
      return res.body;
    }
    if (typeof res.text === "string" && res.text) {
      try {
        return JSON.parse(res.text);
      } catch {
      }
    }
    return res.body ?? null;
  }
  function restErrorMessage(err) {
    const body = err?.body ?? err?.response?.body;
    if (body) {
      try {
        const walk = (o) => {
          if (!o || typeof o !== "object") return void 0;
          if (Array.isArray(o._errors) && o._errors[0]?.message) return o._errors[0].message;
          for (const k of Object.keys(o)) {
            const hit = walk(o[k]);
            if (hit) return hit;
          }
          return void 0;
        };
        const specific = walk(body.errors);
        if (specific) return specific;
      } catch {
      }
      if (typeof body.message === "string") return body.message;
    }
    if (typeof err?.text === "string") {
      try {
        const parsed = JSON.parse(err.text);
        if (parsed?.message) return parsed.message;
      } catch {
      }
    }
    return err?.message ? String(err.message) : "\u672A\u77E5\u9519\u8BEF";
  }
  async function cloneEmoji(guildId, emoji) {
    const blob = await fetchBlobUnderLimit((size) => emojiUrl2(emoji.id, size), MAX_EMOJI_SIZE_BYTES);
    const image = await blobToDataUri(blob);
    const name = sanitizeEmojiName(emoji.name);
    const upload = getUploadEmoji();
    if (typeof upload === "function") {
      try {
        await upload({ guildId, name, image });
        return;
      } catch (err) {
        log22.error("emoji \u4E0A\u4F20\uFF08action\uFF09\u5931\u8D25", err);
        throw new Error(restErrorMessage(err));
      }
    }
    try {
      await RestAPI.post({ url: `/guilds/${guildId}/emojis`, body: { image, name, roles: [] } });
    } catch (err) {
      log22.error("emoji \u4E0A\u4F20\uFF08REST\uFF09\u5931\u8D25", err);
      throw new Error(restErrorMessage(err));
    }
  }
  async function fetchStickerInfo(id) {
    try {
      const cached2 = StickersStore.getStickerById?.(id);
      if (cached2) return cached2;
    } catch {
    }
    try {
      const res = await RestAPI.get({ url: `/stickers/${id}` });
      const body = resPayload(res);
      if (body) {
        try {
          getDispatcher()?.dispatch({ type: "STICKER_FETCH_SUCCESS", sticker: body });
        } catch {
        }
      }
      return body;
    } catch (err) {
      log22.warn("could not fetch sticker info; using fallbacks", err);
      return null;
    }
  }
  async function cloneSticker(guildId, sticker) {
    const info = await fetchStickerInfo(sticker.id);
    if (info?.format_type === 3) {
      throw new Error("\u8FD9\u662F Lottie \u52A8\u6001\u8D34\u7EB8\uFF0C\u65E0\u6CD5\u590D\u5236");
    }
    const name = (info?.name || sticker.name || "sticker").slice(0, 30);
    const tags = sticker.tags || info?.tags || "\u{1F642}";
    const description = (sticker.description ?? info?.description ?? "").slice(0, 100);
    const ext = stickerExt(info?.format_type);
    const blob = await fetchBlobUnderLimit(
      (size) => stickerUrl2(sticker.id, ext, size),
      MAX_STICKER_SIZE_BYTES
    );
    const form = new FormData();
    form.append("name", name);
    form.append("tags", tags);
    form.append("description", description);
    form.append("file", new File([blob], `sticker.${ext}`, { type: ext === "gif" ? "image/gif" : "image/png" }));
    const url = Constants?.Endpoints?.GUILD_STICKER_PACKS?.(guildId) ?? `/guilds/${guildId}/stickers`;
    let created;
    try {
      const res = await RestAPI.post({ url, body: form });
      created = resPayload(res);
      if (created && !created.id && created.sticker?.id) created = created.sticker;
    } catch (err) {
      log22.error("sticker \u4E0A\u4F20\u5931\u8D25", err);
      throw new Error(restErrorMessage(err));
    }
    log22.info("sticker uploaded", { id: created?.id, name: created?.name });
    try {
      getDispatcher()?.dispatch({
        type: "GUILD_STICKERS_CREATE_SUCCESS",
        guildId,
        sticker: { ...created, user: UserStore.getCurrentUser?.() }
      });
    } catch {
    }
  }

  // src/plugins/emote-cloner/resolve.ts
  var log23 = logger("emote-cloner");
  var SNOWFLAKE = /^\d{5,25}$/;
  var EMOJI_NAME = /^\w{1,32}(?:~\d+)?$/;
  function emojiName(raw) {
    if (typeof raw !== "string") return void 0;
    const n = raw.replace(/:/g, "").trim();
    return EMOJI_NAME.test(n) ? n : void 0;
  }
  function stickerName(raw) {
    if (typeof raw !== "string") return void 0;
    const n = raw.trim();
    return n && n.length <= 30 && !n.includes("\n") ? n : void 0;
  }
  function isGifUrl(url) {
    if (!url) return false;
    try {
      const u = new URL(url, location.href);
      return u.pathname.endsWith(".gif") || u.searchParams.get("animated") === "true";
    } catch {
      return /\.gif(\?|$)/.test(url) || url.includes("animated=true");
    }
  }
  function parseEmojiUrl(src) {
    const m = src.match(/\/emojis\/(\d+)\.(\w+)/);
    if (!m) return null;
    let name;
    try {
      const raw = new URL(src, location.href).searchParams.get("name");
      name = raw ? decodeURIComponent(raw) : void 0;
    } catch {
    }
    return { id: m[1], isAnimated: m[2] === "gif" || /animated=true/.test(src), name };
  }
  function parseStickerUrl(src) {
    const m = src.match(/\/stickers\/(\d+)\./);
    return m ? { id: m[1] } : null;
  }
  function isLottie(el) {
    return String(el?.className ?? "").toLowerCase().includes("lottie");
  }
  function gatherImages(target) {
    const seen = /* @__PURE__ */ new Set();
    const out = [];
    const add = (el) => {
      if (el && el.tagName === "IMG" && !seen.has(el)) {
        seen.add(el);
        out.push(el);
      }
    };
    add(target);
    target.querySelectorAll?.("img").forEach(add);
    let cur = target.parentElement;
    for (let depth = 0; depth < 4 && cur; depth++, cur = cur.parentElement) {
      add(cur);
      cur.querySelectorAll?.(":scope > img").forEach(add);
    }
    return out;
  }
  function selfAndAncestors(target, depth = 5) {
    const out = [];
    let cur = target;
    for (let i = 0; cur && i <= depth; i++, cur = cur.parentElement) out.push(cur);
    return out;
  }
  var SCAN_MAX_DEPTH = 5;
  var SCAN_MAX_NODES = 900;
  function findRecordById(root, id) {
    let budget = SCAN_MAX_NODES;
    const seen = /* @__PURE__ */ new Set();
    const walk = (value, depth) => {
      if (value == null || typeof value !== "object") return null;
      if (depth > SCAN_MAX_DEPTH || budget-- <= 0) return null;
      if (seen.has(value)) return null;
      seen.add(value);
      if (Array.isArray(value)) {
        for (const item of value) {
          const hit = walk(item, depth + 1);
          if (hit) return hit;
        }
        return null;
      }
      if (value.$$typeof != null || value.nodeType != null || value.stateNode != null) return null;
      try {
        if (String(value.id ?? "") === id && typeof value.name === "string") {
          return { name: value.name, animated: Boolean(value.animated ?? value.isAnimated) };
        }
        if (typeof value.emojiName === "string" && String(value.emojiId ?? "") === id) {
          return { name: value.emojiName, animated: Boolean(value.animated ?? value.isAnimated) };
        }
      } catch {
      }
      let keys;
      try {
        keys = Object.keys(value);
      } catch {
        return null;
      }
      for (const key of keys) {
        if (key.charCodeAt(0) === 95) continue;
        let child;
        try {
          child = value[key];
        } catch {
          continue;
        }
        if (child == null || typeof child !== "object") continue;
        const hit = walk(child, depth + 1);
        if (hit) return hit;
      }
      return null;
    };
    return walk(root, 0);
  }
  function recordFromFiber(target, id) {
    for (const props of getFiberPropsChain(target)) {
      const hit = findRecordById(props, id);
      if (hit) return hit;
    }
    return null;
  }
  function messageFromDom(target) {
    const el = target.closest?.(
      "[id^='chat-messages-'],[data-list-item-id*='chat-messages']"
    );
    if (!el) return null;
    const raw = el.id || el.dataset?.listItemId || "";
    const ids = raw.match(/\d{5,25}/g);
    if (!ids || ids.length === 0) return null;
    const messageId = ids[ids.length - 1];
    let channelId = ids.length > 1 ? ids[ids.length - 2] : void 0;
    try {
      channelId ??= SelectedChannelStore.getChannelId?.();
    } catch {
    }
    if (!channelId) return null;
    try {
      return MessageStore.getMessage?.(channelId, messageId) ?? null;
    } catch {
      return null;
    }
  }
  function messagesNear(target) {
    const out = [];
    for (const props of getFiberPropsChain(target)) {
      const msg = props?.message;
      if (msg && typeof msg === "object" && typeof msg.content === "string") {
        out.push(msg);
        break;
      }
    }
    const fromDom = messageFromDom(target);
    if (fromDom && typeof fromDom === "object" && fromDom !== out[0]) out.push(fromDom);
    return out;
  }
  function emojiNameFromMessages(target, id) {
    if (!SNOWFLAKE.test(id)) return void 0;
    const inContent = new RegExp(`<a?:(\\w+)(?:~\\d+)?:${id}>`);
    for (const msg of messagesNear(target)) {
      try {
        const m = typeof msg.content === "string" ? inContent.exec(msg.content) : null;
        const fromContent = emojiName(m?.[1]);
        if (fromContent) return fromContent;
        const reactions = Array.isArray(msg.reactions) ? msg.reactions : [];
        for (const r of reactions) {
          if (String(r?.emoji?.id ?? "") === id) {
            const hit = emojiName(r.emoji.name);
            if (hit) return hit;
          }
        }
      } catch {
      }
    }
    return void 0;
  }
  function stickerNameFromMessages(target, id) {
    for (const msg of messagesNear(target)) {
      try {
        const items = Array.isArray(msg.stickerItems) ? msg.stickerItems : Array.isArray(msg.stickers) ? msg.stickers : [];
        for (const s of items) {
          if (String(s?.id ?? "") === id) {
            const hit = stickerName(s.name);
            if (hit) return hit;
          }
        }
      } catch {
      }
    }
    return void 0;
  }
  function emojiNameFromStore(id) {
    const store = EmojiStore;
    const tries = [
      () => store.getCustomEmojiById?.(id),
      () => store.getUsableCustomEmojiById?.(id),
      () => store.getDisambiguatedEmojiContext?.()?.getById?.(id)
    ];
    for (const attempt of tries) {
      try {
        const hit = emojiName(attempt()?.name);
        if (hit) return hit;
      } catch {
      }
    }
    return void 0;
  }
  var NAME_ATTRS = ["data-name", "alt", "aria-label", "title"];
  function emojiNameFromDom(elements) {
    for (const el of elements) {
      for (const attr of NAME_ATTRS) {
        const hit = emojiName(el.getAttribute?.(attr));
        if (hit) return hit;
      }
    }
    return void 0;
  }
  function locate(target) {
    const dataEl = target.closest?.("[data-type='emoji'],[data-type='sticker']");
    if (dataEl) {
      const { id, name, type } = dataEl.dataset;
      const img = dataEl.tagName === "IMG" ? dataEl : dataEl.querySelector("img");
      if (id && SNOWFLAKE.test(id) && type === "emoji") {
        return {
          kind: "emoji",
          id,
          domName: name,
          img,
          isAnimated: isGifUrl(img?.currentSrc || img?.src)
        };
      }
      if (id && SNOWFLAKE.test(id) && type === "sticker" && !isLottie(dataEl)) {
        return { kind: "sticker", id, domName: name, img, isAnimated: false };
      }
    }
    for (const img of gatherImages(target)) {
      const src = img.currentSrc || img.src || "";
      const emoji = parseEmojiUrl(src);
      if (emoji) {
        return {
          kind: "emoji",
          id: emoji.id,
          domName: emoji.name,
          img,
          isAnimated: emoji.isAnimated || isGifUrl(src)
        };
      }
      const sticker = parseStickerUrl(src);
      if (sticker) {
        if (isLottie(img)) return null;
        return { kind: "sticker", id: sticker.id, domName: img.alt, img, isAnimated: false };
      }
    }
    return null;
  }
  function resolveExpression(target) {
    if (!target) return null;
    const found = locate(target);
    if (!found) return null;
    const elements = selfAndAncestors(target);
    if (found.img && !elements.includes(found.img)) elements.push(found.img);
    if (found.kind === "sticker") {
      const record3 = recordFromFiber(target, found.id);
      const name = stickerName(record3?.name) ?? stickerNameFromMessages(target, found.id) ?? stickerName(found.domName) ?? stickerName(found.img?.alt);
      return { kind: "sticker", id: found.id, name };
    }
    const record2 = recordFromFiber(target, found.id);
    const resolved = emojiName(record2?.name) ?? emojiNameFromMessages(target, found.id) ?? emojiNameFromStore(found.id) ?? emojiNameFromDom(elements) ?? emojiName(found.domName);
    if (!resolved) {
      log23.warn(`could not resolve this emoji's name; falling back to "emoji"`, { id: found.id });
    } else {
      log23.debug("resolved emoji", { id: found.id, name: resolved });
    }
    return {
      kind: "emoji",
      id: found.id,
      name: resolved ?? "emoji",
      isAnimated: record2?.animated ?? found.isAnimated
    };
  }

  // src/plugins/emote-cloner/picker.tsx
  var log24 = logger("emote-cloner");
  function iconUrl(g2) {
    const ext = g2.icon && g2.icon.startsWith("a_") ? "gif" : "png";
    return `https://cdn.discordapp.com/icons/${g2.id}/${g2.icon}.${ext}?size=64`;
  }
  var host3 = null;
  var unmount3 = null;
  var keyHandler2 = null;
  function closeGuildPicker() {
    if (keyHandler2) {
      document.removeEventListener("keydown", keyHandler2);
      keyHandler2 = null;
    }
    if (unmount3) {
      try {
        unmount3();
      } catch {
      }
      unmount3 = null;
    }
    if (host3) {
      host3.remove();
      host3 = null;
    }
  }
  function openGuildPicker(opts) {
    injectStyles();
    closeGuildPicker();
    host3 = document.createElement("div");
    host3.className = "halcyon";
    document.body.appendChild(host3);
    keyHandler2 = (event) => {
      if (event.key === "Escape") closeGuildPicker();
    };
    document.addEventListener("keydown", keyHandler2);
    try {
      unmount3 = mountDetached(
        React.createElement(PickerModal, {
          title: opts.title,
          guilds: opts.guilds,
          onPick: opts.onPick,
          onClose: closeGuildPicker
        }),
        host3
      );
    } catch (err) {
      log24.error("could not open guild picker", err);
      closeGuildPicker();
    }
  }
  function PickerModal({
    title,
    guilds,
    onPick,
    onClose
  }) {
    const [query, setQuery] = useState("");
    const [status, setStatus] = useState({ state: "idle" });
    const q = query.trim().toLowerCase();
    const filtered = q ? guilds.filter((g2) => g2.name.toLowerCase().includes(q)) : guilds;
    const pick = (g2) => {
      setStatus({ state: "working", guild: g2.name });
      Promise.resolve().then(() => onPick(g2.id)).then(() => {
        setStatus({ state: "done", guild: g2.name });
        setTimeout(onClose, 1e3);
      }).catch((err) => {
        log24.error("clone failed", err);
        setStatus({ state: "error", guild: g2.name, message: err?.message ?? String(err) });
      });
    };
    return /* @__PURE__ */ React.createElement(
      "div",
      {
        className: "hc-overlay",
        role: "dialog",
        "aria-modal": "true",
        "aria-label": title,
        onMouseDown: (e) => {
          if (e.target === e.currentTarget && status.state !== "working") onClose();
        }
      },
      /* @__PURE__ */ React.createElement("div", { className: "hc-emote-picker" }, /* @__PURE__ */ React.createElement("div", { className: "hc-emote-picker__head" }, /* @__PURE__ */ React.createElement("span", { className: "hc-emote-picker__title" }, title), /* @__PURE__ */ React.createElement(
        "button",
        {
          className: "hc-emote-picker__close",
          onClick: onClose,
          "aria-label": "\u5173\u95ED",
          disabled: status.state === "working"
        },
        "\u2715"
      )), status.state === "idle" ? /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "hc-emote-picker__search" }, /* @__PURE__ */ React.createElement(
        "input",
        {
          className: "hc-input",
          placeholder: "\u641C\u7D22\u670D\u52A1\u5668\u2026",
          value: query,
          autoFocus: true,
          onChange: (e) => setQuery(e.currentTarget.value)
        }
      )), /* @__PURE__ */ React.createElement("div", { className: "hc-emote-picker__list" }, filtered.length === 0 ? /* @__PURE__ */ React.createElement("div", { className: "hc-emote-picker__empty" }, guilds.length === 0 ? "\u6CA1\u6709\u53EF\u7BA1\u7406\u8868\u60C5\u7684\u670D\u52A1\u5668" : "\u6CA1\u6709\u5339\u914D\u7684\u670D\u52A1\u5668") : filtered.map((g2) => /* @__PURE__ */ React.createElement(
        "div",
        {
          key: g2.id,
          className: "hc-emote-picker__item",
          role: "button",
          tabIndex: 0,
          onClick: () => pick(g2),
          onKeyDown: (e) => {
            if (e.key === "Enter") pick(g2);
          }
        },
        /* @__PURE__ */ React.createElement("div", { className: "hc-emote-picker__icon" }, g2.icon ? /* @__PURE__ */ React.createElement("img", { src: iconUrl(g2), alt: "" }) : g2.name.charAt(0).toUpperCase()),
        /* @__PURE__ */ React.createElement("div", { className: "hc-emote-picker__name" }, g2.name)
      )))) : /* @__PURE__ */ React.createElement("div", { className: "hc-emote-picker__status", "data-state": status.state }, /* @__PURE__ */ React.createElement("div", { className: "hc-emote-picker__status-icon" }, status.state === "working" ? "\u23F3" : status.state === "done" ? "\u2713" : "\u2715"), /* @__PURE__ */ React.createElement("div", { className: "hc-emote-picker__status-title" }, status.state === "working" ? `\u6B63\u5728\u590D\u5236\u5230 ${status.guild}\u2026` : status.state === "done" ? `\u5DF2\u590D\u5236\u5230 ${status.guild}` : "\u590D\u5236\u5931\u8D25"), status.state === "error" && /* @__PURE__ */ React.createElement(React.Fragment, null, /* @__PURE__ */ React.createElement("div", { className: "hc-emote-picker__status-detail" }, status.message), /* @__PURE__ */ React.createElement(
        "button",
        {
          className: "hc-btn hc-btn--secondary hc-btn--sm",
          onClick: () => setStatus({ state: "idle" })
        },
        "\u8FD4\u56DE\u5217\u8868"
      ))))
    );
  }

  // src/plugins/emote-cloner/index.tsx
  var log25 = logger("emote-cloner");
  var PERM2 = {
    CREATE_GUILD_EXPRESSIONS: 1n << 43n,
    MANAGE_GUILD_EXPRESSIONS: 1n << 40n,
    MANAGE_EMOJIS_AND_STICKERS: 1n << 30n
  };
  function canManageExpressions(guild) {
    try {
      return Boolean(
        PermissionStore.can?.(PERM2.CREATE_GUILD_EXPRESSIONS, guild) || PermissionStore.can?.(PERM2.MANAGE_GUILD_EXPRESSIONS, guild) || PermissionStore.can?.(PERM2.MANAGE_EMOJIS_AND_STICKERS, guild)
      );
    } catch {
      return false;
    }
  }
  function eligibleGuilds() {
    try {
      const map = GuildStore.getGuilds?.() ?? {};
      return Object.values(map).filter((g2) => canManageExpressions(g2)).map((g2) => ({
        id: String(g2?.id ?? ""),
        name: String(g2?.name ?? g2?.id ?? "\u672A\u77E5\u670D\u52A1\u5668"),
        icon: g2?.icon ? String(g2.icon) : null
      })).filter((g2) => g2.id).sort((a, b) => a.name.localeCompare(b.name, "zh-CN"));
    } catch {
      return [];
    }
  }
  function pickServerAndClone(hit) {
    const isEmoji = hit.kind === "emoji";
    openGuildPicker({
      title: isEmoji ? "\u590D\u5236\u8868\u60C5\u5230\u670D\u52A1\u5668" : "\u590D\u5236\u8D34\u7EB8\u5230\u670D\u52A1\u5668",
      guilds: eligibleGuilds(),
      onPick: (guildId) => isEmoji ? cloneEmoji(guildId, hit) : cloneSticker(guildId, hit)
    });
  }
  function cloneMenuPatch(children) {
    const hit = resolveExpression(getContextMenuTarget());
    if (!hit) return;
    const MenuItem = getMenuItemComponent();
    if (!MenuItem) {
      log25.warn("MenuItem component not learned yet; skipping clone item this open");
      return;
    }
    const label = hit.kind === "emoji" ? `\u590D\u5236\u8868\u60C5 :${hit.name}: \u5230\u670D\u52A1\u5668` : hit.name ? `\u590D\u5236\u8D34\u7EB8 ${hit.name} \u5230\u670D\u52A1\u5668` : "\u590D\u5236\u8D34\u7EB8\u5230\u670D\u52A1\u5668";
    children.push(
      React.createElement(MenuItem, {
        id: hit.kind === "emoji" ? "halcyon-clone-emoji" : "halcyon-clone-sticker",
        label,
        action: () => pickServerAndClone(hit)
      })
    );
  }
  var unpatchers2 = [];
  var emote_cloner_default = definePlugin({
    id: "emote-cloner",
    name: "\u8868\u60C5\u514B\u9686",
    description: "\u53F3\u952E\u4EFB\u610F\u81EA\u5B9A\u4E49\u8868\u60C5\u6216\u8D34\u7EB8\uFF0C\u5373\u53EF\u628A\u5B83\u590D\u5236\u5230\u4F60\u6709\u7BA1\u7406\u6743\u9650\u7684\u670D\u52A1\u5668\uFF08\u4FDD\u7559\u539F\u540D\uFF09\u3002\u652F\u6301\u6D88\u606F\u91CC\u7684\u8868\u60C5 / \u8868\u60C5\u56DE\u5E94 / \u8D34\u7EB8\uFF0C\u4EE5\u53CA\u8868\u60C5\u9009\u62E9\u5668\u91CC\u7684\u9879\u76EE\u3002",
    authors: [{ name: "Vencord" }, { name: "caitemm" }],
    category: "utility",
    start() {
      unpatchers2.push(addContextMenuPatch(["message", "expression-picker"], cloneMenuPatch));
      log25.info("emote-cloner ready \u2014 right-click an emoji or sticker");
    },
    stop() {
      for (const un of unpatchers2) {
        try {
          un();
        } catch {
        }
      }
      unpatchers2 = [];
    }
  });

  // src/core/flux/index.ts
  var log26 = logger("flux");
  var listenersByType = /* @__PURE__ */ new Map();
  var dispatcherHandlers = /* @__PURE__ */ new Map();
  function dispatcher() {
    const d = getDispatcher();
    if (!d) log26.error("dispatcher unavailable; flux subscriptions are inert");
    return d;
  }
  function ensureBridge(type) {
    if (dispatcherHandlers.has(type)) return;
    const handler = (action) => {
      const set = listenersByType.get(type);
      if (!set) return;
      for (const listener of set) {
        try {
          listener(action);
        } catch (err) {
          log26.error(`listener for ${type} threw`, err);
        }
      }
    };
    const d = dispatcher();
    try {
      d?.subscribe(type, handler);
      dispatcherHandlers.set(type, handler);
    } catch (err) {
      log26.error(`could not subscribe to ${type}`, err);
    }
  }
  function teardownBridge(type) {
    const set = listenersByType.get(type);
    if (set && set.size) return;
    const handler = dispatcherHandlers.get(type);
    if (!handler) return;
    try {
      dispatcher()?.unsubscribe(type, handler);
    } catch (err) {
      log26.error(`could not unsubscribe from ${type}`, err);
    }
    dispatcherHandlers.delete(type);
    listenersByType.delete(type);
  }
  var flux = {
    /**
     * Listen for a dispatched action by type. Returns an unsubscribe function.
     * The callback runs synchronously on dispatch; keep it fast and side-effect free.
     */
    subscribe(type, listener) {
      let set = listenersByType.get(type);
      if (!set) {
        set = /* @__PURE__ */ new Set();
        listenersByType.set(type, set);
      }
      set.add(listener);
      ensureBridge(type);
      let live = true;
      return () => {
        if (!live) return;
        live = false;
        set.delete(listener);
        teardownBridge(type);
      };
    },
    /** Dispatch an action. Use sparingly; most plugins only ever listen. */
    dispatch(action) {
      try {
        dispatcher()?.dispatch(action);
      } catch (err) {
        log26.error("dispatch failed", action?.type, err);
      }
    }
  };

  // src/plugins/mark-all-read/mark.ts
  var log27 = logger("mark-all-read");
  var shapeLogged = false;
  function channelIdOf(entry) {
    return entry?.channel?.id ?? entry?.id;
  }
  function collectUnread() {
    const channels = [];
    const guildsWithUnread = /* @__PURE__ */ new Set();
    const guilds = GuildStore.getGuilds?.() ?? {};
    for (const guildId of Object.keys(guilds)) {
      let grouped;
      try {
        grouped = GuildChannelStore.getChannels?.(guildId);
      } catch (err) {
        log27.warn(`could not read channels for guild ${guildId}`, err);
        continue;
      }
      if (!grouped) continue;
      const ackIfUnread = (id) => {
        if (!id) return false;
        try {
          if (!ReadStateStore.hasUnread?.(id)) return false;
        } catch {
          return false;
        }
        channels.push({
          channelId: id,
          messageId: ReadStateStore.lastMessageId?.(id) ?? null,
          readStateType: 0
        });
        return true;
      };
      if (!shapeLogged) {
        shapeLogged = true;
        try {
          const desc = Object.keys(grouped).map((k) => {
            const v = grouped[k];
            if (Array.isArray(v)) return `${k}:array(${v.length})`;
            return `${k}:${typeof v}`;
          }).join(", ");
          log27.info(`getChannels shape for guild ${guildId} \u2014 { ${desc} }`);
          for (const k of Object.keys(grouped)) {
            const v = grouped[k];
            if (Array.isArray(v) && v.length > 0) {
              log27.info(`  first "${k}" entry keys=[${Object.keys(v[0]).join(",")}]`);
              break;
            }
          }
        } catch (err) {
          log27.warn("could not describe getChannels shape", err);
        }
      }
      const buckets = [grouped.SELECTABLE, grouped.VOCAL].filter(Array.isArray);
      for (const bucket of buckets) {
        for (const entry of bucket) {
          if (ackIfUnread(channelIdOf(entry))) guildsWithUnread.add(guildId);
        }
      }
      try {
        const threadGroups = ActiveJoinedThreadsStore.getActiveJoinedThreadsForGuild?.(guildId);
        if (threadGroups && typeof threadGroups === "object") {
          for (const group of Object.values(threadGroups)) {
            if (!group || typeof group !== "object") continue;
            for (const thread of Object.values(group)) {
              if (ackIfUnread(thread?.channel?.id ?? thread?.id)) guildsWithUnread.add(guildId);
            }
          }
        }
      } catch (err) {
        log27.warn(`could not read joined threads for guild ${guildId}`, err);
      }
    }
    return { channels, guilds: guildsWithUnread.size };
  }
  function diagnoseStores() {
    const probe2 = (label, method) => `${label}=${typeof method === "function" ? "ok" : "MISSING"}`;
    log27.info(
      "store check \u2014 " + [
        probe2("GuildStore.getGuilds", GuildStore.getGuilds),
        probe2("GuildChannelStore.getChannels", GuildChannelStore.getChannels),
        probe2("ReadStateStore.hasUnread", ReadStateStore.hasUnread),
        probe2("ReadStateStore.lastMessageId", ReadStateStore.lastMessageId),
        probe2(
          "ActiveJoinedThreadsStore.getActiveJoinedThreadsForGuild",
          ActiveJoinedThreadsStore.getActiveJoinedThreadsForGuild
        )
      ].join(", ")
    );
  }
  function markAllRead() {
    diagnoseStores();
    const guildCount = Object.keys(GuildStore.getGuilds?.() ?? {}).length;
    const { channels, guilds } = collectUnread();
    log27.info(`scanned ${guildCount} guild(s); found ${channels.length} unread channel(s)`);
    if (channels.length === 0) {
      log27.info("nothing unread; skipping BULK_ACK");
      return { channels: 0, guilds: 0 };
    }
    flux.dispatch({
      type: "BULK_ACK",
      context: "APP",
      channels
    });
    log27.info(`BULK_ACK dispatched for ${channels.length} channel(s) across ${guilds} guild(s)`);
    return { channels: channels.length, guilds };
  }

  // src/plugins/mark-all-read/ui/MarkAllReadPage.tsx
  var log28 = logger("mark-all-read");
  function MarkAllReadPage() {
    const [busy, setBusy] = useState(false);
    const [state, setState] = useState("\u5F85\u673A");
    const [detail, setDetail] = useState("\u70B9\u51FB\u4E0B\u65B9\u6309\u94AE\uFF0C\u628A\u6240\u6709\u670D\u52A1\u5668\u91CC\u7684\u672A\u8BFB\u4E00\u6B21\u6027\u6E05\u7A7A\u3002");
    const onMark = () => {
      if (busy) return;
      setBusy(true);
      setState("\u5904\u7406\u4E2D");
      setDetail("\u6B63\u5728\u6536\u96C6\u672A\u8BFB\u9891\u9053\u2026");
      try {
        const result = markAllRead();
        if (result.channels === 0) {
          setState("\u5DF2\u662F\u6700\u65B0");
          setDetail("\u6CA1\u6709\u627E\u5230\u4EFB\u4F55\u672A\u8BFB\uFF0C\u65E0\u9700\u64CD\u4F5C\u3002");
          showToast("\u6CA1\u6709\u672A\u8BFB\u6D88\u606F", "info");
        } else {
          setState("\u5B8C\u6210");
          setDetail(`\u5DF2\u6E05\u7A7A ${result.guilds} \u4E2A\u670D\u52A1\u5668\u4E2D\u7684 ${result.channels} \u4E2A\u9891\u9053\u3002`);
          showToast(`\u5DF2\u6807\u8BB0 ${result.channels} \u4E2A\u9891\u9053\u4E3A\u5DF2\u8BFB`, "success");
        }
      } catch (err) {
        setState("\u5931\u8D25");
        setDetail(err?.message ?? String(err));
        showToast("\u6807\u8BB0\u5931\u8D25", "failure");
        log28.error("mark all read failed", err);
      } finally {
        setBusy(false);
      }
    };
    return /* @__PURE__ */ React.createElement("div", { className: "hc-stack" }, /* @__PURE__ */ React.createElement("div", { className: "hc-inline-note" }, /* @__PURE__ */ React.createElement(InfoIcon, { size: 18 }), /* @__PURE__ */ React.createElement("span", null, "\u4E00\u6B21\u6027\u628A", /* @__PURE__ */ React.createElement("strong", null, "\u6240\u6709\u670D\u52A1\u5668"), "\u7684\u672A\u8BFB\u6D88\u606F\u6807\u4E3A\u5DF2\u8BFB\u3002\u6807\u8BB0\u5DF2\u8BFB\u4E0D\u4F1A\u5220\u9664\u4EFB\u4F55\u6D88\u606F\uFF0C\u4F46\u65E0\u6CD5\u64A4\u9500\u3002")), /* @__PURE__ */ React.createElement(Section, { title: "\u64CD\u4F5C" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cell" }, /* @__PURE__ */ React.createElement(Button, { variant: "primary", icon: /* @__PURE__ */ React.createElement(MessageCheckIcon, { size: 16 }), disabled: busy, onClick: onMark }, "\u5168\u90E8\u6807\u4E3A\u5DF2\u8BFB"))), /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__status" }, /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__status-state" }, state), detail && /* @__PURE__ */ React.createElement("div", { className: "hc-cleaner__status-detail" }, detail)));
  }

  // src/plugins/mark-all-read/index.tsx
  var log29 = logger("mark-all-read");
  function runMark() {
    try {
      const result = markAllRead();
      if (result.channels === 0) {
        showToast("\u6CA1\u6709\u672A\u8BFB\u6D88\u606F", "info");
      } else {
        showToast(`\u5DF2\u6807\u8BB0 ${result.channels} \u4E2A\u9891\u9053\u4E3A\u5DF2\u8BFB`, "success");
      }
    } catch (err) {
      showToast("\u6807\u8BB0\u5931\u8D25", "failure");
      log29.error("mark all read failed", err);
    }
  }
  function RailButton() {
    return /* @__PURE__ */ React.createElement("div", { className: "hc-rail-item" }, /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-rail-btn",
        "aria-label": "\u5168\u90E8\u670D\u52A1\u5668\u6807\u4E3A\u5DF2\u8BFB",
        title: "\u5168\u90E8\u670D\u52A1\u5668\u6807\u4E3A\u5DF2\u8BFB",
        onClick: runMark
      },
      /* @__PURE__ */ React.createElement(MessageCheckIcon, { size: 24 })
    ));
  }
  function QuestRailButton() {
    return /* @__PURE__ */ React.createElement("div", { className: "hc-rail-item" }, /* @__PURE__ */ React.createElement(
      "button",
      {
        type: "button",
        className: "hc-rail-btn hc-quest-btn",
        "aria-label": "\u4EFB\u52A1\u4E2D\u5FC3",
        title: "\u4EFB\u52A1\u4E2D\u5FC3",
        onClick: () => {
          history.pushState(null, "", "/quest-home");
          window.dispatchEvent(new PopStateEvent("popstate"));
        }
      },
      /* @__PURE__ */ React.createElement(QuestIcon, { size: 24 })
    ));
  }
  var GUILD_MENUS = ["guild-context", "guild-header-popout"];
  var patchGuildMenu = (children) => {
    const MenuItem = getMenuItemComponent();
    if (!MenuItem) return;
    const already = children.some((c) => c?.props?.id === "hc-mark-all-read");
    if (already) return;
    children.push(
      React.createElement(MenuItem, {
        id: "hc-mark-all-read",
        label: "\u5168\u90E8\u670D\u52A1\u5668\u6807\u4E3A\u5DF2\u8BFB",
        action: runMark
      })
    );
  };
  var mark_all_read_default = definePlugin({
    id: "mark-all-read",
    name: "\u4E00\u952E\u5DF2\u8BFB",
    description: "\u5728\u670D\u52A1\u5668\u5217\u8868\u7684\u597D\u53CB\u6309\u94AE\u4E0B\u65B9\u52A0\u4E00\u4E2A\u6309\u94AE\uFF0C\u4E00\u952E\u628A\u6240\u6709\u670D\u52A1\u5668\u7684\u672A\u8BFB\u6D88\u606F\u6807\u4E3A\u5DF2\u8BFB\u3002\u4E5F\u53EF\u53F3\u952E\u4EFB\u610F\u670D\u52A1\u5668\uFF0C\u6216\u5728\u672C\u9875\u70B9\u51FB\u3002\u6807\u8BB0\u5DF2\u8BFB\u4E0D\u4F1A\u5220\u9664\u6D88\u606F\uFF0C\u4F46\u65E0\u6CD5\u64A4\u9500\u3002",
    authors: [{ name: "caitemm" }, { name: "Vencord" }],
    category: "utility",
    dependencies: ["context-menu-api"],
    // Same target and transform Vencord's ServerListAPI uses for
    // ServerListRenderPosition.Above, but anchored on the plain runtime string
    // `tutorialId:"friends-list"` instead of Vencord's build-time intl-hash macro
    // (which we can't reproduce). It wraps the friends-button element the guild
    // nav returns into an array and concatenates our button, letting Discord's own
    // SVG layout give it a real slot right after friends.
    patches: [
      {
        label: "read-all-rail-button",
        find: 'tutorialId:"friends-list"',
        replacement: {
          match: /return(\(.{0,200}?tutorialId:"friends-list".+?\}\))(?=\}function)/,
          replace: "return[$1].concat($self.renderRailButton())"
        }
      }
    ],
    /** Called from the patched guild-nav render (via `$self`). Returns the button
     *  as a keyed single-element array so it slots in right after friends. */
    renderRailButton() {
      return [
        React.createElement(RailButton, { key: "hc-mark-all-read-rail" }),
        React.createElement(QuestRailButton, { key: "hc-quest-indicator-rail" })
      ];
    },
    page: {
      title: "\u4E00\u952E\u5DF2\u8BFB",
      icon: MessageCheckIcon,
      component: MarkAllReadPage
    },
    start() {
      injectStyles();
      addContextMenuPatch(GUILD_MENUS, patchGuildMenu);
      log29.info("mark-all-read ready");
    },
    stop() {
      removeContextMenuPatch(GUILD_MENUS, patchGuildMenu);
    }
  });

  // src/plugins/silent-typing/index.ts
  var log30 = logger("silent-typing");
  var settings7 = defineSettings({
    scope: {
      group: "\u8303\u56F4",
      type: "select",
      default: "all",
      label: "\u5728\u54EA\u91CC\u9759\u9ED8",
      description: "\u53EA\u5728\u90E8\u5206\u573A\u666F\u9690\u85CF\u8F93\u5165\u72B6\u6001\u65F6\uFF0C\u5176\u4F59\u573A\u666F\u4ECD\u6309 Discord \u9ED8\u8BA4\u884C\u4E3A\u53D1\u9001\u3002",
      options: [
        { value: "all", label: "\u6240\u6709\u9891\u9053\u4E0E\u79C1\u804A" },
        { value: "guilds", label: "\u53EA\u5728\u670D\u52A1\u5668\u9891\u9053" },
        { value: "dms", label: "\u53EA\u5728\u79C1\u804A / \u7FA4\u804A" }
      ]
    },
    allowChannels: {
      group: "\u4F8B\u5916",
      type: "string-list",
      default: [],
      label: "\u4F8B\u5916\u9891\u9053 ID",
      description: "\u8FD9\u4E9B\u9891\u9053 / \u79C1\u804A\u91CC\u7167\u5E38\u53D1\u9001\u8F93\u5165\u72B6\u6001\u3002\u53F3\u952E\u9891\u9053 \u2192 \u590D\u5236\u9891\u9053 ID\uFF08\u9700\u5148\u5F00\u542F\u5F00\u53D1\u8005\u6A21\u5F0F\uFF09\u3002",
      itemPlaceholder: "\u9891\u9053 ID\uFF08\u7EAF\u6570\u5B57\uFF09"
    },
    silenceStop: {
      group: "\u9AD8\u7EA7",
      type: "boolean",
      default: false,
      label: "\u540C\u65F6\u62E6\u622A\u201C\u505C\u6B62\u8F93\u5165\u201D",
      description: "\u9ED8\u8BA4\u5173\u95ED\u3002stopTyping \u662F\u7528\u6765\u6E05\u9664\u5DF2\u7ECF\u53D1\u51FA\u53BB\u7684\u8F93\u5165\u72B6\u6001\u7684\uFF0C\u62E6\u622A\u5B83\u53CD\u800C\u53EF\u80FD\u8BA9\u6B8B\u7559\u72B6\u6001\u591A\u6302\u51E0\u79D2\uFF0C\u53EA\u6709\u5728\u4F60\u786E\u8BA4\u4ECE\u4E0D\u53D1\u9001\u65F6\u624D\u9700\u8981\u5F00\u542F\u3002"
    }
  });
  var active = false;
  var typingModule;
  var unpatchStart;
  var unpatchStop;
  var suppressed = 0;
  function isPrivateChannel(channelId) {
    try {
      const channel = ChannelStore.getChannel?.(channelId);
      if (!channel) return false;
      if (typeof channel.isPrivate === "function") return Boolean(channel.isPrivate());
      if (channel.guild_id) return false;
      return channel.type === 1 || channel.type === 3;
    } catch {
      return false;
    }
  }
  function silenceFor(channelId) {
    if (!active) return false;
    const id = channelId == null ? "" : String(channelId);
    const s = settings7.store;
    if (id && s.allowChannels.includes(id)) return false;
    if (s.scope === "guilds") return !isPrivateChannel(id);
    if (s.scope === "dms") return isPrivateChannel(id);
    return true;
  }
  function onStartTyping(ctx) {
    try {
      if (silenceFor(ctx.args[0])) {
        suppressed++;
        return void 0;
      }
    } catch (err) {
      log30.error("\u5224\u65AD\u662F\u5426\u9759\u9ED8\u65F6\u51FA\u9519\uFF0C\u672C\u6B21\u6309 Discord \u9ED8\u8BA4\u884C\u4E3A\u5904\u7406", err);
    }
    return ctx.callOriginal();
  }
  function onStopTyping(ctx) {
    try {
      if (settings7.store.silenceStop && silenceFor(ctx.args[0])) return void 0;
    } catch {
    }
    return ctx.callOriginal();
  }
  function clearCurrentTyping() {
    try {
      const channelId = SelectedChannelStore.getChannelId?.();
      if (channelId && typeof typingModule?.stopTyping === "function") {
        typingModule.stopTyping(channelId);
      }
    } catch {
    }
  }
  function reportPatch() {
    const mine = getSourcePatchReport().filter((p) => p.pluginId === "silent-typing");
    if (mine.length === 0) return;
    if (mine.every((p) => p.applied)) {
      log30.info("\u6E90\u7801 patch \u5DF2\u751F\u6548\uFF08\u8F93\u5165\u72B6\u6001\u5728\u6E90\u5934\u5C31\u88AB\u62E6\u6389\uFF09");
    } else {
      log30.warn(
        "\u6E90\u7801 patch \u672A\u5339\u914D\u5F53\u524D Discord \u7248\u672C\uFF0C\u5DF2\u6539\u7528\u8FD0\u884C\u65F6 hook \u515C\u5E95\u3002\u82E5\u53D1\u73B0\u522B\u4EBA\u4ECD\u80FD\u770B\u5230\u4F60\u7684\u8F93\u5165\u72B6\u6001\uFF0C\u8BF7\u53CD\u9988\u8FD9\u6761\u65E5\u5FD7\u3002"
      );
    }
  }
  var silent_typing_default = definePlugin({
    id: "silent-typing",
    name: "\u9759\u9ED8\u8F93\u5165",
    description: "\u4E0D\u518D\u5411\u522B\u4EBA\u53D1\u9001\u201C\u6B63\u5728\u8F93\u5165\u2026\u201D\u72B6\u6001\u3002\u53EF\u4EE5\u53EA\u5728\u670D\u52A1\u5668\u6216\u53EA\u5728\u79C1\u804A\u751F\u6548\uFF0C\u4E5F\u80FD\u4E3A\u6307\u5B9A\u9891\u9053\u5F00\u4F8B\u5916\u3002\u522B\u4EBA\u7684\u8F93\u5165\u72B6\u6001\u7167\u5E38\u663E\u793A\uFF0C\u5173\u95ED\u63D2\u4EF6\u7ACB\u5373\u6062\u590D\u3002",
    authors: [{ name: "Vencord" }, { name: "caitemm" }],
    category: "privacy",
    settings: settings7,
    patches: [
      {
        // The module that owns Discord's typing actions, identified by the local
        // action it dispatches. The replacement is a pure insertion at the top of
        // `startTyping`'s body: the whole match is a zero-width lookbehind, so no
        // existing character is consumed or moved and the code cannot become
        // unbalanced. The `:function` / `async` alternatives cover the shapes
        // Discord's minifier emits for the same method across builds.
        label: "startTyping guard",
        find: '"TYPING_START_LOCAL"',
        replacement: {
          match: /(?<=\bstartTyping\s*(?:[:=]\s*)?(?:async\s+)?(?:function\s*)?\(\s*(\w+)\s*\)\s*(?:=>\s*)?\{)/,
          replace: "if($self.shouldSilence($1))return;"
        }
      }
    ],
    start() {
      suppressed = 0;
      active = true;
      typingModule = findByProps("startTyping", "stopTyping");
      if (!typingModule || typeof typingModule.startTyping !== "function") {
        log30.warn(
          "\u672A\u627E\u5230 Discord \u7684\u8F93\u5165\u72B6\u6001\u6A21\u5757\uFF08startTyping / stopTyping\uFF09\uFF0C\u8FD0\u884C\u65F6\u515C\u5E95\u4E0D\u53EF\u7528\uFF1B\u4ECD\u4F9D\u8D56\u6E90\u7801 patch\u3002\u6253\u5F00\u4EFB\u610F\u9891\u9053\u540E\u91CD\u65B0\u542F\u7528\u63D2\u4EF6\u53EF\u518D\u8BD5\u4E00\u6B21\u3002"
        );
      } else {
        active = false;
        clearCurrentTyping();
        active = true;
        try {
          unpatchStart = patcher.instead(typingModule, "startTyping", onStartTyping);
        } catch (err) {
          log30.warn("\u6302\u63A5 startTyping \u5931\u8D25\uFF0C\u4EC5\u4F9D\u8D56\u6E90\u7801 patch", err);
        }
        if (typeof typingModule.stopTyping === "function") {
          try {
            unpatchStop = patcher.instead(typingModule, "stopTyping", onStopTyping);
          } catch (err) {
            log30.warn("\u6302\u63A5 stopTyping \u5931\u8D25\uFF0C\u201C\u540C\u65F6\u62E6\u622A\u505C\u6B62\u8F93\u5165\u201D\u5F00\u5173\u5C06\u65E0\u6548", err);
          }
        }
      }
      log30.info(`\u5DF2\u62E6\u622A\u8F93\u5165\u72B6\u6001\u4E0A\u62A5\uFF08\u8303\u56F4\uFF1A${settings7.store.scope}\uFF09`);
      setTimeout(reportPatch, 4e3);
    },
    stop() {
      active = false;
      unpatchStart?.();
      unpatchStop?.();
      unpatchStart = void 0;
      unpatchStop = void 0;
      typingModule = void 0;
      log30.info(`\u5DF2\u6062\u590D\u8F93\u5165\u72B6\u6001\u4E0A\u62A5\uFF08\u672C\u6B21\u5171\u62E6\u622A ${suppressed} \u6B21\uFF09`);
    },
    /**
     * Called from the source patch at the top of `startTyping`. Returns true to
     * abort the call. Guarded end to end: a throw here would break the compose
     * box, and the patch stays in the module until the client restarts, so this
     * must keep answering sanely even while the plugin is switched off.
     */
    shouldSilence(channelId) {
      try {
        if (!active) return false;
        if (silenceFor(channelId)) {
          suppressed++;
          return true;
        }
        return false;
      } catch {
        return false;
      }
    },
    /** Diagnostic snapshot. Surfaced through `HalcyonAPI.probe()`. */
    probe() {
      const module = typingModule ?? findByProps("startTyping", "stopTyping");
      return {
        active,
        suppressed,
        scope: settings7.store.scope,
        typingModuleFound: module != null,
        startTypingIsFunction: typeof module?.startTyping === "function",
        /** True once our wrapper is installed on the module. */
        runtimeHookInstalled: unpatchStart != null,
        sourcePatches: getSourcePatchReport().filter((p) => p.pluginId === "silent-typing"),
        currentChannelWouldBeSilenced: (() => {
          try {
            return silenceFor(SelectedChannelStore.getChannelId?.());
          } catch {
            return null;
          }
        })()
      };
    }
  });

  // src/core/dom-probe.ts
  function describe(el) {
    let box = "n/a";
    try {
      const r = el.getBoundingClientRect();
      box = `${Math.round(r.width)}x${Math.round(r.height)}@${Math.round(r.left)},${Math.round(r.top)}`;
    } catch {
    }
    return {
      tag: el.tagName.toLowerCase(),
      classes: typeof el.className === "string" ? el.className : String(el.className ?? ""),
      childCount: el.children.length,
      box
    };
  }
  function probeSelector(selector, limit = 3) {
    try {
      const found = document.querySelectorAll(selector);
      const samples = [];
      for (let i = 0; i < found.length && i < limit; i++) samples.push(describe(found[i]));
      return { selector, count: found.length, samples };
    } catch {
      return { selector, count: -1, samples: [] };
    }
  }
  function probeSelectors(selectors, limit = 2) {
    return selectors.map((s) => probeSelector(s, limit));
  }
  function classNamesContaining(needle, limit = 24) {
    const out = /* @__PURE__ */ new Set();
    try {
      const nodes = document.querySelectorAll(`[class*="${needle}"]`);
      for (let i = 0; i < nodes.length && out.size < limit; i++) {
        const raw = nodes[i].className;
        if (typeof raw !== "string") continue;
        for (const name of raw.split(/\s+/)) {
          if (name.includes(needle)) out.add(name);
          if (out.size >= limit) break;
        }
      }
    } catch {
    }
    return [...out];
  }

  // src/plugins/member-count/settings.ts
  var settings8 = defineSettings({
    placement: {
      group: "\u4F4D\u7F6E",
      type: "select",
      default: "header",
      label: "\u663E\u793A\u4F4D\u7F6E",
      description: "\u9891\u9053\u9876\u680F\u662F\u6A2A\u5411\u5DE5\u5177\u6761\uFF0C\u63D2\u4E00\u4E2A\u5C0F\u6807\u7B7E\u6700\u7A33\uFF0C\u4E5F\u662F Discord \u6CA1\u63D0\u4F9B\u6570\u5B57\u7684\u4F4D\u7F6E\uFF1B\u6210\u5458\u5217\u8868\u9876\u90E8 Discord \u81EA\u5DF1\u5DF2\u7ECF\u663E\u793A\u4E86\u300C\u5728\u7EBF X \xB7 \u5171 Y\u300D\uFF0C\u672C\u63D2\u4EF6\u5728\u90A3\u91CC\u663E\u793A\u53EA\u662F\u8986\u76D6\u540C\u4E00\u4EFD\u4FE1\u606F\uFF0C\u9009\u5B83\u524D\u8BF7\u77E5\u6089\u3002",
      options: [
        { value: "header", label: "\u9891\u9053\u9876\u680F" },
        { value: "member-list", label: "\u6210\u5458\u5217\u8868\u9876\u90E8" },
        { value: "both", label: "\u4E24\u5904\u90FD\u663E\u793A" }
      ]
    },
    showOnline: {
      group: "\u5185\u5BB9",
      type: "boolean",
      default: true,
      label: "\u663E\u793A\u5728\u7EBF\u4EBA\u6570",
      description: "\u5728\u7EBF\u4EBA\u6570\u6765\u81EA\u6210\u5458\u5217\u8868\u7684\u5206\u7EC4\u7EDF\u8BA1\uFF0C\u53EA\u6709\u6210\u5458\u5217\u8868\u6253\u5F00\u8FC7\u624D\u6709\u6570\u636E\uFF1B\u62FF\u4E0D\u5230\u65F6\u81EA\u52A8\u9690\u85CF\u3002"
    },
    showTotal: {
      group: "\u5185\u5BB9",
      type: "boolean",
      default: true,
      label: "\u663E\u793A\u603B\u6210\u5458\u6570",
      description: "\u670D\u52A1\u5668\u7684\u603B\u6210\u5458\u6570\uFF08\u542B\u79BB\u7EBF\uFF09\u3002"
    },
    abbreviate: {
      group: "\u5185\u5BB9",
      type: "boolean",
      default: false,
      label: "\u7F29\u5199\u5927\u6570\u5B57",
      description: "12,345 \u663E\u793A\u4E3A 12.3k\u3002\u5173\u95ED\u5219\u663E\u793A\u5E26\u5343\u4F4D\u5206\u9694\u7684\u5B8C\u6574\u6570\u5B57\u3002"
    },
    showLabels: {
      group: "\u5185\u5BB9",
      type: "boolean",
      default: true,
      label: "\u663E\u793A\u6587\u5B57\u6807\u7B7E",
      description: "\u663E\u793A\u201C\u5728\u7EBF / \u5171\u201D\u8FD9\u6837\u7684\u524D\u7F00\u3002\u5173\u95ED\u540E\u53EA\u5269\u6570\u5B57\u4E0E\u5706\u70B9\uFF0C\u66F4\u7D27\u51D1\u3002"
    },
    preloadCounts: {
      group: "\u9AD8\u7EA7",
      type: "boolean",
      default: true,
      label: "\u7F3A\u6570\u636E\u65F6\u8BF7\u6C42\u52A0\u8F7D",
      description: "\u5728\u7EBF\u4EBA\u6570\u4F9D\u8D56\u670D\u52A1\u5668\u7684\u6210\u5458\u5217\u8868\u6570\u636E\uFF1B\u5982\u679C\u8FD9\u6B21\u542F\u52A8\u540E\u4ECE\u6CA1\u5C55\u5F00\u8FC7\u6210\u5458\u5217\u8868\uFF0CDiscord \u6839\u672C\u6CA1\u62C9\u8FC7\u8FD9\u4EFD\u6570\u636E\u3002\u5F00\u542F\u540E\uFF0C\u9047\u5230\u7F3A\u6570\u5B57\u7684\u670D\u52A1\u5668\u4F1A\u8C03\u7528 Discord \u81EA\u5DF1\u7684\u9891\u9053\u9884\u52A0\u8F7D\uFF08\u548C\u4F60\u70B9\u8FDB\u670D\u52A1\u5668\u65F6\u4E00\u6837\u7684\u52A8\u4F5C\uFF09\uFF0C\u6BCF\u4E2A\u670D\u52A1\u5668\u6BCF\u6B21\u542F\u52A8\u53EA\u505A\u4E00\u6B21\u3002\u5173\u95ED\u5219\u53EA\u663E\u793A\u5DF2\u6709\u7684\u6570\u5B57\u3002"
    }
  });

  // src/plugins/member-count/counts.ts
  var log31 = logger("member-count");
  function memo(resolve) {
    let cached2;
    return () => cached2 ??= resolve();
  }
  var memberCountStore = memo(
    () => findStoreByName("GuildMemberCountStore") ?? findStoreWithMethods("getMemberCount")
  );
  var channelMemberStore = memo(() => findStoreByName("ChannelMemberStore"));
  var channelActions = memo(
    () => find((m) => typeof m?.preload === "function" && typeof m?.preloadAllGuilds === "function") ?? find(
      (m) => typeof m?.preload === "function" && // Reject Discord's answer-everything intl proxy.
      typeof m?.__halcyon_probe__ === "undefined"
    )
  );
  var EMPTY_COUNTS = { total: null, online: null };
  function asCount(value) {
    return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
  }
  function guildIdOfChannel2(channelId) {
    if (!channelId) return null;
    try {
      const channel = ChannelStore.getChannel?.(channelId);
      const guildId = channel?.guild_id ?? channel?.getGuildId?.();
      return guildId ? String(guildId) : null;
    } catch {
      return null;
    }
  }
  var onlineByGuild = /* @__PURE__ */ new Map();
  var totalByGuild = /* @__PURE__ */ new Map();
  var unsubscribes = [];
  function sumGroups(groups) {
    if (!Array.isArray(groups) || groups.length === 0) return null;
    if (groups.length === 1 && groups[0]?.id === "unknown") return null;
    let sum = 0;
    let sawAny = false;
    for (const group of groups) {
      if (group?.id === "offline") continue;
      const count2 = asCount(group?.count);
      if (count2 == null) continue;
      sum += count2;
      sawAny = true;
    }
    return sawAny ? sum : null;
  }
  function startCountTracking() {
    stopCountTracking();
    const remember2 = (map, guildId, value) => {
      const count2 = asCount(value);
      if (guildId != null && count2 != null) map.set(String(guildId), count2);
    };
    unsubscribes = [
      flux.subscribe("GUILD_MEMBER_LIST_UPDATE", (action) => {
        const a = action;
        const sum = sumGroups(a?.groups);
        if (a?.guildId != null && sum != null) onlineByGuild.set(String(a.guildId), sum);
        remember2(totalByGuild, a?.guildId, a?.memberCount ?? a?.member_count);
      }),
      flux.subscribe("ONLINE_GUILD_MEMBER_COUNT_UPDATE", (action) => {
        remember2(onlineByGuild, action?.guildId, action?.count);
      }),
      flux.subscribe("GUILD_CREATE", (action) => {
        const guild = action?.guild;
        remember2(totalByGuild, guild?.id, guild?.member_count ?? guild?.memberCount);
      }),
      flux.subscribe("GUILD_UPDATE", (action) => {
        const guild = action?.guild;
        remember2(totalByGuild, guild?.id, guild?.member_count ?? guild?.memberCount);
      })
    ];
  }
  function stopCountTracking() {
    for (const off of unsubscribes) {
      try {
        off();
      } catch {
      }
    }
    unsubscribes = [];
    onlineByGuild.clear();
    totalByGuild.clear();
    nudged.clear();
  }
  var nudged = /* @__PURE__ */ new Set();
  function nudge(guildId, channelId) {
    if (!settings8.store.preloadCounts) return;
    if (nudged.has(guildId)) return;
    nudged.add(guildId);
    try {
      const api = channelActions();
      if (typeof api?.preload !== "function") return;
      const target = GuildChannelStore.getDefaultChannel?.(guildId)?.id ?? channelId;
      api.preload(guildId, target);
      log31.debug(`\u5DF2\u8BF7\u6C42\u52A0\u8F7D ${guildId} \u7684\u6210\u5458\u5217\u8868\u6570\u636E`);
    } catch (err) {
      log31.debug("preload \u8C03\u7528\u5931\u8D25\uFF0C\u5FFD\u7565", err);
    }
  }
  function readTotal(guildId) {
    try {
      const fromStore = asCount(memberCountStore()?.getMemberCount?.(guildId));
      if (fromStore != null) return fromStore;
    } catch {
    }
    try {
      const guild = GuildStore.getGuild?.(guildId);
      const fromRecord = asCount(guild?.memberCount) ?? asCount(guild?.approximateMemberCount);
      if (fromRecord != null) return fromRecord;
    } catch {
    }
    return totalByGuild.get(guildId) ?? null;
  }
  function readOnline(guildId, channelId) {
    try {
      const fromStore = sumGroups(channelMemberStore()?.getProps?.(guildId, channelId)?.groups);
      if (fromStore != null) return fromStore;
    } catch {
    }
    return onlineByGuild.get(guildId) ?? null;
  }
  function readCounts(channelId) {
    const guildId = guildIdOfChannel2(channelId);
    if (!guildId || !channelId) return EMPTY_COUNTS;
    const counts = {
      total: readTotal(guildId),
      online: readOnline(guildId, String(channelId))
    };
    if (counts.total == null || counts.online == null) nudge(guildId, String(channelId));
    return counts;
  }
  function countsDiagnostics(channelId) {
    const guildId = guildIdOfChannel2(channelId);
    const safe = (fn) => {
      try {
        return fn();
      } catch (err) {
        return `threw: ${String(err)}`;
      }
    };
    return {
      channelId: channelId ?? null,
      guildId,
      stores: {
        memberCountStore: safe(() => memberCountStore()?.getName?.() ?? null),
        memberCountStoreHasMethod: safe(() => typeof memberCountStore()?.getMemberCount === "function"),
        memberCountRaw: safe(() => guildId ? memberCountStore()?.getMemberCount?.(guildId) : null),
        channelMemberStore: safe(() => channelMemberStore()?.getName?.() ?? null),
        rawGroups: safe(
          () => guildId && channelId ? channelMemberStore()?.getProps?.(guildId, String(channelId))?.groups ?? null : null
        ),
        channelActionsFound: safe(() => typeof channelActions()?.preload === "function")
      },
      guildRecord: safe(() => {
        if (!guildId) return null;
        const guild = GuildStore.getGuild?.(guildId);
        if (!guild) return null;
        return {
          memberCount: guild.memberCount ?? null,
          approximateMemberCount: guild.approximateMemberCount ?? null,
          keys: Object.keys(guild).slice(0, 30)
        };
      }),
      captured: {
        total: guildId ? totalByGuild.get(guildId) ?? null : null,
        online: guildId ? onlineByGuild.get(guildId) ?? null : null,
        trackingActive: unsubscribes.length > 0,
        nudged: [...nudged]
      },
      /** Every store this client registered whose name mentions a count/member. */
      storeNameHints: safe(
        () => storeNames().filter((n) => /member|count|presence|session/i.test(n))
      ),
      resolved: readCounts(channelId)
    };
  }
  function formatCount(value, abbreviate) {
    if (!abbreviate) return value.toLocaleString("en-US");
    if (value < 1e3) return String(value);
    if (value < 1e6) {
      const k = value / 1e3;
      return `${k < 10 ? k.toFixed(1) : Math.round(k)}k`;
    }
    const m = value / 1e6;
    return `${m < 10 ? m.toFixed(1) : Math.round(m)}m`;
  }

  // src/plugins/member-count/ui/MemberCountChip.tsx
  var WATCHED_ACTIONS = [
    "CHANNEL_SELECT",
    "GUILD_MEMBER_LIST_UPDATE",
    "GUILD_UPDATE",
    "GUILD_CREATE",
    "THREAD_MEMBER_LIST_UPDATE"
  ];
  var POLL_MS = 5e3;
  function sameCounts(a, b) {
    return a.total === b.total && a.online === b.online;
  }
  function useMemberCounts() {
    const [counts, setCounts] = useState(EMPTY_COUNTS);
    useEffect(() => {
      let live = true;
      const refresh = () => {
        if (!live) return;
        let next;
        try {
          next = readCounts(SelectedChannelStore.getChannelId?.());
        } catch {
          next = EMPTY_COUNTS;
        }
        setCounts((prev) => sameCounts(prev, next) ? prev : next);
      };
      refresh();
      const unsubscribes3 = WATCHED_ACTIONS.map((type) => flux.subscribe(type, refresh));
      const timer3 = setInterval(refresh, POLL_MS);
      return () => {
        live = false;
        clearInterval(timer3);
        for (const off of unsubscribes3) off();
      };
    }, []);
    return counts;
  }
  function MemberCountChip({ variant }) {
    const { total, online } = useMemberCounts();
    const s = settings8.store;
    const showOnline = s.showOnline && online != null;
    const showTotal = s.showTotal && total != null;
    if (!showOnline && !showTotal) return null;
    const parts = [];
    if (showOnline) parts.push(`\u5728\u7EBF ${online.toLocaleString("en-US")}`);
    if (showTotal) parts.push(`\u603B\u6210\u5458 ${total.toLocaleString("en-US")}`);
    return /* @__PURE__ */ React.createElement(
      "div",
      {
        className: `hc-membercount hc-membercount--${variant}`,
        title: parts.join(" \xB7 "),
        "aria-label": parts.join("\uFF0C")
      },
      /* @__PURE__ */ React.createElement(PeopleIcon, { size: 14, className: "hc-membercount__icon" }),
      showOnline && /* @__PURE__ */ React.createElement("span", { className: "hc-membercount__part" }, /* @__PURE__ */ React.createElement("span", { className: "hc-membercount__dot" }), s.showLabels && /* @__PURE__ */ React.createElement("span", { className: "hc-membercount__label" }, "\u5728\u7EBF"), /* @__PURE__ */ React.createElement("span", { className: "hc-membercount__value" }, formatCount(online, s.abbreviate))),
      showOnline && showTotal && /* @__PURE__ */ React.createElement("span", { className: "hc-membercount__sep" }, "\xB7"),
      showTotal && /* @__PURE__ */ React.createElement("span", { className: "hc-membercount__part" }, s.showLabels && /* @__PURE__ */ React.createElement("span", { className: "hc-membercount__label" }, "\u5171"), /* @__PURE__ */ React.createElement("span", { className: "hc-membercount__value" }, formatCount(total, s.abbreviate)))
    );
  }

  // src/plugins/member-count/index.tsx
  var log32 = logger("member-count");
  var ANCHORS = {
    header: [
      'section[class*="title_"] [class*="toolbar_"]',
      'section[class*="title"] [class*="toolbar"]',
      '[class*="upperContainer"] [class*="toolbar"]',
      '[class*="chat_"] [class*="toolbar_"]',
      '[class*="toolbar_"]'
    ],
    // Most-specific first. The two "scroller" entries are what the chip should
    // actually attach to — inside the member list, above the first group header —
    // so the row reads as a natural roster header instead of floating in the
    // aside above everything. The outer `membersWrap` / `aside[class*="members"]`
    // fallbacks are kept in case a build renames the scroller, but they anchor
    // OUTSIDE the scrollable content, which is what produced the "chip floats in
    // empty space above the roster" symptom on the current build.
    list: [
      '[class*="membersWrap"] [class*="members_"]',
      'aside[class*="members"] [class*="members_"]',
      '[class*="members_"]:not([class*="membersWrap"])',
      '[class*="memberList"]',
      '[class*="membersWrap"]',
      'aside[class*="members"]'
    ]
  };
  var ENSURE_MS2 = 1e3;
  var mounted2 = /* @__PURE__ */ new Map();
  var ensureTimer;
  var selfCheckTimer;
  var unsubscribePlacement;
  var lastSelector = /* @__PURE__ */ new Map();
  var warnedNoAnchor = false;
  function firstMatch(selectors) {
    for (const selector of selectors) {
      try {
        const el = document.querySelector(selector);
        if (el) return { element: el, selector };
      } catch {
      }
    }
    return null;
  }
  function wantedVariants() {
    const placement = settings8.store.placement;
    const want = /* @__PURE__ */ new Set();
    if (placement === "header" || placement === "both") want.add("header");
    if (placement === "member-list" || placement === "both") want.add("list");
    return want;
  }
  function teardown2(variant) {
    const entry = mounted2.get(variant);
    if (!entry) return;
    mounted2.delete(variant);
    try {
      entry.unmount();
    } catch {
    }
    entry.host.remove();
  }
  function attach2(variant, hit) {
    const host5 = document.createElement("div");
    host5.className = "hc-membercount-host";
    host5.setAttribute("data-hc-plugin", "member-count");
    try {
      hit.element.insertBefore(host5, hit.element.firstChild);
    } catch (err) {
      log32.debug(`\u65E0\u6CD5\u5728 ${variant} \u4F4D\u7F6E\u63D2\u5165\u5BBF\u4E3B\u8282\u70B9`, err);
      return;
    }
    try {
      const unmount5 = mountDetached(React.createElement(MemberCountChip, { variant }), host5);
      mounted2.set(variant, { host: host5, unmount: unmount5, selector: hit.selector });
      if (lastSelector.get(variant) !== hit.selector) {
        lastSelector.set(variant, hit.selector);
        log32.info(`\u5DF2\u6302\u8F7D\u5230 ${variant}\uFF1A${hit.selector}`);
      }
    } catch (err) {
      host5.remove();
      log32.error(`\u6302\u8F7D\u6210\u5458\u6570\u6807\u7B7E\u5931\u8D25\uFF08${variant}\uFF09`, err);
    }
  }
  function ensureMounted2() {
    const want = wantedVariants();
    for (const [variant, entry] of [...mounted2]) {
      if (!want.has(variant) || !document.contains(entry.host)) teardown2(variant);
    }
    let anyAnchor = false;
    for (const variant of want) {
      if (mounted2.has(variant)) {
        anyAnchor = true;
        continue;
      }
      const hit = firstMatch(ANCHORS[variant]);
      if (!hit) continue;
      anyAnchor = true;
      attach2(variant, hit);
    }
    if (!anyAnchor && !warnedNoAnchor && mounted2.size === 0) {
      warnedNoAnchor = true;
      log32.warn(
        "\u627E\u4E0D\u5230\u53EF\u63D2\u5165\u7684\u4F4D\u7F6E\uFF08\u9891\u9053\u9876\u680F / \u6210\u5458\u5217\u8868\uFF09\u3002\u8BF7\u5148\u6253\u5F00\u4E00\u4E2A\u670D\u52A1\u5668\u9891\u9053\uFF1B\u82E5\u5DF2\u7ECF\u6253\u5F00\u8FD8\u662F\u6CA1\u6709\uFF0C\u5728\u63A7\u5236\u53F0\u8FD0\u884C HalcyonAPI.probe() \u5E76\u628A\u8F93\u51FA\u53D1\u56DE\u6765 \u2014\u2014 \u8BF4\u660E\u8FD9\u4E2A Discord \u7248\u672C\u7684\u5BB9\u5668\u7C7B\u540D\u53D8\u4E86\u3002"
      );
    }
  }
  function currentChannelId() {
    try {
      return SelectedChannelStore.getChannelId?.() ?? null;
    } catch {
      return null;
    }
  }
  function selfCheck() {
    const channelId = currentChannelId();
    if (!guildIdOfChannel2(channelId)) return;
    const { total, online } = readCounts(channelId);
    if (total != null || online != null) return;
    log32.warn(
      "\u5DF2\u6302\u8F7D\u4F46\u62FF\u4E0D\u5230\u6210\u5458\u6570\uFF08\u6240\u6709\u6570\u636E\u6E90\u90FD\u662F\u7A7A\uFF09\u3002\u4E0B\u9762\u662F\u6BCF\u4E2A\u6765\u6E90\u7684\u5B9E\u9645\u7ED3\u679C\uFF1B\u4E5F\u53EF\u4EE5\u5728\u63A7\u5236\u53F0\u8FD0\u884C HalcyonAPI.probe() \u62FF\u5230\u5B8C\u6574\u62A5\u544A\u3002",
      countsDiagnostics(channelId)
    );
  }
  var member_count_default = definePlugin({
    id: "member-count",
    name: "\u6210\u5458\u6570\u663E\u793A",
    description: "\u5728\u9891\u9053\u9876\u680F\u6216\u6210\u5458\u5217\u8868\u9876\u90E8\u663E\u793A\u5F53\u524D\u670D\u52A1\u5668\u7684\u5728\u7EBF\u4EBA\u6570\u4E0E\u603B\u6210\u5458\u6570\u3002\u6570\u5B57\u53D6\u81EA Discord \u81EA\u5DF1\u7684 store\uFF1B\u82E5\u67D0\u670D\u52A1\u5668\u8FD8\u6CA1\u6709\u6210\u5458\u5217\u8868\u6570\u636E\uFF0C\u4F1A\u8C03\u7528\u4E00\u6B21 Discord \u81EA\u8EAB\u7684\u9891\u9053\u9884\u52A0\u8F7D\u6765\u53D6\uFF08\u53EF\u5728\u8BBE\u7F6E\u91CC\u5173\u95ED\uFF09\u3002\u5207\u6362\u670D\u52A1\u5668\u81EA\u52A8\u66F4\u65B0\u3002",
    authors: [{ name: "caitemm" }],
    category: "utility",
    settings: settings8,
    start() {
      injectStyles();
      warnedNoAnchor = false;
      lastSelector.clear();
      startCountTracking();
      ensureMounted2();
      ensureTimer = setInterval(ensureMounted2, ENSURE_MS2);
      unsubscribePlacement = settings8.subscribe("placement", () => {
        warnedNoAnchor = false;
        ensureMounted2();
      });
      selfCheckTimer = setTimeout(selfCheck, 8e3);
      log32.info(`\u6210\u5458\u6570\u6807\u7B7E\u5DF2\u542F\u7528\uFF08\u4F4D\u7F6E\uFF1A${settings8.store.placement}\uFF09`);
    },
    stop() {
      if (ensureTimer) {
        clearInterval(ensureTimer);
        ensureTimer = void 0;
      }
      if (selfCheckTimer) {
        clearTimeout(selfCheckTimer);
        selfCheckTimer = void 0;
      }
      unsubscribePlacement?.();
      unsubscribePlacement = void 0;
      stopCountTracking();
      for (const variant of [...mounted2.keys()]) teardown2(variant);
      lastSelector.clear();
      log32.info("\u6210\u5458\u6570\u6807\u7B7E\u5DF2\u79FB\u9664");
    },
    /** Diagnostic snapshot. Surfaced through `HalcyonAPI.probe()`. */
    probe() {
      const channelId = currentChannelId();
      return {
        placement: settings8.store.placement,
        mounted: [...mounted2.entries()].map(([variant, entry]) => ({
          variant,
          selector: entry.selector,
          attached: document.contains(entry.host),
          renderedHtml: entry.host.innerHTML.slice(0, 200)
        })),
        anchors: {
          header: probeSelectors(ANCHORS.header),
          list: probeSelectors(ANCHORS.list)
        },
        classHints: {
          toolbar: classNamesContaining("toolbar"),
          members: classNamesContaining("members"),
          title: classNamesContaining("title_")
        },
        data: countsDiagnostics(channelId)
      };
    }
  });

  // src/plugins/who-reacted/settings.ts
  var settings9 = defineSettings({
    inlineAvatars: {
      group: "\u5E38\u9A7B\u663E\u793A",
      type: "boolean",
      default: false,
      label: "\u76F4\u63A5\u5728\u8868\u60C5\u65C1\u663E\u793A\u5934\u50CF",
      description: "\u6BCF\u4E2A\u53CD\u5E94\u5185\u5D4C\u4E00\u5C0F\u884C\u5934\u50CF\u3002\u65B0\u7248 Discord \u684C\u9762\u5BA2\u6237\u7AEF\u5DF2\u7ECF\u539F\u751F\u663E\u793A\uFF0C\u7EDD\u5927\u591A\u6570\u60C5\u51B5\u4E0B\u8FD9\u4E00\u9879\u5E94\u5173\u95ED\uFF1B\u53EA\u6709\u5F53\u4F60\u7684 Discord \u7248\u672C\u6CA1\u6709\u539F\u751F\u7684\u53CD\u5E94\u8005\u5934\u50CF\u9884\u89C8\u65F6\u624D\u5F00\u542F\uFF0C\u5426\u5219\u4F1A\u91CD\u590D\u3002"
    },
    inlineAvatarCount: {
      group: "\u5E38\u9A7B\u663E\u793A",
      type: "number",
      default: 3,
      label: "\u6700\u591A\u663E\u793A\u51E0\u4E2A\u5934\u50CF",
      description: "\u53CD\u5E94\u5185\u6700\u591A\u8D34\u51E0\u5F20\u5934\u50CF\u3002\u591A\u4F59\u7684\u4EBA\u4EE5\u300C+N\u300D\u5F62\u5F0F\u6298\u53E0\u3002",
      min: 1,
      max: 6,
      step: 1
    },
    hoverPopout: {
      group: "\u60AC\u505C\u6D6E\u5C42",
      type: "boolean",
      default: false,
      label: "\u60AC\u505C\u65F6\u5F39\u51FA\u5B8C\u6574\u540D\u5355",
      description: "\u9F20\u6807\u505C\u5728\u53CD\u5E94\u4E0A\u65F6\u5F39\u51FA\u5B8C\u6574\u53CD\u5E94\u8005\u5217\u8868\uFF08\u5E26\u540D\u5B57\u3001\u53EF\u9009 ID\uFF09\u3002\u5E38\u9A7B\u5934\u50CF\u5DF2\u7ECF\u591F\u7528\u65F6\u53EF\u4EE5\u5173\u6389\u3002"
    },
    trigger: {
      group: "\u60AC\u505C\u6D6E\u5C42",
      type: "select",
      default: "hover",
      label: "\u89E6\u53D1\u65B9\u5F0F",
      description: "\u60AC\u505C\u5373\u67E5\u4F1A\u5728\u4F60\u5212\u8FC7\u8868\u60C5\u65F6\u5C31\u8BF7\u6C42\u4E00\u6B21\u540D\u5355\uFF1B\u6309\u4F4F Alt \u60AC\u505C\u66F4\u514B\u5236\uFF0C\u9002\u5408\u4E0D\u60F3\u9891\u7E41\u89E6\u53D1\u7684\u573A\u666F\u3002\u4EC5\u5728\u300C\u60AC\u505C\u65F6\u5F39\u51FA\u5B8C\u6574\u540D\u5355\u300D\u5F00\u542F\u65F6\u751F\u6548\u3002",
      options: [
        { value: "hover", label: "\u60AC\u505C\u5373\u67E5" },
        { value: "alt-hover", label: "\u6309\u4F4F Alt \u60AC\u505C" }
      ]
    },
    delay: {
      group: "\u60AC\u505C\u6D6E\u5C42",
      type: "number",
      default: 120,
      label: "\u60AC\u505C\u5EF6\u8FDF\uFF08\u6BEB\u79D2\uFF09",
      description: "\u9F20\u6807\u505C\u7559\u591A\u4E45\u624D\u5F39\u51FA\u540D\u5355\u3002\u592A\u77ED\u4F1A\u5728\u5212\u8FC7\u4E00\u6392\u8868\u60C5\u65F6\u8FDE\u7EED\u53D1\u8BF7\u6C42\u3002\u4EC5\u5728\u300C\u60AC\u505C\u65F6\u5F39\u51FA\u5B8C\u6574\u540D\u5355\u300D\u5F00\u542F\u65F6\u751F\u6548\u3002",
      min: 0,
      max: 2e3,
      step: 50
    },
    maxUsers: {
      group: "\u663E\u793A",
      type: "number",
      default: 20,
      label: "\u6700\u591A\u663E\u793A\u4EBA\u6570",
      description: "\u8D85\u51FA\u7684\u90E8\u5206\u6298\u53E0\u4E3A\u201C\u8FD8\u6709 N \u4EBA\u201D\u3002Discord \u5355\u6B21\u6700\u591A\u8FD4\u56DE 100 \u4EBA\u3002",
      min: 1,
      max: 100,
      step: 5
    },
    showAvatars: {
      group: "\u663E\u793A",
      type: "boolean",
      default: true,
      label: "\u663E\u793A\u5934\u50CF",
      description: "\u5173\u95ED\u540E\u53EA\u663E\u793A\u540D\u5B57\uFF0C\u4E0D\u4F1A\u52A0\u8F7D\u4EFB\u4F55\u5934\u50CF\u56FE\u7247\u3002"
    },
    showIds: {
      group: "\u663E\u793A",
      type: "boolean",
      default: false,
      label: "\u663E\u793A\u7528\u6237 ID",
      description: "\u5728\u540D\u5B57\u540E\u9762\u9644\u4E0A\u7528\u6237 ID\uFF0C\u4FBF\u4E8E\u4E3E\u62A5\u6216\u62C9\u9ED1\u65F6\u590D\u5236\u3002"
    }
  });

  // src/plugins/who-reacted/reactors.ts
  var log33 = logger("who-reacted");
  var CACHE_TTL_MS = 3e4;
  function resolveReaction(node) {
    for (const props of getFiberPropsChain(node, 14)) {
      const emoji = props?.emoji;
      const message = props?.message;
      if (emoji == null || message == null) continue;
      const messageId = message.id;
      const channelId = message.channel_id ?? message.channelId;
      if (!messageId || !channelId) continue;
      if (!emoji.id && !emoji.name) continue;
      return {
        channelId: String(channelId),
        messageId: String(messageId),
        emoji,
        count: typeof props.count === "number" ? props.count : null,
        type: props.type === 1 ? 1 : 0
      };
    }
    return null;
  }
  function emojiParam(emoji) {
    const name = emoji.name ?? "";
    return emoji.id ? `${name}:${emoji.id}` : name;
  }
  function cacheKey(target) {
    return `${target.channelId}/${target.messageId}/${emojiParam(target.emoji)}/${target.type}`;
  }
  function emojiLabel(emoji) {
    if (emoji.id) return `:${emoji.name ?? "emoji"}:`;
    return emoji.name ?? "";
  }
  function defaultAvatar(userId) {
    let index = 0;
    try {
      index = Number((BigInt(userId) >> 22n) % 6n);
    } catch {
      index = 0;
    }
    return `https://cdn.discordapp.com/embed/avatars/${index}.png`;
  }
  function avatarUrl(user) {
    const id = user?.id ? String(user.id) : null;
    if (!id) return null;
    const hash = user?.avatar;
    if (typeof hash !== "string" || hash.length === 0) return defaultAvatar(id);
    const ext = hash.startsWith("a_") ? "gif" : "webp";
    return `https://cdn.discordapp.com/avatars/${id}/${hash}.${ext}?size=32`;
  }
  function toReactor(user) {
    const id = user?.id ? String(user.id) : null;
    if (!id) return null;
    const name = typeof user.global_name === "string" && user.global_name || typeof user.username === "string" && user.username || id;
    return { id, name, avatarUrl: avatarUrl(user), bot: user?.bot === true };
  }
  var cache = /* @__PURE__ */ new Map();
  var inFlight = /* @__PURE__ */ new Map();
  function cachedReactors(target) {
    const entry = cache.get(cacheKey(target));
    if (!entry) return null;
    if (Date.now() - entry.at > CACHE_TTL_MS) {
      cache.delete(cacheKey(target));
      return null;
    }
    return entry.reactors;
  }
  function clearCache() {
    cache.clear();
    inFlight.clear();
  }
  function fetchReactors(target, limit) {
    const key = cacheKey(target);
    const fresh = cachedReactors(target);
    if (fresh) return Promise.resolve(fresh);
    const pending = inFlight.get(key);
    if (pending) return pending;
    const capped = Math.max(1, Math.min(100, Math.trunc(limit) || 20));
    const url = `/channels/${target.channelId}/messages/${target.messageId}/reactions/${encodeURIComponent(emojiParam(target.emoji))}?limit=${capped}` + (target.type === 1 ? "&type=1" : "");
    const request = (async () => {
      const api = RestAPI;
      if (typeof api?.get !== "function") {
        throw new Error("\u672A\u627E\u5230 Discord \u7684 REST \u6A21\u5757");
      }
      const response = await api.get({ url, oldFormErrors: true });
      const body = response?.body;
      if (!Array.isArray(body)) throw new Error("\u8FD4\u56DE\u5185\u5BB9\u4E0D\u662F\u7528\u6237\u5217\u8868");
      const reactors = [];
      for (const user of body) {
        const reactor = toReactor(user);
        if (reactor) reactors.push(reactor);
      }
      cache.set(key, { at: Date.now(), reactors });
      return reactors;
    })();
    const guarded = request.catch((err) => {
      log33.debug("\u62C9\u53D6 reaction \u540D\u5355\u5931\u8D25", err);
      throw err;
    });
    inFlight.set(key, guarded);
    void guarded.catch(() => void 0).then(() => inFlight.delete(key));
    return guarded;
  }

  // src/plugins/who-reacted/ui/ReactorCard.tsx
  function customEmojiUrl(emoji) {
    const ext = emoji.animated ? "gif" : "webp";
    return `https://cdn.discordapp.com/emojis/${emoji.id}.${ext}?size=32`;
  }
  function EmojiPreview({ emoji }) {
    if (emoji.id) {
      return /* @__PURE__ */ React.createElement(
        "img",
        {
          className: "hc-whoreacted__emoji-img",
          src: customEmojiUrl(emoji),
          alt: emojiLabel(emoji),
          width: 18,
          height: 18
        }
      );
    }
    return /* @__PURE__ */ React.createElement("span", { className: "hc-whoreacted__emoji-char" }, emoji.name ?? "");
  }
  function ReactorCard({ target }) {
    const s = settings9.store;
    const [state, setState] = useState(() => {
      const cached2 = cachedReactors(target);
      return cached2 ? { kind: "ready", reactors: cached2 } : { kind: "loading" };
    });
    useEffect(() => {
      let live = true;
      fetchReactors(target, s.maxUsers).then((reactors) => {
        if (live) setState({ kind: "ready", reactors });
      }).catch((err) => {
        if (!live) return;
        const message = err instanceof Error ? err.message : typeof err === "string" ? err : "\u672A\u77E5\u9519\u8BEF";
        setState({ kind: "error", message });
      });
      return () => {
        live = false;
      };
    }, []);
    const shown = state.kind === "ready" ? state.reactors.slice(0, s.maxUsers) : [];
    const total = target.count ?? (state.kind === "ready" ? state.reactors.length : null);
    const hidden = state.kind === "ready" && total != null ? Math.max(0, total - shown.length) : 0;
    return /* @__PURE__ */ React.createElement("div", { className: "hc-whoreacted" }, /* @__PURE__ */ React.createElement("div", { className: "hc-whoreacted__head" }, /* @__PURE__ */ React.createElement(EmojiPreview, { emoji: target.emoji }), /* @__PURE__ */ React.createElement("span", { className: "hc-whoreacted__title" }, "\u8C01\u70B9\u4E86\u8FD9\u4E2A\u8868\u60C5"), total != null && /* @__PURE__ */ React.createElement("span", { className: "hc-whoreacted__count" }, total)), state.kind === "loading" && /* @__PURE__ */ React.createElement("div", { className: "hc-whoreacted__hint" }, "\u6B63\u5728\u67E5\u8BE2\u2026"), state.kind === "error" && /* @__PURE__ */ React.createElement("div", { className: "hc-whoreacted__hint hc-whoreacted__hint--error" }, "\u67E5\u8BE2\u5931\u8D25\uFF1A", state.message), state.kind === "ready" && shown.length === 0 && /* @__PURE__ */ React.createElement("div", { className: "hc-whoreacted__hint" }, "\u6CA1\u6709\u4EBA\uFF08\u53EF\u80FD\u521A\u521A\u88AB\u53D6\u6D88\uFF09"), shown.length > 0 && /* @__PURE__ */ React.createElement("div", { className: "hc-whoreacted__list" }, shown.map((reactor) => /* @__PURE__ */ React.createElement("div", { className: "hc-whoreacted__row", key: reactor.id }, s.showAvatars && reactor.avatarUrl && /* @__PURE__ */ React.createElement(
      "img",
      {
        className: "hc-whoreacted__avatar",
        src: reactor.avatarUrl,
        alt: "",
        width: 20,
        height: 20
      }
    ), /* @__PURE__ */ React.createElement("span", { className: "hc-whoreacted__name" }, reactor.name), reactor.bot && /* @__PURE__ */ React.createElement("span", { className: "hc-whoreacted__tag" }, "BOT"), s.showIds && /* @__PURE__ */ React.createElement("span", { className: "hc-whoreacted__id" }, reactor.id))), hidden > 0 && /* @__PURE__ */ React.createElement("div", { className: "hc-whoreacted__more" }, "\u8FD8\u6709 ", hidden, " \u4EBA")));
  }

  // src/plugins/who-reacted/inline-avatars.ts
  var log34 = logger("who-reacted");
  var DECORATED = /* @__PURE__ */ new WeakSet();
  var HOST_ATTR = "data-hc-reactors";
  var scanTimer;
  var mutationObserver;
  var REACTION_SELECTOR = '[class*="reactionInner"], [class*="reaction_"]';
  function makeHost() {
    const host5 = document.createElement("span");
    host5.className = "hc-inline-reactors";
    host5.setAttribute(HOST_ATTR, "1");
    return host5;
  }
  function fillHost(host5, reactors, totalHint) {
    const max = Math.max(1, Math.min(6, Math.trunc(settings9.store.inlineAvatarCount) || 3));
    const shown = reactors.slice(0, max);
    const total = totalHint ?? reactors.length;
    const overflow = Math.max(0, total - shown.length);
    host5.textContent = "";
    for (const reactor of shown) {
      const img = document.createElement("img");
      img.className = "hc-inline-reactors__avatar";
      if (reactor.avatarUrl) img.src = reactor.avatarUrl;
      img.alt = "";
      img.loading = "lazy";
      img.title = reactor.name;
      img.referrerPolicy = "no-referrer";
      host5.appendChild(img);
    }
    if (shown.length > 0 && overflow > 0) {
      const more = document.createElement("span");
      more.className = "hc-inline-reactors__more";
      more.textContent = `+${overflow}`;
      host5.appendChild(more);
    }
  }
  function pillHasNativePreview(pill) {
    return pill.querySelector('img[src*="cdn.discordapp.com/avatars/"]') != null || pill.querySelector('img[src*="cdn.discordapp.com/embed/avatars/"]') != null;
  }
  async function decorate(pill) {
    if (DECORATED.has(pill)) return;
    if (pillHasNativePreview(pill)) {
      DECORATED.add(pill);
      return;
    }
    DECORATED.add(pill);
    const target = resolveReaction(pill);
    if (!target) return;
    if (target.count != null && target.count <= 0) return;
    const host5 = makeHost();
    try {
      pill.appendChild(host5);
    } catch {
      return;
    }
    try {
      const wanted = Math.min(12, Math.max(6, (settings9.store.inlineAvatarCount || 3) + 3));
      const reactors = await fetchReactors(target, wanted);
      if (!host5.isConnected) return;
      if (reactors.length === 0) {
        host5.remove();
        DECORATED.delete(pill);
        return;
      }
      if (pillHasNativePreview(pill)) {
        host5.remove();
        return;
      }
      fillHost(host5, reactors, target.count);
    } catch (err) {
      log34.debug("inline avatars: fetch failed", err);
      host5.remove();
      DECORATED.delete(pill);
    }
  }
  function scan() {
    if (!settings9.store.inlineAvatars) return;
    let pills;
    try {
      pills = document.querySelectorAll(REACTION_SELECTOR);
    } catch {
      return;
    }
    pills.forEach((pill) => {
      if (!pill.isConnected) return;
      if (DECORATED.has(pill) && !pill.querySelector(`[${HOST_ATTR}]`)) DECORATED.delete(pill);
      void decorate(pill);
    });
  }
  function startInlineAvatars() {
    if (!settings9.store.inlineAvatars) return;
    stopInlineAvatars();
    scan();
    scanTimer = setInterval(scan, 1500);
    if (typeof MutationObserver === "function") {
      mutationObserver = new MutationObserver((records) => {
        for (const record2 of records) {
          record2.addedNodes.forEach((node) => {
            if (!(node instanceof Element)) return;
            if (node.matches?.(REACTION_SELECTOR)) void decorate(node);
            node.querySelectorAll?.(REACTION_SELECTOR).forEach((pill) => void decorate(pill));
          });
        }
      });
      try {
        mutationObserver.observe(document.body, { childList: true, subtree: true });
      } catch {
      }
    }
    log34.info("inline reactor avatars: enabled");
  }
  function stopInlineAvatars() {
    if (scanTimer) {
      clearInterval(scanTimer);
      scanTimer = void 0;
    }
    if (mutationObserver) {
      try {
        mutationObserver.disconnect();
      } catch {
      }
      mutationObserver = void 0;
    }
    try {
      document.querySelectorAll(`[${HOST_ATTR}]`).forEach((host5) => host5.remove());
    } catch {
    }
  }

  // src/plugins/who-reacted/index.tsx
  var log35 = logger("who-reacted");
  var REACTION_SELECTOR2 = '[class*="reactionInner"], [class*="reaction_"]';
  var HIDE_GRACE_MS = 140;
  var ANCHOR_CHECK_MS = 500;
  var host4 = null;
  var unmount4 = null;
  var anchor = null;
  var observer = null;
  var anchorTimer;
  var hovered = null;
  var showTimer;
  var hideTimer;
  var altDown = false;
  var inlineToggleUnsub;
  var inlineCountUnsub;
  var hoverToggleUnsub;
  var hoverListenersAttached = false;
  function reposition() {
    if (!host4 || !anchor) return;
    const rect = anchor.getBoundingClientRect();
    const width = host4.offsetWidth || 220;
    const height = host4.offsetHeight || 110;
    const margin = 8;
    let left = rect.left + rect.width / 2 - width / 2;
    let top = rect.top - height - margin;
    if (top < margin) top = rect.bottom + margin;
    left = Math.max(margin, Math.min(left, window.innerWidth - width - margin));
    top = Math.max(margin, Math.min(top, window.innerHeight - height - margin));
    host4.style.left = `${Math.round(left)}px`;
    host4.style.top = `${Math.round(top)}px`;
  }
  function hide() {
    if (hideTimer) {
      clearTimeout(hideTimer);
      hideTimer = void 0;
    }
    if (anchorTimer) {
      clearInterval(anchorTimer);
      anchorTimer = void 0;
    }
    if (observer) {
      try {
        observer.disconnect();
      } catch {
      }
      observer = null;
    }
    if (unmount4) {
      try {
        unmount4();
      } catch {
      }
      unmount4 = null;
    }
    if (host4) {
      host4.remove();
      host4 = null;
    }
    anchor = null;
  }
  function scheduleHide() {
    if (!host4 || hideTimer) return;
    hideTimer = setTimeout(() => {
      hideTimer = void 0;
      hide();
    }, HIDE_GRACE_MS);
  }
  function cancelHide() {
    if (hideTimer) {
      clearTimeout(hideTimer);
      hideTimer = void 0;
    }
  }
  function show(element, target) {
    hide();
    host4 = document.createElement("div");
    host4.className = "halcyon hc-whoreacted-host";
    host4.setAttribute("data-hc-plugin", "who-reacted");
    document.body.appendChild(host4);
    anchor = element;
    try {
      unmount4 = mountDetached(React.createElement(ReactorCard, { target }), host4);
    } catch (err) {
      log35.error("\u65E0\u6CD5\u663E\u793A reaction \u540D\u5355", err);
      hide();
      return;
    }
    reposition();
    if (typeof ResizeObserver === "function") {
      observer = new ResizeObserver(() => reposition());
      observer.observe(host4);
    } else {
      setTimeout(reposition, 120);
      setTimeout(reposition, 400);
    }
    anchorTimer = setInterval(() => {
      if (!anchor || !document.contains(anchor)) hide();
    }, ANCHOR_CHECK_MS);
  }
  function triggerOpen() {
    return settings9.store.trigger !== "alt-hover" || altDown;
  }
  function tryShow(element) {
    if (!triggerOpen()) return;
    const target = resolveReaction(element);
    if (!target) return;
    show(element, target);
  }
  function clearShowTimer() {
    if (showTimer) {
      clearTimeout(showTimer);
      showTimer = void 0;
    }
  }
  function onMouseOver(event) {
    const target = event.target;
    if (!(target instanceof Element)) return;
    const pill = target.closest(REACTION_SELECTOR2);
    if (!pill) {
      hovered = null;
      clearShowTimer();
      scheduleHide();
      return;
    }
    if (pill === hovered) {
      cancelHide();
      return;
    }
    hovered = pill;
    clearShowTimer();
    cancelHide();
    const delay = Math.max(0, Math.min(2e3, settings9.store.delay));
    showTimer = setTimeout(() => {
      showTimer = void 0;
      if (hovered === pill && document.contains(pill)) tryShow(pill);
    }, delay);
  }
  function onMouseLeaveWindow() {
    hovered = null;
    clearShowTimer();
    hide();
  }
  function onKeyDown2(event) {
    if (!event.altKey) return;
    altDown = true;
    if (settings9.store.trigger === "alt-hover" && hovered && !host4) {
      if (document.contains(hovered)) tryShow(hovered);
    }
  }
  function onKeyUp(event) {
    if (event.key === "Alt" || !event.altKey) {
      altDown = false;
      if (settings9.store.trigger === "alt-hover") hide();
    }
  }
  function onScrollOrResize() {
    if (host4) hide();
  }
  function onWindowBlur() {
    altDown = false;
  }
  function attachHoverListeners() {
    if (hoverListenersAttached) return;
    hoverListenersAttached = true;
    document.addEventListener("mouseover", onMouseOver, true);
    document.addEventListener("mouseleave", onMouseLeaveWindow);
    document.addEventListener("keydown", onKeyDown2, true);
    document.addEventListener("keyup", onKeyUp, true);
    document.addEventListener("scroll", onScrollOrResize, true);
    window.addEventListener("resize", onScrollOrResize);
    window.addEventListener("blur", onWindowBlur);
  }
  function detachHoverListeners() {
    if (!hoverListenersAttached) return;
    hoverListenersAttached = false;
    document.removeEventListener("mouseover", onMouseOver, true);
    document.removeEventListener("mouseleave", onMouseLeaveWindow);
    document.removeEventListener("keydown", onKeyDown2, true);
    document.removeEventListener("keyup", onKeyUp, true);
    document.removeEventListener("scroll", onScrollOrResize, true);
    window.removeEventListener("resize", onScrollOrResize);
    window.removeEventListener("blur", onWindowBlur);
    clearShowTimer();
    hovered = null;
    altDown = false;
    hide();
  }
  var who_reacted_default = definePlugin({
    id: "who-reacted",
    name: "\u8C01\u70B9\u4E86\u8868\u60C5",
    description: "\u5728\u6BCF\u4E2A\u53CD\u5E94\u56DE\u5E94\u5185\u5D4C\u4E00\u5C0F\u884C\u5934\u50CF\uFF08\u524D\u51E0\u4E2A\u53CD\u5E94\u8005\uFF09\uFF0C\u50CF Discord \u684C\u9762\u8FD1\u7248\u7684 Reaction Preview \u4E00\u6837\uFF0C\u4E0D\u7528\u60AC\u505C\u5C31\u770B\u5F97\u5230\u3002\u540D\u5355\u6309\u9700\u67E5\u8BE2\u3001\u7F13\u5B58 30 \u79D2\u3002\u60AC\u505C\u5B8C\u6574\u540D\u5355\u6D6E\u5C42\u9ED8\u8BA4\u5173\u95ED\uFF0C\u9700\u8981\u65F6\u53EF\u5728\u8BBE\u7F6E\u91CC\u5F00\u3002",
    authors: [{ name: "Vencord" }, { name: "caitemm" }],
    category: "utility",
    settings: settings9,
    start() {
      injectStyles();
      clearCache();
      startInlineAvatars();
      inlineToggleUnsub = settings9.subscribe("inlineAvatars", (on) => {
        if (on) startInlineAvatars();
        else stopInlineAvatars();
      });
      inlineCountUnsub = settings9.subscribe("inlineAvatarCount", () => {
        stopInlineAvatars();
        startInlineAvatars();
      });
      if (settings9.store.hoverPopout) attachHoverListeners();
      hoverToggleUnsub = settings9.subscribe("hoverPopout", (on) => {
        if (on) attachHoverListeners();
        else detachHoverListeners();
      });
      log35.info(
        `\u5DF2\u542F\u7528\uFF08\u5185\u5D4C\u5934\u50CF\uFF1A${settings9.store.inlineAvatars ? "\u5F00" : "\u5173"}\uFF0C\u60AC\u505C\u6D6E\u5C42\uFF1A${settings9.store.hoverPopout ? "\u5F00" : "\u5173"}\uFF09`
      );
    },
    stop() {
      detachHoverListeners();
      inlineToggleUnsub?.();
      inlineToggleUnsub = void 0;
      inlineCountUnsub?.();
      inlineCountUnsub = void 0;
      hoverToggleUnsub?.();
      hoverToggleUnsub = void 0;
      stopInlineAvatars();
      clearShowTimer();
      hovered = null;
      altDown = false;
      hide();
      clearCache();
      log35.info("\u5DF2\u505C\u7528");
    },
    /** Diagnostic snapshot. Surfaced through `HalcyonAPI.probe()`. */
    probe() {
      let nodes = null;
      try {
        nodes = document.querySelectorAll(REACTION_SELECTOR2);
      } catch {
        nodes = null;
      }
      let sample = null;
      if (nodes && nodes.length > 0) {
        const target = resolveReaction(nodes[0]);
        sample = target ? {
          channelId: target.channelId,
          messageId: target.messageId,
          emoji: { id: target.emoji.id ?? null, name: target.emoji.name ?? null },
          count: target.count,
          type: target.type
        } : "fiber props \u91CC\u6CA1\u6709 message + emoji \u2014\u2014 \u8BF4\u660E\u8FD9\u4E2A\u7248\u672C\u7684 reaction \u7EC4\u4EF6 props \u53D8\u4E86";
      }
      return {
        trigger: settings9.store.trigger,
        cardShown: host4 != null,
        reactionNodes: nodes?.length ?? -1,
        sample,
        anchors: probeSelectors([REACTION_SELECTOR2, '[class*="reactionInner"]', '[class*="reaction_"]']),
        classHints: classNamesContaining("reaction"),
        restApiAvailable: (() => {
          try {
            return typeof RestAPI?.get === "function";
          } catch {
            return false;
          }
        })()
      };
    }
  });

  // src/plugins/platform-indicators/platforms.ts
  var PresenceStore = lazy((m) => m?.getName?.() === "PresenceStore");
  var SessionsStore = lazy((m) => m?.getName?.() === "SessionsStore");
  var PLATFORM_ORDER = ["desktop", "mobile", "web", "embedded"];
  function normalizeStatus(value) {
    return value === "online" || value === "idle" || value === "dnd" ? value : "online";
  }
  function normalizePlatform(value) {
    switch (value) {
      case "desktop":
      case "mobile":
      case "web":
      case "embedded":
        return value;
      default:
        return null;
    }
  }
  function currentUserId2() {
    try {
      const id = UserStore.getCurrentUser?.()?.id;
      return typeof id === "string" ? id : null;
    } catch {
      return null;
    }
  }
  function isBot(userId) {
    try {
      return UserStore.getUser?.(userId)?.bot === true;
    } catch {
      return false;
    }
  }
  function fromClientStatuses(userId) {
    let entry;
    try {
      const state = PresenceStore.getState?.();
      const map = state?.clientStatuses ?? state?.clientStatus;
      entry = map?.[userId];
    } catch {
      return [];
    }
    if (entry == null || typeof entry !== "object") return [];
    const out = [];
    for (const platform of PLATFORM_ORDER) {
      const status = entry[platform];
      if (status == null) continue;
      out.push({ platform, status: normalizeStatus(status) });
    }
    return out;
  }
  function fromSessions() {
    let sessions;
    try {
      sessions = SessionsStore.getSessions?.();
    } catch {
      return [];
    }
    if (sessions == null || typeof sessions !== "object") return [];
    const best = /* @__PURE__ */ new Map();
    for (const session of Object.values(sessions)) {
      if (session == null || session.sessionId === "all") continue;
      const platform = normalizePlatform(session.clientInfo?.client);
      if (!platform) continue;
      if (!best.has(platform)) best.set(platform, normalizeStatus(session.status));
    }
    const out = [];
    for (const platform of PLATFORM_ORDER) {
      const status = best.get(platform);
      if (status) out.push({ platform, status });
    }
    return out;
  }
  function readPlatforms(userId) {
    if (!userId) return [];
    if (userId === currentUserId2()) {
      const own = fromSessions();
      if (own.length) return own;
    }
    return fromClientStatuses(userId);
  }
  var COALESCE_MS = 400;
  var version = 0;
  var scheduled;
  var subscribers2 = /* @__PURE__ */ new Set();
  function presenceVersion() {
    return version;
  }
  function subscribePresence(listener) {
    subscribers2.add(listener);
    return () => {
      subscribers2.delete(listener);
    };
  }
  function bumpPresence() {
    if (scheduled) return;
    scheduled = setTimeout(() => {
      scheduled = void 0;
      version++;
      for (const listener of [...subscribers2]) {
        try {
          listener();
        } catch {
        }
      }
    }, COALESCE_MS);
  }
  function resetPresenceBus() {
    if (scheduled) {
      clearTimeout(scheduled);
      scheduled = void 0;
    }
    subscribers2.clear();
  }
  function presenceDiagnostics() {
    let presenceStore = false;
    let stateKeys = [];
    let sampleCount = null;
    try {
      const state = PresenceStore.getState?.();
      presenceStore = state != null && typeof state === "object";
      if (presenceStore) stateKeys = Object.keys(state).slice(0, 12);
      const map = state?.clientStatuses ?? state?.clientStatus;
      sampleCount = map && typeof map === "object" ? Object.keys(map).length : null;
    } catch {
      presenceStore = false;
    }
    let sessionsStore = false;
    let sessionCount = null;
    try {
      const sessions = SessionsStore.getSessions?.();
      sessionsStore = sessions != null && typeof sessions === "object";
      if (sessionsStore) sessionCount = Object.keys(sessions).length;
    } catch {
      sessionsStore = false;
    }
    return {
      PresenceStore: presenceStore,
      presenceStateKeys: stateKeys,
      clientStatusesEntries: sampleCount,
      SessionsStore: sessionsStore,
      sessionCount
    };
  }

  // src/plugins/platform-indicators/settings.ts
  var settings10 = defineSettings({
    inMessages: {
      group: "\u663E\u793A\u4F4D\u7F6E",
      type: "boolean",
      default: true,
      label: "\u6D88\u606F\u4F5C\u8005\u65C1",
      description: "\u5728\u804A\u5929\u91CC\u6BCF\u6761\u6D88\u606F\u7684\u7528\u6237\u540D\u540E\u9762\u663E\u793A\u5BF9\u65B9\u6240\u5728\u7684\u5E73\u53F0\u3002"
    },
    inMemberList: {
      group: "\u663E\u793A\u4F4D\u7F6E",
      type: "boolean",
      default: true,
      label: "\u6210\u5458\u5217\u8868",
      description: "\u5728\u53F3\u4FA7\u6210\u5458\u5217\u8868\u7684\u6BCF\u4E2A\u540D\u5B57\u540E\u9762\u663E\u793A\u5E73\u53F0\u56FE\u6807\u3002"
    },
    colorize: {
      group: "\u5916\u89C2",
      type: "select",
      default: "status",
      label: "\u56FE\u6807\u914D\u8272",
      description: "\u6309\u72B6\u6001\u7740\u8272\u65F6\uFF0C\u7EFF=\u5728\u7EBF\u3001\u9EC4=\u7A7A\u95F2\u3001\u7EA2=\u514D\u6253\u6270\uFF0C\u548C Discord \u7684\u72B6\u6001\u70B9\u4E00\u81F4\u3002",
      options: [
        { value: "status", label: "\u6309\u5728\u7EBF\u72B6\u6001\u7740\u8272" },
        { value: "muted", label: "\u7EDF\u4E00\u7070\u8272" }
      ]
    },
    iconSize: {
      group: "\u5916\u89C2",
      type: "select",
      default: "14",
      label: "\u56FE\u6807\u5927\u5C0F",
      options: [
        { value: "12", label: "12\uFF08\u6700\u5C0F\uFF09" },
        { value: "14", label: "14\uFF08\u9ED8\u8BA4\uFF09" },
        { value: "16", label: "16" },
        { value: "18", label: "18" }
      ]
    },
    ignoreBots: {
      group: "\u8FC7\u6EE4",
      type: "boolean",
      default: true,
      label: "\u5FFD\u7565\u673A\u5668\u4EBA",
      description: "\u673A\u5668\u4EBA\u51E0\u4E4E\u603B\u662F\u663E\u793A\u4E3A\u7F51\u9875\u7AEF\uFF0C\u4FE1\u606F\u91CF\u4E3A\u96F6\uFF0C\u9ED8\u8BA4\u4E0D\u663E\u793A\u3002"
    },
    ignoreSelf: {
      group: "\u8FC7\u6EE4",
      type: "boolean",
      default: false,
      label: "\u5FFD\u7565\u81EA\u5DF1",
      description: "\u4E0D\u5728\u81EA\u5DF1\u7684\u6D88\u606F\u65C1\u663E\u793A\u5E73\u53F0\u56FE\u6807\u3002"
    }
  });

  // src/plugins/platform-indicators/ui/PlatformIndicator.tsx
  var ICONS = {
    desktop: DesktopIcon,
    mobile: MobileIcon,
    web: GlobeIcon,
    embedded: GamepadIcon
  };
  var LABELS = {
    desktop: "\u684C\u9762\u5BA2\u6237\u7AEF",
    mobile: "\u624B\u673A",
    web: "\u7F51\u9875 / \u6D4F\u89C8\u5668",
    embedded: "\u6E38\u620F\u4E3B\u673A"
  };
  var STATUS_LABELS = {
    online: "\u5728\u7EBF",
    idle: "\u7A7A\u95F2",
    dnd: "\u514D\u6253\u6270",
    offline: "\u79BB\u7EBF"
  };
  function usePresenceVersion() {
    const [, setVersion] = useState(presenceVersion());
    useEffect(() => subscribePresence(() => setVersion(presenceVersion())), []);
    return presenceVersion();
  }
  function PlatformIndicator({
    userId,
    isSelf
  }) {
    usePresenceVersion();
    const s = settings10.store;
    if (s.ignoreSelf && isSelf) return null;
    if (s.ignoreBots && isBot(userId)) return null;
    const platforms = readPlatforms(userId);
    if (platforms.length === 0) return null;
    const size = Number(s.iconSize) || 14;
    const tone = s.colorize === "status";
    return /* @__PURE__ */ React.createElement("span", { className: "hc-platform" }, platforms.map(({ platform, status }) => {
      const Icon = ICONS[platform];
      const label = `${LABELS[platform]}\uFF08${STATUS_LABELS[status] ?? status}\uFF09`;
      return /* @__PURE__ */ React.createElement(
        "span",
        {
          key: platform,
          className: `hc-platform__item hc-platform__item--${tone ? status : "muted"}`,
          title: label
        },
        /* @__PURE__ */ React.createElement(Icon, { size, "aria-label": label })
      );
    }));
  }

  // src/plugins/platform-indicators/index.tsx
  var log36 = logger("platform-indicators");
  var MARK = "data-hc-platform";
  var MESSAGE_SELECTORS = [
    '[id^="message-username-"]',
    '[class*="headerText"] [class*="username"]',
    '[class*="header_"] [class*="username"]'
  ];
  var MEMBER_SELECTORS = [
    '[class*="membersWrap"] [class*="nameAndDecorators"]',
    '[class*="members"] [class*="nameAndDecorators"]',
    '[class*="nameAndDecorators"]',
    '[class*="membersWrap"] [class*="memberInner"]',
    '[class*="member_"] [class*="username"]'
  ];
  var WATCHED_ACTIONS2 = [
    "PRESENCE_UPDATES",
    "PRESENCE_UPDATE",
    "SESSIONS_REPLACE",
    "GUILD_MEMBER_LIST_UPDATE"
  ];
  var SCAN_MS = 1e3;
  var mounted3 = /* @__PURE__ */ new Map();
  var scanTimer2;
  var unsubscribes2 = [];
  function currentUserId3() {
    try {
      const id = UserStore.getCurrentUser?.()?.id;
      return typeof id === "string" ? id : null;
    } catch {
      return null;
    }
  }
  function resolveUserId(node, kind) {
    const chain = getFiberPropsChain(node, 16);
    if (kind === "message") {
      for (const props of chain) {
        const id = props?.message?.author?.id;
        if (id) return String(id);
      }
    }
    for (const props of chain) {
      const id = props?.user?.id;
      if (id) return String(id);
    }
    for (const props of chain) {
      const id = props?.message?.author?.id;
      if (id) return String(id);
    }
    return null;
  }
  function attach3(anchor2, kind, userId, selfId) {
    const host5 = document.createElement("span");
    host5.className = "hc-platform-host";
    host5.setAttribute("data-hc-plugin", "platform-indicators");
    try {
      anchor2.appendChild(host5);
    } catch {
      return false;
    }
    try {
      const unmount5 = mountDetached(
        React.createElement(PlatformIndicator, { userId, isSelf: userId === selfId }),
        host5
      );
      mounted3.set(host5, { kind, host: host5, anchor: anchor2, unmount: unmount5 });
      return true;
    } catch (err) {
      host5.remove();
      log36.debug("\u6302\u8F7D\u5E73\u53F0\u56FE\u6807\u5931\u8D25", err);
      return false;
    }
  }
  function mountInto(nodes, kind, selfId) {
    for (let i = 0; i < nodes.length; i++) {
      const anchor2 = nodes[i];
      if (anchor2.hasAttribute(MARK)) continue;
      const userId = resolveUserId(anchor2, kind);
      if (!userId) {
        anchor2.setAttribute(MARK, "0");
        continue;
      }
      anchor2.setAttribute(MARK, kind);
      if (!attach3(anchor2, kind, userId, selfId)) anchor2.removeAttribute(MARK);
    }
  }
  function detach(entry) {
    mounted3.delete(entry.host);
    try {
      entry.unmount();
    } catch {
    }
    entry.host.remove();
    try {
      entry.anchor.removeAttribute(MARK);
    } catch {
    }
  }
  function prune() {
    for (const entry of [...mounted3.values()]) {
      if (!document.contains(entry.host)) detach(entry);
    }
  }
  function detachKind(kind) {
    for (const entry of [...mounted3.values()]) {
      if (entry.kind === kind) detach(entry);
    }
  }
  function firstMatchAll(selectors) {
    for (const selector of selectors) {
      try {
        const nodes = document.querySelectorAll(selector);
        if (nodes.length > 0) return { nodes, selector };
      } catch {
      }
    }
    return null;
  }
  var lastSelector2 = /* @__PURE__ */ new Map();
  var warnedNoAnchor2 = false;
  function scanKind(kind, selectors, selfId) {
    const hit = firstMatchAll(selectors);
    if (!hit) return false;
    if (lastSelector2.get(kind) !== hit.selector) {
      lastSelector2.set(kind, hit.selector);
      log36.info(`${kind} \u951A\u70B9\uFF1A${hit.selector}\uFF08${hit.nodes.length} \u4E2A\uFF09`);
    }
    mountInto(hit.nodes, kind, selfId);
    return true;
  }
  function scan2() {
    prune();
    const s = settings10.store;
    const selfId = currentUserId3();
    let anyAnchor = false;
    if (s.inMessages && scanKind("message", MESSAGE_SELECTORS, selfId)) anyAnchor = true;
    if (s.inMemberList && scanKind("member", MEMBER_SELECTORS, selfId)) anyAnchor = true;
    if (!anyAnchor && !warnedNoAnchor2 && (s.inMessages || s.inMemberList)) {
      warnedNoAnchor2 = true;
      log36.warn(
        "\u627E\u4E0D\u5230\u53EF\u6302\u8F7D\u7684\u4F4D\u7F6E\uFF08\u6D88\u606F\u4F5C\u8005 / \u6210\u5458\u5217\u8868\uFF09\u3002\u8BF7\u5148\u6253\u5F00\u4E00\u4E2A\u6709\u6D88\u606F\u7684\u9891\u9053\uFF1B\u82E5\u5DF2\u7ECF\u6253\u5F00\u8FD8\u662F\u6CA1\u6709\uFF0C\u5728\u63A7\u5236\u53F0\u8FD0\u884C HalcyonAPI.probe() \u5E76\u628A\u8F93\u51FA\u53D1\u56DE\u6765\u3002"
      );
    }
  }
  function clearMarks() {
    try {
      for (const node of document.querySelectorAll(`[${MARK}]`)) {
        node.removeAttribute(MARK);
      }
    } catch {
    }
  }
  var platform_indicators_default = definePlugin({
    id: "platform-indicators",
    name: "\u5E73\u53F0\u6807\u8BC6",
    description: "\u5728\u6D88\u606F\u4F5C\u8005\u4E0E\u6210\u5458\u5217\u8868\u65C1\u663E\u793A\u5BF9\u65B9\u5F53\u524D\u6240\u5728\u7684\u5E73\u53F0\uFF08\u684C\u9762\u7AEF / \u624B\u673A / \u7F51\u9875 / \u6E38\u620F\u4E3B\u673A\uFF09\uFF0C\u56FE\u6807\u6309\u5728\u7EBF\u72B6\u6001\u7740\u8272\u3002\u6570\u636E\u53D6\u81EA Discord \u81EA\u5DF1\u7684\u72B6\u6001 store\uFF0C\u4E0D\u53D1\u4EFB\u4F55\u8BF7\u6C42\u3002",
    authors: [{ name: "Vencord" }, { name: "caitemm" }],
    category: "appearance",
    settings: settings10,
    start() {
      injectStyles();
      warnedNoAnchor2 = false;
      lastSelector2.clear();
      scan2();
      scanTimer2 = setInterval(scan2, SCAN_MS);
      unsubscribes2 = WATCHED_ACTIONS2.map((type) => flux.subscribe(type, bumpPresence));
      unsubscribes2.push(
        settings10.subscribe("inMessages", (on) => {
          if (!on) detachKind("message");
          else scan2();
        }),
        settings10.subscribe("inMemberList", (on) => {
          if (!on) detachKind("member");
          else scan2();
        }),
        settings10.subscribe("colorize", () => bumpPresence()),
        settings10.subscribe("iconSize", () => bumpPresence()),
        settings10.subscribe("ignoreBots", () => bumpPresence()),
        settings10.subscribe("ignoreSelf", () => bumpPresence())
      );
      log36.info("\u5E73\u53F0\u6807\u8BC6\u5DF2\u542F\u7528");
    },
    stop() {
      if (scanTimer2) {
        clearInterval(scanTimer2);
        scanTimer2 = void 0;
      }
      for (const off of unsubscribes2) {
        try {
          off();
        } catch {
        }
      }
      unsubscribes2 = [];
      for (const entry of [...mounted3.values()]) detach(entry);
      clearMarks();
      resetPresenceBus();
      lastSelector2.clear();
      log36.info("\u5E73\u53F0\u6807\u8BC6\u5DF2\u79FB\u9664");
    },
    /** Diagnostic snapshot. Surfaced through `HalcyonAPI.probe()`. */
    probe() {
      const selfId = currentUserId3();
      const messageHit = firstMatchAll(MESSAGE_SELECTORS);
      const memberHit = firstMatchAll(MEMBER_SELECTORS);
      const sample = (hit, kind) => {
        if (!hit || hit.nodes.length === 0) return null;
        const node = hit.nodes[0];
        const userId = resolveUserId(node, kind);
        return {
          selector: hit.selector,
          matches: hit.nodes.length,
          userId,
          platforms: userId ? readPlatforms(userId) : null,
          isBot: userId ? isBot(userId) : null
        };
      };
      return {
        settings: {
          inMessages: settings10.store.inMessages,
          inMemberList: settings10.store.inMemberList,
          ignoreBots: settings10.store.ignoreBots
        },
        mountedCount: mounted3.size,
        selfId,
        selfPlatforms: selfId ? readPlatforms(selfId) : null,
        message: sample(messageHit, "message"),
        member: sample(memberHit, "member"),
        anchors: {
          message: probeSelectors(MESSAGE_SELECTORS),
          member: probeSelectors(MEMBER_SELECTORS)
        },
        classHints: {
          username: classNamesContaining("username"),
          nameAndDecorators: classNamesContaining("nameAndDecorators")
        },
        stores: presenceDiagnostics()
      };
    }
  });

  // src/plugins/index.ts
  var plugins = [
    settings_host_default,
    context_menu_api_default,
    message_logger_default,
    show_username_default,
    guild_monitor_default,
    message_cleaner_default,
    fake_nitro_default,
    console_cleaner_default,
    emote_cloner_default,
    mark_all_read_default,
    silent_typing_default,
    member_count_default,
    who_reacted_default,
    platform_indicators_default
  ];

  // src/core/probe.ts
  var log37 = logger("probe");
  function probe() {
    const perPlugin = {};
    for (const view of runtime.list()) {
      const plugin = runtime.getPlugin(view.id);
      const fn = plugin?.probe;
      if (typeof fn !== "function") continue;
      try {
        perPlugin[view.id] = {
          enabled: view.enabled,
          state: view.state,
          needsRestart: view.needsRestart,
          report: fn.call(plugin)
        };
      } catch (err) {
        perPlugin[view.id] = {
          enabled: view.enabled,
          state: view.state,
          probeError: String(err)
        };
      }
    }
    const out = {
      version: true ? "0.6.1" : "dev",
      build: true ? "2026-07-28 06:19:35" : "dev",
      href: (() => {
        try {
          return location.pathname;
        } catch {
          return null;
        }
      })(),
      plugins: perPlugin,
      patches: getSourcePatchReport()
    };
    try {
      globalThis.__halcyonProbe = JSON.stringify(out, null, 2);
      log37.info("probe \u5DF2\u751F\u6210 \u2014\u2014 \u5728\u63A7\u5236\u53F0\u8FD0\u884C  copy(__halcyonProbe)  \u7136\u540E\u628A\u5185\u5BB9\u8D34\u56DE\u6765");
    } catch {
    }
    return out;
  }

  // src/userscript/main.ts
  var log38 = logger("userscript");
  runtime.registerAll(plugins);
  runtime.boot().then(() => {
    injectStyles();
    try {
      globalThis.HalcyonAPI = {
        open: openSettings,
        close: closeSettings,
        runtime,
        patchReport: () => getSourcePatchReport(),
        dumpSource: (needle, radius) => dumpFactorySource(needle, radius),
        diagnose: () => diagnoseSettings(),
        probe
      };
    } catch {
    }
    log38.info("Halcyon (userscript) ready \u2014 press Ctrl/Cmd+Shift+H to open settings");
  }).catch((err) => log38.error("userscript boot failed", err));
})();