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,496 +1,100 @@ |
||||
import { |
||||
ApplicationCommandDataResolvable, |
||||
ApplicationCommandOptionType, |
||||
ApplicationCommandType, |
||||
Client, |
||||
GatewayIntentBits |
||||
} 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.", |
||||
}, |
||||
], |
||||
} |
||||
] |
||||
import {BaseInteraction, Client} from "discord.js" |
||||
import {config} from "dotenv" |
||||
import {isChatInputCommand} from "./types/interactions.js" |
||||
import {commandDefinitions, executeCommand, storeCachedCommands} from "./commands/index.js" |
||||
import {checkIsRestart, reportFailed, reportReady, reportStarted} from "./ipc/restart.js" |
||||
import {defaultPresence} from "./defaultPresence.js" |
||||
|
||||
async function main() { |
||||
await checkIsRestart() |
||||
config() |
||||
const c = new Client({ |
||||
intents: [], |
||||
}) |
||||
c.on('ready', async () => { |
||||
c.on("ready", async () => { |
||||
const app = c.application |
||||
if (!c.application) { |
||||
console.log("Bot is ready, but no application") |
||||
if (!app) { |
||||
c.destroy() |
||||
await reportFailed(c, "No application was given") |
||||
return |
||||
} else { |
||||
try { |
||||
await c.application.commands.set(commands) |
||||
console.log("Commands established") |
||||
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)) |
||||
} |
||||
} catch (ex) { |
||||
console.log("Bot is ready, but setting commands failed: " + ex) |
||||
c.destroy() |
||||
await reportFailed(c, ex) |
||||
return |
||||
} |
||||
} |
||||
const user = c.user |
||||
if (!user) { |
||||
c.destroy() |
||||
await reportFailed(c, "No user found") |
||||
return |
||||
} else { |
||||
user.setPresence(defaultPresence) |
||||
} |
||||
try { |
||||
await reportReady(c) |
||||
} catch (ex) { |
||||
console.log(ex) |
||||
} |
||||
}) |
||||
c.on('error', async (ex) => { |
||||
console.log("Connection error: " + ex) |
||||
c.on("error", async (ex) => { |
||||
c.destroy() |
||||
await reportFailed(c, ex) |
||||
}) |
||||
c.on('interactionCreate', async (ev) => { |
||||
c.on("interactionCreate", async (ev: BaseInteraction) => { |
||||
if (ev.client.user.presence.status !== "online") { |
||||
if (ev.isRepliable()) { |
||||
try { |
||||
await ev.reply("Huuuuuuuh? ... I don't know what to do with that yet.") |
||||
console.log("answered an interaction") |
||||
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 answering an interaction: " + 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