Upgrading from djs v12 (commando) to djs v14 (and sapphire framework) - Unsure on how to Parse Types

Hello! I have recently started upgrading an outdated open source project, and got stuck for a couple days on a specific aspect (almost done with everything else though). Context: In the configuration command, it takes two arguments, first being the key (or what setting to change), and second being the new value. Problem: The old project had a parseType function (under Utils.ts), which uses Commando's ArgumentCollector and ArgumentInfo - these do not exist in vanilla dj.s v14 or Sapphire Framework. This parseType function is extremely crucial, as it checks if the VALUE input is valid for the config (ex. councilorRole would take a role mention or role ID, and the parseType would return the Role object IF VALID somehow?). This function also takes a custom serializer that tells it the Object type and how to serialize/what function to run (ex. councilorRole key has type "role" for value, and runs the getId function). I am unsure on how to move forward with this, I have a working serializer (at least I believe) that hasn't been changed, but I am unsure on how to return the object. Depending on the key, the value could be anything from a boolean, role, channel, or JSON. The original project & files in question: command (line for parseType) https://github.com/evaera/Votum/blob/581f213f84786296f35d418f52178226e8cd2561/src/commands/votum/ConfigCommand.ts#L112 parseType: https://github.com/evaera/Votum/blob/581f213f84786296f35d418f52178226e8cd2561/src/Util.ts#L71 serializer that is fed: https://github.com/evaera/Votum/blob/581f213f84786296f35d418f52178226e8cd2561/src/commands/votum/ConfigCommand.ts#L88C4-L88C4 the actual serializer that is imported: https://github.com/evaera/Votum/blob/581f213f84786296f35d418f52178226e8cd2561/src/CouncilData.ts#L83 Will post my ConfigCommand.ts file below! - Utils.ts and CouncilData.ts (which has the serializer) are the exact same, but I need a new parseType() function that doesn't depend on Commando (so Utils.ts needs to be altered).
6 Replies
d.js toolkit
d.js toolkit8mo ago
- What's your exact discord.js npm list discord.js and node node -v version? - Not a discord.js issue? Check out #other-js-ts. - Consider reading #how-to-get-help to improve your question! - Explain what exactly your issue is. - Post the full error stack trace, not just the top part! - Show your code! - Issue solved? Press the button! - Marked as resolved by OP
Spikers
Spikers8mo ago
My Sapphire.js styled ConfigCommand:
import { Message } from "discord.js"
import { Args, PieceContext } from "@sapphire/framework"
import { ConfigurableCouncilData, ConfigurableCouncilDataSerializers, OptionalCouncilData } from "../../CouncilData"
import { getDefaultValue, getProps, parseType, response, ResponseType } from "../../Util"
import ICommand from "../ICommand"

const makeDisplay = (displayer?: (value: any) => string) => (value: any) => {
if (value === undefined || value === null) {
return "None"
} else if (displayer) {
return displayer(value)
} else {
return value.toString()
}
}

export default class ConfigCommand extends ICommand {
constructor(client: PieceContext) {
super(client, {
name: "config",
aliases: ["votumconfig", "cfg", "vconfig", "vcfg", "councilconfig"],
description: "Designates a specific role for councilors.",
adminOnly: true,
})
}

async execute(msg: Message, args: Args): Promise<Message | Message[]> {
const argsKey = args.next();
const argsValue = args.next();
if (argsKey == null || argsKey.length === 0) {
return msg.reply({
embeds: [response(
ResponseType.Neutral,
`Available configuration points are:\n${Object.keys(
getProps(ConfigurableCouncilData),
)
.map((n) => `~${n}~`)
.join(",\n ")}.`,
).embed],
})
}

const key = argsKey.replace(/\.([a-z0-9])/g, (_, l) =>
l.toUpperCase(),
) as keyof ConfigurableCouncilData

if (!(key in getProps(ConfigurableCouncilData))) {
return msg.reply({
embeds: [response(
ResponseType.Bad,
`:x: \`${key}\` is not a valid configuration point.`,
).embed],
})
}

const serializer = ConfigurableCouncilDataSerializers[key]
const display = makeDisplay(serializer.display)

if (argsValue == null || argsValue.length === 0) {
return msg.reply({
embeds: [response(
ResponseType.Neutral,
`Configuration point ${argsKey} is currently set to ~${display(
this.council.getConfig(key),
)}~.`,
).embed],
})
}

if (argsValue === "$remove" && key in getProps(OptionalCouncilData)) {
this.council.setConfig(key, getDefaultValue(key, OptionalCouncilData))
return msg.reply({
embeds: [response(
ResponseType.Neutral,
`Set configuration point ~${key}~ back to default state.`,
).embed],
})
}

// Line & function in question
const value = await parseType(this.client, msg, argsValue, serializer)
console.log(value)

if (value !== null) {
const serializedValue = serializer.serialize(value)

this.council.setConfig(key, serializedValue)

return msg.reply({
embeds: [response(
ResponseType.Good,
`Set configuration point ~${key}~ to ${display(value)}`,
).embed],
})
} else {
return msg.reply({
embeds: [response(
ResponseType.Bad,
`~${argsValue}~ is not a valid **${serializer.type}**`,
).embed],
})
}
}
}
import { Message } from "discord.js"
import { Args, PieceContext } from "@sapphire/framework"
import { ConfigurableCouncilData, ConfigurableCouncilDataSerializers, OptionalCouncilData } from "../../CouncilData"
import { getDefaultValue, getProps, parseType, response, ResponseType } from "../../Util"
import ICommand from "../ICommand"

