From c35789b14eea6821d12bbbdd939c96834bd30539 Mon Sep 17 00:00:00 2001
From: kmmiio99o <164557248+kmmiio99o@users.noreply.github.com>
Date: Sat, 4 Oct 2025 11:02:27 +0200
Subject: [PATCH] Add Aliucord and NekosLife commands, improve settings UI
- Add Aliucord and NekosLife commands with category browsing - Refactor
settings UI for better grouping and clarity - Improve code formatting
and consistency across commands - Add serve.mjs script for local
development server - Update .gitignore for node_modules and dist -
Update plugin authors and manifest for new features
---
.gitignore | 2 +
base_manifest.json | 9 +-
package.json | 2 +-
plugins/Commands/index.ts | 158 ++--
plugins/Commands/settings.tsx | 677 ++++++++++++------
plugins/Commands/src/commands/facts.ts | 154 ++--
plugins/Commands/src/commands/firstmessage.ts | 236 +++---
plugins/Commands/src/commands/gary.ts | 68 +-
plugins/Commands/src/commands/ip.ts | 238 +++---
plugins/Commands/src/commands/konochan.ts | 196 ++---
plugins/Commands/src/commands/lists.ts | 104 +--
plugins/Commands/src/commands/lovefemboys.ts | 411 ++++-------
plugins/Commands/src/commands/nekoslife.ts | 338 ++++-----
plugins/Commands/src/commands/petpet.ts | 72 +-
plugins/Commands/src/commands/spotify.ts | 160 ++---
plugins/Commands/src/commands/sysinfo.ts | 320 ++++-----
plugins/Commands/src/utils/api.ts | 92 +--
plugins/Commands/src/utils/messages.ts | 94 +--
serve.mjs | 81 +++
19 files changed, 1820 insertions(+), 1592 deletions(-)
create mode 100755 serve.mjs
diff --git a/.gitignore b/.gitignore
index 6eaf09d..b4bedf9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -6,3 +6,5 @@ Themer
RealThemer
ChatMesser
allcommands
+node_modules
+dist
diff --git a/base_manifest.json b/base_manifest.json
index 5bcc327..10abd5d 100644
--- a/base_manifest.json
+++ b/base_manifest.json
@@ -1,13 +1,12 @@
{
"authors": [
- {
- "name": "pylix",
- "id": "492949202121261067"
- },
{
"name": "kmmiio99o",
"id": "879393496627306587"
}
],
- "main": "src/index.ts"
+ "main": "index.js",
+ "vendetta": {
+ "icon": "ic_application_command_24px"
+ }
}
diff --git a/package.json b/package.json
index a163d01..750821e 100644
--- a/package.json
+++ b/package.json
@@ -9,11 +9,11 @@
"devDependencies": {
"@rollup/plugin-commonjs": "^24.1.0",
"@rollup/plugin-node-resolve": "^15.0.2",
+ "@swc/core": "^1.3.51",
"@types/react": "^18.0.37",
"@types/react-native": "^0.70.13",
"@typescript-eslint/eslint-plugin": "^5.59.0",
"@typescript-eslint/parser": "^5.59.0",
- "@swc/core": "^1.3.51",
"esbuild": "^0.17.17",
"eslint": "^8.38.0",
"eslint-plugin-react": "^7.32.2",
diff --git a/plugins/Commands/index.ts b/plugins/Commands/index.ts
index efd76fb..04d2be0 100644
--- a/plugins/Commands/index.ts
+++ b/plugins/Commands/index.ts
@@ -1,9 +1,9 @@
import { registerCommand } from "@vendetta/commands";
import { storage } from "@vendetta/plugin";
import {
- catFactCommand,
- dogFactCommand,
- uselessFactCommand,
+ catFactCommand,
+ dogFactCommand,
+ uselessFactCommand,
} from "./src/commands/facts";
import { pluginListCommand, themeListCommand } from "./src/commands/lists";
import { petPetCommand } from "./src/commands/petpet";
@@ -11,10 +11,10 @@ import { konoSelfCommand, konoSendCommand } from "./src/commands/konochan";
import { firstMessageCommand } from "./src/commands/firstmessage";
import { sysinfoCommand } from "./src/commands/sysinfo";
import {
- spotifyTrackCommand,
- spotifyAlbumCommand,
- spotifyArtistsCommand,
- spotifyCoverCommand
+ spotifyTrackCommand,
+ spotifyAlbumCommand,
+ spotifyArtistsCommand,
+ spotifyCoverCommand
} from "./src/commands/spotify";
import { garyCommand } from "./src/commands/gary";
import { lovefemboysCommand } from "./src/commands/lovefemboys";
@@ -24,103 +24,103 @@ import settings from "./settings";
// Initialize storage with default values
if (!storage.factSettings) {
- storage.factSettings = {
- sendAsReply: true,
- includeCitation: false,
- };
+ storage.factSettings = {
+ sendAsReply: true,
+ includeCitation: false,
+ };
}
if (!storage.listSettings) {
- storage.listSettings = {
- pluginListAlwaysDetailed: false,
- themeListAlwaysDetailed: false,
- };
+ storage.listSettings = {
+ pluginListAlwaysDetailed: false,
+ themeListAlwaysDetailed: false,
+ };
}
if (!storage.garySettings) {
- storage.garySettings = {
- imageSource: "gary",
- };
+ storage.garySettings = {
+ imageSource: "gary",
+ };
}
if (!storage.enabledCommands) {
- storage.enabledCommands = {
- catfact: true,
- dogfact: true,
- useless: true,
- petpet: true,
- pluginList: true,
- themeList: true,
- konoself: true,
- konosend: true,
- firstmessage: true,
- sysinfo: true,
- spotifyTrack: true,
- spotifyAlbum: true,
- spotifyArtists: true,
- spotifyCover: true,
- gary: true,
- ip: true, // IP command - enabled by default
- lovefemboys: false, // Hidden command - disabled by default
- nekoslife: false, // Hidden command - disabled by default
- };
+ storage.enabledCommands = {
+ catfact: true,
+ dogfact: true,
+ useless: true,
+ petpet: true,
+ pluginList: true,
+ themeList: true,
+ konoself: true,
+ konosend: true,
+ firstmessage: true,
+ sysinfo: true,
+ spotifyTrack: true,
+ spotifyAlbum: true,
+ spotifyArtists: true,
+ spotifyCover: true,
+ gary: true,
+ ip: true, // IP command - enabled by default
+ lovefemboys: false, // Hidden command - disabled by default
+ nekoslife: false, // Hidden command - disabled by default
+ };
}
// Initialize hidden settings storage
if (!storage.hiddenSettings) {
- storage.hiddenSettings = {
- enabled: false,
- visible: false,
- konochanBypassNsfw: false, // NSFW bypass option
- };
+ storage.hiddenSettings = {
+ enabled: false,
+ visible: false,
+ konochanBypassNsfw: false, // NSFW bypass option
+ };
}
// Map commands to their storage keys
const commandMap = {
- catfact: catFactCommand,
- dogfact: dogFactCommand,
- useless: uselessFactCommand,
- petpet: petPetCommand,
- pluginList: pluginListCommand,
- themeList: themeListCommand,
- konoself: konoSelfCommand,
- konosend: konoSendCommand,
- firstmessage: firstMessageCommand,
- sysinfo: sysinfoCommand,
- spotifyTrack: spotifyTrackCommand,
- spotifyAlbum: spotifyAlbumCommand,
- spotifyArtists: spotifyArtistsCommand,
- spotifyCover: spotifyCoverCommand,
- gary: garyCommand,
- ip: ipCommand, // Add IP command
- lovefemboys: lovefemboysCommand, // Add hidden command to map
- nekoslife: nekoslifeCommand, // Add NekosLife command
+ catfact: catFactCommand,
+ dogfact: dogFactCommand,
+ useless: uselessFactCommand,
+ petpet: petPetCommand,
+ pluginList: pluginListCommand,
+ themeList: themeListCommand,
+ konoself: konoSelfCommand,
+ konosend: konoSendCommand,
+ firstmessage: firstMessageCommand,
+ sysinfo: sysinfoCommand,
+ spotifyTrack: spotifyTrackCommand,
+ spotifyAlbum: spotifyAlbumCommand,
+ spotifyArtists: spotifyArtistsCommand,
+ spotifyCover: spotifyCoverCommand,
+ gary: garyCommand,
+ ip: ipCommand, // Add IP command
+ lovefemboys: lovefemboysCommand, // Add hidden command to map
+ nekoslife: nekoslifeCommand, // Add NekosLife command
};
// Store registered commands for cleanup
let commands: Array<() => void> = [];
export default {
- onLoad: () => {
- console.log("[All-In-One Commands] Loading plugin...");
+ onLoad: () => {
+ console.log("[All-In-One Commands] Loading plugin...");
- // Register only enabled commands
- for (const [key, command] of Object.entries(commandMap)) {
- if (storage.enabledCommands[key]) {
- commands.push(registerCommand(command));
- console.log(`[All-In-One Commands] Registered command: ${key}`);
- }
- }
+ // Register only enabled commands
+ for (const [key, command] of Object.entries(commandMap)) {
+ if (storage.enabledCommands[key]) {
+ commands.push(registerCommand(command));
+ console.log(`[All-In-One Commands] Registered command: ${key}`);
+ }
+ }
- console.log("[All-In-One Commands] Plugin loaded successfully!");
- },
+ console.log("[All-In-One Commands] Plugin loaded successfully!");
+ },
- onUnload: () => {
- console.log("[All-In-One Commands] Unloading plugin...");
- commands.forEach((unregister) => unregister());
- commands = [];
- console.log("[All-In-One Commands] Plugin unloaded successfully!");
- },
+ onUnload: () => {
+ console.log("[All-In-One Commands] Unloading plugin...");
+ commands.forEach((unregister) => unregister());
+ commands = [];
+ console.log("[All-In-One Commands] Plugin unloaded successfully!");
+ },
- settings,
+ settings,
};
diff --git a/plugins/Commands/settings.tsx b/plugins/Commands/settings.tsx
index 1e05d1b..6fc6ca9 100644
--- a/plugins/Commands/settings.tsx
+++ b/plugins/Commands/settings.tsx
@@ -102,15 +102,9 @@ function BetterTableRowGroup({
{title && (
{icon && (
-
+
)}
-
- {title.toUpperCase()}
-
+ {title.toUpperCase()}
)}
@@ -118,7 +112,9 @@ function BetterTableRowGroup({
{children}
- ) : children}
+ ) : (
+ children
+ )}
);
@@ -127,8 +123,10 @@ function BetterTableRowGroup({
// Header Component with secret unlock
function Header({ onHiddenUnlock }: { onHiddenUnlock?: () => void }) {
const [clickCounter, setClickCounter] = React.useState(0);
- const [clickTimeout, setClickTimeout] = React.useState(null);
-
+ const [clickTimeout, setClickTimeout] = React.useState(
+ null,
+ );
+
const styles = stylesheet.createThemedStyleSheet({
container: {
flexDirection: "column",
@@ -165,7 +163,7 @@ function Header({ onHiddenUnlock }: { onHiddenUnlock?: () => void }) {
storage.hiddenSettings.visible = !storage.hiddenSettings.visible;
showToast(
`Hidden settings ${storage.hiddenSettings.visible ? "visible" : "hidden"}`,
- getAssetIDByName("SettingsIcon")
+ getAssetIDByName("SettingsIcon"),
);
onHiddenUnlock?.();
return;
@@ -187,11 +185,14 @@ function Header({ onHiddenUnlock }: { onHiddenUnlock?: () => void }) {
return;
}
- showToast("π Hidden settings unlocked!", getAssetIDByName("CheckmarkIcon"));
+ showToast(
+ "π Hidden settings unlocked!",
+ getAssetIDByName("CheckmarkIcon"),
+ );
storage.hiddenSettings.enabled = true;
storage.hiddenSettings.visible = true;
setClickCounter(0);
-
+
onHiddenUnlock?.();
};
@@ -216,6 +217,32 @@ function NekosLifeCategoriesPage() {
flex: 1,
backgroundColor: semanticColors.BACKGROUND_PRIMARY,
},
+ tableHeader: {
+ flexDirection: "row",
+ alignItems: "center",
+ paddingVertical: 12,
+ paddingHorizontal: 16,
+ backgroundColor: semanticColors.BACKGROUND_MODIFIER_ACCENT,
+ borderBottomWidth: 2,
+ borderBottomColor: semanticColors.BORDER_STRONG,
+ },
+ headerText: {
+ fontSize: 14,
+ fontWeight: "700",
+ color: semanticColors.TEXT_NORMAL,
+ textTransform: "uppercase",
+ },
+ nameHeader: {
+ flex: 2,
+ },
+ codeHeader: {
+ flex: 2,
+ textAlign: "center",
+ },
+ badgeHeader: {
+ flex: 1,
+ textAlign: "center",
+ },
categoryItem: {
flexDirection: "row",
alignItems: "center",
@@ -228,13 +255,18 @@ function NekosLifeCategoriesPage() {
fontSize: 16,
fontWeight: "500",
color: semanticColors.TEXT_NORMAL,
- flex: 1,
+ flex: 2,
},
categoryValue: {
fontSize: 14,
color: semanticColors.TEXT_MUTED,
fontFamily: "monospace",
- marginRight: 8,
+ flex: 2,
+ textAlign: "center",
+ },
+ sfwBadgeContainer: {
+ flex: 1,
+ alignItems: "center",
},
sfwBadge: {
backgroundColor: semanticColors.STATUS_POSITIVE,
@@ -272,36 +304,56 @@ function NekosLifeCategoriesPage() {
{ name: "Tickle", value: "tickle" },
{ name: "Waifu", value: "waifu" },
{ name: "Wallpaper", value: "wallpaper" },
- { name: "Woof", value: "woof" }
+ { name: "Woof", value: "woof" },
];
// Sort categories alphabetically
- const sortedCategories = sfwCategories.sort((a, b) => a.name.localeCompare(b.name));
+ const sortedCategories = sfwCategories.sort((a, b) =>
+ a.name.localeCompare(b.name),
+ );
return (
-
+
- Total SFW Categories: {sfwCategories.length}{'\n\n'}
- Use these category names with the /nekoslife command.{'\n'}
+ Total SFW Categories: {sfwCategories.length}
+ {"\n\n"}
+ Use these category names with the /nekoslife command.{"\n"}
All categories are Safe For Work (SFW).
-
+
+ {/* Table Header */}
+
+
+ Category Name
+
+
+ Command Code
+
+
+ Type
+
+
+
+ {/* Table Rows */}
{sortedCategories.map((category, index) => (
-
- {category.name}
-
-
- {category.value}
-
-
-
- SFW
-
+ {category.name}
+ {category.value}
+
+
+ SFW
+
))}
@@ -341,16 +393,25 @@ function HiddenSettingsPage() {
return (
-
+
- These are hidden commands that may contain mature content or experimental features.
+ These are hidden commands that may contain mature content or
+ experimental features.
- Use at your own discretion. Commands in this section are disabled by default.
+ Use at your own discretion. Commands in this section are disabled by
+ default.
-
+
- }
- value={storage.enabledCommands.nekoslife}
- onValueChange={(v) => {
- storage.enabledCommands.nekoslife = v;
- storage.pendingRestart = true;
- forceUpdate();
- }}
- />
-
- }
- trailing={}
- onPress={() => navigation.push("VendettaCustomPage", {
- title: "NekosLife Categories",
- render: NekosLifeCategoriesPage,
- })}
- />
-
-
-
+
{
@@ -417,7 +458,10 @@ function HiddenSettingsPage() {
/>
-
+
{
alerts.showConfirmationAlert({
title: "Reset Hidden Settings",
- content: "This will hide the hidden settings section and disable all hidden commands. Are you sure?",
+ content:
+ "This will hide the hidden settings section and disable all hidden commands. Are you sure?",
confirmText: "Reset",
onConfirm: () => {
storage.hiddenSettings.enabled = false;
storage.hiddenSettings.visible = false;
storage.hiddenSettings.konochanBypassNsfw = false;
storage.enabledCommands.lovefemboys = false;
- storage.enabledCommands.nekoslife = false; // Reset nekoslife too
+
storage.pendingRestart = true;
- showToast("Hidden settings reset", getAssetIDByName("CheckmarkIcon"));
+ showToast(
+ "Hidden settings reset",
+ getAssetIDByName("CheckmarkIcon"),
+ );
},
cancelText: "Cancel",
});
@@ -461,13 +509,20 @@ function FactsSettingsPage() {
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
return (
-
+
-
+
}
+ leading={
+
+ }
value={storage.factSettings.sendAsReply}
onValueChange={(v) => {
storage.factSettings.sendAsReply = v;
@@ -484,11 +539,16 @@ function FactsSettingsPage() {
/>
-
+
}
+ leading={
+
+ }
value={storage.enabledCommands.catfact}
onValueChange={(v) => {
storage.enabledCommands.catfact = v;
@@ -499,7 +559,9 @@ function FactsSettingsPage() {
}
+ leading={
+
+ }
value={storage.enabledCommands.dogfact}
onValueChange={(v) => {
storage.enabledCommands.dogfact = v;
@@ -510,7 +572,9 @@ function FactsSettingsPage() {
}
+ leading={
+
+ }
value={storage.enabledCommands.useless}
onValueChange={(v) => {
storage.enabledCommands.useless = v;
@@ -528,7 +592,7 @@ function FactsSettingsPage() {
function GaryAPIPage() {
useProxy(storage);
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
-
+
const styles = stylesheet.createThemedStyleSheet({
container: {
flex: 1,
@@ -553,11 +617,16 @@ function GaryAPIPage() {
return (
-
+
}
+ leading={
+
+ }
value={storage.enabledCommands.gary}
onValueChange={(v) => {
storage.enabledCommands.gary = v;
@@ -567,13 +636,20 @@ function GaryAPIPage() {
/>
-
+
Choose which API the /gary command should use to fetch images.
-
+
-
+
- Currently using: {
- currentSource === "gary" ? "Gary API" :
- currentSource === "catapi" ? "Cat API" :
- currentSource === "minker" ? "Minker API" :
- currentSource === "goober" ? "Goober API" : "Gary API"
- }
+ Currently using:{" "}
+ {currentSource === "gary"
+ ? "Gary API"
+ : currentSource === "catapi"
+ ? "Cat API"
+ : currentSource === "minker"
+ ? "Minker API"
+ : currentSource === "goober"
+ ? "Goober API"
+ : "Gary API"}
@@ -645,13 +729,20 @@ function ListSettingsPage() {
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
return (
-
+
-
+
}
+ leading={
+
+ }
value={storage.listSettings.pluginListAlwaysDetailed}
onValueChange={(v) => {
storage.listSettings.pluginListAlwaysDetailed = v;
@@ -660,7 +751,9 @@ function ListSettingsPage() {
}
+ leading={
+
+ }
value={storage.listSettings.themeListAlwaysDetailed}
onValueChange={(v) => {
storage.listSettings.themeListAlwaysDetailed = v;
@@ -668,11 +761,16 @@ function ListSettingsPage() {
/>
-
+
}
+ leading={
+
+ }
value={storage.enabledCommands.pluginList}
onValueChange={(v) => {
storage.enabledCommands.pluginList = v;
@@ -683,7 +781,9 @@ function ListSettingsPage() {
}
+ leading={
+
+ }
value={storage.enabledCommands.themeList}
onValueChange={(v) => {
storage.enabledCommands.themeList = v;
@@ -703,13 +803,20 @@ function ImageSettingsPage() {
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
return (
-
+
-
+
}
+ leading={
+
+ }
value={storage.enabledCommands.petpet}
onValueChange={(v) => {
storage.enabledCommands.petpet = v;
@@ -719,7 +826,10 @@ function ImageSettingsPage() {
/>
-
+
+
-
+
}
+ leading={
+
+ }
value={storage.enabledCommands.spotifyTrack}
onValueChange={(v) => {
storage.enabledCommands.spotifyTrack = v;
@@ -780,7 +897,9 @@ function SpotifySettingsPage() {
}
+ leading={
+
+ }
value={storage.enabledCommands.spotifyAlbum}
onValueChange={(v) => {
storage.enabledCommands.spotifyAlbum = v;
@@ -791,7 +910,9 @@ function SpotifySettingsPage() {
}
+ leading={
+
+ }
value={storage.enabledCommands.spotifyArtists}
onValueChange={(v) => {
storage.enabledCommands.spotifyArtists = v;
@@ -802,7 +923,9 @@ function SpotifySettingsPage() {
}
+ leading={
+
+ }
value={storage.enabledCommands.spotifyCover}
onValueChange={(v) => {
storage.enabledCommands.spotifyCover = v;
@@ -812,9 +935,15 @@ function SpotifySettingsPage() {
/>
-
+
- These commands allow you to share your current Spotify activity in Discord. Make sure you have Spotify connected to Discord for these commands to work properly.
+ These commands allow you to share your current Spotify activity in
+ Discord. Make sure you have Spotify connected to Discord for these
+ commands to work properly.
@@ -828,9 +957,14 @@ function OtherSettingsPage() {
const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
return (
-
+
-
+
-
+
-
-
- }
- value={storage.enabledCommands.ip}
- onValueChange={(v) => {
- storage.enabledCommands.ip = v;
- storage.pendingRestart = true;
- forceUpdate();
- }}
- />
-
);
}
// Credits Page
+function AliucordPage() {
+ useProxy(storage);
+ const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
+ const navigation = NavigationNative.useNavigation();
+
+ return (
+
+
+ }
+ value={storage.enabledCommands.ip}
+ onValueChange={(v) => {
+ storage.enabledCommands.ip = v;
+ storage.pendingRestart = true;
+ forceUpdate();
+ }}
+ />
+
+
+
+ }
+ value={storage.enabledCommands.nekoslife}
+ onValueChange={(v) => {
+ storage.enabledCommands.nekoslife = v;
+ storage.pendingRestart = true;
+ forceUpdate();
+ }}
+ />
+ }
+ trailing={}
+ onPress={() =>
+ navigation.push("VendettaCustomPage", {
+ title: "NekosLife Categories",
+ render: NekosLifeCategoriesPage,
+ })
+ }
+ />
+
+
+
+
+ );
+}
+
function CreditsPage() {
const styles = stylesheet.createThemedStyleSheet({
container: {
@@ -941,59 +1124,59 @@ function CreditsPage() {
});
const credits = [
- {
- command: "Facts Commands",
- author: "jdev082",
+ {
+ command: "Facts Commands",
+ author: "jdev082",
avatar: "https://github.com/jdev082.png",
- github: "https://github.com/jdev082"
+ github: "https://github.com/jdev082",
},
- {
- command: "List Commands",
- author: "Kitomanari",
+ {
+ command: "List Commands",
+ author: "Kitomanari",
avatar: "https://github.com/Kitosight.png",
- github: "https://github.com/Kitosight"
+ github: "https://github.com/Kitosight",
},
- {
- command: "PetPet",
- author: "wolfieeee",
+ {
+ command: "PetPet",
+ author: "wolfieeee",
avatar: "https://github.com/WolfPlugs.png",
- github: "https://github.com/WolfPlugs"
+ github: "https://github.com/WolfPlugs",
},
- {
- command: "KonoChan Commands",
- author: "btmc727 & Rico040",
+ {
+ command: "KonoChan Commands",
+ author: "btmc727 & Rico040",
avatar: "https://github.com/OTKUSteyler.png",
- github: "https://github.com/OTKUSteyler"
+ github: "https://github.com/OTKUSteyler",
},
- {
- command: "FirstMessage Command",
- author: "sapphire",
+ {
+ command: "FirstMessage Command",
+ author: "sapphire",
avatar: "https://github.com/aeongdesu.png",
- github: "https://github.com/aeongdesu"
+ github: "https://github.com/aeongdesu",
},
- {
- command: "Sysinfo Command",
- author: "mugman",
+ {
+ command: "Sysinfo Command",
+ author: "mugman",
avatar: "https://github.com/mugman174.png",
- github: "https://github.com/mugman174"
+ github: "https://github.com/mugman174",
},
- {
- command: "Spotify Commands",
- author: "Kitomanari",
+ {
+ command: "Spotify Commands",
+ author: "Kitomanari",
avatar: "https://github.com/Kitosight.png",
- github: "https://github.com/Kitosight"
+ github: "https://github.com/Kitosight",
},
- {
- command: "Gary Command",
- author: "Zach Orange",
- avatar: "https://github.com/Zach11111.png",
- github: "https://github.com/Zach11111"
+ {
+ command: "Gary Command",
+ author: "Zach Orange",
+ avatar: "https://github.com/IAmGaryTheCat.png",
+ github: "https://github.com/IAmGaryTheCat",
},
- {
- command: "IP & NekosLife Commands",
- author: "scruzism",
+ {
+ command: "IP & NekosLife Commands",
+ author: "scruzism",
avatar: "https://github.com/scrazzz.png",
- github: "https://github.com/scrazzz"
+ github: "https://github.com/scrazzz",
},
];
@@ -1004,9 +1187,13 @@ function CreditsPage() {
return (
-
+
- Thanks to this developers for creating such a nice plugins!{'\n'}
+ Thanks to this developers for creating such a nice plugins!{"\n"}
Tap on any developer to visit their GitHub profile.
@@ -1016,24 +1203,30 @@ function CreditsPage() {
key={index}
style={styles.creditItem}
onPress={() => handleProfilePress(credit.github)}
- android_ripple={{ color: semanticColors.ANDROID_RIPPLE, radius: 200 }}
+ android_ripple={{
+ color: semanticColors.ANDROID_RIPPLE,
+ radius: 200,
+ }}
>
-
+
{credit.command}
by {credit.author}
- {credit.github.replace('https://github.com/', '@')}
+
+ {credit.github.replace("https://github.com/", "@")}
+
))}
-
+
- Commands Plugin Collection{'\n'}
- Version 1.1.0
+ Commands Plugin Collection{"\n"}
+ Version 1.2.0
@@ -1063,100 +1256,154 @@ export default function Settings() {
}, []);
return (
-
+
-
+
}
trailing={}
- onPress={() => navigation.push("VendettaCustomPage", {
- title: "Facts Commands",
- render: FactsSettingsPage,
- })}
+ onPress={() =>
+ navigation.push("VendettaCustomPage", {
+ title: "Facts Commands",
+ render: FactsSettingsPage,
+ })
+ }
/>
}
trailing={}
- onPress={() => navigation.push("VendettaCustomPage", {
- title: "List Commands",
- render: ListSettingsPage,
- })}
+ onPress={() =>
+ navigation.push("VendettaCustomPage", {
+ title: "List Commands",
+ render: ListSettingsPage,
+ })
+ }
/>
}
trailing={}
- onPress={() => navigation.push("VendettaCustomPage", {
- title: "Image Commands",
- render: ImageSettingsPage,
- })}
+ onPress={() =>
+ navigation.push("VendettaCustomPage", {
+ title: "Image Commands",
+ render: ImageSettingsPage,
+ })
+ }
/>
}
trailing={}
- onPress={() => navigation.push("VendettaCustomPage", {
- title: "Gary Commands",
- render: GaryAPIPage,
- })}
+ onPress={() =>
+ navigation.push("VendettaCustomPage", {
+ title: "Gary Commands",
+ render: GaryAPIPage,
+ })
+ }
/>
}
+ leading={
+
+ }
trailing={}
- onPress={() => navigation.push("VendettaCustomPage", {
- title: "Spotify Commands",
- render: SpotifySettingsPage,
- })}
+ onPress={() =>
+ navigation.push("VendettaCustomPage", {
+ title: "Spotify Commands",
+ render: SpotifySettingsPage,
+ })
+ }
+ />
+
+ }
+ trailing={}
+ onPress={() =>
+ navigation.push("VendettaCustomPage", {
+ title: "Aliucord",
+ render: AliucordPage,
+ })
+ }
/>
}
+ subLabel="Utility and system commands"
+ leading={}
trailing={}
- onPress={() => navigation.push("VendettaCustomPage", {
- title: "Other Commands",
- render: OtherSettingsPage,
- })}
+ onPress={() =>
+ navigation.push("VendettaCustomPage", {
+ title: "Other Commands",
+ render: OtherSettingsPage,
+ })
+ }
/>
- {storage.hiddenSettings?.enabled && storage.hiddenSettings?.visible && (
-
+ {storage.hiddenSettings?.visible && (
+
}
+ subLabel="Access to experimental and NSFW commands"
+ leading={}
trailing={}
- onPress={() => navigation.push("VendettaCustomPage", {
- title: "Hidden Settings",
- render: HiddenSettingsPage,
- })}
+ onPress={() =>
+ navigation.push("VendettaCustomPage", {
+ title: "Hidden Settings",
+ render: HiddenSettingsPage,
+ })
+ }
/>
)}
-
+
}
trailing={}
- onPress={() => navigation.push("VendettaCustomPage", {
- title: "Credits",
- render: CreditsPage,
- })}
+ onPress={() =>
+ navigation.push("VendettaCustomPage", {
+ title: "Credits",
+ render: CreditsPage,
+ })
+ }
/>
diff --git a/plugins/Commands/src/commands/facts.ts b/plugins/Commands/src/commands/facts.ts
index 1d5bf1a..d2f1188 100644
--- a/plugins/Commands/src/commands/facts.ts
+++ b/plugins/Commands/src/commands/facts.ts
@@ -6,90 +6,90 @@ const MessageActions = findByProps("sendMessage");
// Helper function to format fact response
const formatFactResponse = (fact: { text: string; source?: string }) => {
- let response = fact.text;
- if (storage.factSettings?.includeCitation && fact.source) {
- response += `\n\nSource: ${fact.source}`;
- }
- return response;
+ let response = fact.text;
+ if (storage.factSettings?.includeCitation && fact.source) {
+ response += `\n\nSource: ${fact.source}`;
+ }
+ return response;
};
export const catFactCommand = {
- name: "catfact",
- displayName: "catfact",
- description: "Sends a random cat fact.",
- displayDescription: "Sends a random cat fact.",
- applicationId: "-1",
- inputType: 1,
- type: 1,
- execute: async (args: any, ctx: any) => {
- try {
- const fact = await catFact();
- const fixNonce = Date.now().toString();
- MessageActions.sendMessage(
- ctx.channel.id,
- { content: formatFactResponse(fact) },
- void 0,
- { nonce: fixNonce }
- );
- return { type: 4 };
- } catch (error) {
- console.error('[CatFact] Error:', error);
- // Silent fail - no error message in chat
- return { type: 4 };
- }
- },
+ name: "catfact",
+ displayName: "catfact",
+ description: "Sends a random cat fact.",
+ displayDescription: "Sends a random cat fact.",
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
+ execute: async (args: any, ctx: any) => {
+ try {
+ const fact = await catFact();
+ const fixNonce = Date.now().toString();
+ MessageActions.sendMessage(
+ ctx.channel.id,
+ { content: formatFactResponse(fact) },
+ void 0,
+ { nonce: fixNonce }
+ );
+ return { type: 4 };
+ } catch (error) {
+ console.error("[CatFact] Error:", error);
+ // Silent fail - no error message in chat
+ return { type: 4 };
+ }
+ },
};
export const dogFactCommand = {
- name: "dogfact",
- displayName: "dogfact",
- description: "Sends a dog fact.",
- displayDescription: "Sends a dog fact.",
- applicationId: "-1",
- inputType: 1,
- type: 1,
- execute: async (args: any, ctx: any) => {
- try {
- const fact = await dogFact();
- const fixNonce = Date.now().toString();
- MessageActions.sendMessage(
- ctx.channel.id,
- { content: formatFactResponse(fact) },
- void 0,
- { nonce: fixNonce }
- );
- return { type: 4 };
- } catch (error) {
- console.error('[DogFact] Error:', error);
- // Silent fail - no error message in chat
- return { type: 4 };
- }
- },
+ name: "dogfact",
+ displayName: "dogfact",
+ description: "Sends a dog fact.",
+ displayDescription: "Sends a dog fact.",
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
+ execute: async (args: any, ctx: any) => {
+ try {
+ const fact = await dogFact();
+ const fixNonce = Date.now().toString();
+ MessageActions.sendMessage(
+ ctx.channel.id,
+ { content: formatFactResponse(fact) },
+ void 0,
+ { nonce: fixNonce }
+ );
+ return { type: 4 };
+ } catch (error) {
+ console.error("[DogFact] Error:", error);
+ // Silent fail - no error message in chat
+ return { type: 4 };
+ }
+ },
};
export const uselessFactCommand = {
- name: "useless",
- displayName: "useless",
- description: "Sends a useless fact.",
- displayDescription: "Sends a useless fact.",
- applicationId: "-1",
- inputType: 1,
- type: 1,
- execute: async (args: any, ctx: any) => {
- try {
- const fact = await uselessFact();
- const fixNonce = Date.now().toString();
- MessageActions.sendMessage(
- ctx.channel.id,
- { content: formatFactResponse(fact) },
- void 0,
- { nonce: fixNonce }
- );
- return { type: 4 };
- } catch (error) {
- console.error('[UselessFact] Error:', error);
- // Silent fail - no error message in chat
- return { type: 4 };
- }
- },
+ name: "useless",
+ displayName: "useless",
+ description: "Sends a useless fact.",
+ displayDescription: "Sends a useless fact.",
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
+ execute: async (args: any, ctx: any) => {
+ try {
+ const fact = await uselessFact();
+ const fixNonce = Date.now().toString();
+ MessageActions.sendMessage(
+ ctx.channel.id,
+ { content: formatFactResponse(fact) },
+ void 0,
+ { nonce: fixNonce }
+ );
+ return { type: 4 };
+ } catch (error) {
+ console.error("[UselessFact] Error:", error);
+ // Silent fail - no error message in chat
+ return { type: 4 };
+ }
+ },
};
diff --git a/plugins/Commands/src/commands/firstmessage.ts b/plugins/Commands/src/commands/firstmessage.ts
index 6aee255..bcc8032 100644
--- a/plugins/Commands/src/commands/firstmessage.ts
+++ b/plugins/Commands/src/commands/firstmessage.ts
@@ -6,130 +6,130 @@ const MessageActions = findByProps("sendMessage");
// Helper functions for fetching messages
const getFirstGuildMessage = async (
- guildId: string,
- userId?: string,
- channelId?: string,
+ guildId: string,
+ userId?: string,
+ channelId?: string,
) => {
- const userParam = userId ? `&author_id=${userId}` : "";
- const channelParam = channelId ? `&channel_id=${channelId}` : "";
- const minIdParam = userId ? "" : `&min_id=0`;
- return (
- await APIUtils.get({
- url: `/guilds/${guildId}/messages/search`,
- query: `include_nsfw=true${userParam}${channelParam}${minIdParam}&sort_by=timestamp&sort_order=asc&offset=0`,
- })
- ).body.messages[0][0];
+ const userParam = userId ? `&author_id=${userId}` : "";
+ const channelParam = channelId ? `&channel_id=${channelId}` : "";
+ const minIdParam = userId ? "" : "&min_id=0";
+ return (
+ await APIUtils.get({
+ url: `/guilds/${guildId}/messages/search`,
+ query: `include_nsfw=true${userParam}${channelParam}${minIdParam}&sort_by=timestamp&sort_order=asc&offset=0`,
+ })
+ ).body.messages[0][0];
};
const getFirstDMMessage = async (dmId: string, userId?: string) => {
- const userParam = userId ? `&author_id=${userId}` : "";
- const minIdParam = userId ? "" : `&min_id=0`;
- return (
- await APIUtils.get({
- url: `/channels/${dmId}/messages/search`,
- query: `&sort_by=timestamp&sort_order=asc&offset=0${userParam}${minIdParam}`,
- })
- ).body.messages[0][0];
+ const userParam = userId ? `&author_id=${userId}` : "";
+ const minIdParam = userId ? "" : "&min_id=0";
+ return (
+ await APIUtils.get({
+ url: `/channels/${dmId}/messages/search`,
+ query: `&sort_by=timestamp&sort_order=asc&offset=0${userParam}${minIdParam}`,
+ })
+ ).body.messages[0][0];
};
export const firstMessageCommand = {
- name: "firstmessage",
- displayName: "firstmessage",
- description: "Tired of scrolling to first message?",
- displayDescription: "Tired of scrolling to first message?",
- options: [
- {
- name: "user",
- displayName: "user",
- description: "Target user to get their first message in this server/dm",
- displayDescription: "Target user to get their first message in this server/dm",
- type: 6, // USER type
- required: false,
+ name: "firstmessage",
+ displayName: "firstmessage",
+ description: "Tired of scrolling to first message?",
+ displayDescription: "Tired of scrolling to first message?",
+ options: [
+ {
+ name: "user",
+ displayName: "user",
+ description: "Target user to get their first message in this server/dm",
+ displayDescription: "Target user to get their first message in this server/dm",
+ type: 6, // USER type
+ required: false,
+ },
+ {
+ name: "channel",
+ displayName: "channel",
+ description: "Target channel to get first message of",
+ displayDescription: "Target channel to get first message of",
+ type: 7, // CHANNEL type
+ required: false,
+ },
+ {
+ name: "send",
+ displayName: "send",
+ description: "Whether to send the resulting url",
+ displayDescription: "Whether to send the resulting url",
+ type: 5, // BOOLEAN type
+ required: false,
+ },
+ ],
+ execute: async (args: any, ctx: any) => {
+ try {
+ const options = new Map(args.map((option: any) => [option.name, option]));
+ const user = options.get("user")?.value;
+ const channel = options.get("channel")?.value;
+ const send = options.get("send")?.value;
+
+ const guildId = ctx.guild?.id;
+ const channelId = ctx.channel.id;
+ const isDM = ctx.channel.type === 1;
+
+ let result = "https://discord.com/channels/";
+
+ let message;
+ if (!user && !channel) {
+ if (isDM) {
+ message = await getFirstDMMessage(channelId);
+ result += `@me/${channelId}/${message.id}`;
+ } else {
+ message = await getFirstGuildMessage(guildId);
+ result += `${guildId}/${message.channel_id}/${message.id}`;
+ }
+ } else if (user) {
+ if (isDM) {
+ message = await getFirstDMMessage(channelId, user);
+ result += `@me/${channelId}/${message.id}`;
+ } else {
+ message = await getFirstGuildMessage(guildId, user);
+ result += `${guildId}/${message.channel_id}/${message.id}`;
+ }
+ } else if (channel) {
+ if (isDM) {
+ // Silent fail for invalid combination
+ return { type: 4 };
+ }
+ message = await getFirstGuildMessage(guildId, null, channel);
+ result += `${guildId}/${channel}/${message.id}`;
+ } else {
+ // both user and channel specified
+ if (isDM) {
+ // Silent fail for invalid combination
+ return { type: 4 };
+ }
+ message = await getFirstGuildMessage(guildId, user, channel);
+ result += `${guildId}/${channel}/${message.id}`;
+ }
+
+ if (send) {
+ const fixNonce = Date.now().toString();
+ MessageActions.sendMessage(
+ ctx.channel.id,
+ { content: result },
+ void 0,
+ { nonce: fixNonce }
+ );
+ return { type: 4 };
+ } else {
+ url.openDeeplink(result);
+ return { type: 4 };
+ }
+ } catch (error) {
+ console.error("[FirstMessage] Error:", error);
+ // Silent fail - no error message in chat
+ return { type: 4 };
+ }
},
- {
- name: "channel",
- displayName: "channel",
- description: "Target channel to get first message of",
- displayDescription: "Target channel to get first message of",
- type: 7, // CHANNEL type
- required: false,
- },
- {
- name: "send",
- displayName: "send",
- description: "Whether to send the resulting url",
- displayDescription: "Whether to send the resulting url",
- type: 5, // BOOLEAN type
- required: false,
- },
- ],
- execute: async (args: any, ctx: any) => {
- try {
- const options = new Map(args.map((option: any) => [option.name, option]));
- const user = options.get("user")?.value;
- const channel = options.get("channel")?.value;
- const send = options.get("send")?.value;
-
- const guildId = ctx.guild?.id;
- const channelId = ctx.channel.id;
- const isDM = ctx.channel.type === 1;
-
- let result = "https://discord.com/channels/";
-
- let message;
- if (!user && !channel) {
- if (isDM) {
- message = await getFirstDMMessage(channelId);
- result += `@me/${channelId}/${message.id}`;
- } else {
- message = await getFirstGuildMessage(guildId);
- result += `${guildId}/${message.channel_id}/${message.id}`;
- }
- } else if (user) {
- if (isDM) {
- message = await getFirstDMMessage(channelId, user);
- result += `@me/${channelId}/${message.id}`;
- } else {
- message = await getFirstGuildMessage(guildId, user);
- result += `${guildId}/${message.channel_id}/${message.id}`;
- }
- } else if (channel) {
- if (isDM) {
- // Silent fail for invalid combination
- return { type: 4 };
- }
- message = await getFirstGuildMessage(guildId, null, channel);
- result += `${guildId}/${channel}/${message.id}`;
- } else {
- // both user and channel specified
- if (isDM) {
- // Silent fail for invalid combination
- return { type: 4 };
- }
- message = await getFirstGuildMessage(guildId, user, channel);
- result += `${guildId}/${channel}/${message.id}`;
- }
-
- if (send) {
- const fixNonce = Date.now().toString();
- MessageActions.sendMessage(
- ctx.channel.id,
- { content: result },
- void 0,
- { nonce: fixNonce }
- );
- return { type: 4 };
- } else {
- url.openDeeplink(result);
- return { type: 4 };
- }
- } catch (error) {
- console.error("[FirstMessage] Error:", error);
- // Silent fail - no error message in chat
- return { type: 4 };
- }
- },
- applicationId: "-1",
- inputType: 1,
- type: 1,
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
};
diff --git a/plugins/Commands/src/commands/gary.ts b/plugins/Commands/src/commands/gary.ts
index fea00ce..989c30a 100644
--- a/plugins/Commands/src/commands/gary.ts
+++ b/plugins/Commands/src/commands/gary.ts
@@ -5,42 +5,42 @@ import { getGaryUrl } from "../utils/api";
const MessageActions = findByProps("sendMessage");
export const garyCommand = {
- name: "gary",
- displayName: "gary",
- description: "Send a random Gary image to the channel.",
- displayDescription: "Send a random Gary image to the channel.",
- options: [],
- execute: async (args: any, ctx: any) => {
- try {
- // Ensure garySettings exists and get the source
- if (!storage.garySettings) {
- storage.garySettings = { imageSource: "gary" };
- }
+ name: "gary",
+ displayName: "gary",
+ description: "Send a random Gary image to the channel.",
+ displayDescription: "Send a random Gary image to the channel.",
+ options: [],
+ execute: async (args: any, ctx: any) => {
+ try {
+ // Ensure garySettings exists and get the source
+ if (!storage.garySettings) {
+ storage.garySettings = { imageSource: "gary" };
+ }
- const source = storage.garySettings.imageSource || "gary";
- console.log(`[Gary Command] Using image source: ${source}`); // Debug log
+ const source = storage.garySettings.imageSource || "gary";
+ console.log(`[Gary Command] Using image source: ${source}`); // Debug log
- const imageUrl = await getGaryUrl(source);
+ const imageUrl = await getGaryUrl(source);
- if (!imageUrl) {
- console.log("[Gary Command] No image URL received");
- // Silent fail - no error message
- return { type: 4 };
- }
+ if (!imageUrl) {
+ console.log("[Gary Command] No image URL received");
+ // Silent fail - no error message
+ return { type: 4 };
+ }
- console.log(`[Gary Command] Sending image: ${imageUrl}`); // Debug log
- const fixNonce = Date.now().toString();
- MessageActions.sendMessage(ctx.channel.id, { content: imageUrl }, void 0, {
- nonce: fixNonce,
- });
- return { type: 4 };
- } catch (error) {
- console.error('[Gary] Error:', error);
- // Silent fail - no error message in chat
- return { type: 4 };
- }
- },
- applicationId: "-1",
- inputType: 1,
- type: 1,
+ console.log(`[Gary Command] Sending image: ${imageUrl}`); // Debug log
+ const fixNonce = Date.now().toString();
+ MessageActions.sendMessage(ctx.channel.id, { content: imageUrl }, void 0, {
+ nonce: fixNonce,
+ });
+ return { type: 4 };
+ } catch (error) {
+ console.error("[Gary] Error:", error);
+ // Silent fail - no error message in chat
+ return { type: 4 };
+ }
+ },
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
};
diff --git a/plugins/Commands/src/commands/ip.ts b/plugins/Commands/src/commands/ip.ts
index 2d2bcbf..0ab38c3 100644
--- a/plugins/Commands/src/commands/ip.ts
+++ b/plugins/Commands/src/commands/ip.ts
@@ -26,131 +26,131 @@ interface IpWhoisResponse {
}
export const ipCommand = {
- name: "ip",
- displayName: "ip",
- description: "IP address and domain lookup",
- displayDescription: "IP address and domain lookup",
- options: [
- {
- name: "query",
- displayName: "query",
- description: "Enter IPv4/IPv6 address or domain name",
- displayDescription: "Enter IPv4/IPv6 address or domain name",
- type: 3, // String
- required: true,
- },
- {
- name: "send",
- displayName: "send",
- description: "Send to chat",
- displayDescription: "Send to chat",
- type: 5, // Boolean
- required: false,
- },
- {
- name: "ephemeral",
- displayName: "ephemeral",
- description: "Send as ephemeral message (only you can see)",
- displayDescription: "Send as ephemeral message (only you can see)",
- type: 5, // Boolean
- required: false,
- }
- ],
- execute: async (args: any, ctx: any) => {
- try {
- const query = args.find((arg: any) => arg.name === "query")?.value;
- const shouldSend = args.find((arg: any) => arg.name === "send")?.value || false;
- const isEphemeral = args.find((arg: any) => arg.name === "ephemeral")?.value || false;
-
- if (!query) {
- const errorMsg = "β Query parameter is required!";
- if (isEphemeral) {
- return {
- type: 4,
- data: {
- content: errorMsg,
- flags: 64,
- },
- };
+ name: "ip",
+ displayName: "ip",
+ description: "IP address and domain lookup",
+ displayDescription: "IP address and domain lookup",
+ options: [
+ {
+ name: "query",
+ displayName: "query",
+ description: "Enter IPv4/IPv6 address or domain name",
+ displayDescription: "Enter IPv4/IPv6 address or domain name",
+ type: 3, // String
+ required: true,
+ },
+ {
+ name: "send",
+ displayName: "send",
+ description: "Send to chat",
+ displayDescription: "Send to chat",
+ type: 5, // Boolean
+ required: false,
+ },
+ {
+ name: "ephemeral",
+ displayName: "ephemeral",
+ description: "Send as ephemeral message (only you can see)",
+ displayDescription: "Send as ephemeral message (only you can see)",
+ type: 5, // Boolean
+ required: false,
}
- showToast(errorMsg, getAssetIDByName("CircleXIcon"));
- return { type: 4 };
- }
+ ],
+ execute: async (args: any, ctx: any) => {
+ try {
+ const query = args.find((arg: any) => arg.name === "query")?.value;
+ const shouldSend = args.find((arg: any) => arg.name === "send")?.value || false;
+ const isEphemeral = args.find((arg: any) => arg.name === "ephemeral")?.value || false;
- // Show loading toast
- if (!isEphemeral && !shouldSend) {
- showToast(`Looking up IP information for ${query}...`, getAssetIDByName("DownloadIcon"));
- }
+ if (!query) {
+ const errorMsg = "β Query parameter is required!";
+ if (isEphemeral) {
+ return {
+ type: 4,
+ data: {
+ content: errorMsg,
+ flags: 64,
+ },
+ };
+ }
+ showToast(errorMsg, getAssetIDByName("CircleXIcon"));
+ return { type: 4 };
+ }
- // Build API URL with all requested fields
- const url = `http://ipwhois.app/json/${encodeURIComponent(query)}?objects=` +
- `success,ip,type,as,org,isp,continent,country,country_capital,city,region,` +
- `country_phone,latitude,longitude,timezone,currency,completed_requests`;
+ // Show loading toast
+ if (!isEphemeral && !shouldSend) {
+ showToast(`Looking up IP information for ${query}...`, getAssetIDByName("DownloadIcon"));
+ }
- const response = await fetch(url);
+ // Build API URL with all requested fields
+ const url = `http://ipwhois.app/json/${encodeURIComponent(query)}?objects=` +
+ "success,ip,type,as,org,isp,continent,country,country_capital,city,region," +
+ "country_phone,latitude,longitude,timezone,currency,completed_requests";
+
+ const response = await fetch(url);
- if (!response.ok) {
- throw new Error(`HTTP ${response.status}: ${response.statusText}`);
- }
+ if (!response.ok) {
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
+ }
- const data: IpWhoisResponse = await response.json();
+ const data: IpWhoisResponse = await response.json();
- if (!data.success) {
- const errorMsg = `β Failed to lookup IP information for: ${query}`;
- if (isEphemeral) {
- return {
- type: 4,
- data: {
- content: errorMsg,
- flags: 64,
- },
- };
+ if (!data.success) {
+ const errorMsg = `β Failed to lookup IP information for: ${query}`;
+ if (isEphemeral) {
+ return {
+ type: 4,
+ data: {
+ content: errorMsg,
+ flags: 64,
+ },
+ };
+ }
+ showToast(errorMsg, getAssetIDByName("CircleXIcon"));
+ return { type: 4 };
+ }
+
+ // Format the response as code block
+ const formattedData = JSON.stringify(data, null, 4);
+ const content = `\`\`\`json\n${formattedData}\n\`\`\``;
+
+ if (isEphemeral) {
+ return {
+ type: 4,
+ data: {
+ content,
+ flags: 64,
+ },
+ };
+ } else if (shouldSend) {
+ const fixNonce = Date.now().toString();
+ MessageActions.sendMessage(ctx.channel.id, { content }, void 0, { nonce: fixNonce });
+ return { type: 4 };
+ } else {
+ // Send as bot message (ephemeral-like)
+ messageUtil.sendBotMessage(ctx.channel.id, content);
+ return { type: 4 };
+ }
+ } catch (error) {
+ console.error("[IP] Command error:", error);
+ const errorMessage = `β An error occurred while looking up IP information: ${error.message || "Unknown error"}`;
+
+ const isEphemeral = args?.find?.((arg: any) => arg.name === "ephemeral")?.value ?? false;
+
+ if (isEphemeral) {
+ return {
+ type: 4,
+ data: {
+ content: errorMessage,
+ flags: 64,
+ },
+ };
+ }
+ showToast(errorMessage, getAssetIDByName("CircleXIcon"));
+ return { type: 4 };
}
- showToast(errorMsg, getAssetIDByName("CircleXIcon"));
- return { type: 4 };
- }
-
- // Format the response as code block
- const formattedData = JSON.stringify(data, null, 4);
- const content = `\`\`\`json\n${formattedData}\n\`\`\``;
-
- if (isEphemeral) {
- return {
- type: 4,
- data: {
- content,
- flags: 64,
- },
- };
- } else if (shouldSend) {
- const fixNonce = Date.now().toString();
- MessageActions.sendMessage(ctx.channel.id, { content }, void 0, { nonce: fixNonce });
- return { type: 4 };
- } else {
- // Send as bot message (ephemeral-like)
- messageUtil.sendBotMessage(ctx.channel.id, content);
- return { type: 4 };
- }
- } catch (error) {
- console.error('[IP] Command error:', error);
- const errorMessage = `β An error occurred while looking up IP information: ${error.message || 'Unknown error'}`;
-
- const isEphemeral = args?.find?.((arg: any) => arg.name === "ephemeral")?.value ?? false;
-
- if (isEphemeral) {
- return {
- type: 4,
- data: {
- content: errorMessage,
- flags: 64,
- },
- };
- }
- showToast(errorMessage, getAssetIDByName("CircleXIcon"));
- return { type: 4 };
- }
- },
- applicationId: "-1",
- inputType: 1,
- type: 1,
+ },
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
};
diff --git a/plugins/Commands/src/commands/konochan.ts b/plugins/Commands/src/commands/konochan.ts
index 159ed4b..e0113c9 100644
--- a/plugins/Commands/src/commands/konochan.ts
+++ b/plugins/Commands/src/commands/konochan.ts
@@ -11,128 +11,128 @@ const messageUtil = findByProps("sendBotMessage", "sendMessage", "receiveMessage
* @returns The URL of the fetched image or null if none are found.
*/
const fetchImage = async (isNSFW: boolean): Promise => {
- const baseURL = "https://konachan.com/post.json";
- const tag = isNSFW ? "rating:explicit" : "rating:safe";
- const randomPage = Math.floor(Math.random() * 100);
+ const baseURL = "https://konachan.com/post.json";
+ const tag = isNSFW ? "rating:explicit" : "rating:safe";
+ const randomPage = Math.floor(Math.random() * 100);
- try {
- const response = await fetch(`${baseURL}?tags=${tag}&limit=1&page=${randomPage}`);
- const data = await response.json();
+ try {
+ const response = await fetch(`${baseURL}?tags=${tag}&limit=1&page=${randomPage}`);
+ const data = await response.json();
- if (!Array.isArray(data) || data.length === 0) {
- return null;
+ if (!Array.isArray(data) || data.length === 0) {
+ return null;
+ }
+
+ return data[0].file_url;
+ } catch (error) {
+ console.error("[KonoChan Randomizer] Error fetching image:", error);
+ return null;
}
-
- return data[0].file_url;
- } catch (error) {
- console.error("[KonoChan Randomizer] Error fetching image:", error);
- return null;
- }
};
const showNSFWWarning = () => {
- return alerts.showConfirmationAlert({
- title: "NSFW Content Warning",
- content: "NSFW content can only be sent in NSFW channels!",
- confirmText: "Okay",
- cancelText: null,
- confirmColor: "brand",
- isDismissable: true,
- });
+ return alerts.showConfirmationAlert({
+ title: "NSFW Content Warning",
+ content: "NSFW content can only be sent in NSFW channels!",
+ confirmText: "Okay",
+ cancelText: null,
+ confirmColor: "brand",
+ isDismissable: true,
+ });
};
// Common options for both commands
const nsfwOption = {
- name: "nsfw",
- description: "Include NSFW content?",
- type: 5,
- required: false,
- displayName: "nsfw",
- displayDescription: "Include NSFW content?",
+ name: "nsfw",
+ description: "Include NSFW content?",
+ type: 5,
+ required: false,
+ displayName: "nsfw",
+ displayDescription: "Include NSFW content?",
};
export const konoSelfCommand = {
- name: "konoself",
- displayName: "konoself",
- description: "Fetch a random image from KonoChan for yourself.",
- displayDescription: "Fetch a random image from KonoChan for yourself.",
- options: [nsfwOption],
- execute: async (args: any, ctx: any) => {
- try {
- const options = new Map(args.map((option: any) => [option.name, option]));
- const isNSFW = options.get("nsfw")?.value || false;
+ name: "konoself",
+ displayName: "konoself",
+ description: "Fetch a random image from KonoChan for yourself.",
+ displayDescription: "Fetch a random image from KonoChan for yourself.",
+ options: [nsfwOption],
+ execute: async (args: any, ctx: any) => {
+ try {
+ const options = new Map(args.map((option: any) => [option.name, option]));
+ const isNSFW = options.get("nsfw")?.value || false;
- // Check if channel is NSFW for NSFW content (unless bypass is enabled)
- const bypassEnabled = storage.hiddenSettings?.konochanBypassNsfw || false;
- console.log(`[KonoSelf] NSFW: ${isNSFW}, Channel NSFW: ${ctx.channel.nsfw}, Bypass: ${bypassEnabled}`);
+ // Check if channel is NSFW for NSFW content (unless bypass is enabled)
+ const bypassEnabled = storage.hiddenSettings?.konochanBypassNsfw || false;
+ console.log(`[KonoSelf] NSFW: ${isNSFW}, Channel NSFW: ${ctx.channel.nsfw}, Bypass: ${bypassEnabled}`);
- if (isNSFW && !ctx.channel.nsfw && !bypassEnabled) {
- showNSFWWarning();
- return { type: 4 };
- }
+ if (isNSFW && !ctx.channel.nsfw && !bypassEnabled) {
+ showNSFWWarning();
+ return { type: 4 };
+ }
- const imageUrl = await fetchImage(isNSFW);
+ const imageUrl = await fetchImage(isNSFW);
- if (!imageUrl) {
- // Silent fail - no error message
- return { type: 4 };
- }
+ if (!imageUrl) {
+ // Silent fail - no error message
+ return { type: 4 };
+ }
- messageUtil.sendBotMessage(
- ctx.channel.id,
- `Here's your random image: ${imageUrl}`,
- );
- return { type: 4 };
- } catch (error) {
- console.error('[KonoSelf] Error:', error);
- // Silent fail - no error message in chat
- return { type: 4 };
- }
- },
- applicationId: "-1",
- inputType: 1,
- type: 1,
+ messageUtil.sendBotMessage(
+ ctx.channel.id,
+ `Here's your random image: ${imageUrl}`,
+ );
+ return { type: 4 };
+ } catch (error) {
+ console.error("[KonoSelf] Error:", error);
+ // Silent fail - no error message in chat
+ return { type: 4 };
+ }
+ },
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
};
export const konoSendCommand = {
- name: "konosend",
- displayName: "konosend",
- description: "Fetch a random image from KonoChan and send it to the channel.",
- displayDescription: "Fetch a random image from KonoChan and send it to the channel.",
- options: [nsfwOption],
- execute: async (args: any, ctx: any) => {
- try {
- const options = new Map(args.map((option: any) => [option.name, option]));
- const isNSFW = options.get("nsfw")?.value || false;
+ name: "konosend",
+ displayName: "konosend",
+ description: "Fetch a random image from KonoChan and send it to the channel.",
+ displayDescription: "Fetch a random image from KonoChan and send it to the channel.",
+ options: [nsfwOption],
+ execute: async (args: any, ctx: any) => {
+ try {
+ const options = new Map(args.map((option: any) => [option.name, option]));
+ const isNSFW = options.get("nsfw")?.value || false;
- // Check if channel is NSFW for NSFW content (unless bypass is enabled)
- const bypassEnabled = storage.hiddenSettings?.konochanBypassNsfw || false;
- console.log(`[KonoSend] NSFW: ${isNSFW}, Channel NSFW: ${ctx.channel.nsfw}, Bypass: ${bypassEnabled}`);
+ // Check if channel is NSFW for NSFW content (unless bypass is enabled)
+ const bypassEnabled = storage.hiddenSettings?.konochanBypassNsfw || false;
+ console.log(`[KonoSend] NSFW: ${isNSFW}, Channel NSFW: ${ctx.channel.nsfw}, Bypass: ${bypassEnabled}`);
- if (isNSFW && !ctx.channel.nsfw && !bypassEnabled) {
- showNSFWWarning();
- return { type: 4 };
- }
+ if (isNSFW && !ctx.channel.nsfw && !bypassEnabled) {
+ showNSFWWarning();
+ return { type: 4 };
+ }
- const imageUrl = await fetchImage(isNSFW);
+ const imageUrl = await fetchImage(isNSFW);
- if (!imageUrl) {
- // Silent fail - no error message
- return { type: 4 };
- }
+ if (!imageUrl) {
+ // Silent fail - no error message
+ return { type: 4 };
+ }
- const fixNonce = Date.now().toString();
- MessageActions.sendMessage(ctx.channel.id, { content: imageUrl }, void 0, {
- nonce: fixNonce,
- });
- return { type: 4 };
- } catch (error) {
- console.error('[KonoSend] Error:', error);
- // Silent fail - no error message in chat
- return { type: 4 };
- }
- },
- applicationId: "-1",
- inputType: 1,
- type: 1,
+ const fixNonce = Date.now().toString();
+ MessageActions.sendMessage(ctx.channel.id, { content: imageUrl }, void 0, {
+ nonce: fixNonce,
+ });
+ return { type: 4 };
+ } catch (error) {
+ console.error("[KonoSend] Error:", error);
+ // Silent fail - no error message in chat
+ return { type: 4 };
+ }
+ },
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
};
diff --git a/plugins/Commands/src/commands/lists.ts b/plugins/Commands/src/commands/lists.ts
index a3f40fc..71f8cf9 100644
--- a/plugins/Commands/src/commands/lists.ts
+++ b/plugins/Commands/src/commands/lists.ts
@@ -38,16 +38,16 @@ const STATUS = {
NOT_SELECTED: "β"
};
-const MessageActions = findByProps('sendMessage', 'receiveMessage');
-const Clyde = findByProps('sendBotMessage');
+const MessageActions = findByProps("sendMessage", "receiveMessage");
+const Clyde = findByProps("sendBotMessage");
const maxMessageLength = (() => {
try {
- return findByStoreName('UserStore')
+ return findByStoreName("UserStore")
.getCurrentUser()
?.premiumType === 2
- ? 4000
- : 2000;
+ ? 4000
+ : 2000;
} catch (e) {
return 2000; // fallback
}
@@ -75,8 +75,8 @@ const addonAuthors = (authors: any) => {
if (authors.length === 0) return "Unknown";
return authors
- .filter(author => author && (typeof author === 'string' || (typeof author === 'object' && author.name)))
- .map(author => typeof author === 'string' ? author : (author.name || "Unknown"))
+ .filter(author => author && (typeof author === "string" || (typeof author === "object" && author.name)))
+ .map(author => typeof author === "string" ? author : (author.name || "Unknown"))
.join(JOINERS.SEMICOL) || "Unknown";
};
@@ -94,7 +94,7 @@ const sendList = async (channelID: string, list: string[]) => {
}, void 0, { nonce: fixNonce });
};
-const baseListHeader = (type: 'Plugin' | 'Theme', length: number) => [
+const baseListHeader = (type: "Plugin" | "Theme", length: number) => [
`**My ${type} List | ${length} ${type}s**`,
EMPTY
];
@@ -105,7 +105,7 @@ export async function themeList(args: any[], ctx: any) {
const alwaysDetailed = storage.themeListAlwaysDetailed ?? false;
// Ensure themes is valid
- if (!themes || typeof themes !== 'object') {
+ if (!themes || typeof themes !== "object") {
const channelID: string = ctx.channel.id;
Clyde.sendBotMessage(channelID, "No themes found or themes not loaded yet.");
return { type: 4 };
@@ -115,11 +115,11 @@ export async function themeList(args: any[], ctx: any) {
const channelID: string = ctx.channel.id;
- const themeList = baseListHeader('Theme', Object.keys(themes).length);
+ const themeList = baseListHeader("Theme", Object.keys(themes).length);
if (objectValues.length) {
for (const theme of objectValues) {
- if (!theme || typeof theme !== 'object') continue;
+ if (!theme || typeof theme !== "object") continue;
const { selected, data, id } = theme;
@@ -161,7 +161,7 @@ export async function themeList(args: any[], ctx: any) {
await sendList(channelID, themeList);
}
} catch (error) {
- console.error('[ThemeList] Error:', error);
+ console.error("[ThemeList] Error:", error);
return { type: 4 };
}
}
@@ -174,15 +174,15 @@ export async function pluginList(args: any[], ctx: any) {
const channelID: string = ctx.channel.id;
// Ensure plugins is valid
- if (!plugins || typeof plugins !== 'object') {
+ if (!plugins || typeof plugins !== "object") {
Clyde.sendBotMessage(channelID, "No plugins found or plugins not loaded yet.");
return { type: 4 };
}
- const pluginList = baseListHeader('Plugin', Object.keys(plugins).length);
+ const pluginList = baseListHeader("Plugin", Object.keys(plugins).length);
for (const plugin of Object.values(plugins)) {
- if (!plugin || typeof plugin !== 'object') continue;
+ if (!plugin || typeof plugin !== "object") continue;
const { enabled, manifest, id } = plugin;
@@ -222,50 +222,50 @@ export async function pluginList(args: any[], ctx: any) {
await sendList(channelID, pluginList);
}
} catch (error) {
- console.error('[PluginList] Error:', error);
+ console.error("[PluginList] Error:", error);
return { type: 4 };
}
}
// Export commands for your existing structure
export const pluginListCommand = {
- name: "plugin-list",
- displayName: "plugin list",
- description: "Send your plugin list to the current channel",
- displayDescription: "Send your plugin list to the current channel",
- options: [
- {
- name: "detailed",
- description: "Whether to send a list with detailed information.",
- type: 5,
- required: false,
- displayName: "detailed",
- displayDescription: "Whether to send a list with detailed information.",
- },
- ],
- execute: pluginList,
- applicationId: "-1",
- inputType: 1,
- type: 1,
+ name: "plugin-list",
+ displayName: "plugin list",
+ description: "Send your plugin list to the current channel",
+ displayDescription: "Send your plugin list to the current channel",
+ options: [
+ {
+ name: "detailed",
+ description: "Whether to send a list with detailed information.",
+ type: 5,
+ required: false,
+ displayName: "detailed",
+ displayDescription: "Whether to send a list with detailed information.",
+ },
+ ],
+ execute: pluginList,
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
};
export const themeListCommand = {
- name: "theme-list",
- displayName: "theme list",
- description: "Send your theme list to the current channel",
- displayDescription: "Send your theme list to the current channel",
- options: [
- {
- name: "detailed",
- description: "Whether to send a list with detailed information.",
- type: 5,
- required: false,
- displayName: "detailed",
- displayDescription: "Whether to send a list with detailed information.",
- },
- ],
- execute: themeList,
- applicationId: "-1",
- inputType: 1,
- type: 1,
+ name: "theme-list",
+ displayName: "theme list",
+ description: "Send your theme list to the current channel",
+ displayDescription: "Send your theme list to the current channel",
+ options: [
+ {
+ name: "detailed",
+ description: "Whether to send a list with detailed information.",
+ type: 5,
+ required: false,
+ displayName: "detailed",
+ displayDescription: "Whether to send a list with detailed information.",
+ },
+ ],
+ execute: themeList,
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
};
diff --git a/plugins/Commands/src/commands/lovefemboys.ts b/plugins/Commands/src/commands/lovefemboys.ts
index fa0e2f4..72b7344 100644
--- a/plugins/Commands/src/commands/lovefemboys.ts
+++ b/plugins/Commands/src/commands/lovefemboys.ts
@@ -1,274 +1,173 @@
import { findByProps } from "@vendetta/metro";
-import { showToast } from "@vendetta/ui/toasts";
-import { getAssetIDByName } from "@vendetta/ui/assets";
+import { storage } from "@vendetta/plugin";
import { alerts } from "@vendetta/ui";
-const MessageActions = findByProps("sendMessage");
+const MessageActions = findByProps("sendMessage", "receiveMessage");
+const Channels = findByProps("getLastSelectedChannelId");
+const BotMessage = findByProps("createBotMessage");
+const Avatars = findByProps("BOT_AVATARS");
-interface RedditPost {
- kind: string;
- data: {
- title: string;
- url: string;
- is_video: boolean;
- over_18: boolean;
- thumbnail: string;
- preview?: {
- images: Array<{
- source: {
- url: string;
- width: number;
- height: number;
- };
- resolutions: Array<{
- url: string;
- width: number;
- height: number;
- }>;
- }>;
- };
- secure_media?: {
- oembed?: {
- thumbnail_url: string;
- type: string;
- };
- reddit_video?: {
- fallback_url: string;
- width: number;
- height: number;
- };
- };
- media?: {
- reddit_video?: {
- fallback_url: string;
- width: number;
- height: number;
- };
- };
- media_embed?: {
- content: string;
- };
- url_overridden_by_dest?: string;
- domain: string;
- post_hint?: string;
- selftext: string;
- };
-}
-
-interface RedditResponse {
- kind: string;
- data: {
- children: RedditPost[];
- after: string;
- dist: number;
- };
-}
-
-function isValidMediaUrl(url: string): boolean {
- if (!url) return false;
-
- const lowerUrl = url.toLowerCase();
-
- // Check for image/video/gif extensions
- const mediaExtensions = ['.jpg', '.jpeg', '.png', '.gif', '.webp', '.mp4', '.webm', '.mov'];
- if (mediaExtensions.some(ext => lowerUrl.includes(ext))) {
- return true;
- }
-
- // Known media hosting domains (including video/gif hosts)
- const mediaHosts = [
- 'i.redd.it',
- 'i.imgur.com',
- 'preview.redd.it',
- 'v.redd.it',
- 'redgifs.com',
- 'gfycat.com',
- 'imgur.com'
- ];
-
- return mediaHosts.some(host => lowerUrl.includes(host));
-}
-
-function extractMediaUrl(post: RedditPost): string | null {
- const postData = post.data;
-
- // Try Reddit video first
- if (postData.secure_media?.reddit_video?.fallback_url) {
- return postData.secure_media.reddit_video.fallback_url;
- }
-
- if (postData.media?.reddit_video?.fallback_url) {
- return postData.media.reddit_video.fallback_url;
- }
-
- // Try the main URL
- const mainUrl = postData.url;
- if (isValidMediaUrl(mainUrl)) {
- return mainUrl;
- }
-
- // Try preview images
- if (postData.preview?.images?.[0]) {
- const previewImage = postData.preview.images[0];
-
- // Try source image first (highest quality)
- if (previewImage.source?.url) {
- const sourceUrl = previewImage.source.url.replace(/&/g, '&');
- if (isValidMediaUrl(sourceUrl)) {
- return sourceUrl;
- }
- }
-
- // Try highest resolution preview
- if (previewImage.resolutions?.length > 0) {
- const highestRes = previewImage.resolutions[previewImage.resolutions.length - 1];
- if (highestRes?.url) {
- const resUrl = highestRes.url.replace(/&/g, '&');
- if (isValidMediaUrl(resUrl)) {
- return resUrl;
- }
- }
- }
- }
-
- // Try secure_media oembed thumbnail
- if (postData.secure_media?.oembed?.thumbnail_url) {
- const thumbUrl = postData.secure_media.oembed.thumbnail_url;
- if (isValidMediaUrl(thumbUrl)) {
- return thumbUrl;
- }
- }
-
- // Try regular thumbnail as last resort
- if (postData.thumbnail &&
- postData.thumbnail !== 'self' &&
- postData.thumbnail !== 'default' &&
- postData.thumbnail !== 'nsfw' &&
- postData.thumbnail.startsWith('http')) {
- if (isValidMediaUrl(postData.thumbnail)) {
- return postData.thumbnail;
- }
- }
-
- return null;
-}
-
-async function getFemboyMedia(): Promise {
- try {
- console.log(`[LoveFemboys] Fetching random media`);
-
- const response = await fetch('https://reddit.com/r/femboys.json?limit=100&raw_json=1');
- if (!response.ok) {
- console.log('[LoveFemboys] Failed to fetch from Reddit API:', response.status, response.statusText);
- return null;
- }
-
- const data: RedditResponse = await response.json();
- console.log(`[LoveFemboys] Fetched ${data.data.children.length} posts`);
-
- const validPosts = [];
-
- for (const post of data.data.children) {
- const postData = post.data;
-
- // Skip text-only posts
- if (postData.selftext && !postData.url) {
- continue;
- }
-
- // Try to extract media URL
- const mediaUrl = extractMediaUrl(post);
- if (mediaUrl) {
- console.log(`[LoveFemboys] Found valid media: ${mediaUrl}`);
- validPosts.push(mediaUrl);
- }
- }
-
- console.log(`[LoveFemboys] Found ${validPosts.length} valid media posts`);
-
- if (validPosts.length === 0) {
- console.log('[LoveFemboys] No suitable media found');
- return null;
- }
-
- // Get random media
- const randomUrl = validPosts[Math.floor(Math.random() * validPosts.length)];
- console.log(`[LoveFemboys] Selected: ${randomUrl}`);
-
- return randomUrl;
- } catch (error) {
- console.error('[LoveFemboys] Error fetching media:', error);
- return null;
+function sendReply(channelID, content, embed) {
+ const channel = channelID ?? Channels?.getChannelId?.();
+ const msg = BotMessage.createBotMessage({
+ channelId: channel,
+ content: "",
+ embeds: embed,
+ });
+ msg.author.username = "Astolfo";
+ msg.author.avatar = "Astolfo";
+ Avatars.BOT_AVATARS.Astolfo =
+ "https://i.pinimg.com/736x/50/77/1f/50771f45b1c015cfbb8b0853ba7b8521.jpg";
+ if (typeof content === "string") {
+ msg.content = content;
+ } else {
+ Object.assign(msg, content);
}
+ MessageActions.receiveMessage(channel, msg);
}
export const lovefemboysCommand = {
name: "lovefemboys",
displayName: "lovefemboys",
- description: "Get random femboy content from r/femboys",
- displayDescription: "Get random femboy content from r/femboys",
+ description: "Get an image of a femboy",
+ displayDescription: "Get an image of a femboy",
options: [
{
- name: "ephemeral",
- displayName: "ephemeral",
- description: "Send as ephemeral message (only you can see)",
- displayDescription: "Send as ephemeral message (only you can see)",
- type: 5, // Boolean
+ name: "nsfw",
+ displayName: "nsfw",
+ description: "Get the result from r/femboys instead of r/femboy (NSFW)",
+ displayDescription:
+ "Get the result from r/femboys instead of r/femboy (NSFW)",
required: false,
- }
+ type: 5,
+ },
+ {
+ name: "sort",
+ displayName: "sort",
+ description: "Changes the way reddit sorts.",
+ displayDescription: "Changes the way reddit sorts",
+ required: false,
+ type: 3,
+ },
+ {
+ name: "silent",
+ displayName: "silent",
+ description: "Makes it so only you can see the message.",
+ displayDescription: "Makes it so only you can see the message.",
+ required: false,
+ type: 5,
+ },
],
- execute: async (args: any, ctx: any) => {
- try {
- const isEphemeral = args?.find?.((arg: any) => arg.name === "ephemeral")?.value ?? false;
-
- console.log(`[LoveFemboys] Command executed with ephemeral: ${isEphemeral}`);
-
- // Show NSFW warning popup before proceeding
- const shouldProceed = await new Promise((resolve) => {
- alerts.showConfirmationAlert({
- title: "β οΈ NSFW Content Warning",
- content: "This command will send potentially NSFW (Not Safe For Work) content from r/femboys. Are you sure you want to continue?",
- confirmText: "Yes, Continue",
- onConfirm: () => resolve(true),
- cancelText: "Cancel",
- onCancel: () => resolve(false),
- });
- });
-
- if (!shouldProceed) {
- console.log(`[LoveFemboys] User cancelled the command`);
- return { type: 4 }; // Just dismiss the command
- }
-
- const mediaUrl = await getFemboyMedia();
-
- if (!mediaUrl) {
- showToast("β Failed to fetch femboy content", getAssetIDByName("CircleXIcon"));
- return { type: 4 };
- }
-
- if (isEphemeral) {
- console.log(`[LoveFemboys] Sending ephemeral response`);
- return {
- type: 4,
- data: {
- content: mediaUrl,
- flags: 64, // Ephemeral flag
- },
- };
- } else {
- console.log(`[LoveFemboys] Sending public message`);
- const fixNonce = Date.now().toString();
- MessageActions.sendMessage(ctx.channel.id, { content: mediaUrl }, void 0, {nonce: fixNonce});
- return { type: 4 };
- }
- } catch (error) {
- console.error('[LoveFemboys] Command error:', error);
- showToast("β An error occurred while fetching content", getAssetIDByName("CircleXIcon"));
- return { type: 4 };
- }
- },
applicationId: "-1",
inputType: 1,
type: 1,
+ execute: async (args, ctx) => {
+ try {
+ // Some code taken from enmity gotfemboys plugin by spinfal & was modified with the help of meqativ
+ let nsfw = args.find((arg) => arg.name === "nsfw")?.value;
+ let sort = args.find((arg) => arg.name === "sort")?.value;
+ let silent = args.find((arg) => arg.name === "silent")?.value;
+
+ if (typeof sort === "undefined") sort = storage.sortdefs;
+ if (
+ !["best", "hot", "new", "rising", "top", "controversial"].includes(sort)
+ ) {
+ sendReply(
+ ctx.channel.id,
+ "Incorrect sorting type. Valid options are\n`best`, `hot`, `new`, `rising`, `top`, `controversial`.",
+ [],
+ );
+ return;
+ }
+
+ // Show NSFW warning popup only when nsfw option is selected
+ if (nsfw) {
+ const shouldProceed = await new Promise((resolve) => {
+ alerts.showConfirmationAlert({
+ title: "β οΈ NSFW Content Warning",
+ content:
+ "This command will send NSFW (Not Safe For Work) content from r/femboys. Are you sure you want to continue?",
+ confirmText: "Yes, Continue",
+ onConfirm: () => resolve(true),
+ cancelText: "Cancel",
+ onCancel: () => resolve(false),
+ });
+ });
+
+ if (!shouldProceed) {
+ return { type: 4 };
+ }
+ }
+
+ let response = await fetch(
+ `https://www.reddit.com/r/femboy/${sort}.json?limit=100`,
+ ).then((res) => res.json());
+
+ if (!ctx.channel.nsfw_ && nsfw && storage.nsfwwarn && !(silent ?? true)) {
+ sendReply(
+ ctx.channel.id,
+ "This channel is not marked as NSFW\n(You can disable this check in plugin settings)",
+ [],
+ );
+ return;
+ }
+
+ if (nsfw) {
+ response = await fetch(
+ `https://www.reddit.com/r/femboys/${sort}.json?limit=100`,
+ ).then((res) => res.json());
+ }
+
+ response =
+ response.data?.children?.[
+ Math.floor(Math.random() * response.data?.children?.length)
+ ]?.data;
+ let author = await fetch(
+ `https://www.reddit.com/u/${response?.author}/about.json`,
+ ).then((res) => res.json());
+
+ if (silent ?? true) {
+ sendReply(ctx.channel.id, "", [
+ {
+ type: "rich",
+ title: response?.title,
+ url: `https://reddit.com${response?.permalink}`,
+ author: {
+ name: `u/${response?.author} β’ r/${response?.subreddit}`,
+ proxy_icon_url: author?.data.icon_img.split("?")[0],
+ icon_url: author?.data.icon_img.split("?")[0],
+ },
+ image: {
+ proxy_url:
+ response?.url_overridden_by_dest?.replace(/.gifv$/g, ".gif") ??
+ response?.url.replace(/.gifv$/g, ".gif"),
+ url:
+ response?.url_overridden_by_dest?.replace(/.gifv$/g, ".gif") ??
+ response?.url?.replace(/.gifv$/g, ".gif"),
+ width: response?.preview?.images?.[0]?.source?.width,
+ height: response?.preview?.images?.[0]?.source?.height,
+ },
+ color: "0xf4b8e4",
+ },
+ ]);
+ } else {
+ const fixNonce = Date.now().toString();
+ MessageActions.sendMessage(
+ ctx.channel.id,
+ {
+ content: response?.url_overridden_by_dest ?? response?.url,
+ },
+ void 0,
+ { nonce: fixNonce },
+ );
+ }
+ } catch (err) {
+ console.error("[LoveFemboys] Error:", err);
+ sendReply(
+ ctx.channel.id,
+ "ERROR !!!!!!!!!!!! πππ Check debug logs!! π₯Ίπ₯Ίπ₯Ί",
+ [],
+ );
+ }
+ },
};
diff --git a/plugins/Commands/src/commands/nekoslife.ts b/plugins/Commands/src/commands/nekoslife.ts
index 37e2c12..ef0ecc7 100644
--- a/plugins/Commands/src/commands/nekoslife.ts
+++ b/plugins/Commands/src/commands/nekoslife.ts
@@ -11,203 +11,203 @@ interface NekosLifeResult {
// Valid SFW categories for validation
const validSfwCategories = [
- "avatar", "classic", "cuddle", "fox_girl", "gecg", "holo",
- "kemonomimi", "kiss", "neko", "ngif", "smug", "spank",
- "tickle", "waifu", "wallpaper", "woof"
+ "avatar", "classic", "cuddle", "fox_girl", "gecg", "holo",
+ "kemonomimi", "kiss", "neko", "ngif", "smug", "spank",
+ "tickle", "waifu", "wallpaper", "woof"
];
async function fetchNekosLifeImages(category: string, count: number): Promise {
- const urls: string[] = [];
+ const urls: string[] = [];
- for (let i = 0; i < count; i++) {
- try {
- // Add delay between requests to avoid rate limiting
- if (i > 0) {
- await new Promise(resolve => setTimeout(resolve, 1000));
- }
+ for (let i = 0; i < count; i++) {
+ try {
+ // Add delay between requests to avoid rate limiting
+ if (i > 0) {
+ await new Promise(resolve => setTimeout(resolve, 1000));
+ }
- const response = await fetch(`https://nekos.life/api/v2/img/${category}`);
- if (!response.ok) {
- console.error(`[NekosLife] API request failed: ${response.status}`);
- continue;
- }
+ const response = await fetch(`https://nekos.life/api/v2/img/${category}`);
+ if (!response.ok) {
+ console.error(`[NekosLife] API request failed: ${response.status}`);
+ continue;
+ }
- const data: NekosLifeResult = await response.json();
- if (data.url) {
- urls.push(data.url);
- }
- } catch (error) {
- console.error(`[NekosLife] Error fetching image ${i + 1}:`, error);
+ const data: NekosLifeResult = await response.json();
+ if (data.url) {
+ urls.push(data.url);
+ }
+ } catch (error) {
+ console.error(`[NekosLife] Error fetching image ${i + 1}:`, error);
+ }
}
- }
- return urls;
+ return urls;
}
function isValidSfwCategory(category: string): boolean {
- if (!category || typeof category !== 'string') return false;
- return validSfwCategories.includes(category.toLowerCase().trim());
+ if (!category || typeof category !== "string") return false;
+ return validSfwCategories.includes(category.toLowerCase().trim());
}
export const nekoslifeCommand = {
- name: "nekoslife",
- displayName: "nekoslife",
- description: "Get SFW images/gifs from nekos.life",
- displayDescription: "Get SFW images/gifs from nekos.life",
- options: [
- {
- name: "category",
- displayName: "category",
- description: "Category name (e.g. neko, waifu, cuddle, etc.)",
- displayDescription: "Category name (e.g. neko, waifu, cuddle, etc.)",
- type: 3, // String - free text input
- required: true,
- },
- {
- name: "limit",
- displayName: "limit",
- description: "Number of images (1-5, default: 1)",
- displayDescription: "Number of images (1-5, default: 1)",
- type: 4, // Integer
- required: false,
- },
- {
- name: "send",
- displayName: "send",
- description: "Send to chat",
- displayDescription: "Send to chat",
- type: 5, // Boolean
- required: false,
- },
- {
- name: "ephemeral",
- displayName: "ephemeral",
- description: "Send as ephemeral message (only you can see)",
- displayDescription: "Send as ephemeral message (only you can see)",
- type: 5, // Boolean
- required: false,
- }
- ],
- execute: async (args: any, ctx: any) => {
- try {
- console.log('[NekosLife] Command executed with args:', args);
+ name: "nekoslife",
+ displayName: "nekoslife",
+ description: "Get SFW images/gifs from nekos.life",
+ displayDescription: "Get SFW images/gifs from nekos.life",
+ options: [
+ {
+ name: "category",
+ displayName: "category",
+ description: "Category name (e.g. neko, waifu, cuddle, etc.)",
+ displayDescription: "Category name (e.g. neko, waifu, cuddle, etc.)",
+ type: 3, // String - free text input
+ required: true,
+ },
+ {
+ name: "limit",
+ displayName: "limit",
+ description: "Number of images (1-5, default: 1)",
+ displayDescription: "Number of images (1-5, default: 1)",
+ type: 4, // Integer
+ required: false,
+ },
+ {
+ name: "send",
+ displayName: "send",
+ description: "Send to chat",
+ displayDescription: "Send to chat",
+ type: 5, // Boolean
+ required: false,
+ },
+ {
+ name: "ephemeral",
+ displayName: "ephemeral",
+ description: "Send as ephemeral message (only you can see)",
+ displayDescription: "Send as ephemeral message (only you can see)",
+ type: 5, // Boolean
+ required: false,
+ }
+ ],
+ execute: async (args: any, ctx: any) => {
+ try {
+ console.log("[NekosLife] Command executed with args:", args);
- // Parse arguments
- const categoryInput = args.find((arg: any) => arg.name === "category")?.value;
- const limitInput = args.find((arg: any) => arg.name === "limit")?.value;
- const shouldSend = args.find((arg: any) => arg.name === "send")?.value || false;
- const isEphemeral = args.find((arg: any) => arg.name === "ephemeral")?.value || false;
+ // Parse arguments
+ const categoryInput = args.find((arg: any) => arg.name === "category")?.value;
+ const limitInput = args.find((arg: any) => arg.name === "limit")?.value;
+ const shouldSend = args.find((arg: any) => arg.name === "send")?.value || false;
+ const isEphemeral = args.find((arg: any) => arg.name === "ephemeral")?.value || false;
- console.log('[NekosLife] Parsed values:', { categoryInput, limitInput, shouldSend, isEphemeral });
+ console.log("[NekosLife] Parsed values:", { categoryInput, limitInput, shouldSend, isEphemeral });
- if (!categoryInput || typeof categoryInput !== 'string') {
- const errorMsg = "β Category is required! Examples: neko, waifu, cuddle, kiss";
- console.error('[NekosLife] No category provided');
+ if (!categoryInput || typeof categoryInput !== "string") {
+ const errorMsg = "β Category is required! Examples: neko, waifu, cuddle, kiss";
+ console.error("[NekosLife] No category provided");
- if (isEphemeral) {
- return {
- type: 4,
- data: {
- content: errorMsg,
- flags: 64,
- },
- };
- }
- showToast(errorMsg, getAssetIDByName("CircleXIcon"));
- return { type: 4 };
- }
+ if (isEphemeral) {
+ return {
+ type: 4,
+ data: {
+ content: errorMsg,
+ flags: 64,
+ },
+ };
+ }
+ showToast(errorMsg, getAssetIDByName("CircleXIcon"));
+ return { type: 4 };
+ }
- // Clean and validate category
- const category = categoryInput.toLowerCase().trim();
+ // Clean and validate category
+ const category = categoryInput.toLowerCase().trim();
- if (!isValidSfwCategory(category)) {
- const errorMsg = `β Invalid SFW category "${categoryInput}". Valid: neko, waifu, cuddle, kiss, holo, etc.`;
+ if (!isValidSfwCategory(category)) {
+ const errorMsg = `β Invalid SFW category "${categoryInput}". Valid: neko, waifu, cuddle, kiss, holo, etc.`;
- if (isEphemeral) {
- return {
- type: 4,
- data: {
- content: errorMsg,
- flags: 64,
- },
- };
- }
- showToast(errorMsg, getAssetIDByName("CircleXIcon"));
- return { type: 4 };
- }
+ if (isEphemeral) {
+ return {
+ type: 4,
+ data: {
+ content: errorMsg,
+ flags: 64,
+ },
+ };
+ }
+ showToast(errorMsg, getAssetIDByName("CircleXIcon"));
+ return { type: 4 };
+ }
- // Parse and validate limit
- let limit = 1;
- if (limitInput !== undefined && limitInput !== null) {
- limit = parseInt(String(limitInput)) || 1;
- limit = Math.max(1, Math.min(5, limit)); // Clamp between 1-5
- }
+ // Parse and validate limit
+ let limit = 1;
+ if (limitInput !== undefined && limitInput !== null) {
+ limit = parseInt(String(limitInput)) || 1;
+ limit = Math.max(1, Math.min(5, limit)); // Clamp between 1-5
+ }
- console.log('[NekosLife] Processing SFW request:', { category, limit, shouldSend, isEphemeral });
+ console.log("[NekosLife] Processing SFW request:", { category, limit, shouldSend, isEphemeral });
- // Show loading toast
- if (!isEphemeral) {
- showToast(`Fetching ${limit} SFW image(s) from nekos.life...`, getAssetIDByName("DownloadIcon"));
- }
+ // Show loading toast
+ if (!isEphemeral) {
+ showToast(`Fetching ${limit} SFW image(s) from nekos.life...`, getAssetIDByName("DownloadIcon"));
+ }
- const urls = await fetchNekosLifeImages(category, limit);
+ const urls = await fetchNekosLifeImages(category, limit);
- if (urls.length === 0) {
- const errorMsg = "β Failed to fetch images from nekos.life. Try again later!";
- if (isEphemeral) {
- return {
- type: 4,
- data: {
- content: errorMsg,
- flags: 64,
- },
- };
- }
- showToast(errorMsg, getAssetIDByName("CircleXIcon"));
- return { type: 4 };
- }
+ if (urls.length === 0) {
+ const errorMsg = "β Failed to fetch images from nekos.life. Try again later!";
+ if (isEphemeral) {
+ return {
+ type: 4,
+ data: {
+ content: errorMsg,
+ flags: 64,
+ },
+ };
+ }
+ showToast(errorMsg, getAssetIDByName("CircleXIcon"));
+ return { type: 4 };
+ }
- const content = urls.join("\n");
+ const content = urls.join("\n");
- if (isEphemeral) {
- console.log('[NekosLife] Sending ephemeral response');
- return {
- type: 4,
- data: {
- content,
- flags: 64,
- },
- };
- } else if (shouldSend) {
- console.log('[NekosLife] Sending to chat');
- const fixNonce = Date.now().toString();
- MessageActions.sendMessage(ctx.channel.id, { content }, void 0, { nonce: fixNonce });
- return { type: 4 };
- } else {
- console.log('[NekosLife] Sending as bot message');
- messageUtil.sendBotMessage(ctx.channel.id, content);
- return { type: 4 };
- }
- } catch (error) {
- console.error('[NekosLife] Command error:', error);
- const errorMessage = "β An error occurred while fetching images.";
+ if (isEphemeral) {
+ console.log("[NekosLife] Sending ephemeral response");
+ return {
+ type: 4,
+ data: {
+ content,
+ flags: 64,
+ },
+ };
+ } else if (shouldSend) {
+ console.log("[NekosLife] Sending to chat");
+ const fixNonce = Date.now().toString();
+ MessageActions.sendMessage(ctx.channel.id, { content }, void 0, { nonce: fixNonce });
+ return { type: 4 };
+ } else {
+ console.log("[NekosLife] Sending as bot message");
+ messageUtil.sendBotMessage(ctx.channel.id, content);
+ return { type: 4 };
+ }
+ } catch (error) {
+ console.error("[NekosLife] Command error:", error);
+ const errorMessage = "β An error occurred while fetching images.";
- const isEphemeral = args?.find?.((arg: any) => arg.name === "ephemeral")?.value ?? false;
+ const isEphemeral = args?.find?.((arg: any) => arg.name === "ephemeral")?.value ?? false;
- if (isEphemeral) {
- return {
- type: 4,
- data: {
- content: errorMessage,
- flags: 64,
- },
- };
- }
- showToast(errorMessage, getAssetIDByName("CircleXIcon"));
- return { type: 4 };
- }
- },
- applicationId: "-1",
- inputType: 1,
- type: 1,
+ if (isEphemeral) {
+ return {
+ type: 4,
+ data: {
+ content: errorMessage,
+ flags: 64,
+ },
+ };
+ }
+ showToast(errorMessage, getAssetIDByName("CircleXIcon"));
+ return { type: 4 };
+ }
+ },
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
};
diff --git a/plugins/Commands/src/commands/petpet.ts b/plugins/Commands/src/commands/petpet.ts
index 5fc33c7..91d0340 100644
--- a/plugins/Commands/src/commands/petpet.ts
+++ b/plugins/Commands/src/commands/petpet.ts
@@ -5,41 +5,41 @@ const UserStore = findByStoreName("UserStore");
const MessageActions = findByProps("sendMessage");
export const petPetCommand = {
- name: "petpet",
- displayName: "petpet",
- description: "PetPet someone",
- displayDescription: "PetPet someone",
- options: [
- {
- name: "user",
- description: "The user(or their id) to be patted",
- type: 6,
- required: true,
- displayName: "user",
- displayDescription: "The user(or their id) to be patted",
- },
- ],
- execute: async (args: any, ctx: any) => {
- try {
- const user = await UserStore.getUser(args[0].value);
- const image = user.getAvatarURL(512);
- const data = await getPetPetData(image);
+ name: "petpet",
+ displayName: "petpet",
+ description: "PetPet someone",
+ displayDescription: "PetPet someone",
+ options: [
+ {
+ name: "user",
+ description: "The user(or their id) to be patted",
+ type: 6,
+ required: true,
+ displayName: "user",
+ displayDescription: "The user(or their id) to be patted",
+ },
+ ],
+ execute: async (args: any, ctx: any) => {
+ try {
+ const user = await UserStore.getUser(args[0].value);
+ const image = user.getAvatarURL(512);
+ const data = await getPetPetData(image);
- const fixNonce = Date.now().toString();
- MessageActions.sendMessage(
- ctx.channel.id,
- { content: data.url },
- void 0,
- { nonce: fixNonce }
- );
- return { type: 4 };
- } catch (error) {
- console.error('[PetPet] Error:', error);
- // Silent fail - no error message in chat
- return { type: 4 };
- }
- },
- applicationId: "-1",
- inputType: 1,
- type: 1,
+ const fixNonce = Date.now().toString();
+ MessageActions.sendMessage(
+ ctx.channel.id,
+ { content: data.url },
+ void 0,
+ { nonce: fixNonce }
+ );
+ return { type: 4 };
+ } catch (error) {
+ console.error("[PetPet] Error:", error);
+ // Silent fail - no error message in chat
+ return { type: 4 };
+ }
+ },
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
};
diff --git a/plugins/Commands/src/commands/spotify.ts b/plugins/Commands/src/commands/spotify.ts
index 3f0030f..5009e7c 100644
--- a/plugins/Commands/src/commands/spotify.ts
+++ b/plugins/Commands/src/commands/spotify.ts
@@ -1,115 +1,115 @@
import { findByProps, findByStoreName } from "@vendetta/metro";
import { storage } from "@vendetta/plugin";
-const MessageActions = findByProps('sendMessage', 'receiveMessage');
-const SpotifyStore = findByStoreName('SpotifyStore');
+const MessageActions = findByProps("sendMessage", "receiveMessage");
+const SpotifyStore = findByStoreName("SpotifyStore");
// Individual command functions
async function spotifyTrackExecute(args: any[], ctx: any) {
- if (!storage.enabledCommands?.spotifyTrack) return { type: 4 };
- return handleSpotifyCommand(ctx, (track: any) =>
- `https://open.spotify.com/track/${track.id}`
- );
+ if (!storage.enabledCommands?.spotifyTrack) return { type: 4 };
+ return handleSpotifyCommand(ctx, (track: any) =>
+ `https://open.spotify.com/track/${track.id}`
+ );
}
async function spotifyAlbumExecute(args: any[], ctx: any) {
- if (!storage.enabledCommands?.spotifyAlbum) return { type: 4 };
- return handleSpotifyCommand(ctx, (track: any) =>
- `https://open.spotify.com/album/${track.album.id}`
- );
+ if (!storage.enabledCommands?.spotifyAlbum) return { type: 4 };
+ return handleSpotifyCommand(ctx, (track: any) =>
+ `https://open.spotify.com/album/${track.album.id}`
+ );
}
async function spotifyArtistsExecute(args: any[], ctx: any) {
- if (!storage.enabledCommands?.spotifyArtists) return { type: 4 };
- return handleSpotifyCommand(ctx, (track: any) =>
- track.artists.map((artist: any) =>
- `[${artist.name}](${artist.external_urls.spotify})`
- ).join('\n')
- );
+ if (!storage.enabledCommands?.spotifyArtists) return { type: 4 };
+ return handleSpotifyCommand(ctx, (track: any) =>
+ track.artists.map((artist: any) =>
+ `[${artist.name}](${artist.external_urls.spotify})`
+ ).join("\n")
+ );
}
async function spotifyCoverExecute(args: any[], ctx: any) {
- if (!storage.enabledCommands?.spotifyCover) return { type: 4 };
- return handleSpotifyCommand(ctx, (track: any) =>
- track.album.image.url
- );
+ if (!storage.enabledCommands?.spotifyCover) return { type: 4 };
+ return handleSpotifyCommand(ctx, (track: any) =>
+ track.album.image.url
+ );
}
// Common handler for all Spotify commands
async function handleSpotifyCommand(ctx: any, contentGenerator: (track: any) => string) {
- try {
- const channelId = ctx.channel.id;
- const track = SpotifyStore?.getTrack?.();
+ try {
+ const channelId = ctx.channel.id;
+ const track = SpotifyStore?.getTrack?.();
- if (!track) {
- const fixNonce = Date.now().toString();
- MessageActions.sendMessage(
- channelId,
- { content: 'You are not listening to any track.' },
- void 0,
- { nonce: fixNonce }
- );
- return { type: 4 };
+ if (!track) {
+ const fixNonce = Date.now().toString();
+ MessageActions.sendMessage(
+ channelId,
+ { content: "You are not listening to any track." },
+ void 0,
+ { nonce: fixNonce }
+ );
+ return { type: 4 };
+ }
+
+ const content = contentGenerator(track);
+ const fixNonce = Date.now().toString();
+
+ MessageActions.sendMessage(
+ channelId,
+ { content },
+ void 0,
+ { nonce: fixNonce }
+ );
+
+ return { type: 4 };
+ } catch (error) {
+ console.error("[Spotify Command] Error:", error);
+ return { type: 4 };
}
-
- const content = contentGenerator(track);
- const fixNonce = Date.now().toString();
-
- MessageActions.sendMessage(
- channelId,
- { content },
- void 0,
- { nonce: fixNonce }
- );
-
- return { type: 4 };
- } catch (error) {
- console.error('[Spotify Command] Error:', error);
- return { type: 4 };
- }
}
// Export individual command objects
export const spotifyTrackCommand = {
- name: 'spotify track',
- displayName: 'spotify track',
- description: 'Sends your current Spotify track',
- displayDescription: 'Sends your current Spotify track',
- applicationId: '-1',
- inputType: 1,
- type: 1,
- execute: spotifyTrackExecute
+ name: "spotify track",
+ displayName: "spotify track",
+ description: "Sends your current Spotify track",
+ displayDescription: "Sends your current Spotify track",
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
+ execute: spotifyTrackExecute
};
export const spotifyAlbumCommand = {
- name: 'spotify album',
- displayName: 'spotify album',
- description: "Sends your current track's album",
- displayDescription: "Sends your current track's album",
- applicationId: '-1',
- inputType: 1,
- type: 1,
- execute: spotifyAlbumExecute
+ name: "spotify album",
+ displayName: "spotify album",
+ description: "Sends your current track's album",
+ displayDescription: "Sends your current track's album",
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
+ execute: spotifyAlbumExecute
};
export const spotifyArtistsCommand = {
- name: 'spotify artists',
- displayName: 'spotify artists',
- description: "Sends your current track's artists",
- displayDescription: "Sends your current track's artists",
- applicationId: '-1',
- inputType: 1,
- type: 1,
- execute: spotifyArtistsExecute
+ name: "spotify artists",
+ displayName: "spotify artists",
+ description: "Sends your current track's artists",
+ displayDescription: "Sends your current track's artists",
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
+ execute: spotifyArtistsExecute
};
export const spotifyCoverCommand = {
- name: 'spotify cover',
- displayName: 'spotify cover',
- description: "Sends your current track's cover",
- displayDescription: "Sends your current track's cover",
- applicationId: '-1',
- inputType: 1,
- type: 1,
- execute: spotifyCoverExecute
+ name: "spotify cover",
+ displayName: "spotify cover",
+ description: "Sends your current track's cover",
+ displayDescription: "Sends your current track's cover",
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
+ execute: spotifyCoverExecute
};
diff --git a/plugins/Commands/src/commands/sysinfo.ts b/plugins/Commands/src/commands/sysinfo.ts
index 900d89a..6a045a7 100644
--- a/plugins/Commands/src/commands/sysinfo.ts
+++ b/plugins/Commands/src/commands/sysinfo.ts
@@ -8,207 +8,207 @@ const messageUtil = findByProps("sendBotMessage", "sendMessage", "receiveMessage
// Device info helper
function getDeviceInfo() {
- let { height, width, scale } = ReactNative.Dimensions.get("screen");
- height *= scale;
- width *= scale;
- return { width, height };
+ let { height, width, scale } = ReactNative.Dimensions.get("screen");
+ height *= scale;
+ width *= scale;
+ return { width, height };
}
// Get current UTC date and time
function getCurrentUTCDateTime() {
- try {
- const now = new Date();
- const utc = new Date(now.getTime() + (now.getTimezoneOffset() * 60000));
+ try {
+ const now = new Date();
+ const utc = new Date(now.getTime() + (now.getTimezoneOffset() * 60000));
- const year = utc.getFullYear();
- const month = String(utc.getMonth() + 1).padStart(2, '0');
- const day = String(utc.getDate()).padStart(2, '0');
- const hours = String(utc.getHours()).padStart(2, '0');
- const minutes = String(utc.getMinutes()).padStart(2, '0');
- const seconds = String(utc.getSeconds()).padStart(2, '0');
+ const year = utc.getFullYear();
+ const month = String(utc.getMonth() + 1).padStart(2, "0");
+ const day = String(utc.getDate()).padStart(2, "0");
+ const hours = String(utc.getHours()).padStart(2, "0");
+ const minutes = String(utc.getMinutes()).padStart(2, "0");
+ const seconds = String(utc.getSeconds()).padStart(2, "0");
- return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
- } catch (e) {
- return "Unknown";
- }
+ return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
+ } catch (e) {
+ return "Unknown";
+ }
}
// Hardware info helper
function getHardwareInfo() {
- try {
- const hwProps = findByProps("memory");
- if (!hwProps) return { cpuCoreCount: "N/A", cpuPerc: "N/A", memUsage: "N/A" };
+ try {
+ const hwProps = findByProps("memory");
+ if (!hwProps) return { cpuCoreCount: "N/A", cpuPerc: "N/A", memUsage: "N/A" };
- const { cpuCoreCount, cpuPercentage, memory } = hwProps;
- const cpuPerc = cpuPercentage ? cpuPercentage.toFixed(2) + "%" : "N/A";
- const memUsage = memory ? parseFloat((memory / 1000).toPrecision(3)) + " MB" : "N/A";
+ const { cpuCoreCount, cpuPercentage, memory } = hwProps;
+ const cpuPerc = cpuPercentage ? cpuPercentage.toFixed(2) + "%" : "N/A";
+ const memUsage = memory ? parseFloat((memory / 1000).toPrecision(3)) + " MB" : "N/A";
- return { cpuCoreCount, cpuPerc, memUsage };
- } catch (e) {
- console.warn("Hardware info unavailable:", e);
- return { cpuCoreCount: "N/A", cpuPerc: "N/A", memUsage: "N/A" };
- }
+ return { cpuCoreCount, cpuPerc, memUsage };
+ } catch (e) {
+ console.warn("Hardware info unavailable:", e);
+ return { cpuCoreCount: "N/A", cpuPerc: "N/A", memUsage: "N/A" };
+ }
}
// Discord info helper
function getDiscordInfo() {
- try {
- const { NativeModules } = ReactNative;
- const discordInfo = NativeModules.InfoDictionaryManager ?? NativeModules.RTNClientInfoManager;
- return discordInfo || {};
- } catch (e) {
- console.warn("Discord info unavailable:", e);
- return {};
- }
+ try {
+ const { NativeModules } = ReactNative;
+ const discordInfo = NativeModules.InfoDictionaryManager ?? NativeModules.RTNClientInfoManager;
+ return discordInfo || {};
+ } catch (e) {
+ console.warn("Discord info unavailable:", e);
+ return {};
+ }
}
// Generate system info
function generateSystemInfo() {
- try {
- const { cpuCoreCount, cpuPerc, memUsage } = getHardwareInfo();
- const discordInfo = getDiscordInfo();
- const { vendetta, discord, react, hermes, os, device } = getDebugInfo();
- const utcDateTime = getCurrentUTCDateTime();
+ try {
+ const { cpuCoreCount, cpuPerc, memUsage } = getHardwareInfo();
+ const discordInfo = getDiscordInfo();
+ const { vendetta, discord, react, hermes, os, device } = getDebugInfo();
+ const utcDateTime = getCurrentUTCDateTime();
- const { version: HermesRelease, bytecodeVersion: HermesBytecode } = hermes || {};
- const { version: ReactVersion, nativeVersion: RNVersion } = react || {};
- const { name: osName, version: osVersion, sdk: osSdk } = os || {};
- const {
- manufacturer: deviceManufacturer,
- brand: deviceBrand,
- model: deviceModel,
- codename: deviceCodename,
- } = device || {};
- const { version: vendettaVersion } = vendetta || {};
- const { version: discordVersion, build: discordBuild } = discord || {};
+ const { version: HermesRelease, bytecodeVersion: HermesBytecode } = hermes || {};
+ const { version: ReactVersion, nativeVersion: RNVersion } = react || {};
+ const { name: osName, version: osVersion, sdk: osSdk } = os || {};
+ const {
+ manufacturer: deviceManufacturer,
+ brand: deviceBrand,
+ model: deviceModel,
+ codename: deviceCodename,
+ } = device || {};
+ const { version: vendettaVersion } = vendetta || {};
+ const { version: discordVersion, build: discordBuild } = discord || {};
- const deviceName = osName == "iOS" ? deviceCodename : `${deviceBrand} ${deviceModel}`;
- const { width, height } = getDeviceInfo();
+ const deviceName = osName == "iOS" ? deviceCodename : `${deviceBrand} ${deviceModel}`;
+ const { width, height } = getDeviceInfo();
- let output = {
- Device: {
- Device: deviceName || "Unknown",
- Model: deviceModel || "Unknown",
- Manufacturer: deviceManufacturer || "Unknown",
- Brand: deviceBrand || "Unknown",
- Display: `${width}x${height}`,
- },
- Hardware: {
- "CPU Cores": cpuCoreCount || "N/A",
- "CPU Usage": cpuPerc,
- "Memory Usage": memUsage,
- },
- Software: {
- OS: osName || "Unknown",
- Version: osVersion || "Unknown",
- },
- Discord: {
- Version: discordVersion || "Unknown",
- Build: discordBuild || "Unknown",
- Vendetta: vendettaVersion || "Unknown",
- },
- React: {
- Version: ReactVersion || "Unknown",
- "Hermes Bytecode": HermesBytecode || "Unknown",
- Hermes: HermesRelease || "Unknown",
- Native: RNVersion || "Unknown",
- },
- Time: {
- "UTC": utcDateTime,
- },
- };
+ const output = {
+ Device: {
+ Device: deviceName || "Unknown",
+ Model: deviceModel || "Unknown",
+ Manufacturer: deviceManufacturer || "Unknown",
+ Brand: deviceBrand || "Unknown",
+ Display: `${width}x${height}`,
+ },
+ Hardware: {
+ "CPU Cores": cpuCoreCount || "N/A",
+ "CPU Usage": cpuPerc,
+ "Memory Usage": memUsage,
+ },
+ Software: {
+ OS: osName || "Unknown",
+ Version: osVersion || "Unknown",
+ },
+ Discord: {
+ Version: discordVersion || "Unknown",
+ Build: discordBuild || "Unknown",
+ Vendetta: vendettaVersion || "Unknown",
+ },
+ React: {
+ Version: ReactVersion || "Unknown",
+ "Hermes Bytecode": HermesBytecode || "Unknown",
+ Hermes: HermesRelease || "Unknown",
+ Native: RNVersion || "Unknown",
+ },
+ Time: {
+ "UTC": utcDateTime,
+ },
+ };
- // Add additional mod versions if available
- if ((window as any).enmity) {
- output.Discord.Enmity = (window as any).enmity.version;
+ // Add additional mod versions if available
+ if ((window as any).enmity) {
+ output.Discord.Enmity = (window as any).enmity.version;
+ }
+ if ((window as any).aliucord) {
+ output.Discord.Aliucord = (window as any).aliucord.version;
+ }
+ if (osSdk) {
+ output.Software["SDK Version"] = osSdk;
+ }
+
+ return output;
+ } catch (e) {
+ console.error("Error generating system info:", e);
+ throw e;
}
- if ((window as any).aliucord) {
- output.Discord.Aliucord = (window as any).aliucord.version;
- }
- if (osSdk) {
- output.Software["SDK Version"] = osSdk;
- }
-
- return output;
- } catch (e) {
- console.error("Error generating system info:", e);
- throw e;
- }
}
// Initialize storage defaults
const categories = ["device", "hardware", "software", "discord", "react", "time", "ephemeral"];
for (const cat of categories) {
- if (storage[cat] === undefined) storage[cat] = true;
+ if (storage[cat] === undefined) storage[cat] = true;
}
// Command execution function
function executeSysinfoCommand(args: any[], ctx: any) {
- try {
- let output = ["__System Information__\n"];
- const data = generateSystemInfo();
+ try {
+ const output = ["__System Information__\n"];
+ const data = generateSystemInfo();
- Object.keys(data).forEach((option) => {
- const permit = storage[option.toLowerCase()];
- const slasharg = args.find((i) => i.name == option.toLowerCase());
- const slashval = slasharg?.value ?? permit;
+ Object.keys(data).forEach((option) => {
+ const permit = storage[option.toLowerCase()];
+ const slasharg = args.find((i) => i.name == option.toLowerCase());
+ const slashval = slasharg?.value ?? permit;
- if ((slashval || permit) && !(permit && !slashval)) {
- output.push(`[**${option}**]`);
- Object.keys(data[option as keyof typeof data]).forEach((subOption) => {
- output.push(`> ${subOption}: ${(data[option as keyof typeof data] as any)[subOption]}`);
+ if ((slashval || permit) && !(permit && !slashval)) {
+ output.push(`[**${option}**]`);
+ Object.keys(data[option as keyof typeof data]).forEach((subOption) => {
+ output.push(`> ${subOption}: ${(data[option as keyof typeof data] as any)[subOption]}`);
+ });
+ }
});
- }
- });
- const epermit = storage["ephemeral"];
- const ephemeral = args.find((i) => i.name == "ephemeral")?.value ?? epermit;
+ const epermit = storage["ephemeral"];
+ const ephemeral = args.find((i) => i.name == "ephemeral")?.value ?? epermit;
- if ((ephemeral || epermit) && !(epermit && !ephemeral)) {
- messageUtil.sendBotMessage(ctx.channel.id, output.join("\n"));
- return { type: 4 };
- } else {
- const fixNonce = Date.now().toString();
- MessageActions.sendMessage(
- ctx.channel.id,
- { content: output.join("\n") },
- void 0,
- { nonce: fixNonce }
- );
- return { type: 4 };
+ if ((ephemeral || epermit) && !(epermit && !ephemeral)) {
+ messageUtil.sendBotMessage(ctx.channel.id, output.join("\n"));
+ return { type: 4 };
+ } else {
+ const fixNonce = Date.now().toString();
+ MessageActions.sendMessage(
+ ctx.channel.id,
+ { content: output.join("\n") },
+ void 0,
+ { nonce: fixNonce }
+ );
+ return { type: 4 };
+ }
+ } catch (e) {
+ console.error("[Sysinfo] Error:", e);
+ // Silent fail - no chat message
+ return { type: 4 };
}
- } catch (e) {
- console.error("[Sysinfo] Error:", e);
- // Silent fail - no chat message
- return { type: 4 };
- }
}
export const sysinfoCommand = {
- name: "sysinfo",
- displayName: "sysinfo",
- description: "Display system information",
- displayDescription: "Display system information",
- options: [
- {
- name: "ephemeral",
- displayName: "ephemeral",
- description: "Keep sysinfo ephemeral (default: true)",
- displayDescription: "Keep sysinfo ephemeral (default: true)",
- type: 5,
- required: false,
- },
- ...categories.slice(0, -1).map((cat) => ({
- name: cat,
- displayName: cat,
- description: `Display the ${cat} section. Set default in settings.`,
- displayDescription: `Display the ${cat} section. Set default in settings.`,
- type: 5,
- required: false,
- })),
- ],
- execute: executeSysinfoCommand,
- applicationId: "-1",
- inputType: 1,
- type: 1,
+ name: "sysinfo",
+ displayName: "sysinfo",
+ description: "Display system information",
+ displayDescription: "Display system information",
+ options: [
+ {
+ name: "ephemeral",
+ displayName: "ephemeral",
+ description: "Keep sysinfo ephemeral (default: true)",
+ displayDescription: "Keep sysinfo ephemeral (default: true)",
+ type: 5,
+ required: false,
+ },
+ ...categories.slice(0, -1).map((cat) => ({
+ name: cat,
+ displayName: cat,
+ description: `Display the ${cat} section. Set default in settings.`,
+ displayDescription: `Display the ${cat} section. Set default in settings.`,
+ type: 5,
+ required: false,
+ })),
+ ],
+ execute: executeSysinfoCommand,
+ applicationId: "-1",
+ inputType: 1,
+ type: 1,
};
diff --git a/plugins/Commands/src/utils/api.ts b/plugins/Commands/src/utils/api.ts
index d726171..2c81fec 100644
--- a/plugins/Commands/src/utils/api.ts
+++ b/plugins/Commands/src/utils/api.ts
@@ -2,64 +2,64 @@
// Facts APIs
export const uselessFact = async () => {
- const response = await fetch("https://uselessfacts.jsph.pl/api/v2/facts/random");
- const resp = await response.json();
- return {
- text: resp["text"],
- source: resp["source"],
- language: resp["language"],
- };
+ const response = await fetch("https://uselessfacts.jsph.pl/api/v2/facts/random");
+ const resp = await response.json();
+ return {
+ text: resp["text"],
+ source: resp["source"],
+ language: resp["language"],
+ };
};
export const dogFact = async () => {
- const response = await fetch("https://dogapi.dog/api/v2/facts?limit=1");
- const resp = await response.json();
- return {
- text: resp["data"]["0"]["attributes"]["body"],
- };
+ const response = await fetch("https://dogapi.dog/api/v2/facts?limit=1");
+ const resp = await response.json();
+ return {
+ text: resp["data"]["0"]["attributes"]["body"],
+ };
};
export const catFact = async () => {
- const response = await fetch("https://catfact.ninja/fact");
- const resp = await response.json();
- return {
- text: resp["fact"],
- length: resp["length"],
- };
+ const response = await fetch("https://catfact.ninja/fact");
+ const resp = await response.json();
+ return {
+ text: resp["fact"],
+ length: resp["length"],
+ };
};
// Image APIs
export const getPetPetData = async (image: string) => {
- const data = await fetch(
- `https://api.obamabot.me/v2/image/petpet?image=${image.replace("webp", "png")}`
- );
- const body = await data.json();
- return body;
+ const data = await fetch(
+ `https://api.obamabot.me/v2/image/petpet?image=${image.replace("webp", "png")}`
+ );
+ const body = await data.json();
+ return body;
};
// Gary API function
-export const getGaryUrl = async (source: string = "gary"): Promise => {
- try {
- switch (source) {
- case "gary":
- const response = await fetch("https://api.garythe.cat/gary");
- const json = await response.json();
- return json.url;
- case "goober":
- const gooberResponse = await fetch("https://api.garythe.cat/goober");
- const gooberJson = await gooberResponse.json();
- return gooberJson.url;
- case "catapi":
- const catResponse = await fetch("https://api.thecatapi.com/v1/images/search");
- const catJson = await catResponse.json();
- return catJson[0].url;
- case "minker":
- return "https://minky.materii.dev/";
- default:
- throw new Error("Invalid Gary image source value");
+export const getGaryUrl = async (source = "gary"): Promise => {
+ try {
+ switch (source) {
+ case "gary":
+ const response = await fetch("https://api.garythe.cat/gary");
+ const json = await response.json();
+ return json.url;
+ case "goober":
+ const gooberResponse = await fetch("https://api.garythe.cat/goober");
+ const gooberJson = await gooberResponse.json();
+ return gooberJson.url;
+ case "catapi":
+ const catResponse = await fetch("https://api.thecatapi.com/v1/images/search");
+ const catJson = await catResponse.json();
+ return catJson[0].url;
+ case "minker":
+ return "https://minky.materii.dev/";
+ default:
+ throw new Error("Invalid Gary image source value");
+ }
+ } catch (error) {
+ console.error(`[Gary API] Error fetching image from ${source}:`, error);
+ return null;
}
- } catch (error) {
- console.error(`[Gary API] Error fetching image from ${source}:`, error);
- return null;
- }
};
diff --git a/plugins/Commands/src/utils/messages.ts b/plugins/Commands/src/utils/messages.ts
index 429d104..be75871 100644
--- a/plugins/Commands/src/utils/messages.ts
+++ b/plugins/Commands/src/utils/messages.ts
@@ -4,65 +4,65 @@ const MessageActions = findByProps("sendMessage");
// Channel type validation
const isValidChannel = (channelType: number) => {
- // Allow: Text (0), DM (1), Voice (2), Group DM (3), News (5)
- // Block: Threads (10,11,12), Forum (15), Stage (13), Category (4)
- const unsupportedTypes = [4, 10, 11, 12, 13, 15];
- return !unsupportedTypes.includes(channelType);
+ // Allow: Text (0), DM (1), Voice (2), Group DM (3), News (5)
+ // Block: Threads (10,11,12), Forum (15), Stage (13), Category (4)
+ const unsupportedTypes = [4, 10, 11, 12, 13, 15];
+ return !unsupportedTypes.includes(channelType);
};
export const sendMessage = (
- channelId: string,
- content: string,
- replyToId?: string,
- storage?: any,
- ephemeral?: boolean,
+ channelId: string,
+ content: string,
+ replyToId?: string,
+ storage?: any,
+ ephemeral?: boolean,
) => {
- // If no content, just acknowledge the command
- if (!content) return { type: 4 };
+ // If no content, just acknowledge the command
+ if (!content) return { type: 4 };
- // For ephemeral messages, return special format
- if (ephemeral) {
- return {
- type: 4,
- data: {
- content,
- flags: 64,
- },
- };
- }
+ // For ephemeral messages, return special format
+ if (ephemeral) {
+ return {
+ type: 4,
+ data: {
+ content,
+ flags: 64,
+ },
+ };
+ }
- try {
+ try {
// For normal messages, send through MessageActions
- const message = {
- content,
- ...(replyToId && storage?.factSettings?.sendAsReply
- ? { messageReference: { messageId: replyToId } }
- : {}),
- };
+ const message = {
+ content,
+ ...(replyToId && storage?.factSettings?.sendAsReply
+ ? { messageReference: { messageId: replyToId } }
+ : {}),
+ };
- // Use consistent, collision-resistant nonce generation
- const nonce = (BigInt(Date.now()) * BigInt(4194304) + BigInt(Math.floor(Math.random() * 4194304))).toString();
+ // Use consistent, collision-resistant nonce generation
+ const nonce = (BigInt(Date.now()) * BigInt(4194304) + BigInt(Math.floor(Math.random() * 4194304))).toString();
- MessageActions.sendMessage(channelId, message, void 0, { nonce });
+ MessageActions.sendMessage(channelId, message, void 0, { nonce });
- return { type: 4 }; // Acknowledge the command
- } catch (error) {
- console.error('[Commands] Message send failed:', error);
- // Silent fail - no error message in chat
- return { type: 4 };
- }
+ return { type: 4 }; // Acknowledge the command
+ } catch (error) {
+ console.error("[Commands] Message send failed:", error);
+ // Silent fail - no error message in chat
+ return { type: 4 };
+ }
};
// Helper function to validate channel types in commands
export const validateChannelForCommand = (ctx: any) => {
- if (!isValidChannel(ctx.channel.type)) {
- return {
- type: 4,
- data: {
- content: "This command cannot be used in threads, forum channels, or voice channels.",
- flags: 64 // Ephemeral
- }
- };
- }
- return null;
+ if (!isValidChannel(ctx.channel.type)) {
+ return {
+ type: 4,
+ data: {
+ content: "This command cannot be used in threads, forum channels, or voice channels.",
+ flags: 64 // Ephemeral
+ }
+ };
+ }
+ return null;
};
diff --git a/serve.mjs b/serve.mjs
new file mode 100755
index 0000000..493dd9c
--- /dev/null
+++ b/serve.mjs
@@ -0,0 +1,81 @@
+#!/usr/bin/env node
+
+import { exec } from "child_process";
+import { networkInterfaces } from "os";
+import path from "path";
+import { fileURLToPath } from "url";
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+// Get local IP address
+function getLocalIP() {
+ const nets = networkInterfaces();
+ const results = {};
+
+ for (const name of Object.keys(nets)) {
+ for (const net of nets[name]) {
+ // Skip over non-IPv4 and internal addresses
+ if (net.family === "IPv4" && !net.internal) {
+ if (!results[name]) {
+ results[name] = [];
+ }
+ results[name].push(net.address);
+ }
+ }
+ }
+
+ // Return the first available IP
+ const interfaces = Object.keys(results);
+ if (interfaces.length > 0) {
+ return results[interfaces[0]][0];
+ }
+ return "localhost";
+}
+
+const distPath = path.join(__dirname, "dist");
+const port = process.env.PORT || 3000;
+const host = "0.0.0.0"; // Bind to all interfaces
+const localIP = getLocalIP();
+
+console.log("π Starting server...");
+console.log(`π Serving files from: ${distPath}`);
+console.log(`π Server will be available at:`);
+console.log(` - Local: http://localhost:${port}`);
+console.log(` - Network: http://${localIP}:${port}`);
+console.log(
+ ` - Public: http://0.0.0.0:${port} (accessible from any device on your network)`,
+);
+console.log("\nπ‘ Press Ctrl+C to stop the server\n");
+
+// Use the serve package to start the server
+const serveCommand = `npx serve -s "${distPath}" -l ${port}`;
+
+const serverProcess = exec(serveCommand, (error, stdout, stderr) => {
+ if (error) {
+ console.error(`β Error starting server: ${error.message}`);
+ process.exit(1);
+ }
+});
+
+// Forward output from serve command
+serverProcess.stdout.on("data", (data) => {
+ console.log(data.toString());
+});
+
+serverProcess.stderr.on("data", (data) => {
+ console.error(data.toString());
+});
+
+// Handle graceful shutdown
+process.on("SIGINT", () => {
+ console.log("\nπ Shutting down server...");
+ serverProcess.kill("SIGTERM");
+ process.exit(0);
+});
+
+process.on("SIGTERM", () => {
+ console.log("\nπ Shutting down server...");
+ serverProcess.kill("SIGTERM");
+ process.exit(0);
+});