Skip to content

Commit

Permalink
feat: slash command for certificate helper
Browse files Browse the repository at this point in the history
  • Loading branch information
tyrone-sudeium committed Jan 4, 2024
1 parent e8954a9 commit b90c52a
Show file tree
Hide file tree
Showing 6 changed files with 144 additions and 24 deletions.
2 changes: 1 addition & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"editor.codeActionsOnSave": {
"source.fixAll": true
"source.fixAll": "explicit"
},
"eslint.alwaysShowStatus": true
}
60 changes: 37 additions & 23 deletions src/features/ffxiv_certificate_helper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import * as Discord from "discord.js"
import { ACCESSORIES, MAJORS, MINORS, ITEM_NAMES } from "../model/ffxiv-items"
import { getJSON, queryStringFromObject } from "../util/http"
import { log } from "../log"
import { DataCenter, isDataCenter } from "../model/ffxiv-datacenters"
import { stupidTitleCase } from "../util/string_stuff"
import { GlobalFeature, MessageContext } from "./feature"


Expand All @@ -42,25 +44,6 @@ interface PriceInfo {
itemId: number
}

const DATA_CENTERS = [
"aether",
"crystal",
"dynamis",
"primal",
"chaos",
"light",
"elemental",
"gaia",
"mana",
"meteor",
"materia",
] as const
type DataCenter = typeof DATA_CENTERS[number]

function isDataCenter(str: string): str is DataCenter {
return (DATA_CENTERS as readonly string[]).includes(str)
}

function certificateValue(itemId: number): number {
if (MAJORS.has(itemId)) {
return 17
Expand All @@ -87,15 +70,46 @@ function formatPriceInfo(info: PriceInfo): string {
return `${price} on ${info.worldName} (${perCert} per cert)`
}

function stupidTitleCase(str: string): string {
return str[0].toUpperCase() + str.slice(1)
}

function isPriceInfoEqual(p1: PriceInfo, p2: PriceInfo): boolean {
return p1.itemName === p2.itemName && p1.price === p2.price && p1.worldName === p2.worldName
}

export class FFXIVCertificateFeature extends GlobalFeature {
public async handleInteraction(interaction: Discord.Interaction<Discord.CacheType>): Promise<void> {
if (!interaction.isChatInputCommand()) {
return
}
const dc = interaction.options.getString("datacentre")
if (!dc) {
// This is required? lul.
await interaction.reply({content: "⚠️ Missing data centre. Try `/xiv certificates [dc]`", ephemeral: true})
return
}
if (!isDataCenter(dc)) {
await interaction.reply({content: `⚠️ \`${dc}\` is not a recognised data centre`, ephemeral: true})
return
}

try {
const prices = await this.getPricesFromUniversalis(dc)
const embeds = prices.map((priceInfo, index) => {
const embed = new Discord.EmbedBuilder()
const thumb = `https://universalis-ffxiv.github.io/universalis-assets/icon2x/${priceInfo.itemId}.png`
embed.setAuthor({
name: `#${index+1} ${priceInfo.itemName}`,
iconURL: thumb,
url: `https://universalis.app/market/${priceInfo.itemId}`,
})
embed.setFooter({text: formatPriceInfo(priceInfo)})
return embed
})
interaction.reply({embeds, ephemeral: true})
} catch(err) {
log(`ffxiv_certificate_helper error: ${err}`, "always")
interaction.reply({content: "oops something's cooked. check the logs", ephemeral: true})
}
}

public handleMessage(context: MessageContext<this>): boolean {
const tokens = this.commandTokens(context)

Expand Down
56 changes: 56 additions & 0 deletions src/features/ffxiv_slash_commands.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/**
* Container for all FFXIV slash commands
*/

/*
* AetheBot - A Discord Chatbot
*
* Created by Tyrone Trevorrow on 04/01/24.
* Copyright (c) 2024 Tyrone Trevorrow. All rights reserved.
*
* This source code is licensed under the permissive MIT license.
*/

import * as Discord from "discord.js"
import { DATA_CENTERS } from "../model/ffxiv-datacenters"
import { stupidTitleCase } from "../util/string_stuff"
import { GlobalFeature, SlashCommand } from "./feature"
import { FFXIVCertificateFeature } from "./ffxiv_certificate_helper"

export class FFXIVSlashCommandsFeature extends GlobalFeature {
public static slashCommands?: SlashCommand[] | undefined = [
new Discord.SlashCommandBuilder()
.setName("xiv")
.setDescription("FFXIV-related subcommands")
.addSubcommand(subcommand =>
subcommand.setName("certificates")
.setDescription("Find the best market board items to trade for certificates (FFXIV)")
.addStringOption(option =>
option.setName("datacentre")
.setDescription("Data centre")
.setRequired(true)
.setChoices(...DATA_CENTERS.map(dc => ({ name: stupidTitleCase(dc), value: dc })))
),
),
]

public async handleInteraction(interaction: Discord.Interaction<Discord.CacheType>): Promise<void> {
if (interaction.isChatInputCommand() && interaction.options.getSubcommand() === "certificates") {
const feature = this.bot.loadedFeatureForName<FFXIVCertificateFeature>("FFXIVCertificateFeature")
if (!feature) {
await interaction.reply({
content: "⚠️ FFXIVCertificates feature not loaded in this bot.",
ephemeral: true,
})
return
}
feature.handleInteraction(interaction)
return
}
}

public handleMessage(): boolean {
// Doesn't handle any chat messages directly, only slash commands
return false
}
}
2 changes: 2 additions & 0 deletions src/features/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ import { ReactionRolesFeature } from "./reaction_roles"
import { AmaroQuestFeature } from "./amaroquest"
import { FFXIVCertificateFeature } from "./ffxiv_certificate_helper"
import { MemeFeature } from "./memegen/meme"
import { FFXIVSlashCommandsFeature } from "./ffxiv_slash_commands"

export { GlobalFeature }

Expand Down Expand Up @@ -67,6 +68,7 @@ export const allFeatures: GlobalFeatureConstructor<GlobalFeature>[] = [
AmaroQuestFeature,
FFXIVCertificateFeature,
MemeFeature,
FFXIVSlashCommandsFeature,
]

export const allServerFeatures: ServerFeatureConstructor<ServerFeature>[] = [
Expand Down
31 changes: 31 additions & 0 deletions src/model/ffxiv-datacenters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* FFXIV Data Centres
*/

/*
* AetheBot - A Discord Chatbot
*
* Created by Tyrone Trevorrow on 04/01/24.
* Copyright (c) 2024 Tyrone Trevorrow. All rights reserved.
*
* This source code is licensed under the permissive MIT license.
*/

export const DATA_CENTERS = [
"aether",
"crystal",
"dynamis",
"primal",
"chaos",
"light",
"elemental",
"gaia",
"mana",
"meteor",
"materia",
] as const
export type DataCenter = typeof DATA_CENTERS[number]

export function isDataCenter(str: string): str is DataCenter {
return (DATA_CENTERS as readonly string[]).includes(str)
}
17 changes: 17 additions & 0 deletions src/util/string_stuff.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
/**
* Just some string stuff
*/

/*
* AetheBot - A Discord Chatbot
*
* Created by Tyrone Trevorrow on 04/01/24.
* Copyright (c) 2024 Tyrone Trevorrow. All rights reserved.
*
* This source code is licensed under the permissive MIT license.
*/

/** Extremely naïve title case: just upcases first char. No bounds checking. */
export function stupidTitleCase(str: string): string {
return str[0].toUpperCase() + str.slice(1)
}

0 comments on commit b90c52a

Please sign in to comment.