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
|
RealThemer
|
||||||
ChatMesser
|
ChatMesser
|
||||||
allcommands
|
allcommands
|
||||||
|
node_modules
|
||||||
|
dist
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,12 @@
|
||||||
{
|
{
|
||||||
"authors": [
|
"authors": [
|
||||||
{
|
|
||||||
"name": "pylix",
|
|
||||||
"id": "492949202121261067"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "kmmiio99o",
|
"name": "kmmiio99o",
|
||||||
"id": "879393496627306587"
|
"id": "879393496627306587"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"main": "src/index.ts"
|
"main": "index.js",
|
||||||
|
"vendetta": {
|
||||||
|
"icon": "ic_application_command_24px"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -9,11 +9,11 @@
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@rollup/plugin-commonjs": "^24.1.0",
|
"@rollup/plugin-commonjs": "^24.1.0",
|
||||||
"@rollup/plugin-node-resolve": "^15.0.2",
|
"@rollup/plugin-node-resolve": "^15.0.2",
|
||||||
|
"@swc/core": "^1.3.51",
|
||||||
"@types/react": "^18.0.37",
|
"@types/react": "^18.0.37",
|
||||||
"@types/react-native": "^0.70.13",
|
"@types/react-native": "^0.70.13",
|
||||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||||
"@typescript-eslint/parser": "^5.59.0",
|
"@typescript-eslint/parser": "^5.59.0",
|
||||||
"@swc/core": "^1.3.51",
|
|
||||||
"esbuild": "^0.17.17",
|
"esbuild": "^0.17.17",
|
||||||
"eslint": "^8.38.0",
|
"eslint": "^8.38.0",
|
||||||
"eslint-plugin-react": "^7.32.2",
|
"eslint-plugin-react": "^7.32.2",
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
import { registerCommand } from "@vendetta/commands";
|
import { registerCommand } from "@vendetta/commands";
|
||||||
import { storage } from "@vendetta/plugin";
|
import { storage } from "@vendetta/plugin";
|
||||||
import {
|
import {
|
||||||
catFactCommand,
|
catFactCommand,
|
||||||
dogFactCommand,
|
dogFactCommand,
|
||||||
uselessFactCommand,
|
uselessFactCommand,
|
||||||
} from "./src/commands/facts";
|
} from "./src/commands/facts";
|
||||||
import { pluginListCommand, themeListCommand } from "./src/commands/lists";
|
import { pluginListCommand, themeListCommand } from "./src/commands/lists";
|
||||||
import { petPetCommand } from "./src/commands/petpet";
|
import { petPetCommand } from "./src/commands/petpet";
|
||||||
|
|
@ -11,10 +11,10 @@ import { konoSelfCommand, konoSendCommand } from "./src/commands/konochan";
|
||||||
import { firstMessageCommand } from "./src/commands/firstmessage";
|
import { firstMessageCommand } from "./src/commands/firstmessage";
|
||||||
import { sysinfoCommand } from "./src/commands/sysinfo";
|
import { sysinfoCommand } from "./src/commands/sysinfo";
|
||||||
import {
|
import {
|
||||||
spotifyTrackCommand,
|
spotifyTrackCommand,
|
||||||
spotifyAlbumCommand,
|
spotifyAlbumCommand,
|
||||||
spotifyArtistsCommand,
|
spotifyArtistsCommand,
|
||||||
spotifyCoverCommand
|
spotifyCoverCommand
|
||||||
} from "./src/commands/spotify";
|
} from "./src/commands/spotify";
|
||||||
import { garyCommand } from "./src/commands/gary";
|
import { garyCommand } from "./src/commands/gary";
|
||||||
import { lovefemboysCommand } from "./src/commands/lovefemboys";
|
import { lovefemboysCommand } from "./src/commands/lovefemboys";
|
||||||
|
|
@ -24,103 +24,103 @@ import settings from "./settings";
|
||||||
|
|
||||||
// Initialize storage with default values
|
// Initialize storage with default values
|
||||||
if (!storage.factSettings) {
|
if (!storage.factSettings) {
|
||||||
storage.factSettings = {
|
storage.factSettings = {
|
||||||
sendAsReply: true,
|
sendAsReply: true,
|
||||||
includeCitation: false,
|
includeCitation: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!storage.listSettings) {
|
if (!storage.listSettings) {
|
||||||
storage.listSettings = {
|
storage.listSettings = {
|
||||||
pluginListAlwaysDetailed: false,
|
pluginListAlwaysDetailed: false,
|
||||||
themeListAlwaysDetailed: false,
|
themeListAlwaysDetailed: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!storage.garySettings) {
|
if (!storage.garySettings) {
|
||||||
storage.garySettings = {
|
storage.garySettings = {
|
||||||
imageSource: "gary",
|
imageSource: "gary",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!storage.enabledCommands) {
|
if (!storage.enabledCommands) {
|
||||||
storage.enabledCommands = {
|
storage.enabledCommands = {
|
||||||
catfact: true,
|
catfact: true,
|
||||||
dogfact: true,
|
dogfact: true,
|
||||||
useless: true,
|
useless: true,
|
||||||
petpet: true,
|
petpet: true,
|
||||||
pluginList: true,
|
pluginList: true,
|
||||||
themeList: true,
|
themeList: true,
|
||||||
konoself: true,
|
konoself: true,
|
||||||
konosend: true,
|
konosend: true,
|
||||||
firstmessage: true,
|
firstmessage: true,
|
||||||
sysinfo: true,
|
sysinfo: true,
|
||||||
spotifyTrack: true,
|
spotifyTrack: true,
|
||||||
spotifyAlbum: true,
|
spotifyAlbum: true,
|
||||||
spotifyArtists: true,
|
spotifyArtists: true,
|
||||||
spotifyCover: true,
|
spotifyCover: true,
|
||||||
gary: true,
|
gary: true,
|
||||||
ip: true, // IP command - enabled by default
|
ip: true, // IP command - enabled by default
|
||||||
lovefemboys: false, // Hidden command - disabled by default
|
lovefemboys: false, // Hidden command - disabled by default
|
||||||
nekoslife: false, // Hidden command - disabled by default
|
nekoslife: false, // Hidden command - disabled by default
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize hidden settings storage
|
// Initialize hidden settings storage
|
||||||
if (!storage.hiddenSettings) {
|
if (!storage.hiddenSettings) {
|
||||||
storage.hiddenSettings = {
|
storage.hiddenSettings = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
visible: false,
|
visible: false,
|
||||||
konochanBypassNsfw: false, // NSFW bypass option
|
konochanBypassNsfw: false, // NSFW bypass option
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Map commands to their storage keys
|
// Map commands to their storage keys
|
||||||
const commandMap = {
|
const commandMap = {
|
||||||
catfact: catFactCommand,
|
catfact: catFactCommand,
|
||||||
dogfact: dogFactCommand,
|
dogfact: dogFactCommand,
|
||||||
useless: uselessFactCommand,
|
useless: uselessFactCommand,
|
||||||
petpet: petPetCommand,
|
petpet: petPetCommand,
|
||||||
pluginList: pluginListCommand,
|
pluginList: pluginListCommand,
|
||||||
themeList: themeListCommand,
|
themeList: themeListCommand,
|
||||||
konoself: konoSelfCommand,
|
konoself: konoSelfCommand,
|
||||||
konosend: konoSendCommand,
|
konosend: konoSendCommand,
|
||||||
firstmessage: firstMessageCommand,
|
firstmessage: firstMessageCommand,
|
||||||
sysinfo: sysinfoCommand,
|
sysinfo: sysinfoCommand,
|
||||||
spotifyTrack: spotifyTrackCommand,
|
spotifyTrack: spotifyTrackCommand,
|
||||||
spotifyAlbum: spotifyAlbumCommand,
|
spotifyAlbum: spotifyAlbumCommand,
|
||||||
spotifyArtists: spotifyArtistsCommand,
|
spotifyArtists: spotifyArtistsCommand,
|
||||||
spotifyCover: spotifyCoverCommand,
|
spotifyCover: spotifyCoverCommand,
|
||||||
gary: garyCommand,
|
gary: garyCommand,
|
||||||
ip: ipCommand, // Add IP command
|
ip: ipCommand, // Add IP command
|
||||||
lovefemboys: lovefemboysCommand, // Add hidden command to map
|
lovefemboys: lovefemboysCommand, // Add hidden command to map
|
||||||
nekoslife: nekoslifeCommand, // Add NekosLife command
|
nekoslife: nekoslifeCommand, // Add NekosLife command
|
||||||
};
|
};
|
||||||
|
|
||||||
// Store registered commands for cleanup
|
// Store registered commands for cleanup
|
||||||
let commands: Array<() => void> = [];
|
let commands: Array<() => void> = [];
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
onLoad: () => {
|
onLoad: () => {
|
||||||
console.log("[All-In-One Commands] Loading plugin...");
|
console.log("[All-In-One Commands] Loading plugin...");
|
||||||
|
|
||||||
// Register only enabled commands
|
// Register only enabled commands
|
||||||
for (const [key, command] of Object.entries(commandMap)) {
|
for (const [key, command] of Object.entries(commandMap)) {
|
||||||
if (storage.enabledCommands[key]) {
|
if (storage.enabledCommands[key]) {
|
||||||
commands.push(registerCommand(command));
|
commands.push(registerCommand(command));
|
||||||
console.log(`[All-In-One Commands] Registered command: ${key}`);
|
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: () => {
|
onUnload: () => {
|
||||||
console.log("[All-In-One Commands] Unloading plugin...");
|
console.log("[All-In-One Commands] Unloading plugin...");
|
||||||
commands.forEach((unregister) => unregister());
|
commands.forEach((unregister) => unregister());
|
||||||
commands = [];
|
commands = [];
|
||||||
console.log("[All-In-One Commands] Plugin unloaded successfully!");
|
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
|
// Helper function to format fact response
|
||||||
const formatFactResponse = (fact: { text: string; source?: string }) => {
|
const formatFactResponse = (fact: { text: string; source?: string }) => {
|
||||||
let response = fact.text;
|
let response = fact.text;
|
||||||
if (storage.factSettings?.includeCitation && fact.source) {
|
if (storage.factSettings?.includeCitation && fact.source) {
|
||||||
response += `\n\nSource: ${fact.source}`;
|
response += `\n\nSource: ${fact.source}`;
|
||||||
}
|
}
|
||||||
return response;
|
return response;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const catFactCommand = {
|
export const catFactCommand = {
|
||||||
name: "catfact",
|
name: "catfact",
|
||||||
displayName: "catfact",
|
displayName: "catfact",
|
||||||
description: "Sends a random cat fact.",
|
description: "Sends a random cat fact.",
|
||||||
displayDescription: "Sends a random cat fact.",
|
displayDescription: "Sends a random cat fact.",
|
||||||
applicationId: "-1",
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 1,
|
type: 1,
|
||||||
execute: async (args: any, ctx: any) => {
|
execute: async (args: any, ctx: any) => {
|
||||||
try {
|
try {
|
||||||
const fact = await catFact();
|
const fact = await catFact();
|
||||||
const fixNonce = Date.now().toString();
|
const fixNonce = Date.now().toString();
|
||||||
MessageActions.sendMessage(
|
MessageActions.sendMessage(
|
||||||
ctx.channel.id,
|
ctx.channel.id,
|
||||||
{ content: formatFactResponse(fact) },
|
{ content: formatFactResponse(fact) },
|
||||||
void 0,
|
void 0,
|
||||||
{ nonce: fixNonce }
|
{ nonce: fixNonce }
|
||||||
);
|
);
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[CatFact] Error:', error);
|
console.error("[CatFact] Error:", error);
|
||||||
// Silent fail - no error message in chat
|
// Silent fail - no error message in chat
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dogFactCommand = {
|
export const dogFactCommand = {
|
||||||
name: "dogfact",
|
name: "dogfact",
|
||||||
displayName: "dogfact",
|
displayName: "dogfact",
|
||||||
description: "Sends a dog fact.",
|
description: "Sends a dog fact.",
|
||||||
displayDescription: "Sends a dog fact.",
|
displayDescription: "Sends a dog fact.",
|
||||||
applicationId: "-1",
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 1,
|
type: 1,
|
||||||
execute: async (args: any, ctx: any) => {
|
execute: async (args: any, ctx: any) => {
|
||||||
try {
|
try {
|
||||||
const fact = await dogFact();
|
const fact = await dogFact();
|
||||||
const fixNonce = Date.now().toString();
|
const fixNonce = Date.now().toString();
|
||||||
MessageActions.sendMessage(
|
MessageActions.sendMessage(
|
||||||
ctx.channel.id,
|
ctx.channel.id,
|
||||||
{ content: formatFactResponse(fact) },
|
{ content: formatFactResponse(fact) },
|
||||||
void 0,
|
void 0,
|
||||||
{ nonce: fixNonce }
|
{ nonce: fixNonce }
|
||||||
);
|
);
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[DogFact] Error:', error);
|
console.error("[DogFact] Error:", error);
|
||||||
// Silent fail - no error message in chat
|
// Silent fail - no error message in chat
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
export const uselessFactCommand = {
|
export const uselessFactCommand = {
|
||||||
name: "useless",
|
name: "useless",
|
||||||
displayName: "useless",
|
displayName: "useless",
|
||||||
description: "Sends a useless fact.",
|
description: "Sends a useless fact.",
|
||||||
displayDescription: "Sends a useless fact.",
|
displayDescription: "Sends a useless fact.",
|
||||||
applicationId: "-1",
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 1,
|
type: 1,
|
||||||
execute: async (args: any, ctx: any) => {
|
execute: async (args: any, ctx: any) => {
|
||||||
try {
|
try {
|
||||||
const fact = await uselessFact();
|
const fact = await uselessFact();
|
||||||
const fixNonce = Date.now().toString();
|
const fixNonce = Date.now().toString();
|
||||||
MessageActions.sendMessage(
|
MessageActions.sendMessage(
|
||||||
ctx.channel.id,
|
ctx.channel.id,
|
||||||
{ content: formatFactResponse(fact) },
|
{ content: formatFactResponse(fact) },
|
||||||
void 0,
|
void 0,
|
||||||
{ nonce: fixNonce }
|
{ nonce: fixNonce }
|
||||||
);
|
);
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[UselessFact] Error:', error);
|
console.error("[UselessFact] Error:", error);
|
||||||
// Silent fail - no error message in chat
|
// Silent fail - no error message in chat
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -6,130 +6,130 @@ const MessageActions = findByProps("sendMessage");
|
||||||
|
|
||||||
// Helper functions for fetching messages
|
// Helper functions for fetching messages
|
||||||
const getFirstGuildMessage = async (
|
const getFirstGuildMessage = async (
|
||||||
guildId: string,
|
guildId: string,
|
||||||
userId?: string,
|
userId?: string,
|
||||||
channelId?: string,
|
channelId?: string,
|
||||||
) => {
|
) => {
|
||||||
const userParam = userId ? `&author_id=${userId}` : "";
|
const userParam = userId ? `&author_id=${userId}` : "";
|
||||||
const channelParam = channelId ? `&channel_id=${channelId}` : "";
|
const channelParam = channelId ? `&channel_id=${channelId}` : "";
|
||||||
const minIdParam = userId ? "" : `&min_id=0`;
|
const minIdParam = userId ? "" : "&min_id=0";
|
||||||
return (
|
return (
|
||||||
await APIUtils.get({
|
await APIUtils.get({
|
||||||
url: `/guilds/${guildId}/messages/search`,
|
url: `/guilds/${guildId}/messages/search`,
|
||||||
query: `include_nsfw=true${userParam}${channelParam}${minIdParam}&sort_by=timestamp&sort_order=asc&offset=0`,
|
query: `include_nsfw=true${userParam}${channelParam}${minIdParam}&sort_by=timestamp&sort_order=asc&offset=0`,
|
||||||
})
|
})
|
||||||
).body.messages[0][0];
|
).body.messages[0][0];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFirstDMMessage = async (dmId: string, userId?: string) => {
|
const getFirstDMMessage = async (dmId: string, userId?: string) => {
|
||||||
const userParam = userId ? `&author_id=${userId}` : "";
|
const userParam = userId ? `&author_id=${userId}` : "";
|
||||||
const minIdParam = userId ? "" : `&min_id=0`;
|
const minIdParam = userId ? "" : "&min_id=0";
|
||||||
return (
|
return (
|
||||||
await APIUtils.get({
|
await APIUtils.get({
|
||||||
url: `/channels/${dmId}/messages/search`,
|
url: `/channels/${dmId}/messages/search`,
|
||||||
query: `&sort_by=timestamp&sort_order=asc&offset=0${userParam}${minIdParam}`,
|
query: `&sort_by=timestamp&sort_order=asc&offset=0${userParam}${minIdParam}`,
|
||||||
})
|
})
|
||||||
).body.messages[0][0];
|
).body.messages[0][0];
|
||||||
};
|
};
|
||||||
|
|
||||||
export const firstMessageCommand = {
|
export const firstMessageCommand = {
|
||||||
name: "firstmessage",
|
name: "firstmessage",
|
||||||
displayName: "firstmessage",
|
displayName: "firstmessage",
|
||||||
description: "Tired of scrolling to first message?",
|
description: "Tired of scrolling to first message?",
|
||||||
displayDescription: "Tired of scrolling to first message?",
|
displayDescription: "Tired of scrolling to first message?",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "user",
|
name: "user",
|
||||||
displayName: "user",
|
displayName: "user",
|
||||||
description: "Target user to get their first message in this server/dm",
|
description: "Target user to get their first message in this server/dm",
|
||||||
displayDescription: "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
|
type: 6, // USER type
|
||||||
required: false,
|
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 };
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
applicationId: "-1",
|
||||||
name: "channel",
|
inputType: 1,
|
||||||
displayName: "channel",
|
type: 1,
|
||||||
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,
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,42 +5,42 @@ import { getGaryUrl } from "../utils/api";
|
||||||
const MessageActions = findByProps("sendMessage");
|
const MessageActions = findByProps("sendMessage");
|
||||||
|
|
||||||
export const garyCommand = {
|
export const garyCommand = {
|
||||||
name: "gary",
|
name: "gary",
|
||||||
displayName: "gary",
|
displayName: "gary",
|
||||||
description: "Send a random Gary image to the channel.",
|
description: "Send a random Gary image to the channel.",
|
||||||
displayDescription: "Send a random Gary image to the channel.",
|
displayDescription: "Send a random Gary image to the channel.",
|
||||||
options: [],
|
options: [],
|
||||||
execute: async (args: any, ctx: any) => {
|
execute: async (args: any, ctx: any) => {
|
||||||
try {
|
try {
|
||||||
// Ensure garySettings exists and get the source
|
// Ensure garySettings exists and get the source
|
||||||
if (!storage.garySettings) {
|
if (!storage.garySettings) {
|
||||||
storage.garySettings = { imageSource: "gary" };
|
storage.garySettings = { imageSource: "gary" };
|
||||||
}
|
}
|
||||||
|
|
||||||
const source = storage.garySettings.imageSource || "gary";
|
const source = storage.garySettings.imageSource || "gary";
|
||||||
console.log(`[Gary Command] Using image source: ${source}`); // Debug log
|
console.log(`[Gary Command] Using image source: ${source}`); // Debug log
|
||||||
|
|
||||||
const imageUrl = await getGaryUrl(source);
|
const imageUrl = await getGaryUrl(source);
|
||||||
|
|
||||||
if (!imageUrl) {
|
if (!imageUrl) {
|
||||||
console.log("[Gary Command] No image URL received");
|
console.log("[Gary Command] No image URL received");
|
||||||
// Silent fail - no error message
|
// Silent fail - no error message
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[Gary Command] Sending image: ${imageUrl}`); // Debug log
|
console.log(`[Gary Command] Sending image: ${imageUrl}`); // Debug log
|
||||||
const fixNonce = Date.now().toString();
|
const fixNonce = Date.now().toString();
|
||||||
MessageActions.sendMessage(ctx.channel.id, { content: imageUrl }, void 0, {
|
MessageActions.sendMessage(ctx.channel.id, { content: imageUrl }, void 0, {
|
||||||
nonce: fixNonce,
|
nonce: fixNonce,
|
||||||
});
|
});
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Gary] Error:', error);
|
console.error("[Gary] Error:", error);
|
||||||
// Silent fail - no error message in chat
|
// Silent fail - no error message in chat
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
applicationId: "-1",
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 1,
|
type: 1,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -26,131 +26,131 @@ interface IpWhoisResponse {
|
||||||
}
|
}
|
||||||
|
|
||||||
export const ipCommand = {
|
export const ipCommand = {
|
||||||
name: "ip",
|
name: "ip",
|
||||||
displayName: "ip",
|
displayName: "ip",
|
||||||
description: "IP address and domain lookup",
|
description: "IP address and domain lookup",
|
||||||
displayDescription: "IP address and domain lookup",
|
displayDescription: "IP address and domain lookup",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "query",
|
name: "query",
|
||||||
displayName: "query",
|
displayName: "query",
|
||||||
description: "Enter IPv4/IPv6 address or domain name",
|
description: "Enter IPv4/IPv6 address or domain name",
|
||||||
displayDescription: "Enter IPv4/IPv6 address or domain name",
|
displayDescription: "Enter IPv4/IPv6 address or domain name",
|
||||||
type: 3, // String
|
type: 3, // String
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "send",
|
name: "send",
|
||||||
displayName: "send",
|
displayName: "send",
|
||||||
description: "Send to chat",
|
description: "Send to chat",
|
||||||
displayDescription: "Send to chat",
|
displayDescription: "Send to chat",
|
||||||
type: 5, // Boolean
|
type: 5, // Boolean
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ephemeral",
|
name: "ephemeral",
|
||||||
displayName: "ephemeral",
|
displayName: "ephemeral",
|
||||||
description: "Send as ephemeral message (only you can see)",
|
description: "Send as ephemeral message (only you can see)",
|
||||||
displayDescription: "Send as ephemeral message (only you can see)",
|
displayDescription: "Send as ephemeral message (only you can see)",
|
||||||
type: 5, // Boolean
|
type: 5, // Boolean
|
||||||
required: false,
|
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,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
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 (!query) {
|
||||||
if (!isEphemeral && !shouldSend) {
|
const errorMsg = "❌ Query parameter is required!";
|
||||||
showToast(`Looking up IP information for ${query}...`, getAssetIDByName("DownloadIcon"));
|
if (isEphemeral) {
|
||||||
}
|
return {
|
||||||
|
type: 4,
|
||||||
|
data: {
|
||||||
|
content: errorMsg,
|
||||||
|
flags: 64,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
showToast(errorMsg, getAssetIDByName("CircleXIcon"));
|
||||||
|
return { type: 4 };
|
||||||
|
}
|
||||||
|
|
||||||
// Build API URL with all requested fields
|
// Show loading toast
|
||||||
const url = `http://ipwhois.app/json/${encodeURIComponent(query)}?objects=` +
|
if (!isEphemeral && !shouldSend) {
|
||||||
`success,ip,type,as,org,isp,continent,country,country_capital,city,region,` +
|
showToast(`Looking up IP information for ${query}...`, getAssetIDByName("DownloadIcon"));
|
||||||
`country_phone,latitude,longitude,timezone,currency,completed_requests`;
|
}
|
||||||
|
|
||||||
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";
|
||||||
|
|
||||||
if (!response.ok) {
|
const response = await fetch(url);
|
||||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const data: IpWhoisResponse = await response.json();
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (!data.success) {
|
const data: IpWhoisResponse = await response.json();
|
||||||
const errorMsg = `❌ Failed to lookup IP information for: ${query}`;
|
|
||||||
if (isEphemeral) {
|
if (!data.success) {
|
||||||
return {
|
const errorMsg = `❌ Failed to lookup IP information for: ${query}`;
|
||||||
type: 4,
|
if (isEphemeral) {
|
||||||
data: {
|
return {
|
||||||
content: errorMsg,
|
type: 4,
|
||||||
flags: 64,
|
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 };
|
applicationId: "-1",
|
||||||
}
|
inputType: 1,
|
||||||
|
type: 1,
|
||||||
// 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,
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -11,128 +11,128 @@ const messageUtil = findByProps("sendBotMessage", "sendMessage", "receiveMessage
|
||||||
* @returns The URL of the fetched image or null if none are found.
|
* @returns The URL of the fetched image or null if none are found.
|
||||||
*/
|
*/
|
||||||
const fetchImage = async (isNSFW: boolean): Promise<string | null> => {
|
const fetchImage = async (isNSFW: boolean): Promise<string | null> => {
|
||||||
const baseURL = "https://konachan.com/post.json";
|
const baseURL = "https://konachan.com/post.json";
|
||||||
const tag = isNSFW ? "rating:explicit" : "rating:safe";
|
const tag = isNSFW ? "rating:explicit" : "rating:safe";
|
||||||
const randomPage = Math.floor(Math.random() * 100);
|
const randomPage = Math.floor(Math.random() * 100);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch(`${baseURL}?tags=${tag}&limit=1&page=${randomPage}`);
|
const response = await fetch(`${baseURL}?tags=${tag}&limit=1&page=${randomPage}`);
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (!Array.isArray(data) || data.length === 0) {
|
if (!Array.isArray(data) || data.length === 0) {
|
||||||
return null;
|
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 = () => {
|
const showNSFWWarning = () => {
|
||||||
return alerts.showConfirmationAlert({
|
return alerts.showConfirmationAlert({
|
||||||
title: "NSFW Content Warning",
|
title: "NSFW Content Warning",
|
||||||
content: "NSFW content can only be sent in NSFW channels!",
|
content: "NSFW content can only be sent in NSFW channels!",
|
||||||
confirmText: "Okay",
|
confirmText: "Okay",
|
||||||
cancelText: null,
|
cancelText: null,
|
||||||
confirmColor: "brand",
|
confirmColor: "brand",
|
||||||
isDismissable: true,
|
isDismissable: true,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// Common options for both commands
|
// Common options for both commands
|
||||||
const nsfwOption = {
|
const nsfwOption = {
|
||||||
name: "nsfw",
|
name: "nsfw",
|
||||||
description: "Include NSFW content?",
|
description: "Include NSFW content?",
|
||||||
type: 5,
|
type: 5,
|
||||||
required: false,
|
required: false,
|
||||||
displayName: "nsfw",
|
displayName: "nsfw",
|
||||||
displayDescription: "Include NSFW content?",
|
displayDescription: "Include NSFW content?",
|
||||||
};
|
};
|
||||||
|
|
||||||
export const konoSelfCommand = {
|
export const konoSelfCommand = {
|
||||||
name: "konoself",
|
name: "konoself",
|
||||||
displayName: "konoself",
|
displayName: "konoself",
|
||||||
description: "Fetch a random image from KonoChan for yourself.",
|
description: "Fetch a random image from KonoChan for yourself.",
|
||||||
displayDescription: "Fetch a random image from KonoChan for yourself.",
|
displayDescription: "Fetch a random image from KonoChan for yourself.",
|
||||||
options: [nsfwOption],
|
options: [nsfwOption],
|
||||||
execute: async (args: any, ctx: any) => {
|
execute: async (args: any, ctx: any) => {
|
||||||
try {
|
try {
|
||||||
const options = new Map(args.map((option: any) => [option.name, option]));
|
const options = new Map(args.map((option: any) => [option.name, option]));
|
||||||
const isNSFW = options.get("nsfw")?.value || false;
|
const isNSFW = options.get("nsfw")?.value || false;
|
||||||
|
|
||||||
// Check if channel is NSFW for NSFW content (unless bypass is enabled)
|
// Check if channel is NSFW for NSFW content (unless bypass is enabled)
|
||||||
const bypassEnabled = storage.hiddenSettings?.konochanBypassNsfw || false;
|
const bypassEnabled = storage.hiddenSettings?.konochanBypassNsfw || false;
|
||||||
console.log(`[KonoSelf] NSFW: ${isNSFW}, Channel NSFW: ${ctx.channel.nsfw}, Bypass: ${bypassEnabled}`);
|
console.log(`[KonoSelf] NSFW: ${isNSFW}, Channel NSFW: ${ctx.channel.nsfw}, Bypass: ${bypassEnabled}`);
|
||||||
|
|
||||||
if (isNSFW && !ctx.channel.nsfw && !bypassEnabled) {
|
if (isNSFW && !ctx.channel.nsfw && !bypassEnabled) {
|
||||||
showNSFWWarning();
|
showNSFWWarning();
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageUrl = await fetchImage(isNSFW);
|
const imageUrl = await fetchImage(isNSFW);
|
||||||
|
|
||||||
if (!imageUrl) {
|
if (!imageUrl) {
|
||||||
// Silent fail - no error message
|
// Silent fail - no error message
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
|
|
||||||
messageUtil.sendBotMessage(
|
messageUtil.sendBotMessage(
|
||||||
ctx.channel.id,
|
ctx.channel.id,
|
||||||
`Here's your random image: ${imageUrl}`,
|
`Here's your random image: ${imageUrl}`,
|
||||||
);
|
);
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[KonoSelf] Error:', error);
|
console.error("[KonoSelf] Error:", error);
|
||||||
// Silent fail - no error message in chat
|
// Silent fail - no error message in chat
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
applicationId: "-1",
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 1,
|
type: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const konoSendCommand = {
|
export const konoSendCommand = {
|
||||||
name: "konosend",
|
name: "konosend",
|
||||||
displayName: "konosend",
|
displayName: "konosend",
|
||||||
description: "Fetch a random image from KonoChan and send it to the channel.",
|
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.",
|
displayDescription: "Fetch a random image from KonoChan and send it to the channel.",
|
||||||
options: [nsfwOption],
|
options: [nsfwOption],
|
||||||
execute: async (args: any, ctx: any) => {
|
execute: async (args: any, ctx: any) => {
|
||||||
try {
|
try {
|
||||||
const options = new Map(args.map((option: any) => [option.name, option]));
|
const options = new Map(args.map((option: any) => [option.name, option]));
|
||||||
const isNSFW = options.get("nsfw")?.value || false;
|
const isNSFW = options.get("nsfw")?.value || false;
|
||||||
|
|
||||||
// Check if channel is NSFW for NSFW content (unless bypass is enabled)
|
// Check if channel is NSFW for NSFW content (unless bypass is enabled)
|
||||||
const bypassEnabled = storage.hiddenSettings?.konochanBypassNsfw || false;
|
const bypassEnabled = storage.hiddenSettings?.konochanBypassNsfw || false;
|
||||||
console.log(`[KonoSend] NSFW: ${isNSFW}, Channel NSFW: ${ctx.channel.nsfw}, Bypass: ${bypassEnabled}`);
|
console.log(`[KonoSend] NSFW: ${isNSFW}, Channel NSFW: ${ctx.channel.nsfw}, Bypass: ${bypassEnabled}`);
|
||||||
|
|
||||||
if (isNSFW && !ctx.channel.nsfw && !bypassEnabled) {
|
if (isNSFW && !ctx.channel.nsfw && !bypassEnabled) {
|
||||||
showNSFWWarning();
|
showNSFWWarning();
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const imageUrl = await fetchImage(isNSFW);
|
const imageUrl = await fetchImage(isNSFW);
|
||||||
|
|
||||||
if (!imageUrl) {
|
if (!imageUrl) {
|
||||||
// Silent fail - no error message
|
// Silent fail - no error message
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
|
|
||||||
const fixNonce = Date.now().toString();
|
const fixNonce = Date.now().toString();
|
||||||
MessageActions.sendMessage(ctx.channel.id, { content: imageUrl }, void 0, {
|
MessageActions.sendMessage(ctx.channel.id, { content: imageUrl }, void 0, {
|
||||||
nonce: fixNonce,
|
nonce: fixNonce,
|
||||||
});
|
});
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[KonoSend] Error:', error);
|
console.error("[KonoSend] Error:", error);
|
||||||
// Silent fail - no error message in chat
|
// Silent fail - no error message in chat
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
applicationId: "-1",
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 1,
|
type: 1,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -38,16 +38,16 @@ const STATUS = {
|
||||||
NOT_SELECTED: "❌"
|
NOT_SELECTED: "❌"
|
||||||
};
|
};
|
||||||
|
|
||||||
const MessageActions = findByProps('sendMessage', 'receiveMessage');
|
const MessageActions = findByProps("sendMessage", "receiveMessage");
|
||||||
const Clyde = findByProps('sendBotMessage');
|
const Clyde = findByProps("sendBotMessage");
|
||||||
|
|
||||||
const maxMessageLength = (() => {
|
const maxMessageLength = (() => {
|
||||||
try {
|
try {
|
||||||
return findByStoreName('UserStore')
|
return findByStoreName("UserStore")
|
||||||
.getCurrentUser()
|
.getCurrentUser()
|
||||||
?.premiumType === 2
|
?.premiumType === 2
|
||||||
? 4000
|
? 4000
|
||||||
: 2000;
|
: 2000;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return 2000; // fallback
|
return 2000; // fallback
|
||||||
}
|
}
|
||||||
|
|
@ -75,8 +75,8 @@ const addonAuthors = (authors: any) => {
|
||||||
if (authors.length === 0) return "Unknown";
|
if (authors.length === 0) return "Unknown";
|
||||||
|
|
||||||
return authors
|
return authors
|
||||||
.filter(author => author && (typeof author === 'string' || (typeof author === 'object' && author.name)))
|
.filter(author => author && (typeof author === "string" || (typeof author === "object" && author.name)))
|
||||||
.map(author => typeof author === 'string' ? author : (author.name || "Unknown"))
|
.map(author => typeof author === "string" ? author : (author.name || "Unknown"))
|
||||||
.join(JOINERS.SEMICOL) || "Unknown";
|
.join(JOINERS.SEMICOL) || "Unknown";
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -94,7 +94,7 @@ const sendList = async (channelID: string, list: string[]) => {
|
||||||
}, void 0, { nonce: fixNonce });
|
}, void 0, { nonce: fixNonce });
|
||||||
};
|
};
|
||||||
|
|
||||||
const baseListHeader = (type: 'Plugin' | 'Theme', length: number) => [
|
const baseListHeader = (type: "Plugin" | "Theme", length: number) => [
|
||||||
`**My ${type} List | ${length} ${type}s**`,
|
`**My ${type} List | ${length} ${type}s**`,
|
||||||
EMPTY
|
EMPTY
|
||||||
];
|
];
|
||||||
|
|
@ -105,7 +105,7 @@ export async function themeList(args: any[], ctx: any) {
|
||||||
const alwaysDetailed = storage.themeListAlwaysDetailed ?? false;
|
const alwaysDetailed = storage.themeListAlwaysDetailed ?? false;
|
||||||
|
|
||||||
// Ensure themes is valid
|
// Ensure themes is valid
|
||||||
if (!themes || typeof themes !== 'object') {
|
if (!themes || typeof themes !== "object") {
|
||||||
const channelID: string = ctx.channel.id;
|
const channelID: string = ctx.channel.id;
|
||||||
Clyde.sendBotMessage(channelID, "No themes found or themes not loaded yet.");
|
Clyde.sendBotMessage(channelID, "No themes found or themes not loaded yet.");
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
|
|
@ -115,11 +115,11 @@ export async function themeList(args: any[], ctx: any) {
|
||||||
|
|
||||||
const channelID: string = ctx.channel.id;
|
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) {
|
if (objectValues.length) {
|
||||||
for (const theme of objectValues) {
|
for (const theme of objectValues) {
|
||||||
if (!theme || typeof theme !== 'object') continue;
|
if (!theme || typeof theme !== "object") continue;
|
||||||
|
|
||||||
const { selected, data, id } = theme;
|
const { selected, data, id } = theme;
|
||||||
|
|
||||||
|
|
@ -161,7 +161,7 @@ export async function themeList(args: any[], ctx: any) {
|
||||||
await sendList(channelID, themeList);
|
await sendList(channelID, themeList);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[ThemeList] Error:', error);
|
console.error("[ThemeList] Error:", error);
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -174,15 +174,15 @@ export async function pluginList(args: any[], ctx: any) {
|
||||||
const channelID: string = ctx.channel.id;
|
const channelID: string = ctx.channel.id;
|
||||||
|
|
||||||
// Ensure plugins is valid
|
// 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.");
|
Clyde.sendBotMessage(channelID, "No plugins found or plugins not loaded yet.");
|
||||||
return { type: 4 };
|
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)) {
|
for (const plugin of Object.values(plugins)) {
|
||||||
if (!plugin || typeof plugin !== 'object') continue;
|
if (!plugin || typeof plugin !== "object") continue;
|
||||||
|
|
||||||
const { enabled, manifest, id } = plugin;
|
const { enabled, manifest, id } = plugin;
|
||||||
|
|
||||||
|
|
@ -222,50 +222,50 @@ export async function pluginList(args: any[], ctx: any) {
|
||||||
await sendList(channelID, pluginList);
|
await sendList(channelID, pluginList);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[PluginList] Error:', error);
|
console.error("[PluginList] Error:", error);
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Export commands for your existing structure
|
// Export commands for your existing structure
|
||||||
export const pluginListCommand = {
|
export const pluginListCommand = {
|
||||||
name: "plugin-list",
|
name: "plugin-list",
|
||||||
displayName: "plugin list",
|
displayName: "plugin list",
|
||||||
description: "Send your plugin list to the current channel",
|
description: "Send your plugin list to the current channel",
|
||||||
displayDescription: "Send your plugin list to the current channel",
|
displayDescription: "Send your plugin list to the current channel",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "detailed",
|
name: "detailed",
|
||||||
description: "Whether to send a list with detailed information.",
|
description: "Whether to send a list with detailed information.",
|
||||||
type: 5,
|
type: 5,
|
||||||
required: false,
|
required: false,
|
||||||
displayName: "detailed",
|
displayName: "detailed",
|
||||||
displayDescription: "Whether to send a list with detailed information.",
|
displayDescription: "Whether to send a list with detailed information.",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
execute: pluginList,
|
execute: pluginList,
|
||||||
applicationId: "-1",
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 1,
|
type: 1,
|
||||||
};
|
};
|
||||||
|
|
||||||
export const themeListCommand = {
|
export const themeListCommand = {
|
||||||
name: "theme-list",
|
name: "theme-list",
|
||||||
displayName: "theme list",
|
displayName: "theme list",
|
||||||
description: "Send your theme list to the current channel",
|
description: "Send your theme list to the current channel",
|
||||||
displayDescription: "Send your theme list to the current channel",
|
displayDescription: "Send your theme list to the current channel",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "detailed",
|
name: "detailed",
|
||||||
description: "Whether to send a list with detailed information.",
|
description: "Whether to send a list with detailed information.",
|
||||||
type: 5,
|
type: 5,
|
||||||
required: false,
|
required: false,
|
||||||
displayName: "detailed",
|
displayName: "detailed",
|
||||||
displayDescription: "Whether to send a list with detailed information.",
|
displayDescription: "Whether to send a list with detailed information.",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
execute: themeList,
|
execute: themeList,
|
||||||
applicationId: "-1",
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 1,
|
type: 1,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,274 +1,173 @@
|
||||||
import { findByProps } from "@vendetta/metro";
|
import { findByProps } from "@vendetta/metro";
|
||||||
import { showToast } from "@vendetta/ui/toasts";
|
import { storage } from "@vendetta/plugin";
|
||||||
import { getAssetIDByName } from "@vendetta/ui/assets";
|
|
||||||
import { alerts } from "@vendetta/ui";
|
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 {
|
function sendReply(channelID, content, embed) {
|
||||||
kind: string;
|
const channel = channelID ?? Channels?.getChannelId?.();
|
||||||
data: {
|
const msg = BotMessage.createBotMessage({
|
||||||
title: string;
|
channelId: channel,
|
||||||
url: string;
|
content: "",
|
||||||
is_video: boolean;
|
embeds: embed,
|
||||||
over_18: boolean;
|
});
|
||||||
thumbnail: string;
|
msg.author.username = "Astolfo";
|
||||||
preview?: {
|
msg.author.avatar = "Astolfo";
|
||||||
images: Array<{
|
Avatars.BOT_AVATARS.Astolfo =
|
||||||
source: {
|
"https://i.pinimg.com/736x/50/77/1f/50771f45b1c015cfbb8b0853ba7b8521.jpg";
|
||||||
url: string;
|
if (typeof content === "string") {
|
||||||
width: number;
|
msg.content = content;
|
||||||
height: number;
|
} else {
|
||||||
};
|
Object.assign(msg, content);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
MessageActions.receiveMessage(channel, msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
export const lovefemboysCommand = {
|
export const lovefemboysCommand = {
|
||||||
name: "lovefemboys",
|
name: "lovefemboys",
|
||||||
displayName: "lovefemboys",
|
displayName: "lovefemboys",
|
||||||
description: "Get random femboy content from r/femboys",
|
description: "Get an image of a femboy",
|
||||||
displayDescription: "Get random femboy content from r/femboys",
|
displayDescription: "Get an image of a femboy",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "ephemeral",
|
name: "nsfw",
|
||||||
displayName: "ephemeral",
|
displayName: "nsfw",
|
||||||
description: "Send as ephemeral message (only you can see)",
|
description: "Get the result from r/femboys instead of r/femboy (NSFW)",
|
||||||
displayDescription: "Send as ephemeral message (only you can see)",
|
displayDescription:
|
||||||
type: 5, // Boolean
|
"Get the result from r/femboys instead of r/femboy (NSFW)",
|
||||||
required: false,
|
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",
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 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
|
// Valid SFW categories for validation
|
||||||
const validSfwCategories = [
|
const validSfwCategories = [
|
||||||
"avatar", "classic", "cuddle", "fox_girl", "gecg", "holo",
|
"avatar", "classic", "cuddle", "fox_girl", "gecg", "holo",
|
||||||
"kemonomimi", "kiss", "neko", "ngif", "smug", "spank",
|
"kemonomimi", "kiss", "neko", "ngif", "smug", "spank",
|
||||||
"tickle", "waifu", "wallpaper", "woof"
|
"tickle", "waifu", "wallpaper", "woof"
|
||||||
];
|
];
|
||||||
|
|
||||||
async function fetchNekosLifeImages(category: string, count: number): Promise<string[]> {
|
async function fetchNekosLifeImages(category: string, count: number): Promise<string[]> {
|
||||||
const urls: string[] = [];
|
const urls: string[] = [];
|
||||||
|
|
||||||
for (let i = 0; i < count; i++) {
|
for (let i = 0; i < count; i++) {
|
||||||
try {
|
try {
|
||||||
// Add delay between requests to avoid rate limiting
|
// Add delay between requests to avoid rate limiting
|
||||||
if (i > 0) {
|
if (i > 0) {
|
||||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await fetch(`https://nekos.life/api/v2/img/${category}`);
|
const response = await fetch(`https://nekos.life/api/v2/img/${category}`);
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
console.error(`[NekosLife] API request failed: ${response.status}`);
|
console.error(`[NekosLife] API request failed: ${response.status}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data: NekosLifeResult = await response.json();
|
const data: NekosLifeResult = await response.json();
|
||||||
if (data.url) {
|
if (data.url) {
|
||||||
urls.push(data.url);
|
urls.push(data.url);
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[NekosLife] Error fetching image ${i + 1}:`, error);
|
console.error(`[NekosLife] Error fetching image ${i + 1}:`, error);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return urls;
|
return urls;
|
||||||
}
|
}
|
||||||
|
|
||||||
function isValidSfwCategory(category: string): boolean {
|
function isValidSfwCategory(category: string): boolean {
|
||||||
if (!category || typeof category !== 'string') return false;
|
if (!category || typeof category !== "string") return false;
|
||||||
return validSfwCategories.includes(category.toLowerCase().trim());
|
return validSfwCategories.includes(category.toLowerCase().trim());
|
||||||
}
|
}
|
||||||
|
|
||||||
export const nekoslifeCommand = {
|
export const nekoslifeCommand = {
|
||||||
name: "nekoslife",
|
name: "nekoslife",
|
||||||
displayName: "nekoslife",
|
displayName: "nekoslife",
|
||||||
description: "Get SFW images/gifs from nekos.life",
|
description: "Get SFW images/gifs from nekos.life",
|
||||||
displayDescription: "Get SFW images/gifs from nekos.life",
|
displayDescription: "Get SFW images/gifs from nekos.life",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "category",
|
name: "category",
|
||||||
displayName: "category",
|
displayName: "category",
|
||||||
description: "Category name (e.g. neko, waifu, cuddle, etc.)",
|
description: "Category name (e.g. neko, waifu, cuddle, etc.)",
|
||||||
displayDescription: "Category name (e.g. neko, waifu, cuddle, etc.)",
|
displayDescription: "Category name (e.g. neko, waifu, cuddle, etc.)",
|
||||||
type: 3, // String - free text input
|
type: 3, // String - free text input
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "limit",
|
name: "limit",
|
||||||
displayName: "limit",
|
displayName: "limit",
|
||||||
description: "Number of images (1-5, default: 1)",
|
description: "Number of images (1-5, default: 1)",
|
||||||
displayDescription: "Number of images (1-5, default: 1)",
|
displayDescription: "Number of images (1-5, default: 1)",
|
||||||
type: 4, // Integer
|
type: 4, // Integer
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "send",
|
name: "send",
|
||||||
displayName: "send",
|
displayName: "send",
|
||||||
description: "Send to chat",
|
description: "Send to chat",
|
||||||
displayDescription: "Send to chat",
|
displayDescription: "Send to chat",
|
||||||
type: 5, // Boolean
|
type: 5, // Boolean
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "ephemeral",
|
name: "ephemeral",
|
||||||
displayName: "ephemeral",
|
displayName: "ephemeral",
|
||||||
description: "Send as ephemeral message (only you can see)",
|
description: "Send as ephemeral message (only you can see)",
|
||||||
displayDescription: "Send as ephemeral message (only you can see)",
|
displayDescription: "Send as ephemeral message (only you can see)",
|
||||||
type: 5, // Boolean
|
type: 5, // Boolean
|
||||||
required: false,
|
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;
|
|
||||||
|
|
||||||
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 (isEphemeral) {
|
|
||||||
return {
|
|
||||||
type: 4,
|
|
||||||
data: {
|
|
||||||
content: errorMsg,
|
|
||||||
flags: 64,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
showToast(errorMsg, getAssetIDByName("CircleXIcon"));
|
],
|
||||||
return { type: 4 };
|
execute: async (args: any, ctx: any) => {
|
||||||
}
|
try {
|
||||||
|
console.log("[NekosLife] Command executed with args:", args);
|
||||||
|
|
||||||
// Clean and validate category
|
// Parse arguments
|
||||||
const category = categoryInput.toLowerCase().trim();
|
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;
|
||||||
|
|
||||||
if (!isValidSfwCategory(category)) {
|
console.log("[NekosLife] Parsed values:", { categoryInput, limitInput, shouldSend, isEphemeral });
|
||||||
const errorMsg = `❌ Invalid SFW category "${categoryInput}". Valid: neko, waifu, cuddle, kiss, holo, etc.`;
|
|
||||||
|
|
||||||
if (isEphemeral) {
|
if (!categoryInput || typeof categoryInput !== "string") {
|
||||||
return {
|
const errorMsg = "❌ Category is required! Examples: neko, waifu, cuddle, kiss";
|
||||||
type: 4,
|
console.error("[NekosLife] No category provided");
|
||||||
data: {
|
|
||||||
content: errorMsg,
|
if (isEphemeral) {
|
||||||
flags: 64,
|
return {
|
||||||
},
|
type: 4,
|
||||||
};
|
data: {
|
||||||
|
content: errorMsg,
|
||||||
|
flags: 64,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
showToast(errorMsg, getAssetIDByName("CircleXIcon"));
|
||||||
|
return { type: 4 };
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 (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
|
||||||
|
}
|
||||||
|
|
||||||
|
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"));
|
||||||
|
}
|
||||||
|
|
||||||
|
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 };
|
||||||
|
}
|
||||||
|
|
||||||
|
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.";
|
||||||
|
|
||||||
|
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 };
|
applicationId: "-1",
|
||||||
}
|
inputType: 1,
|
||||||
|
type: 1,
|
||||||
// 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 });
|
|
||||||
|
|
||||||
// Show loading toast
|
|
||||||
if (!isEphemeral) {
|
|
||||||
showToast(`Fetching ${limit} SFW image(s) from nekos.life...`, getAssetIDByName("DownloadIcon"));
|
|
||||||
}
|
|
||||||
|
|
||||||
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 };
|
|
||||||
}
|
|
||||||
|
|
||||||
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.";
|
|
||||||
|
|
||||||
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,
|
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,41 +5,41 @@ const UserStore = findByStoreName("UserStore");
|
||||||
const MessageActions = findByProps("sendMessage");
|
const MessageActions = findByProps("sendMessage");
|
||||||
|
|
||||||
export const petPetCommand = {
|
export const petPetCommand = {
|
||||||
name: "petpet",
|
name: "petpet",
|
||||||
displayName: "petpet",
|
displayName: "petpet",
|
||||||
description: "PetPet someone",
|
description: "PetPet someone",
|
||||||
displayDescription: "PetPet someone",
|
displayDescription: "PetPet someone",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "user",
|
name: "user",
|
||||||
description: "The user(or their id) to be patted",
|
description: "The user(or their id) to be patted",
|
||||||
type: 6,
|
type: 6,
|
||||||
required: true,
|
required: true,
|
||||||
displayName: "user",
|
displayName: "user",
|
||||||
displayDescription: "The user(or their id) to be patted",
|
displayDescription: "The user(or their id) to be patted",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
execute: async (args: any, ctx: any) => {
|
execute: async (args: any, ctx: any) => {
|
||||||
try {
|
try {
|
||||||
const user = await UserStore.getUser(args[0].value);
|
const user = await UserStore.getUser(args[0].value);
|
||||||
const image = user.getAvatarURL(512);
|
const image = user.getAvatarURL(512);
|
||||||
const data = await getPetPetData(image);
|
const data = await getPetPetData(image);
|
||||||
|
|
||||||
const fixNonce = Date.now().toString();
|
const fixNonce = Date.now().toString();
|
||||||
MessageActions.sendMessage(
|
MessageActions.sendMessage(
|
||||||
ctx.channel.id,
|
ctx.channel.id,
|
||||||
{ content: data.url },
|
{ content: data.url },
|
||||||
void 0,
|
void 0,
|
||||||
{ nonce: fixNonce }
|
{ nonce: fixNonce }
|
||||||
);
|
);
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[PetPet] Error:', error);
|
console.error("[PetPet] Error:", error);
|
||||||
// Silent fail - no error message in chat
|
// Silent fail - no error message in chat
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
applicationId: "-1",
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 1,
|
type: 1,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -1,115 +1,115 @@
|
||||||
import { findByProps, findByStoreName } from "@vendetta/metro";
|
import { findByProps, findByStoreName } from "@vendetta/metro";
|
||||||
import { storage } from "@vendetta/plugin";
|
import { storage } from "@vendetta/plugin";
|
||||||
|
|
||||||
const MessageActions = findByProps('sendMessage', 'receiveMessage');
|
const MessageActions = findByProps("sendMessage", "receiveMessage");
|
||||||
const SpotifyStore = findByStoreName('SpotifyStore');
|
const SpotifyStore = findByStoreName("SpotifyStore");
|
||||||
|
|
||||||
// Individual command functions
|
// Individual command functions
|
||||||
async function spotifyTrackExecute(args: any[], ctx: any) {
|
async function spotifyTrackExecute(args: any[], ctx: any) {
|
||||||
if (!storage.enabledCommands?.spotifyTrack) return { type: 4 };
|
if (!storage.enabledCommands?.spotifyTrack) return { type: 4 };
|
||||||
return handleSpotifyCommand(ctx, (track: any) =>
|
return handleSpotifyCommand(ctx, (track: any) =>
|
||||||
`https://open.spotify.com/track/${track.id}`
|
`https://open.spotify.com/track/${track.id}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function spotifyAlbumExecute(args: any[], ctx: any) {
|
async function spotifyAlbumExecute(args: any[], ctx: any) {
|
||||||
if (!storage.enabledCommands?.spotifyAlbum) return { type: 4 };
|
if (!storage.enabledCommands?.spotifyAlbum) return { type: 4 };
|
||||||
return handleSpotifyCommand(ctx, (track: any) =>
|
return handleSpotifyCommand(ctx, (track: any) =>
|
||||||
`https://open.spotify.com/album/${track.album.id}`
|
`https://open.spotify.com/album/${track.album.id}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function spotifyArtistsExecute(args: any[], ctx: any) {
|
async function spotifyArtistsExecute(args: any[], ctx: any) {
|
||||||
if (!storage.enabledCommands?.spotifyArtists) return { type: 4 };
|
if (!storage.enabledCommands?.spotifyArtists) return { type: 4 };
|
||||||
return handleSpotifyCommand(ctx, (track: any) =>
|
return handleSpotifyCommand(ctx, (track: any) =>
|
||||||
track.artists.map((artist: any) =>
|
track.artists.map((artist: any) =>
|
||||||
`[${artist.name}](${artist.external_urls.spotify})`
|
`[${artist.name}](${artist.external_urls.spotify})`
|
||||||
).join('\n')
|
).join("\n")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function spotifyCoverExecute(args: any[], ctx: any) {
|
async function spotifyCoverExecute(args: any[], ctx: any) {
|
||||||
if (!storage.enabledCommands?.spotifyCover) return { type: 4 };
|
if (!storage.enabledCommands?.spotifyCover) return { type: 4 };
|
||||||
return handleSpotifyCommand(ctx, (track: any) =>
|
return handleSpotifyCommand(ctx, (track: any) =>
|
||||||
track.album.image.url
|
track.album.image.url
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Common handler for all Spotify commands
|
// Common handler for all Spotify commands
|
||||||
async function handleSpotifyCommand(ctx: any, contentGenerator: (track: any) => string) {
|
async function handleSpotifyCommand(ctx: any, contentGenerator: (track: any) => string) {
|
||||||
try {
|
try {
|
||||||
const channelId = ctx.channel.id;
|
const channelId = ctx.channel.id;
|
||||||
const track = SpotifyStore?.getTrack?.();
|
const track = SpotifyStore?.getTrack?.();
|
||||||
|
|
||||||
if (!track) {
|
if (!track) {
|
||||||
const fixNonce = Date.now().toString();
|
const fixNonce = Date.now().toString();
|
||||||
MessageActions.sendMessage(
|
MessageActions.sendMessage(
|
||||||
channelId,
|
channelId,
|
||||||
{ content: 'You are not listening to any track.' },
|
{ content: "You are not listening to any track." },
|
||||||
void 0,
|
void 0,
|
||||||
{ nonce: fixNonce }
|
{ nonce: fixNonce }
|
||||||
);
|
);
|
||||||
return { type: 4 };
|
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 individual command objects
|
||||||
export const spotifyTrackCommand = {
|
export const spotifyTrackCommand = {
|
||||||
name: 'spotify track',
|
name: "spotify track",
|
||||||
displayName: 'spotify track',
|
displayName: "spotify track",
|
||||||
description: 'Sends your current Spotify track',
|
description: "Sends your current Spotify track",
|
||||||
displayDescription: 'Sends your current Spotify track',
|
displayDescription: "Sends your current Spotify track",
|
||||||
applicationId: '-1',
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 1,
|
type: 1,
|
||||||
execute: spotifyTrackExecute
|
execute: spotifyTrackExecute
|
||||||
};
|
};
|
||||||
|
|
||||||
export const spotifyAlbumCommand = {
|
export const spotifyAlbumCommand = {
|
||||||
name: 'spotify album',
|
name: "spotify album",
|
||||||
displayName: 'spotify album',
|
displayName: "spotify album",
|
||||||
description: "Sends your current track's album",
|
description: "Sends your current track's album",
|
||||||
displayDescription: "Sends your current track's album",
|
displayDescription: "Sends your current track's album",
|
||||||
applicationId: '-1',
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 1,
|
type: 1,
|
||||||
execute: spotifyAlbumExecute
|
execute: spotifyAlbumExecute
|
||||||
};
|
};
|
||||||
|
|
||||||
export const spotifyArtistsCommand = {
|
export const spotifyArtistsCommand = {
|
||||||
name: 'spotify artists',
|
name: "spotify artists",
|
||||||
displayName: 'spotify artists',
|
displayName: "spotify artists",
|
||||||
description: "Sends your current track's artists",
|
description: "Sends your current track's artists",
|
||||||
displayDescription: "Sends your current track's artists",
|
displayDescription: "Sends your current track's artists",
|
||||||
applicationId: '-1',
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 1,
|
type: 1,
|
||||||
execute: spotifyArtistsExecute
|
execute: spotifyArtistsExecute
|
||||||
};
|
};
|
||||||
|
|
||||||
export const spotifyCoverCommand = {
|
export const spotifyCoverCommand = {
|
||||||
name: 'spotify cover',
|
name: "spotify cover",
|
||||||
displayName: 'spotify cover',
|
displayName: "spotify cover",
|
||||||
description: "Sends your current track's cover",
|
description: "Sends your current track's cover",
|
||||||
displayDescription: "Sends your current track's cover",
|
displayDescription: "Sends your current track's cover",
|
||||||
applicationId: '-1',
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 1,
|
type: 1,
|
||||||
execute: spotifyCoverExecute
|
execute: spotifyCoverExecute
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -8,207 +8,207 @@ const messageUtil = findByProps("sendBotMessage", "sendMessage", "receiveMessage
|
||||||
|
|
||||||
// Device info helper
|
// Device info helper
|
||||||
function getDeviceInfo() {
|
function getDeviceInfo() {
|
||||||
let { height, width, scale } = ReactNative.Dimensions.get("screen");
|
let { height, width, scale } = ReactNative.Dimensions.get("screen");
|
||||||
height *= scale;
|
height *= scale;
|
||||||
width *= scale;
|
width *= scale;
|
||||||
return { width, height };
|
return { width, height };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get current UTC date and time
|
// Get current UTC date and time
|
||||||
function getCurrentUTCDateTime() {
|
function getCurrentUTCDateTime() {
|
||||||
try {
|
try {
|
||||||
const now = new Date();
|
const now = new Date();
|
||||||
const utc = new Date(now.getTime() + (now.getTimezoneOffset() * 60000));
|
const utc = new Date(now.getTime() + (now.getTimezoneOffset() * 60000));
|
||||||
|
|
||||||
const year = utc.getFullYear();
|
const year = utc.getFullYear();
|
||||||
const month = String(utc.getMonth() + 1).padStart(2, '0');
|
const month = String(utc.getMonth() + 1).padStart(2, "0");
|
||||||
const day = String(utc.getDate()).padStart(2, '0');
|
const day = String(utc.getDate()).padStart(2, "0");
|
||||||
const hours = String(utc.getHours()).padStart(2, '0');
|
const hours = String(utc.getHours()).padStart(2, "0");
|
||||||
const minutes = String(utc.getMinutes()).padStart(2, '0');
|
const minutes = String(utc.getMinutes()).padStart(2, "0");
|
||||||
const seconds = String(utc.getSeconds()).padStart(2, '0');
|
const seconds = String(utc.getSeconds()).padStart(2, "0");
|
||||||
|
|
||||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
return "Unknown";
|
return "Unknown";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hardware info helper
|
// Hardware info helper
|
||||||
function getHardwareInfo() {
|
function getHardwareInfo() {
|
||||||
try {
|
try {
|
||||||
const hwProps = findByProps("memory");
|
const hwProps = findByProps("memory");
|
||||||
if (!hwProps) return { cpuCoreCount: "N/A", cpuPerc: "N/A", memUsage: "N/A" };
|
if (!hwProps) return { cpuCoreCount: "N/A", cpuPerc: "N/A", memUsage: "N/A" };
|
||||||
|
|
||||||
const { cpuCoreCount, cpuPercentage, memory } = hwProps;
|
const { cpuCoreCount, cpuPercentage, memory } = hwProps;
|
||||||
const cpuPerc = cpuPercentage ? cpuPercentage.toFixed(2) + "%" : "N/A";
|
const cpuPerc = cpuPercentage ? cpuPercentage.toFixed(2) + "%" : "N/A";
|
||||||
const memUsage = memory ? parseFloat((memory / 1000).toPrecision(3)) + " MB" : "N/A";
|
const memUsage = memory ? parseFloat((memory / 1000).toPrecision(3)) + " MB" : "N/A";
|
||||||
|
|
||||||
return { cpuCoreCount, cpuPerc, memUsage };
|
return { cpuCoreCount, cpuPerc, memUsage };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Hardware info unavailable:", e);
|
console.warn("Hardware info unavailable:", e);
|
||||||
return { cpuCoreCount: "N/A", cpuPerc: "N/A", memUsage: "N/A" };
|
return { cpuCoreCount: "N/A", cpuPerc: "N/A", memUsage: "N/A" };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Discord info helper
|
// Discord info helper
|
||||||
function getDiscordInfo() {
|
function getDiscordInfo() {
|
||||||
try {
|
try {
|
||||||
const { NativeModules } = ReactNative;
|
const { NativeModules } = ReactNative;
|
||||||
const discordInfo = NativeModules.InfoDictionaryManager ?? NativeModules.RTNClientInfoManager;
|
const discordInfo = NativeModules.InfoDictionaryManager ?? NativeModules.RTNClientInfoManager;
|
||||||
return discordInfo || {};
|
return discordInfo || {};
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.warn("Discord info unavailable:", e);
|
console.warn("Discord info unavailable:", e);
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate system info
|
// Generate system info
|
||||||
function generateSystemInfo() {
|
function generateSystemInfo() {
|
||||||
try {
|
try {
|
||||||
const { cpuCoreCount, cpuPerc, memUsage } = getHardwareInfo();
|
const { cpuCoreCount, cpuPerc, memUsage } = getHardwareInfo();
|
||||||
const discordInfo = getDiscordInfo();
|
const discordInfo = getDiscordInfo();
|
||||||
const { vendetta, discord, react, hermes, os, device } = getDebugInfo();
|
const { vendetta, discord, react, hermes, os, device } = getDebugInfo();
|
||||||
const utcDateTime = getCurrentUTCDateTime();
|
const utcDateTime = getCurrentUTCDateTime();
|
||||||
|
|
||||||
const { version: HermesRelease, bytecodeVersion: HermesBytecode } = hermes || {};
|
const { version: HermesRelease, bytecodeVersion: HermesBytecode } = hermes || {};
|
||||||
const { version: ReactVersion, nativeVersion: RNVersion } = react || {};
|
const { version: ReactVersion, nativeVersion: RNVersion } = react || {};
|
||||||
const { name: osName, version: osVersion, sdk: osSdk } = os || {};
|
const { name: osName, version: osVersion, sdk: osSdk } = os || {};
|
||||||
const {
|
const {
|
||||||
manufacturer: deviceManufacturer,
|
manufacturer: deviceManufacturer,
|
||||||
brand: deviceBrand,
|
brand: deviceBrand,
|
||||||
model: deviceModel,
|
model: deviceModel,
|
||||||
codename: deviceCodename,
|
codename: deviceCodename,
|
||||||
} = device || {};
|
} = device || {};
|
||||||
const { version: vendettaVersion } = vendetta || {};
|
const { version: vendettaVersion } = vendetta || {};
|
||||||
const { version: discordVersion, build: discordBuild } = discord || {};
|
const { version: discordVersion, build: discordBuild } = discord || {};
|
||||||
|
|
||||||
const deviceName = osName == "iOS" ? deviceCodename : `${deviceBrand} ${deviceModel}`;
|
const deviceName = osName == "iOS" ? deviceCodename : `${deviceBrand} ${deviceModel}`;
|
||||||
const { width, height } = getDeviceInfo();
|
const { width, height } = getDeviceInfo();
|
||||||
|
|
||||||
let output = {
|
const output = {
|
||||||
Device: {
|
Device: {
|
||||||
Device: deviceName || "Unknown",
|
Device: deviceName || "Unknown",
|
||||||
Model: deviceModel || "Unknown",
|
Model: deviceModel || "Unknown",
|
||||||
Manufacturer: deviceManufacturer || "Unknown",
|
Manufacturer: deviceManufacturer || "Unknown",
|
||||||
Brand: deviceBrand || "Unknown",
|
Brand: deviceBrand || "Unknown",
|
||||||
Display: `${width}x${height}`,
|
Display: `${width}x${height}`,
|
||||||
},
|
},
|
||||||
Hardware: {
|
Hardware: {
|
||||||
"CPU Cores": cpuCoreCount || "N/A",
|
"CPU Cores": cpuCoreCount || "N/A",
|
||||||
"CPU Usage": cpuPerc,
|
"CPU Usage": cpuPerc,
|
||||||
"Memory Usage": memUsage,
|
"Memory Usage": memUsage,
|
||||||
},
|
},
|
||||||
Software: {
|
Software: {
|
||||||
OS: osName || "Unknown",
|
OS: osName || "Unknown",
|
||||||
Version: osVersion || "Unknown",
|
Version: osVersion || "Unknown",
|
||||||
},
|
},
|
||||||
Discord: {
|
Discord: {
|
||||||
Version: discordVersion || "Unknown",
|
Version: discordVersion || "Unknown",
|
||||||
Build: discordBuild || "Unknown",
|
Build: discordBuild || "Unknown",
|
||||||
Vendetta: vendettaVersion || "Unknown",
|
Vendetta: vendettaVersion || "Unknown",
|
||||||
},
|
},
|
||||||
React: {
|
React: {
|
||||||
Version: ReactVersion || "Unknown",
|
Version: ReactVersion || "Unknown",
|
||||||
"Hermes Bytecode": HermesBytecode || "Unknown",
|
"Hermes Bytecode": HermesBytecode || "Unknown",
|
||||||
Hermes: HermesRelease || "Unknown",
|
Hermes: HermesRelease || "Unknown",
|
||||||
Native: RNVersion || "Unknown",
|
Native: RNVersion || "Unknown",
|
||||||
},
|
},
|
||||||
Time: {
|
Time: {
|
||||||
"UTC": utcDateTime,
|
"UTC": utcDateTime,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
// Add additional mod versions if available
|
// Add additional mod versions if available
|
||||||
if ((window as any).enmity) {
|
if ((window as any).enmity) {
|
||||||
output.Discord.Enmity = (window as any).enmity.version;
|
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
|
// Initialize storage defaults
|
||||||
const categories = ["device", "hardware", "software", "discord", "react", "time", "ephemeral"];
|
const categories = ["device", "hardware", "software", "discord", "react", "time", "ephemeral"];
|
||||||
for (const cat of categories) {
|
for (const cat of categories) {
|
||||||
if (storage[cat] === undefined) storage[cat] = true;
|
if (storage[cat] === undefined) storage[cat] = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Command execution function
|
// Command execution function
|
||||||
function executeSysinfoCommand(args: any[], ctx: any) {
|
function executeSysinfoCommand(args: any[], ctx: any) {
|
||||||
try {
|
try {
|
||||||
let output = ["__System Information__\n"];
|
const output = ["__System Information__\n"];
|
||||||
const data = generateSystemInfo();
|
const data = generateSystemInfo();
|
||||||
|
|
||||||
Object.keys(data).forEach((option) => {
|
Object.keys(data).forEach((option) => {
|
||||||
const permit = storage[option.toLowerCase()];
|
const permit = storage[option.toLowerCase()];
|
||||||
const slasharg = args.find((i) => i.name == option.toLowerCase());
|
const slasharg = args.find((i) => i.name == option.toLowerCase());
|
||||||
const slashval = slasharg?.value ?? permit;
|
const slashval = slasharg?.value ?? permit;
|
||||||
|
|
||||||
if ((slashval || permit) && !(permit && !slashval)) {
|
if ((slashval || permit) && !(permit && !slashval)) {
|
||||||
output.push(`[**${option}**]`);
|
output.push(`[**${option}**]`);
|
||||||
Object.keys(data[option as keyof typeof data]).forEach((subOption) => {
|
Object.keys(data[option as keyof typeof data]).forEach((subOption) => {
|
||||||
output.push(`> ${subOption}: ${(data[option as keyof typeof data] as any)[subOption]}`);
|
output.push(`> ${subOption}: ${(data[option as keyof typeof data] as any)[subOption]}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const epermit = storage["ephemeral"];
|
const epermit = storage["ephemeral"];
|
||||||
const ephemeral = args.find((i) => i.name == "ephemeral")?.value ?? epermit;
|
const ephemeral = args.find((i) => i.name == "ephemeral")?.value ?? epermit;
|
||||||
|
|
||||||
if ((ephemeral || epermit) && !(epermit && !ephemeral)) {
|
if ((ephemeral || epermit) && !(epermit && !ephemeral)) {
|
||||||
messageUtil.sendBotMessage(ctx.channel.id, output.join("\n"));
|
messageUtil.sendBotMessage(ctx.channel.id, output.join("\n"));
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
} else {
|
} else {
|
||||||
const fixNonce = Date.now().toString();
|
const fixNonce = Date.now().toString();
|
||||||
MessageActions.sendMessage(
|
MessageActions.sendMessage(
|
||||||
ctx.channel.id,
|
ctx.channel.id,
|
||||||
{ content: output.join("\n") },
|
{ content: output.join("\n") },
|
||||||
void 0,
|
void 0,
|
||||||
{ nonce: fixNonce }
|
{ nonce: fixNonce }
|
||||||
);
|
);
|
||||||
return { type: 4 };
|
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 = {
|
export const sysinfoCommand = {
|
||||||
name: "sysinfo",
|
name: "sysinfo",
|
||||||
displayName: "sysinfo",
|
displayName: "sysinfo",
|
||||||
description: "Display system information",
|
description: "Display system information",
|
||||||
displayDescription: "Display system information",
|
displayDescription: "Display system information",
|
||||||
options: [
|
options: [
|
||||||
{
|
{
|
||||||
name: "ephemeral",
|
name: "ephemeral",
|
||||||
displayName: "ephemeral",
|
displayName: "ephemeral",
|
||||||
description: "Keep sysinfo ephemeral (default: true)",
|
description: "Keep sysinfo ephemeral (default: true)",
|
||||||
displayDescription: "Keep sysinfo ephemeral (default: true)",
|
displayDescription: "Keep sysinfo ephemeral (default: true)",
|
||||||
type: 5,
|
type: 5,
|
||||||
required: false,
|
required: false,
|
||||||
},
|
},
|
||||||
...categories.slice(0, -1).map((cat) => ({
|
...categories.slice(0, -1).map((cat) => ({
|
||||||
name: cat,
|
name: cat,
|
||||||
displayName: cat,
|
displayName: cat,
|
||||||
description: `Display the ${cat} section. Set default in settings.`,
|
description: `Display the ${cat} section. Set default in settings.`,
|
||||||
displayDescription: `Display the ${cat} section. Set default in settings.`,
|
displayDescription: `Display the ${cat} section. Set default in settings.`,
|
||||||
type: 5,
|
type: 5,
|
||||||
required: false,
|
required: false,
|
||||||
})),
|
})),
|
||||||
],
|
],
|
||||||
execute: executeSysinfoCommand,
|
execute: executeSysinfoCommand,
|
||||||
applicationId: "-1",
|
applicationId: "-1",
|
||||||
inputType: 1,
|
inputType: 1,
|
||||||
type: 1,
|
type: 1,
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -2,64 +2,64 @@
|
||||||
|
|
||||||
// Facts APIs
|
// Facts APIs
|
||||||
export const uselessFact = async () => {
|
export const uselessFact = async () => {
|
||||||
const response = await fetch("https://uselessfacts.jsph.pl/api/v2/facts/random");
|
const response = await fetch("https://uselessfacts.jsph.pl/api/v2/facts/random");
|
||||||
const resp = await response.json();
|
const resp = await response.json();
|
||||||
return {
|
return {
|
||||||
text: resp["text"],
|
text: resp["text"],
|
||||||
source: resp["source"],
|
source: resp["source"],
|
||||||
language: resp["language"],
|
language: resp["language"],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const dogFact = async () => {
|
export const dogFact = async () => {
|
||||||
const response = await fetch("https://dogapi.dog/api/v2/facts?limit=1");
|
const response = await fetch("https://dogapi.dog/api/v2/facts?limit=1");
|
||||||
const resp = await response.json();
|
const resp = await response.json();
|
||||||
return {
|
return {
|
||||||
text: resp["data"]["0"]["attributes"]["body"],
|
text: resp["data"]["0"]["attributes"]["body"],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export const catFact = async () => {
|
export const catFact = async () => {
|
||||||
const response = await fetch("https://catfact.ninja/fact");
|
const response = await fetch("https://catfact.ninja/fact");
|
||||||
const resp = await response.json();
|
const resp = await response.json();
|
||||||
return {
|
return {
|
||||||
text: resp["fact"],
|
text: resp["fact"],
|
||||||
length: resp["length"],
|
length: resp["length"],
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// Image APIs
|
// Image APIs
|
||||||
export const getPetPetData = async (image: string) => {
|
export const getPetPetData = async (image: string) => {
|
||||||
const data = await fetch(
|
const data = await fetch(
|
||||||
`https://api.obamabot.me/v2/image/petpet?image=${image.replace("webp", "png")}`
|
`https://api.obamabot.me/v2/image/petpet?image=${image.replace("webp", "png")}`
|
||||||
);
|
);
|
||||||
const body = await data.json();
|
const body = await data.json();
|
||||||
return body;
|
return body;
|
||||||
};
|
};
|
||||||
|
|
||||||
// Gary API function
|
// Gary API function
|
||||||
export const getGaryUrl = async (source: string = "gary"): Promise<string | null> => {
|
export const getGaryUrl = async (source = "gary"): Promise<string | null> => {
|
||||||
try {
|
try {
|
||||||
switch (source) {
|
switch (source) {
|
||||||
case "gary":
|
case "gary":
|
||||||
const response = await fetch("https://api.garythe.cat/gary");
|
const response = await fetch("https://api.garythe.cat/gary");
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
return json.url;
|
return json.url;
|
||||||
case "goober":
|
case "goober":
|
||||||
const gooberResponse = await fetch("https://api.garythe.cat/goober");
|
const gooberResponse = await fetch("https://api.garythe.cat/goober");
|
||||||
const gooberJson = await gooberResponse.json();
|
const gooberJson = await gooberResponse.json();
|
||||||
return gooberJson.url;
|
return gooberJson.url;
|
||||||
case "catapi":
|
case "catapi":
|
||||||
const catResponse = await fetch("https://api.thecatapi.com/v1/images/search");
|
const catResponse = await fetch("https://api.thecatapi.com/v1/images/search");
|
||||||
const catJson = await catResponse.json();
|
const catJson = await catResponse.json();
|
||||||
return catJson[0].url;
|
return catJson[0].url;
|
||||||
case "minker":
|
case "minker":
|
||||||
return "https://minky.materii.dev/";
|
return "https://minky.materii.dev/";
|
||||||
default:
|
default:
|
||||||
throw new Error("Invalid Gary image source value");
|
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
|
// Channel type validation
|
||||||
const isValidChannel = (channelType: number) => {
|
const isValidChannel = (channelType: number) => {
|
||||||
// Allow: Text (0), DM (1), Voice (2), Group DM (3), News (5)
|
// Allow: Text (0), DM (1), Voice (2), Group DM (3), News (5)
|
||||||
// Block: Threads (10,11,12), Forum (15), Stage (13), Category (4)
|
// Block: Threads (10,11,12), Forum (15), Stage (13), Category (4)
|
||||||
const unsupportedTypes = [4, 10, 11, 12, 13, 15];
|
const unsupportedTypes = [4, 10, 11, 12, 13, 15];
|
||||||
return !unsupportedTypes.includes(channelType);
|
return !unsupportedTypes.includes(channelType);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const sendMessage = (
|
export const sendMessage = (
|
||||||
channelId: string,
|
channelId: string,
|
||||||
content: string,
|
content: string,
|
||||||
replyToId?: string,
|
replyToId?: string,
|
||||||
storage?: any,
|
storage?: any,
|
||||||
ephemeral?: boolean,
|
ephemeral?: boolean,
|
||||||
) => {
|
) => {
|
||||||
// If no content, just acknowledge the command
|
// If no content, just acknowledge the command
|
||||||
if (!content) return { type: 4 };
|
if (!content) return { type: 4 };
|
||||||
|
|
||||||
// For ephemeral messages, return special format
|
// For ephemeral messages, return special format
|
||||||
if (ephemeral) {
|
if (ephemeral) {
|
||||||
return {
|
return {
|
||||||
type: 4,
|
type: 4,
|
||||||
data: {
|
data: {
|
||||||
content,
|
content,
|
||||||
flags: 64,
|
flags: 64,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// For normal messages, send through MessageActions
|
// For normal messages, send through MessageActions
|
||||||
const message = {
|
const message = {
|
||||||
content,
|
content,
|
||||||
...(replyToId && storage?.factSettings?.sendAsReply
|
...(replyToId && storage?.factSettings?.sendAsReply
|
||||||
? { messageReference: { messageId: replyToId } }
|
? { messageReference: { messageId: replyToId } }
|
||||||
: {}),
|
: {}),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Use consistent, collision-resistant nonce generation
|
// Use consistent, collision-resistant nonce generation
|
||||||
const nonce = (BigInt(Date.now()) * BigInt(4194304) + BigInt(Math.floor(Math.random() * 4194304))).toString();
|
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
|
return { type: 4 }; // Acknowledge the command
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('[Commands] Message send failed:', error);
|
console.error("[Commands] Message send failed:", error);
|
||||||
// Silent fail - no error message in chat
|
// Silent fail - no error message in chat
|
||||||
return { type: 4 };
|
return { type: 4 };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Helper function to validate channel types in commands
|
// Helper function to validate channel types in commands
|
||||||
export const validateChannelForCommand = (ctx: any) => {
|
export const validateChannelForCommand = (ctx: any) => {
|
||||||
if (!isValidChannel(ctx.channel.type)) {
|
if (!isValidChannel(ctx.channel.type)) {
|
||||||
return {
|
return {
|
||||||
type: 4,
|
type: 4,
|
||||||
data: {
|
data: {
|
||||||
content: "This command cannot be used in threads, forum channels, or voice channels.",
|
content: "This command cannot be used in threads, forum channels, or voice channels.",
|
||||||
flags: 64 // Ephemeral
|
flags: 64 // Ephemeral
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return null;
|
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