const makeDisplay = (displayer?: (value: any) => string) => (value: any) => {
if (value === undefined || value === null) {
return "None"
} else if (displayer) {
return displayer(value)
} else {
return value.toString()
}
}

export default class ConfigCommand extends ICommand {
constructor(client: PieceContext) {
super(client, {
name: "config",
aliases: ["votumconfig", "cfg", "vconfig", "vcfg", "councilconfig"],
description: "Designates a specific role for councilors.",
adminOnly: true,
})
}

async execute(msg: Message, args: Args): Promise<Message | Message[]> {
const argsKey = args.next();
const argsValue = args.next();
if (argsKey == null || argsKey.length === 0) {
return msg.reply({
embeds: [response(
ResponseType.Neutral,
`Available configuration points are:\n${Object.keys(
getProps(ConfigurableCouncilData),
)
.map((n) => `~${n}~`)
.join(",\n ")}.`,
).embed],
})
}

const key = argsKey.replace(/\.([a-z0-9])/g, (_, l) =>
l.toUpperCase(),
) as keyof ConfigurableCouncilData

if (!(key in getProps(ConfigurableCouncilData))) {
return msg.reply({
embeds: [response(
ResponseType.Bad,
`:x: \`${key}\` is not a valid configuration point.`,
).embed],
})
}

const serializer = ConfigurableCouncilDataSerializers[key]
const display = makeDisplay(serializer.display)

if (argsValue == null || argsValue.length === 0) {
return msg.reply({
embeds: [response(
ResponseType.Neutral,
`Configuration point ${argsKey} is currently set to ~${display(
this.council.getConfig(key),
)}~.`,
).embed],
})
}

if (argsValue === "$remove" && key in getProps(OptionalCouncilData)) {
this.council.setConfig(key, getDefaultValue(key, OptionalCouncilData))
return msg.reply({
embeds: [response(
ResponseType.Neutral,
`Set configuration point ~${key}~ back to default state.`,
).embed],
})
}

// Line & function in question
const value = await parseType(this.client, msg, argsValue, serializer)
console.log(value)

if (value !== null) {
const serializedValue = serializer.serialize(value)

this.council.setConfig(key, serializedValue)

return msg.reply({
embeds: [response(
ResponseType.Good,
`Set configuration point ~${key}~ to ${display(value)}`,
).embed],
})
} else {
return msg.reply({
embeds: [response(
ResponseType.Bad,
`~${argsValue}~ is not a valid **${serializer.type}**`,
).embed],
})
}
}
}
souji
souji8mo ago
if you need specifically help about converting to sapphire, you are probably better asking off in their support server, it is linked in #useful-servers
Spikers
Spikers8mo ago
I joined their server as well, will dive into that if necessary, but I was just wondering if there was a vanilla djs v14 way of doing such (as it was just a helper function)!
souji
souji8mo ago
discord.js does still not come with a fully fledged framework and command parser/handler there is https://www.npmjs.com/package/create-discord-bot which can help you get v14 set up and the guide https://discordjs.guide/ has sections about a somewhat basic command handler (with slash commands) explaining what's what as discord.js we recommend switching over to slash commands versus content parsing/serializing and as such haven't been and aren't adding any utility to achieve things with prefixed commands so if you do want to stick with command parsing from message content, sapphire is very likely the space to get your answers in the knowledge and support around parsing has decreased here since switching to slash commands, which allow you to do things message commands do not (modals, ephemeral messages only the command author can see) to name two
Spikers
Spikers8mo ago
I appreciate it, I'll go there now👍 Will leave this up in case someone from here is able to help, but yes I understand the push for slash commands.