parent
f11b5b28bf
commit
fe448ce99c
@ -0,0 +1,21 @@ |
|||||||
|
import {BaseChatInputCommandData, CommandWithSubcommandsData} from "../types.js" |
||||||
|
import {ApplicationCommandType} from "discord.js" |
||||||
|
import {commandBotRestart} from "./restart.js" |
||||||
|
import {commandBotShutdown} from "./shutdown.js" |
||||||
|
import {commandBotRebuild} from "./rebuild.js" |
||||||
|
|
||||||
|
class BotCommandData extends CommandWithSubcommandsData { |
||||||
|
readonly baseDefinition: BaseChatInputCommandData = { |
||||||
|
name: "bot", |
||||||
|
type: ApplicationCommandType.ChatInput, |
||||||
|
description: "Commands to manage the bot's status.", |
||||||
|
} |
||||||
|
|
||||||
|
readonly subcommands = [ |
||||||
|
commandBotRebuild, |
||||||
|
commandBotRestart, |
||||||
|
commandBotShutdown, |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
export const commandBot = new BotCommandData() |
@ -0,0 +1,78 @@ |
|||||||
|
import {adminId, SubcommandData} from "../types.js" |
||||||
|
import { |
||||||
|
ActivityType, |
||||||
|
ApplicationCommandOptionType, |
||||||
|
ApplicationCommandSubCommandData, |
||||||
|
ChatInputCommandInteraction, |
||||||
|
} from "discord.js" |
||||||
|
import {wrappedRestart} from "../../ipc/restart.js" |
||||||
|
import {spawn} from "child_process" |
||||||
|
import {defaultPresence} from "../../defaultPresence.js" |
||||||
|
import {resolve as resolvePath} from "path" |
||||||
|
|
||||||
|
class RebuildCommand extends SubcommandData { |
||||||
|
readonly definition: ApplicationCommandSubCommandData = { |
||||||
|
name: "rebuild", |
||||||
|
description: "Rebuilds and restarts the bot.", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
} |
||||||
|
|
||||||
|
async execute(b: ChatInputCommandInteraction): Promise<void> { |
||||||
|
const user = b.user || b.member |
||||||
|
if (!!user && user.id === adminId) { |
||||||
|
await b.reply("Mmm... let's see... this goes here...") |
||||||
|
const self = b.client.user |
||||||
|
self.setPresence({ |
||||||
|
status: "online", |
||||||
|
afk: false, |
||||||
|
activities: [ |
||||||
|
{ |
||||||
|
type: ActivityType.Watching, |
||||||
|
name: "Compilers at Work", |
||||||
|
}, |
||||||
|
], |
||||||
|
}) |
||||||
|
try { |
||||||
|
await new Promise<void>((resolve, reject) => { |
||||||
|
const result = spawn(resolvePath("./node_modules/.bin/tsc"), { |
||||||
|
cwd: process.cwd(), |
||||||
|
stdio: "inherit", |
||||||
|
detached: true, |
||||||
|
shell: true, |
||||||
|
}) |
||||||
|
result.on("error", (ex) => { |
||||||
|
reject(ex) |
||||||
|
}) |
||||||
|
result.on("exit", (code) => { |
||||||
|
if (code !== 0) { |
||||||
|
reject(`Bad exit code ${code}`) |
||||||
|
} else { |
||||||
|
resolve() |
||||||
|
} |
||||||
|
}) |
||||||
|
}) |
||||||
|
} catch (ex) { |
||||||
|
console.log(ex) |
||||||
|
self.setPresence(defaultPresence) |
||||||
|
await b.followUp("Oops... I think it's broke...") |
||||||
|
return |
||||||
|
} |
||||||
|
self.setPresence({ |
||||||
|
status: "idle", |
||||||
|
afk: true, |
||||||
|
activities: [ |
||||||
|
{ |
||||||
|
type: ActivityType.Listening, |
||||||
|
name: "Born-Again Bot Girl", |
||||||
|
}, |
||||||
|
], |
||||||
|
}) |
||||||
|
await b.followUp("Phewwww... now I'll just... take a quick nap after all that hard work...") |
||||||
|
await wrappedRestart(b) |
||||||
|
} else { |
||||||
|
await b.reply("Heeey... I don't gotta do what you say...") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const commandBotRebuild = new RebuildCommand() |
@ -0,0 +1,39 @@ |
|||||||
|
import {adminId, SubcommandData} from "../types.js" |
||||||
|
import { |
||||||
|
ActivityType, |
||||||
|
ApplicationCommandOptionType, |
||||||
|
ApplicationCommandSubCommandData, |
||||||
|
ChatInputCommandInteraction, |
||||||
|
} from "discord.js" |
||||||
|
import {wrappedRestart} from "../../ipc/restart.js" |
||||||
|
|
||||||
|
class RestartCommand extends SubcommandData { |
||||||
|
readonly definition: ApplicationCommandSubCommandData = { |
||||||
|
name: "restart", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Tells the bot to restart.", |
||||||
|
} |
||||||
|
|
||||||
|
async execute(b: ChatInputCommandInteraction): Promise<void> { |
||||||
|
const user = b.user || b.member |
||||||
|
if (!!user && user.id === adminId) { |
||||||
|
await b.reply("Yaaaawwn... Okay... Just a quick nap then...") |
||||||
|
const self = b.client.user |
||||||
|
self.setPresence({ |
||||||
|
status: "idle", |
||||||
|
afk: true, |
||||||
|
activities: [ |
||||||
|
{ |
||||||
|
type: ActivityType.Listening, |
||||||
|
name: "A New Day", |
||||||
|
}, |
||||||
|
], |
||||||
|
}) |
||||||
|
await wrappedRestart(b) |
||||||
|
} else { |
||||||
|
await b.reply("Heeey... I don't gotta do what you say...") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const commandBotRestart = new RestartCommand() |
@ -0,0 +1,27 @@ |
|||||||
|
import {adminId, SubcommandData} from "../types.js" |
||||||
|
import {ApplicationCommandOptionType, ApplicationCommandSubCommandData, ChatInputCommandInteraction} from "discord.js" |
||||||
|
|
||||||
|
class ShutdownCommand extends SubcommandData { |
||||||
|
readonly definition: ApplicationCommandSubCommandData = { |
||||||
|
name: "shutdown", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Tells the bot to shut down.", |
||||||
|
} |
||||||
|
|
||||||
|
async execute(b: ChatInputCommandInteraction): Promise<void> { |
||||||
|
const user = b.user || b.member |
||||||
|
if (!!user && user.id === adminId) { |
||||||
|
await b.reply("Good night =w=") |
||||||
|
const self = b.client.user |
||||||
|
self.presence.set({ |
||||||
|
status: "invisible", |
||||||
|
activities: [], |
||||||
|
}) |
||||||
|
b.client.destroy() |
||||||
|
} else { |
||||||
|
await b.reply("Heeey... I don't gotta do what you say...") |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const commandBotShutdown = new ShutdownCommand() |
@ -0,0 +1,25 @@ |
|||||||
|
import {ApplicationCommandOptionType, ApplicationCommandSubCommandData, ChatInputCommandInteraction} from "discord.js" |
||||||
|
import {SubcommandData} from "../types.js" |
||||||
|
|
||||||
|
class CharacterCreateCommandData extends SubcommandData { |
||||||
|
readonly definition: ApplicationCommandSubCommandData = { |
||||||
|
name: "create", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Creates a new character. Activates them on the current server.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "template", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "Optionally, an existing character of yours to use as a template.", |
||||||
|
autocomplete: true, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
], |
||||||
|
} |
||||||
|
|
||||||
|
async execute(b: ChatInputCommandInteraction) { |
||||||
|
await b.reply("Okaaaay, I'll make you a character ❤\n\nRight after this nap...") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export const commandCharacterCreate = new CharacterCreateCommandData() |
@ -0,0 +1,135 @@ |
|||||||
|
import {ApplicationCommandOptionType, ApplicationCommandSubCommandData, ApplicationCommandType} from "discord.js" |
||||||
|
import {commandCharacterCreate} from "./create.js" |
||||||
|
import {BaseChatInputCommandData, CommandWithSubcommandsData} from "../types.js" |
||||||
|
|
||||||
|
class CharacterCommandData extends CommandWithSubcommandsData { |
||||||
|
readonly baseDefinition: BaseChatInputCommandData = { |
||||||
|
name: "character", |
||||||
|
type: ApplicationCommandType.ChatInput, |
||||||
|
description: "Commands to manage your characters.", |
||||||
|
} |
||||||
|
|
||||||
|
readonly subcommands = [ |
||||||
|
commandCharacterCreate, |
||||||
|
] |
||||||
|
} |
||||||
|
|
||||||
|
export const commandCharacter = new CharacterCommandData() |
||||||
|
|
||||||
|
const otherSubcommands: ApplicationCommandSubCommandData[] = [ |
||||||
|
{ |
||||||
|
name: "select", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Selects (and activates, if necessary) a character for use with other commands used in this server.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "character", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "The character to switch to. If not supplied, shows you the current selected character.", |
||||||
|
autocomplete: true, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "activate", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Allows a character to be targeted on the current server.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "character", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "The character to activate on this server.", |
||||||
|
autocomplete: true, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "deactivate", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Prevents a character from being targeted on the current server. Deselects them if they're selected.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "character", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "The character to deactivate on this server.", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "edit", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Edits an existing character. Note that characters currently in battle cannot be edited.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "character", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "An existing character of yours to edit. Omit to edit the current character.", |
||||||
|
autocomplete: true, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "list", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Lists your characters.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "public", |
||||||
|
type: ApplicationCommandOptionType.Boolean, |
||||||
|
description: "True to share your character list with the current channel.", |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "show", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Shows the data about one of your characters.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "character", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "An existing character of yours to display in this channel. Omit to display the current character.", |
||||||
|
autocomplete: true, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "public", |
||||||
|
type: ApplicationCommandOptionType.Boolean, |
||||||
|
description: "True to share your character list with the current channel.", |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "archive", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Removes a character from all servers and hides it from the default list view.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "character", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "An existing unarchived character of yours to add to the archive.", |
||||||
|
autocomplete: true, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "unarchive", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Unarchives a character.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "character", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "An existing archived character of yours to take back out of the archive.", |
||||||
|
autocomplete: true, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
] |
@ -0,0 +1,356 @@ |
|||||||
|
import { |
||||||
|
ApplicationCommand, |
||||||
|
ApplicationCommandDataResolvable, |
||||||
|
ApplicationCommandOptionType, |
||||||
|
ApplicationCommandType, |
||||||
|
ChatInputCommandInteraction, |
||||||
|
Collection, |
||||||
|
} from "discord.js" |
||||||
|
import {commandCharacter} from "./character/index.js" |
||||||
|
import {CommandData} from "./types.js" |
||||||
|
import {commandBot} from "./bot/index.js" |
||||||
|
|
||||||
|
const commands: CommandData[] = [ |
||||||
|
commandCharacter, |
||||||
|
commandBot, |
||||||
|
] |
||||||
|
|
||||||
|
export const commandDefinitions: ApplicationCommandDataResolvable[] = commands.map((c) => c.definition) |
||||||
|
|
||||||
|
const _commandNameCache: Record<string, CommandData> = Object.fromEntries( |
||||||
|
commands.map((c) => [c.definition.name, c]), |
||||||
|
) |
||||||
|
|
||||||
|
export const invalidCommandError = "No command by that name exists." |
||||||
|
|
||||||
|
export function storeCachedCommands(data: Collection<string, ApplicationCommand>) { |
||||||
|
for (const command of data.values()) { |
||||||
|
if (_commandNameCache.hasOwnProperty(command.name)) { |
||||||
|
_commandNameCache[command.name].setCached(command) |
||||||
|
} else { |
||||||
|
console.log("No such command when caching commands: " + command.name) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export async function executeCommand(ev: ChatInputCommandInteraction) { |
||||||
|
const name = ev.commandName |
||||||
|
if (!_commandNameCache.hasOwnProperty(name)) { |
||||||
|
throw invalidCommandError |
||||||
|
} |
||||||
|
await _commandNameCache[name].execute(ev) |
||||||
|
} |
||||||
|
|
||||||
|
const otherCommands: ApplicationCommandDataResolvable[] = [ |
||||||
|
{ |
||||||
|
name: "playstyle", |
||||||
|
type: ApplicationCommandType.ChatInput, |
||||||
|
description: "Updates your play style.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "difficulty", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Alters or displays the reformation difficulty of your currently selected character.", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "preference", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Alters or displays the pred/prey preference of your currently selected character.", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "team", |
||||||
|
type: ApplicationCommandType.ChatInput, |
||||||
|
description: "Affects teams in battle.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "join", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Leaves the current team, if any, and requests to join the same team (and battle) as another player.", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "leave", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Leaves the current team, if any, and strikes it out alone.", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "battle", |
||||||
|
type: ApplicationCommandType.ChatInput, |
||||||
|
description: "Takes actions in battle.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "challenge", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Challenges another character to a battle.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "target", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "The character you want to challenge.", |
||||||
|
autocomplete: true, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "menu", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Opens the battle menu to select an action for this turn.", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "skill", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Uses a skill.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "name", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "The name of the skill to use. Omit to see a list.", |
||||||
|
autocomplete: true, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "target", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "The target to use the skill on, if there are multiple options. Omit to see a list.", |
||||||
|
autocomplete: true, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "item", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Uses an item.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "name", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "The name of the item to use. Omit to see a list.", |
||||||
|
autocomplete: true, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "target", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "The target to use the item on, if there are multiple options. Omit to see a list.", |
||||||
|
autocomplete: true, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "vore", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Devours your opponent. Requires your opponent to be at low Confidence.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "style", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "The style of devouring to use. Omit to see a list.", |
||||||
|
autocomplete: true, |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "rest", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Takes a breather, recovering your Confidence and Energy at a faster-than-normal rate for this turn.", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "escape", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Tries to escape from a battle or stomach. Requires your opponent to be at low Confidence.", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "surrender", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Lets your opponent do as they will with you by reducing your Confidence or Health to 0.", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "prey", |
||||||
|
type: ApplicationCommandOptionType.SubcommandGroup, |
||||||
|
description: "Commands for when you have a full belly.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "release", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Releases your prey from your stomach.", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "squish", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Squishes your prey, ending the combat. Can only be performed when the prey is at or below 0% Health.", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "safety", |
||||||
|
type: ApplicationCommandType.ChatInput, |
||||||
|
description: "Safety commands for protecting consent and user data.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "exit", |
||||||
|
type: ApplicationCommandOptionType.SubcommandGroup, |
||||||
|
description: "Exits battle(s) and reverts its/their effects on you. To flee in-character, use /battle escape.", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "block", |
||||||
|
type: ApplicationCommandOptionType.SubcommandGroup, |
||||||
|
description: "Manages your block list.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "add", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Prevents a user or character from challenging you or joining battles you're in.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "user", |
||||||
|
type: ApplicationCommandOptionType.User, |
||||||
|
description: "The user to block from challenging you or joining battles you're in.", |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "character", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "The character to block from challenging you or joining battles you're in.", |
||||||
|
required: false, |
||||||
|
autocomplete: true, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "remove", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Allows a user or character to challenge you and join battles you're in once more.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "user", |
||||||
|
type: ApplicationCommandOptionType.User, |
||||||
|
description: "The user to allow to challenge you and join battles you're in once more.", |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "character", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "The character to allow to challenge you and join battles you're in once more.", |
||||||
|
required: false, |
||||||
|
autocomplete: true, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "list", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Displays your current block list.", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "kick", |
||||||
|
type: ApplicationCommandOptionType.SubcommandGroup, |
||||||
|
description: "Manages the list of users kicked from the current battle.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "add", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Removes a user or character from the current battle and prevents them from rejoining.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "user", |
||||||
|
type: ApplicationCommandOptionType.User, |
||||||
|
description: "The user to remove from the current battle and prevent from rejoining.", |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "character", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "The character to remove from the current battle and prevent from rejoining.", |
||||||
|
required: false, |
||||||
|
autocomplete: true, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "remove", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Allows a user or character to rejoin the current battle.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "user", |
||||||
|
type: ApplicationCommandOptionType.User, |
||||||
|
description: "The user to allow to rejoin the current battle.", |
||||||
|
required: false, |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "character", |
||||||
|
type: ApplicationCommandOptionType.String, |
||||||
|
description: "The user to allow to rejoin the current battle.", |
||||||
|
required: false, |
||||||
|
autocomplete: true, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "list", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Displays the list of users forbidden from rejoining the current battle.", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "ban", |
||||||
|
type: ApplicationCommandOptionType.SubcommandGroup, |
||||||
|
description: "Manages the ban list of the current server.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "add", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Bans a user from participating in battles in the current server and kicks them from any they're in.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "user", |
||||||
|
type: ApplicationCommandOptionType.User, |
||||||
|
description: "The user to ban from participating in battles in the current server.", |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "remove", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Allows a user to resume participating in battles in the current server.", |
||||||
|
options: [ |
||||||
|
{ |
||||||
|
name: "user", |
||||||
|
type: ApplicationCommandOptionType.User, |
||||||
|
description: "The user to allow to participate in battles in the current server once more.", |
||||||
|
required: true, |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "list", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "Displays this server's current ban list.", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "delete", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "PERMANENTLY deletes a character, exiting any battles they were part of. See also /character archive.", |
||||||
|
}, |
||||||
|
{ |
||||||
|
name: "wipe", |
||||||
|
type: ApplicationCommandOptionType.Subcommand, |
||||||
|
description: "PERMANENTLY deletes ALL data about you in the system, erasing you from the system's knowledge.", |
||||||
|
}, |
||||||
|
], |
||||||
|
}, |
||||||
|
] |
@ -0,0 +1,154 @@ |
|||||||
|
import { |
||||||
|
ApplicationCommand, |
||||||
|
ApplicationCommandSubCommandData, |
||||||
|
ApplicationCommandSubGroupData, |
||||||
|
ChatInputApplicationCommandData, |
||||||
|
ChatInputCommandInteraction, |
||||||
|
} from "discord.js" |
||||||
|
|
||||||
|
export const noSubcommandError = "No subcommand was provided, but one is required." |
||||||
|
export const invalidSubcommandGroupError = "The subcommand group provided does not exist." |
||||||
|
export const invalidSubcommandError = "The subcommand provided does not exist." |
||||||
|
|
||||||
|
export const adminId = "126936953789743104" |
||||||
|
|
||||||
|
export abstract class CommandData { |
||||||
|
private _cachedGlobal: ApplicationCommand | null = null |
||||||
|
private _cacheByGuild: Record<string, ApplicationCommand> = {} |
||||||
|
|
||||||
|
abstract get definition(): ChatInputApplicationCommandData |
||||||
|
|
||||||
|
getCached(guildId: string | null): ApplicationCommand { |
||||||
|
if (guildId === null) { |
||||||
|
if (this._cachedGlobal === null) { |
||||||
|
throw Error("Not yet cached") |
||||||
|
} |
||||||
|
return this._cachedGlobal |
||||||
|
} else if (this._cacheByGuild.hasOwnProperty(guildId)) { |
||||||
|
return this._cacheByGuild[guildId] |
||||||
|
} else { |
||||||
|
throw Error("Not yet cached") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
setCached(command: ApplicationCommand) { |
||||||
|
if (command.guildId === null) { |
||||||
|
this._cachedGlobal = command |
||||||
|
} else { |
||||||
|
this._cacheByGuild[command.guildId] = command |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
abstract execute(b: ChatInputCommandInteraction): Promise<void> |
||||||
|
} |
||||||
|
|
||||||
|
export type BaseChatInputCommandData = Omit<ChatInputApplicationCommandData, "options"> |
||||||
|
|
||||||
|
export abstract class CommandWithSubcommandsData extends CommandData { |
||||||
|
private _subcommandGroupsCache: Record<string, SubcommandGroupData> | null = null |
||||||
|
private _subcommandsCache: Record<string, SubcommandData> | null = null |
||||||
|
|
||||||
|
get definition(): ChatInputApplicationCommandData { |
||||||
|
return { |
||||||
|
options: this.subcommands.map((s) => s.definition), |
||||||
|
...this.baseDefinition, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected abstract get baseDefinition(): BaseChatInputCommandData |
||||||
|
|
||||||
|
protected abstract get subcommands(): (SubcommandData | SubcommandGroupData)[] |
||||||
|
|
||||||
|
async execute(b: ChatInputCommandInteraction) { |
||||||
|
const group = b.options.getSubcommandGroup() |
||||||
|
const subcommand = b.options.getSubcommand() |
||||||
|
if (group !== null) { |
||||||
|
await this.resolveSubcommandGroup(group).execute(b) |
||||||
|
} else if (subcommand !== null) { |
||||||
|
await this.resolveSubcommand(subcommand).execute(b) |
||||||
|
} else { |
||||||
|
throw noSubcommandError |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected resolveSubcommandGroup(name: string): SubcommandGroupData { |
||||||
|
let cache = this._subcommandGroupsCache |
||||||
|
if (cache === null) { |
||||||
|
cache = {} |
||||||
|
for (const item of this.subcommands) { |
||||||
|
if (item instanceof SubcommandGroupData) { |
||||||
|
cache[item.definition.name] = item |
||||||
|
} |
||||||
|
} |
||||||
|
this._subcommandGroupsCache = cache |
||||||
|
} |
||||||
|
if (!cache.hasOwnProperty(name)) { |
||||||
|
throw invalidSubcommandGroupError |
||||||
|
} |
||||||
|
return cache[name] |
||||||
|
} |
||||||
|
|
||||||
|
protected resolveSubcommand(name: string): SubcommandData { |
||||||
|
let cache = this._subcommandsCache |
||||||
|
if (cache === null) { |
||||||
|
cache = {} |
||||||
|
for (const item of this.subcommands) { |
||||||
|
if (item instanceof SubcommandData) { |
||||||
|
cache[item.definition.name] = item |
||||||
|
} |
||||||
|
} |
||||||
|
this._subcommandsCache = cache |
||||||
|
} |
||||||
|
if (!cache.hasOwnProperty(name)) { |
||||||
|
throw invalidSubcommandGroupError |
||||||
|
} |
||||||
|
return cache[name] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export type BaseSubcommandGroupData = Omit<ApplicationCommandSubGroupData, "options"> |
||||||
|
|
||||||
|
export abstract class SubcommandGroupData { |
||||||
|
private _subcommandsCache: Record<string, SubcommandData> | null = null |
||||||
|
|
||||||
|
get definition(): ApplicationCommandSubGroupData { |
||||||
|
return { |
||||||
|
...this.baseDefinition, |
||||||
|
options: this.subcommands.map((s) => s.definition), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected abstract get baseDefinition(): BaseSubcommandGroupData |
||||||
|
|
||||||
|
protected abstract get subcommands(): SubcommandData[] |
||||||
|
|
||||||
|
async execute(b: ChatInputCommandInteraction): Promise<void> { |
||||||
|
const subcommand = b.options.getSubcommand() |
||||||
|
if (subcommand !== null) { |
||||||
|
await this.resolveSubcommand(subcommand).execute(b) |
||||||
|
} else { |
||||||
|
throw noSubcommandError |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
protected resolveSubcommand(name: string): SubcommandData { |
||||||
|
let cache = this._subcommandsCache |
||||||
|
if (cache === null) { |
||||||
|
cache = {} |
||||||
|
for (const item of this.subcommands) { |
||||||
|
cache[item.definition.name] = item |
||||||
|
} |
||||||
|
this._subcommandsCache = cache |
||||||
|
} |
||||||
|
if (!cache.hasOwnProperty(name)) { |
||||||
|
throw invalidSubcommandError |
||||||
|
} |
||||||
|
return cache[name] |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export abstract class SubcommandData { |
||||||
|
abstract get definition(): ApplicationCommandSubCommandData |
||||||
|
|
||||||
|
abstract execute(b: ChatInputCommandInteraction): Promise<void> |
||||||
|
} |
@ -0,0 +1,11 @@ |
|||||||
|
import {ActivityType, PresenceData} from "discord.js" |
||||||
|
|
||||||
|
export const defaultPresence: PresenceData = { |
||||||
|
status: "online", |
||||||
|
activities: [ |
||||||
|
{ |
||||||
|
name: "Vore PvP", |
||||||
|
type: ActivityType.Playing, |
||||||
|
}, |
||||||
|
], |
||||||
|
} |
@ -0,0 +1,200 @@ |
|||||||
|
import {fork} from "child_process" |
||||||
|
import {ChatInputCommandInteraction, Client, InteractionWebhook} from "discord.js" |
||||||
|
|
||||||
|
export enum RestartReason { |
||||||
|
RestartCommand = "restart", |
||||||
|
} |
||||||
|
|
||||||
|
export enum StartState { |
||||||
|
NeverStarted = "never_started", |
||||||
|
Errored = "errored", |
||||||
|
Crashed = "crashed", |
||||||
|
Started = "started", |
||||||
|
Ready = "ready", |
||||||
|
Failed = "failed", |
||||||
|
} |
||||||
|
|
||||||
|
export interface StartData { |
||||||
|
state: StartState, |
||||||
|
status?: string |
||||||
|
} |
||||||
|
|
||||||
|
export interface RestartData { |
||||||
|
appId: string |
||||||
|
token: string |
||||||
|
reason: RestartReason |
||||||
|
} |
||||||
|
|
||||||
|
let restartState: RestartData | null = null |
||||||
|
|
||||||
|
export async function checkIsRestart(): Promise<RestartData | null> { |
||||||
|
if (!process.connected) { |
||||||
|
return null |
||||||
|
} else { |
||||||
|
try { |
||||||
|
const result = await new Promise<RestartData | null>((resolve) => { |
||||||
|
process.on("message", (value: { restart?: RestartData }) => { |
||||||
|
if (value && value.restart) { |
||||||
|
resolve(value.restart) |
||||||
|
} else { |
||||||
|
resolve(null) |
||||||
|
} |
||||||
|
}) |
||||||
|
setTimeout(() => resolve(null), 50) |
||||||
|
}) |
||||||
|
if (result !== null) { |
||||||
|
restartState = result |
||||||
|
} |
||||||
|
return result |
||||||
|
} catch (ex) { |
||||||
|
return null |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export async function reportStarted(): Promise<void> { |
||||||
|
if (!process.send) { |
||||||
|
return |
||||||
|
} |
||||||
|
const startData: StartData = { |
||||||
|
state: StartState.Started, |
||||||
|
} |
||||||
|
process.send(startData) |
||||||
|
} |
||||||
|
|
||||||
|
export async function reportReady(c: Client<true>): Promise<boolean> { |
||||||
|
if (restartState) { |
||||||
|
try { |
||||||
|
const hook = new InteractionWebhook(c, restartState.appId, restartState.token) |
||||||
|
await hook.send({ |
||||||
|
content: "yawwwwn... Good morning...", |
||||||
|
}) |
||||||
|
} catch (ex) { |
||||||
|
console.log("followup failed to send", ex) |
||||||
|
} |
||||||
|
} |
||||||
|
if (!process.send) { |
||||||
|
return false |
||||||
|
} |
||||||
|
const startData: StartData = { |
||||||
|
state: StartState.Ready, |
||||||
|
} |
||||||
|
process.send(startData) |
||||||
|
return true |
||||||
|
} |
||||||
|
|
||||||
|
export async function reportFailed(c: Client, error: unknown): Promise<void> { |
||||||
|
console.log("failed to start", error) |
||||||
|
if (restartState) { |
||||||
|
try { |
||||||
|
const hook = new InteractionWebhook(c, restartState.appId, restartState.token) |
||||||
|
await hook.send({ |
||||||
|
content: "Ugh... I can't get up... Head hurts... sorry...", |
||||||
|
}) |
||||||
|
} catch (ex) { |
||||||
|
console.log("followup failed to send", ex) |
||||||
|
} |
||||||
|
} |
||||||
|
if (!process.send) { |
||||||
|
return |
||||||
|
} |
||||||
|
const startData: StartData = { |
||||||
|
state: StartState.Failed, |
||||||
|
status: `${error}`, |
||||||
|
} |
||||||
|
process.send(startData) |
||||||
|
} |
||||||
|
|
||||||
|
export async function wrappedRestart(b: ChatInputCommandInteraction) { |
||||||
|
try { |
||||||
|
await doRestart(b.client, b.webhook.id, b.webhook.token, RestartReason.RestartCommand) |
||||||
|
} catch (ex) { |
||||||
|
console.log("failed doing restart", ex) |
||||||
|
let data: StartData | null = null |
||||||
|
if (typeof ex === "object" && ex !== null && ex.hasOwnProperty("state")) { |
||||||
|
data = ex as StartData |
||||||
|
} |
||||||
|
if ((data && data.state)) { |
||||||
|
try { |
||||||
|
b.client.user.presence.set({ |
||||||
|
status: "invisible", |
||||||
|
activities: [], |
||||||
|
}) |
||||||
|
await b.followUp("Mhhhh... I don't feel good...") |
||||||
|
} catch (ex) { |
||||||
|
console.log("failed resetting presence and sending followup", ex) |
||||||
|
} |
||||||
|
} |
||||||
|
} finally { |
||||||
|
b.client.destroy() |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
export async function doRestart(client: Client, appId: string, token: string, |
||||||
|
restartReason: RestartReason): Promise<StartData> { |
||||||
|
return new Promise((resolve, reject) => { |
||||||
|
const child = fork(process.argv[1], process.argv.slice(2), { |
||||||
|
execPath: process.execPath, |
||||||
|
cwd: process.cwd(), |
||||||
|
detached: true, |
||||||
|
stdio: "inherit", |
||||||
|
}) |
||||||
|
let result: StartData = { |
||||||
|
state: StartState.NeverStarted, |
||||||
|
} |
||||||
|
child.once("error", (err) => { |
||||||
|
result.state = StartState.Errored |
||||||
|
result.status = `${err}` |
||||||
|
if (child.connected) { |
||||||
|
child.unref() |
||||||
|
child.disconnect() |
||||||
|
} |
||||||
|
reject({...result}) |
||||||
|
}) |
||||||
|
child.once("exit", (code, signal) => { |
||||||
|
result.state = StartState.Crashed |
||||||
|
result.status = `Exited unexpectedly with code ${code} (signal ${signal})` |
||||||
|
if (child.connected) { |
||||||
|
child.unref() |
||||||
|
child.disconnect() |
||||||
|
} |
||||||
|
reject({...result}) |
||||||
|
}) |
||||||
|
child.on("message", (message: StartData) => { |
||||||
|
switch (typeof message === "object" && message.state) { |
||||||
|
case StartState.Started: |
||||||
|
result.state = StartState.Started |
||||||
|
break |
||||||
|
case StartState.Ready: |
||||||
|
resolve({...message}) |
||||||
|
if (child.connected) { |
||||||
|
child.unref() |
||||||
|
child.disconnect() |
||||||
|
} |
||||||
|
break |
||||||
|
case StartState.Failed: |
||||||
|
reject({...message}) |
||||||
|
if (child.connected) { |
||||||
|
child.unref() |
||||||
|
child.disconnect() |
||||||
|
} |
||||||
|
break |
||||||
|
default: |
||||||
|
result.state = StartState.Failed |
||||||
|
result.status = `Unexpected message: ${message}` |
||||||
|
reject({...message}) |
||||||
|
if (child.connected) { |
||||||
|
child.unref() |
||||||
|
child.disconnect() |
||||||
|
} |
||||||
|
break |
||||||
|
} |
||||||
|
}) |
||||||
|
process.on("disconnect", () => { |
||||||
|
result.state = StartState.Crashed |
||||||
|
reject({...result}) |
||||||
|
child.unref() |
||||||
|
}) |
||||||
|
child.send({restart: {appId, token, reason: restartReason}}) |
||||||
|
}) |
||||||
|
} |
@ -1,7 +1,7 @@ |
|||||||
import { describe, test, expect } from '@jest/globals' |
import {describe, expect, test} from "@jest/globals" |
||||||
|
|
||||||
describe("Jest testing", () => { |
describe("Jest testing", () => { |
||||||
test("runs", () => { |
test("runs", () => { |
||||||
expect(test).toBeDefined() |
expect(test).toBeDefined() |
||||||
}) |
}) |
||||||
}) |
}) |
||||||
|
@ -1,496 +1,100 @@ |
|||||||
import { |
import {BaseInteraction, Client} from "discord.js" |
||||||
ApplicationCommandDataResolvable, |
import {config} from "dotenv" |
||||||
ApplicationCommandOptionType, |
import {isChatInputCommand} from "./types/interactions.js" |
||||||
ApplicationCommandType, |
import {commandDefinitions, executeCommand, storeCachedCommands} from "./commands/index.js" |
||||||
Client, |
import {checkIsRestart, reportFailed, reportReady, reportStarted} from "./ipc/restart.js" |
||||||
GatewayIntentBits |
import {defaultPresence} from "./defaultPresence.js" |
||||||
} from 'discord.js' |
|
||||||
import {config} from 'dotenv' |
|
||||||
|
|
||||||
const commands: ApplicationCommandDataResolvable[] = [ |
|
||||||
{ |
|
||||||
name: "character", |
|
||||||
type: ApplicationCommandType.ChatInput, |
|
||||||
description: "Commands to manage your characters.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "create", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Creates a new character. Activates them on the current server.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "template", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "Optionally, an existing character of yours to use as a template.", |
|
||||||
autocomplete: true, |
|
||||||
required: false, |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "select", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Selects (and activates, if necessary) a character for use with other commands used in this server.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "character", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "The character to switch to. If not supplied, shows you the current selected character.", |
|
||||||
autocomplete: true, |
|
||||||
required: false, |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "activate", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Allows a character to be targeted on the current server.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "character", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "The character to activate on this server.", |
|
||||||
autocomplete: true, |
|
||||||
required: false, |
|
||||||
}, |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "deactivate", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Prevents a character from being targeted on the current server. Deselects them if they're selected.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "character", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "The character to deactivate on this server." |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "edit", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Edits an existing character. Note that characters currently in battle cannot be edited.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "character", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "An existing character of yours to edit. Omit to edit the current character.", |
|
||||||
autocomplete: true, |
|
||||||
required: false, |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "list", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Lists your characters.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "public", |
|
||||||
type: ApplicationCommandOptionType.Boolean, |
|
||||||
description: "True to share your character list with the current channel.", |
|
||||||
required: false, |
|
||||||
} |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "show", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Shows the data about one of your characters.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "character", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "An existing character of yours to display in this channel. Omit to display the current character.", |
|
||||||
autocomplete: true, |
|
||||||
required: false, |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "public", |
|
||||||
type: ApplicationCommandOptionType.Boolean, |
|
||||||
description: "True to share your character list with the current channel.", |
|
||||||
required: false, |
|
||||||
} |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "archive", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Removes a character from all servers and hides it from the default list view.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "character", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "An existing unarchived character of yours to add to the archive.", |
|
||||||
autocomplete: true, |
|
||||||
required: false, |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "unarchive", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Unarchives a character.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "character", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "An existing archived character of yours to take back out of the archive.", |
|
||||||
autocomplete: true, |
|
||||||
required: false, |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "playstyle", |
|
||||||
type: ApplicationCommandType.ChatInput, |
|
||||||
description: "Updates your play style.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "difficulty", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Alters or displays the reformation difficulty of your currently selected character." |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "preference", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Alters or displays the pred/prey preference of your currently selected character." |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "team", |
|
||||||
type: ApplicationCommandType.ChatInput, |
|
||||||
description: "Affects teams in battle.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "join", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Leaves the current team, if any, and requests to join the same team (and battle) as another player." |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "leave", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Leaves the current team, if any, and strikes it out alone." |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "battle", |
|
||||||
type: ApplicationCommandType.ChatInput, |
|
||||||
description: "Takes actions in battle.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "challenge", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Challenges another character to a battle.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "target", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "The character you want to challenge.", |
|
||||||
autocomplete: true, |
|
||||||
} |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "menu", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Opens the battle menu to select an action for this turn.", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "skill", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Uses a skill.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "name", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "The name of the skill to use. Omit to see a list.", |
|
||||||
autocomplete: true, |
|
||||||
required: false, |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "target", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "The target to use the skill on, if there are multiple options. Omit to see a list.", |
|
||||||
autocomplete: true, |
|
||||||
required: false, |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "item", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Uses an item.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "name", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "The name of the item to use. Omit to see a list.", |
|
||||||
autocomplete: true, |
|
||||||
required: false, |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "target", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "The target to use the item on, if there are multiple options. Omit to see a list.", |
|
||||||
autocomplete: true, |
|
||||||
required: false, |
|
||||||
}, |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "vore", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Devours your opponent. Requires your opponent to be at low Confidence.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "style", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "The style of devouring to use. Omit to see a list.", |
|
||||||
autocomplete: true, |
|
||||||
required: false, |
|
||||||
}, |
|
||||||
] |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "rest", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Takes a breather, recovering your Confidence and Energy at a faster-than-normal rate for this turn." |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "escape", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Tries to escape from a battle or stomach. Requires your opponent to be at low Confidence." |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "surrender", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Lets your opponent do as they will with you by reducing your Confidence or Health to 0." |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "prey", |
|
||||||
type: ApplicationCommandOptionType.SubcommandGroup, |
|
||||||
description: "Commands for when you have a full belly.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "release", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Releases your prey from your stomach." |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "squish", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Squishes your prey, ending the combat. Can only be performed when the prey is at or below 0% Health." |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "safety", |
|
||||||
type: ApplicationCommandType.ChatInput, |
|
||||||
description: "Safety commands for protecting consent and user data.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "exit", |
|
||||||
type: ApplicationCommandOptionType.SubcommandGroup, |
|
||||||
description: "Exits battle(s) and reverts its/their effects on you. To flee in-character, use /battle escape.", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "block", |
|
||||||
type: ApplicationCommandOptionType.SubcommandGroup, |
|
||||||
description: "Manages your block list.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "add", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Prevents a user or character from challenging you or joining battles you're in.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "user", |
|
||||||
type: ApplicationCommandOptionType.User, |
|
||||||
description: "The user to block from challenging you or joining battles you're in.", |
|
||||||
required: false |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "character", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "The character to block from challenging you or joining battles you're in.", |
|
||||||
required: false, |
|
||||||
autocomplete: true |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "remove", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Allows a user or character to challenge you and join battles you're in once more.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "user", |
|
||||||
type: ApplicationCommandOptionType.User, |
|
||||||
description: "The user to allow to challenge you and join battles you're in once more.", |
|
||||||
required: false |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "character", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "The character to allow to challenge you and join battles you're in once more.", |
|
||||||
required: false, |
|
||||||
autocomplete: true |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "list", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Displays your current block list." |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "kick", |
|
||||||
type: ApplicationCommandOptionType.SubcommandGroup, |
|
||||||
description: "Manages the list of users kicked from the current battle.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "add", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Removes a user or character from the current battle and prevents them from rejoining.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "user", |
|
||||||
type: ApplicationCommandOptionType.User, |
|
||||||
description: "The user to remove from the current battle and prevent from rejoining.", |
|
||||||
required: false |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "character", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "The character to remove from the current battle and prevent from rejoining.", |
|
||||||
required: false, |
|
||||||
autocomplete: true |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "remove", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Allows a user or character to rejoin the current battle.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "user", |
|
||||||
type: ApplicationCommandOptionType.User, |
|
||||||
description: "The user to allow to rejoin the current battle.", |
|
||||||
required: false |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "character", |
|
||||||
type: ApplicationCommandOptionType.String, |
|
||||||
description: "The user to allow to rejoin the current battle.", |
|
||||||
required: false, |
|
||||||
autocomplete: true |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "list", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Displays the list of users forbidden from rejoining the current battle." |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "ban", |
|
||||||
type: ApplicationCommandOptionType.SubcommandGroup, |
|
||||||
description: "Manages the ban list of the current server.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "add", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Bans a user from participating in battles in the current server and kicks them from any they're in.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "user", |
|
||||||
type: ApplicationCommandOptionType.User, |
|
||||||
description: "The user to ban from participating in battles in the current server.", |
|
||||||
required: true |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "remove", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Allows a user to resume participating in battles in the current server.", |
|
||||||
options: [ |
|
||||||
{ |
|
||||||
name: "user", |
|
||||||
type: ApplicationCommandOptionType.User, |
|
||||||
description: "The user to allow to participate in battles in the current server once more.", |
|
||||||
required: true |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "list", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "Displays this server's current ban list." |
|
||||||
}, |
|
||||||
], |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "delete", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "PERMANENTLY deletes a character, exiting any battles they were part of. See also /character archive.", |
|
||||||
}, |
|
||||||
{ |
|
||||||
name: "wipe", |
|
||||||
type: ApplicationCommandOptionType.Subcommand, |
|
||||||
description: "PERMANENTLY deletes ALL data about you in the system, erasing you from the system's knowledge.", |
|
||||||
}, |
|
||||||
], |
|
||||||
} |
|
||||||
] |
|
||||||
|
|
||||||
async function main() { |
async function main() { |
||||||
config() |
await checkIsRestart() |
||||||
const c = new Client({ |
config() |
||||||
intents: [], |
const c = new Client({ |
||||||
}) |
intents: [], |
||||||
c.on('ready', async () => { |
}) |
||||||
const app = c.application |
c.on("ready", async () => { |
||||||
if (!c.application) { |
const app = c.application |
||||||
console.log("Bot is ready, but no application") |
if (!app) { |
||||||
} else { |
c.destroy() |
||||||
try { |
await reportFailed(c, "No application was given") |
||||||
await c.application.commands.set(commands) |
return |
||||||
console.log("Commands established") |
} else { |
||||||
} catch (ex) { |
try { |
||||||
console.log("Bot is ready, but setting commands failed: " + ex) |
storeCachedCommands(await app.commands.set(commandDefinitions)) |
||||||
} |
const g = await c.guilds.fetch() |
||||||
} |
for (const guild of g.values()) { |
||||||
}) |
storeCachedCommands(await app.commands.set(commandDefinitions, guild.id)) |
||||||
c.on('error', async (ex) => { |
} |
||||||
console.log("Connection error: " + ex) |
} catch (ex) { |
||||||
}) |
c.destroy() |
||||||
c.on('interactionCreate', async (ev) => { |
await reportFailed(c, ex) |
||||||
if (ev.isRepliable()) { |
return |
||||||
try { |
} |
||||||
await ev.reply("Huuuuuuuh? ... I don't know what to do with that yet.") |
} |
||||||
console.log("answered an interaction") |
const user = c.user |
||||||
} catch (ex) { |
if (!user) { |
||||||
console.log("failed answering an interaction: " + ex) |
c.destroy() |
||||||
} |
await reportFailed(c, "No user found") |
||||||
} else { |
return |
||||||
console.log("got an interaction but can't reply to it") |
} else { |
||||||
} |
user.setPresence(defaultPresence) |
||||||
}) |
} |
||||||
await c.login(process.env.DISCORD_TOKEN || "") |
try { |
||||||
|
await reportReady(c) |
||||||
|
} catch (ex) { |
||||||
|
console.log(ex) |
||||||
|
} |
||||||
|
}) |
||||||
|
c.on("error", async (ex) => { |
||||||
|
c.destroy() |
||||||
|
await reportFailed(c, ex) |
||||||
|
}) |
||||||
|
c.on("interactionCreate", async (ev: BaseInteraction) => { |
||||||
|
if (ev.client.user.presence.status !== "online") { |
||||||
|
if (ev.isRepliable()) { |
||||||
|
try { |
||||||
|
await ev.reply({ |
||||||
|
content: "Shhh... I'm sleeping!! Try again later.", |
||||||
|
ephemeral: true, |
||||||
|
}) |
||||||
|
} catch (ex) { |
||||||
|
console.log("failed sending busy reply", ex) |
||||||
|
} |
||||||
|
} |
||||||
|
return |
||||||
|
} |
||||||
|
if (isChatInputCommand(ev)) { |
||||||
|
try { |
||||||
|
await executeCommand(ev) |
||||||
|
} catch (ex) { |
||||||
|
console.log("failed executing command", ev, ex) |
||||||
|
if (!ev.replied) { |
||||||
|
try { |
||||||
|
await ev.reply({ |
||||||
|
ephemeral: true, |
||||||
|
content: "Uuguuu... I can't think straight... try again later, 'kay?", |
||||||
|
}) |
||||||
|
} catch (innerEx) { |
||||||
|
console.log("failed sending error response", innerEx) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
} else if (ev.isRepliable()) { |
||||||
|
try { |
||||||
|
await ev.reply({ |
||||||
|
ephemeral: true, |
||||||
|
content: "Huuuuuuuh? ... I don't know what to do with that yet.", |
||||||
|
}) |
||||||
|
} catch (ex) { |
||||||
|
console.log("failed sending unknown command response", ex) |
||||||
|
} |
||||||
|
} else { |
||||||
|
console.log("got an interaction but can't reply to it") |
||||||
|
} |
||||||
|
}) |
||||||
|
await reportStarted() |
||||||
|
await c.login(process.env.DISCORD_TOKEN || "") |
||||||
} |
} |
||||||
|
|
||||||
main().catch((ex) => {console.log("main thread failed: " + ex)}) |
main().catch((ex) => { |
||||||
|
console.log("main thread failed", ex) |
||||||
|
}) |
@ -0,0 +1,5 @@ |
|||||||
|
import {BaseInteraction, ChatInputCommandInteraction} from "discord.js" |
||||||
|
|
||||||
|
export function isChatInputCommand(x: BaseInteraction): x is ChatInputCommandInteraction { |
||||||
|
return x.isChatInputCommand() |
||||||
|
} |
Loading…
Reference in new issue