From 04e72ee3a0d818f69df0d57a98172f09715563ba Mon Sep 17 00:00:00 2001 From: kmmiio99o <164557248+kmmiio99o@users.noreply.github.com> Date: Sun, 12 Oct 2025 01:22:49 +0200 Subject: [PATCH] Make a new pre-release called MINING Why? Well, to mine things, guessing this release will be updated someday, but eh --- scripts/build.mjs | 2 +- src/core/plugins/index.ts | 1 + src/core/plugins/themelister/index.ts | 232 ++++++++++++++++++++++++++ src/modules.d.ts | 2 +- 4 files changed, 235 insertions(+), 2 deletions(-) create mode 100644 src/core/plugins/themelister/index.ts diff --git a/scripts/build.mjs b/scripts/build.mjs index 7f0a54c..4491efa 100644 --- a/scripts/build.mjs +++ b/scripts/build.mjs @@ -74,7 +74,7 @@ const config = { constModules: { globals: { "bunny-build-info": { - version: `"1.0.1"`, + version: `"MINING"`, }, }, }, diff --git a/src/core/plugins/index.ts b/src/core/plugins/index.ts index 6657185..0ee9ede 100644 --- a/src/core/plugins/index.ts +++ b/src/core/plugins/index.ts @@ -12,6 +12,7 @@ export const getCorePlugins = (): Record => ({ "bunny.notrack": { ...require("./notrack"), preenabled: true }, "bunny.messagefix": { ...require("./messagefix") }, "bunny.fixembed": { ...require("./fixembed"), preenabled: true }, + "bunny.themelister": { ...require("./themelister"), preenabled: false }, }); /** diff --git a/src/core/plugins/themelister/index.ts b/src/core/plugins/themelister/index.ts new file mode 100644 index 0000000..984b21e --- /dev/null +++ b/src/core/plugins/themelister/index.ts @@ -0,0 +1,232 @@ +import { defineCorePlugin } from ".."; +import { showToast } from "@ui/toasts"; +import { findAssetId } from "@lib/api/assets"; +import { logger } from "@lib/utils/logger"; +import { clipboard } from "@metro/common"; +import { createFileBackend } from "@core/vendetta/storage"; +import { + semanticColors, + rawColors, + resolveSemanticColor, + isSemanticColor, +} from "@ui/color"; + +function safeStringify(obj: any, maxDepth = 5, maxLen = 200): string { + const seen = new WeakSet(); + function _stringify(value: any, depth: number): string { + if (value === null) return "null"; + if (typeof value === "undefined") return "undefined"; + if (typeof value === "string") { + if (value.length > maxLen) return value.slice(0, maxLen) + "..."; + return value; + } + if (typeof value === "number" || typeof value === "boolean") + return String(value); + if (typeof value === "function") return "[Function]"; + if (depth <= 0) return "[MaxDepth]"; + if (typeof value === "object") { + if (seen.has(value)) return "[Circular]"; + seen.add(value); + try { + if (Array.isArray(value)) { + return ( + "[" + value.map((v) => _stringify(v, depth - 1)).join(", ") + "]" + ); + } + const keys = Object.keys(value).slice(0, 100); + return ( + "{" + + keys + .map((k) => `${k}: ${_stringify(value[k], depth - 1)}`) + .join(", ") + + "}" + ); + } catch (e) { + return "[UnserializableObject]"; + } + } + try { + return String(value); + } catch (e) { + return "[Unserializable]"; + } + } + return _stringify(obj, maxDepth); +} + +function formatRawColors(rc: Record): string { + return Object.entries(rc) + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([k, v]) => `${k}: ${typeof v === "string" ? v : safeStringify(v)}`) + .join("\n"); +} + +function formatSemanticColors(sc: Record): string { + return Object.entries(sc) + .sort((a, b) => a[0].localeCompare(b[0])) + .map(([k, v]) => { + const def = typeof v === "object" ? safeStringify(v) : String(v); + let resolved = "N/A"; + try { + if (typeof resolveSemanticColor === "function") { + // Attempt to resolve using the semantic token name. This may return a color string. + // Some clients expect the token symbol itself; in those cases, resolveSemanticColor may still handle it. + resolved = resolveSemanticColor(k) ?? "N/A"; + } + } catch (e) { + // ignore resolution errors + } + // Mark if token is considered a semantic color by helper (if available) + let semanticFlag = ""; + try { + if (typeof isSemanticColor === "function" && isSemanticColor(k)) + semanticFlag = " (semantic)"; + } catch (e) { + // ignore + } + return `${k}: ${def} => ${resolved}${semanticFlag}`; + }) + .join("\n"); +} + +export default defineCorePlugin({ + manifest: { + id: "bunny.themelister", + version: "1.0.0", + type: "plugin", + spec: 3, + main: "", + display: { + name: "Theme Mining", + description: + "Mine all semantic and raw colors directly from Discord's app.", + authors: [{ name: "kmmiio99o.dev" }], + }, + }, + + async start() { + // Wait for Discord's color data to become available by polling for a short time. + // This helps when the module isn't ready immediately on plugin start. + const timeoutMs = 10000; + const intervalMs = 200; + const maxAttempts = Math.ceil(timeoutMs / intervalMs); + let attempts = 0; + + let raw = rawColors || {}; + let semantic = semanticColors || {}; + + while ( + (Object.keys(raw).length === 0 || Object.keys(semantic).length === 0) && + attempts < maxAttempts + ) { + await new Promise((res) => setTimeout(res, intervalMs)); + raw = rawColors || {}; + semantic = semanticColors || {}; + attempts++; + } + + if (Object.keys(raw).length === 0 && Object.keys(semantic).length === 0) { + // If both are empty after waiting, notify and bail out + showToast( + "Failed to retrieve Discord color definitions (timed out).", + findAssetId("CircleXIcon-primary"), + ); + logger.log( + `ThemeLister: Failed to retrieve color data after ${attempts} attempts (${timeoutMs}ms).`, + ); + return; + } + + const rawText = "Raw Colors:\n" + formatRawColors(raw); + const semanticText = "Semantic Colors:\n" + formatSemanticColors(semantic); + + const output = [rawText, "", semanticText].join("\n"); + + // Copy info to clipboard and notify user + clipboard.setString(output); + showToast( + "Discord colors & semantics copied to clipboard!", + findAssetId("PaintPaletteIcon"), + ); + + // Log the exported data + logger.log( + "ThemeLister: Exported Discord colors and semantics:\n" + output, + ); + + // Build a serializable dump to write to a file in Documents/ShiggyCord/semantics.json + const dump: any = { + generatedAt: new Date().toISOString(), + rawCount: Object.keys(raw).length, + semanticCount: Object.keys(semantic).length, + raw: {}, + semantic: {}, + resolved: {}, + semanticTokenChecks: {}, + }; + + // Serialize raw and semantic maps safely + for (const k of Object.keys(raw).sort()) { + const v = raw[k]; + dump.raw[k] = typeof v === "string" ? v : safeStringify(v); + } + + for (const k of Object.keys(semantic).sort()) { + const v = semantic[k]; + dump.semantic[k] = typeof v === "string" ? v : safeStringify(v); + + try { + dump.resolved[k] = + typeof resolveSemanticColor === "function" + ? resolveSemanticColor(k) + : null; + } catch (e) { + dump.resolved[k] = null; + } + + try { + dump.semanticTokenChecks[k] = + typeof isSemanticColor === "function" ? !!isSemanticColor(k) : null; + } catch (e) { + dump.semanticTokenChecks[k] = null; + } + } + + try { + const filename = "ShiggyCord/semantics.json"; + await createFileBackend(filename).set(dump); + + // Try to resolve an absolute path to show the user + try { + // eslint-disable-next-line @typescript-eslint/no-var-requires + const { NativeFileModule } = require("@lib/api/native/modules"); + const documents = NativeFileModule.getConstants().DocumentsDirPath; + const abs = `${documents}/${filename}`; + showToast( + `Semantic dump written to ${abs}`, + findAssetId("PaintPaletteIcon"), + ); + logger.log("ThemeLister: Wrote semantics.json to", abs); + } catch (e) { + // Fallback when NativeFileModule is not available at build-time + showToast( + "Semantic dump written to Documents/ShiggyCord/semantics.json", + findAssetId("PaintPaletteIcon"), + ); + logger.log( + "ThemeLister: Wrote semantics.json to Documents/ShiggyCord/semantics.json (absolute path unavailable)", + ); + } + } catch (e) { + showToast( + "Failed to write semantics.json", + findAssetId("CircleXIcon-primary"), + ); + logger.log("ThemeLister: Failed to write semantics.json", e); + } + }, + + stop() { + logger.log("ThemeLister: Disabled"); + }, +}); diff --git a/src/modules.d.ts b/src/modules.d.ts index 25b1e06..0bed22b 100644 --- a/src/modules.d.ts +++ b/src/modules.d.ts @@ -1,5 +1,5 @@ declare module "bunny-build-info" { - const version: "1.0.1"; + const version: "MINING"; } declare module "*.png" {