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
This commit is contained in:
parent
ba0d3d7679
commit
c35789b14e
19 changed files with 1820 additions and 1592 deletions
2
.gitignore
vendored
2
.gitignore
vendored
|
|
@ -6,3 +6,5 @@ Themer
|
|||
RealThemer
|
||||
ChatMesser
|
||||
allcommands
|
||||
node_modules
|
||||
dist
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -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 };
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<string | null> => {
|
||||
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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<string | null> {
|
||||
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<boolean>((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!! 🥺🥺🥺",
|
||||
[],
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<string[]> {
|
||||
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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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<string | null> => {
|
||||
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<string | null> => {
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
};
|
||||
|
|
|
|||
81
serve.mjs
Executable file
81
serve.mjs
Executable file
|
|
@ -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);
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue