error/missing data handling + extracted embeds + better interaction replies
This commit is contained in:
parent
2b341e5107
commit
c1bfce676f
@ -14,6 +14,7 @@
|
||||
"@discordx/importer": "^1.3.0",
|
||||
"@discordx/pagination": "^3.5.1",
|
||||
"better-sqlite3": "^11.0.0",
|
||||
"chalk": "^4.1.2",
|
||||
"discord.js": "^14.15.3",
|
||||
"discordx": "^11.9.2",
|
||||
"dotenv": "^16.4.5",
|
||||
|
@ -17,6 +17,9 @@ importers:
|
||||
better-sqlite3:
|
||||
specifier: ^11.0.0
|
||||
version: 11.0.0
|
||||
chalk:
|
||||
specifier: ^4.1.2
|
||||
version: 4.1.2
|
||||
discord.js:
|
||||
specifier: ^14.15.3
|
||||
version: 14.15.3
|
||||
@ -45,9 +48,6 @@ importers:
|
||||
prettier:
|
||||
specifier: ^3.3.0
|
||||
version: 3.3.1
|
||||
ts-node:
|
||||
specifier: ^10.9.2
|
||||
version: 10.9.2(@types/node@20.14.2)(typescript@5.4.5)
|
||||
tsup:
|
||||
specifier: ^8.1.0
|
||||
version: 8.1.0(ts-node@10.9.2(@types/node@20.14.2)(typescript@5.4.5))(typescript@5.4.5)
|
||||
@ -628,6 +628,10 @@ packages:
|
||||
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
chalk@4.1.2:
|
||||
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
chokidar@3.6.0:
|
||||
resolution: {integrity: sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==}
|
||||
engines: {node: '>= 8.10.0'}
|
||||
@ -796,6 +800,10 @@ packages:
|
||||
resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
has-flag@4.0.0:
|
||||
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
human-signals@2.1.0:
|
||||
resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==}
|
||||
engines: {node: '>=10.17.0'}
|
||||
@ -1120,6 +1128,10 @@ packages:
|
||||
resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
supports-color@7.2.0:
|
||||
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
|
||||
engines: {node: '>=8'}
|
||||
|
||||
tar-fs@2.1.1:
|
||||
resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==}
|
||||
|
||||
@ -1281,6 +1293,7 @@ snapshots:
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
optional: true
|
||||
|
||||
'@discordjs/builders@1.8.2':
|
||||
dependencies:
|
||||
@ -1517,6 +1530,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.4.15
|
||||
optional: true
|
||||
|
||||
'@nodelib/fs.scandir@2.1.5':
|
||||
dependencies:
|
||||
@ -1592,13 +1606,17 @@ snapshots:
|
||||
|
||||
'@sapphire/snowflake@3.5.3': {}
|
||||
|
||||
'@tsconfig/node10@1.0.11': {}
|
||||
'@tsconfig/node10@1.0.11':
|
||||
optional: true
|
||||
|
||||
'@tsconfig/node12@1.0.11': {}
|
||||
'@tsconfig/node12@1.0.11':
|
||||
optional: true
|
||||
|
||||
'@tsconfig/node14@1.0.3': {}
|
||||
'@tsconfig/node14@1.0.3':
|
||||
optional: true
|
||||
|
||||
'@tsconfig/node16@1.0.4': {}
|
||||
'@tsconfig/node16@1.0.4':
|
||||
optional: true
|
||||
|
||||
'@types/estree@1.0.5': {}
|
||||
|
||||
@ -1612,9 +1630,11 @@ snapshots:
|
||||
|
||||
'@vladfrangu/async_event_emitter@2.2.4': {}
|
||||
|
||||
acorn-walk@8.3.2: {}
|
||||
acorn-walk@8.3.2:
|
||||
optional: true
|
||||
|
||||
acorn@8.11.3: {}
|
||||
acorn@8.11.3:
|
||||
optional: true
|
||||
|
||||
ansi-regex@5.0.1: {}
|
||||
|
||||
@ -1633,7 +1653,8 @@ snapshots:
|
||||
normalize-path: 3.0.0
|
||||
picomatch: 2.3.1
|
||||
|
||||
arg@4.1.3: {}
|
||||
arg@4.1.3:
|
||||
optional: true
|
||||
|
||||
array-union@2.1.0: {}
|
||||
|
||||
@ -1683,6 +1704,11 @@ snapshots:
|
||||
|
||||
cac@6.7.14: {}
|
||||
|
||||
chalk@4.1.2:
|
||||
dependencies:
|
||||
ansi-styles: 4.3.0
|
||||
supports-color: 7.2.0
|
||||
|
||||
chokidar@3.6.0:
|
||||
dependencies:
|
||||
anymatch: 3.1.3
|
||||
@ -1707,7 +1733,8 @@ snapshots:
|
||||
|
||||
concat-map@0.0.1: {}
|
||||
|
||||
create-require@1.1.1: {}
|
||||
create-require@1.1.1:
|
||||
optional: true
|
||||
|
||||
cross-spawn@7.0.3:
|
||||
dependencies:
|
||||
@ -1729,7 +1756,8 @@ snapshots:
|
||||
|
||||
detect-libc@2.0.3: {}
|
||||
|
||||
diff@4.0.2: {}
|
||||
diff@4.0.2:
|
||||
optional: true
|
||||
|
||||
dir-glob@3.0.1:
|
||||
dependencies:
|
||||
@ -1908,6 +1936,8 @@ snapshots:
|
||||
|
||||
has-flag@3.0.0: {}
|
||||
|
||||
has-flag@4.0.0: {}
|
||||
|
||||
human-signals@2.1.0: {}
|
||||
|
||||
ieee754@1.2.1: {}
|
||||
@ -1962,7 +1992,8 @@ snapshots:
|
||||
|
||||
magic-bytes.js@1.10.0: {}
|
||||
|
||||
make-error@1.3.6: {}
|
||||
make-error@1.3.6:
|
||||
optional: true
|
||||
|
||||
merge-stream@2.0.0: {}
|
||||
|
||||
@ -2209,6 +2240,10 @@ snapshots:
|
||||
dependencies:
|
||||
has-flag: 3.0.0
|
||||
|
||||
supports-color@7.2.0:
|
||||
dependencies:
|
||||
has-flag: 4.0.0
|
||||
|
||||
tar-fs@2.1.1:
|
||||
dependencies:
|
||||
chownr: 1.1.4
|
||||
@ -2265,6 +2300,7 @@ snapshots:
|
||||
typescript: 5.4.5
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
optional: true
|
||||
|
||||
tslib@1.14.1: {}
|
||||
|
||||
@ -2321,7 +2357,8 @@ snapshots:
|
||||
|
||||
util-deprecate@1.0.2: {}
|
||||
|
||||
v8-compile-cache-lib@3.0.1: {}
|
||||
v8-compile-cache-lib@3.0.1:
|
||||
optional: true
|
||||
|
||||
webidl-conversions@4.0.2: {}
|
||||
|
||||
@ -2353,4 +2390,5 @@ snapshots:
|
||||
|
||||
yaml@2.4.3: {}
|
||||
|
||||
yn@3.1.1: {}
|
||||
yn@3.1.1:
|
||||
optional: true
|
||||
|
@ -1,22 +1,29 @@
|
||||
import { ButtonComponent, Discord, Slash, SlashOption } from 'discordx'
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
type AllowedThreadTypeForTextChannel,
|
||||
ApplicationCommandOptionType,
|
||||
ButtonBuilder,
|
||||
ButtonInteraction,
|
||||
ButtonStyle,
|
||||
channelMention,
|
||||
CommandInteraction,
|
||||
EmbedBuilder,
|
||||
GuildMember,
|
||||
MessageActionRowComponentBuilder,
|
||||
Role,
|
||||
ChannelType,
|
||||
CommandInteraction,
|
||||
GuildMember,
|
||||
GuildTextThreadCreateOptions,
|
||||
MessageCreateOptions,
|
||||
Role,
|
||||
roleMention,
|
||||
userMention,
|
||||
} from 'discord.js'
|
||||
|
||||
import { db, DBTableEnum } from '../../db'
|
||||
import { Status } from '../../utils/enums'
|
||||
import {
|
||||
Workload,
|
||||
ticketCreateButton,
|
||||
ticketCreatedEmbed,
|
||||
ticketCreateEmbed,
|
||||
ticketEntityButton,
|
||||
ticketEntityEmbed,
|
||||
ticketWorkloadEmbed,
|
||||
} from '../../utils'
|
||||
import { logger } from '../../lib'
|
||||
|
||||
@Discord()
|
||||
export class CreateTicketSystem {
|
||||
@ -35,139 +42,151 @@ export class CreateTicketSystem {
|
||||
role: Role,
|
||||
interaction: CommandInteraction,
|
||||
) {
|
||||
await interaction.deferReply({ ephemeral: true })
|
||||
|
||||
// exit on undefined channel
|
||||
if (!interaction.channel) {
|
||||
await interaction.editReply(
|
||||
'❌ Failed to access interaction channel',
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
const pricesChannelId = await db.get(DBTableEnum.PRICE_CHANNEL)
|
||||
const bannerURL = await db.get(DBTableEnum.BANNER_URL)
|
||||
const workload = await db.get<Status>(DBTableEnum.WORKLOAD)
|
||||
await db.set(DBTableEnum.TICKET_ROLE, role.id)
|
||||
const workload = await db.get<Workload>(DBTableEnum.WORKLOAD)
|
||||
|
||||
// create ticket embed
|
||||
const createBtn = new ButtonBuilder()
|
||||
.setLabel('Open a ticket')
|
||||
.setEmoji('👉')
|
||||
.setStyle(ButtonStyle.Success)
|
||||
.setCustomId('create-btn')
|
||||
|
||||
const row =
|
||||
new ActionRowBuilder<MessageActionRowComponentBuilder>().addComponents(
|
||||
createBtn,
|
||||
if (!pricesChannelId) {
|
||||
logger.error(
|
||||
'Missing prices channel',
|
||||
'Set using /set-prices-channel',
|
||||
)
|
||||
|
||||
let embedCreate = new EmbedBuilder()
|
||||
.setTitle(`Create a ticket`)
|
||||
.setDescription(
|
||||
`Hey, want to order a design?
|
||||
Then open a ticket and we'll answer it in a jiffy!
|
||||
Dear customer, before opening a ticket\n
|
||||
I strongly recommend that you familiarize yourself with the prices of basic interfaces - ` +
|
||||
channelMention(pricesChannelId),
|
||||
await interaction.editReply(
|
||||
'❌ Missing prices channel\nSet using /set-prices-channel',
|
||||
)
|
||||
.setImage(bannerURL)
|
||||
return
|
||||
}
|
||||
if (!bannerURL) {
|
||||
logger.error('Missing banner', 'Set using /set-banner-url')
|
||||
await interaction.editReply(
|
||||
'❌ Missing banner\nSet using /set-banner-url',
|
||||
)
|
||||
return
|
||||
}
|
||||
if (!workload) {
|
||||
logger.error(
|
||||
'Missing workload status',
|
||||
'Set using /set-workload-status',
|
||||
)
|
||||
await interaction.editReply(
|
||||
'❌ Missing workload status\nSet using /set-workload-status',
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
await db
|
||||
.set(DBTableEnum.TICKET_ROLE, role.id)
|
||||
.then(() => logger.database(DBTableEnum.TICKET_ROLE, role.id))
|
||||
|
||||
// create ticket embed + button
|
||||
await interaction.channel?.send({
|
||||
components: [row],
|
||||
embeds: [embedCreate],
|
||||
components: [ticketCreateButton()],
|
||||
embeds: [ticketCreateEmbed({ bannerURL, pricesChannelId })],
|
||||
})
|
||||
|
||||
// workload embed
|
||||
let embedStatus = new EmbedBuilder()
|
||||
.setTitle(`Workload status by orders`)
|
||||
.setDescription('**Status:**\n' + getStatusMessage(workload))
|
||||
|
||||
const workloadMessage = await interaction.channel?.send({
|
||||
embeds: [embedStatus],
|
||||
const workloadMessage = await interaction.channel.send({
|
||||
embeds: [ticketWorkloadEmbed({ workload })],
|
||||
})
|
||||
await db.set(DBTableEnum.WORKLOAD_MESSAGE, workloadMessage.id)
|
||||
|
||||
await db.set(DBTableEnum.WORKLOAD_MESSAGE, workloadMessage?.id)
|
||||
// close interaction
|
||||
await interaction.editReply('✔️ Created ticket system')
|
||||
}
|
||||
|
||||
// listen to create button click
|
||||
@ButtonComponent({ id: 'create-btn' })
|
||||
async createBtn(interaction: ButtonInteraction): Promise<void> {
|
||||
await interaction.deferReply({ ephemeral: true })
|
||||
|
||||
// exit on undefined
|
||||
if (
|
||||
!(interaction.member instanceof GuildMember) ||
|
||||
!interaction.channel ||
|
||||
interaction.channel.type !== ChannelType.GuildText
|
||||
) {
|
||||
await interaction.editReply('❌ Failed to access interaction data')
|
||||
return
|
||||
}
|
||||
if (!interaction.guild) {
|
||||
await interaction.editReply('❌ Failed to access interaction guild')
|
||||
return
|
||||
}
|
||||
|
||||
const ticketRole = await db.get(DBTableEnum.TICKET_ROLE)
|
||||
|
||||
// create ticket channel
|
||||
const threadChannelSettings: GuildTextThreadCreateOptions<AllowedThreadTypeForTextChannel> =
|
||||
{
|
||||
name: `${interaction.user.username}`,
|
||||
type: ChannelType.PrivateThread,
|
||||
invitable: false,
|
||||
}
|
||||
const threadChannel = await interaction.channel.threads.create(
|
||||
threadChannelSettings,
|
||||
)
|
||||
|
||||
// welcoming message in ticket
|
||||
const threadChannelMessage: MessageCreateOptions = {
|
||||
content: `${userMention(interaction.user.id)} ${roleMention(ticketRole)}`,
|
||||
embeds: [
|
||||
ticketEntityEmbed({
|
||||
username: interaction.user.username,
|
||||
}),
|
||||
],
|
||||
components: [ticketEntityButton()],
|
||||
}
|
||||
await threadChannel.send(threadChannelMessage)
|
||||
|
||||
// add role members and ticket owner
|
||||
const user = await interaction.guild.members.fetch(interaction.user.id)
|
||||
const role = await interaction.guild.roles.fetch(ticketRole)
|
||||
const owner = await interaction.guild.fetchOwner()
|
||||
|
||||
if (!role) {
|
||||
logger.error(
|
||||
'Missing ticket role',
|
||||
'Recreate using /create-ticket-system',
|
||||
)
|
||||
} else {
|
||||
role.members.map(
|
||||
async member => await threadChannel.members.add(member),
|
||||
)
|
||||
}
|
||||
|
||||
await threadChannel.members.add(user)
|
||||
await threadChannel.members.add(owner)
|
||||
|
||||
// close interaction
|
||||
await interaction.reply({
|
||||
content: 'Created ticket system',
|
||||
embeds: [
|
||||
ticketCreatedEmbed({
|
||||
username: interaction.user.username,
|
||||
threadChannelId: threadChannel.id,
|
||||
}),
|
||||
],
|
||||
ephemeral: true,
|
||||
})
|
||||
}
|
||||
|
||||
// listen to button
|
||||
@ButtonComponent({ id: 'create-btn' })
|
||||
async createBtn(interaction: ButtonInteraction): Promise<void> {
|
||||
if (
|
||||
!(interaction.member instanceof GuildMember) ||
|
||||
interaction.channel?.type !== ChannelType.GuildText
|
||||
) {
|
||||
await interaction.reply('failed')
|
||||
return
|
||||
}
|
||||
|
||||
const threadChannel = await interaction.channel.threads.create({
|
||||
name: `${interaction.user.username}`,
|
||||
type: ChannelType.PrivateThread,
|
||||
invitable: false,
|
||||
})
|
||||
|
||||
let createdEmbed = new EmbedBuilder()
|
||||
.setTitle(`${interaction.user.username} opened a ticket`)
|
||||
.setDescription(
|
||||
`You successfully opened a ticket. Your ticket: ${channelMention(threadChannel.id)}`,
|
||||
)
|
||||
|
||||
await interaction.reply({ embeds: [createdEmbed], ephemeral: true })
|
||||
|
||||
let threadEmbed = new EmbedBuilder()
|
||||
.setTitle(`${interaction.user.username} opened a ticket`)
|
||||
.setDescription(
|
||||
`You successfully opened a ticket. We will be with you soon`,
|
||||
)
|
||||
.setFooter({
|
||||
text: 'You can close ticket by clicking the button below',
|
||||
})
|
||||
|
||||
const ticketRole = await db.get(DBTableEnum.TICKET_ROLE)
|
||||
|
||||
const closeBtn = new ButtonBuilder()
|
||||
.setLabel('Close ticket')
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
.setCustomId('close-btn')
|
||||
|
||||
const row =
|
||||
new ActionRowBuilder<MessageActionRowComponentBuilder>().addComponents(
|
||||
closeBtn,
|
||||
)
|
||||
|
||||
await threadChannel.send({
|
||||
content: `${userMention(interaction.user.id)} ${roleMention(ticketRole)}`,
|
||||
embeds: [threadEmbed],
|
||||
components: [row],
|
||||
})
|
||||
|
||||
const user = await interaction.guild?.members.fetch(interaction.user.id)
|
||||
const role = await interaction.guild?.roles.fetch(ticketRole)
|
||||
|
||||
role?.members.map(
|
||||
async members => await threadChannel.members.add(members),
|
||||
)
|
||||
|
||||
if (!user) return
|
||||
|
||||
await threadChannel.members.add(user)
|
||||
}
|
||||
|
||||
@ButtonComponent({ id: 'close-btn' })
|
||||
async closeBtn(interaction: ButtonInteraction): Promise<void> {
|
||||
console.log('close button')
|
||||
await interaction.channel?.delete()
|
||||
|
||||
await interaction.reply('Deleted')
|
||||
}
|
||||
}
|
||||
|
||||
function getStatusMessage(status: Status | null) {
|
||||
if (!status) return 'No information provided'
|
||||
switch (status) {
|
||||
case Status.AVAILABLE:
|
||||
return ':green_circle: - Available for orders'
|
||||
case Status.BUSY:
|
||||
return ':yellow_circle: - Available for orders, but there may be delays'
|
||||
case Status.NOT_AVAILABLE:
|
||||
return ':red_circle: - Currently unavailable for orders'
|
||||
default:
|
||||
return 'No information provided'
|
||||
await interaction.deferReply({ ephemeral: true })
|
||||
if (!interaction.channel) {
|
||||
await interaction.editReply('❌ Ticket channel does not exist')
|
||||
return
|
||||
}
|
||||
await interaction.channel.delete()
|
||||
await interaction.editReply('Deleted ticket')
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,8 @@
|
||||
import { Discord, Slash, SlashOption } from 'discordx'
|
||||
import {
|
||||
ApplicationCommandOptionType,
|
||||
CommandInteraction,
|
||||
TextChannel,
|
||||
} from 'discord.js'
|
||||
import { ApplicationCommandOptionType, CommandInteraction } from 'discord.js'
|
||||
|
||||
import { db, DBTableEnum } from '../../db'
|
||||
import { logger } from '../../lib'
|
||||
|
||||
@Discord()
|
||||
export class SetFeedbackChannel {
|
||||
@ -23,7 +21,16 @@ export class SetFeedbackChannel {
|
||||
url: string,
|
||||
interaction: CommandInteraction,
|
||||
) {
|
||||
await db.set(DBTableEnum.BANNER_URL, url)
|
||||
await interaction.reply({ ephemeral: true, content: 'Success.' })
|
||||
await interaction.deferReply({ ephemeral: true })
|
||||
await db.set(DBTableEnum.BANNER_URL, url).catch(async () => {
|
||||
await interaction.editReply({
|
||||
content: `❌ Failed to set banner`,
|
||||
})
|
||||
return
|
||||
})
|
||||
logger.database(DBTableEnum.BANNER_URL, url)
|
||||
await interaction.editReply({
|
||||
content: `✔️ Set banner URL to ${url}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ import {
|
||||
CommandInteraction,
|
||||
TextChannel,
|
||||
} from 'discord.js'
|
||||
|
||||
import { db, DBTableEnum } from '../../db'
|
||||
import { logger } from '../../lib'
|
||||
|
||||
@Discord()
|
||||
export class SetFeedbackChannel {
|
||||
@ -16,14 +18,25 @@ export class SetFeedbackChannel {
|
||||
async setFeedbackChannel(
|
||||
@SlashOption({
|
||||
name: 'channel',
|
||||
description: 'channel description',
|
||||
description: 'Channel description',
|
||||
type: ApplicationCommandOptionType.Channel,
|
||||
required: true,
|
||||
})
|
||||
channel: TextChannel,
|
||||
interaction: CommandInteraction,
|
||||
) {
|
||||
await db.set(DBTableEnum.FEEDBACK_CHANNEL, channel.id)
|
||||
await interaction.reply({ ephemeral: true, content: 'Success.' })
|
||||
await interaction.deferReply({ ephemeral: true })
|
||||
await db
|
||||
.set(DBTableEnum.FEEDBACK_CHANNEL, channel.id)
|
||||
.catch(async () => {
|
||||
await interaction.editReply({
|
||||
content: `❌ Failed to set feedback channel`,
|
||||
})
|
||||
return
|
||||
})
|
||||
logger.database(DBTableEnum.FEEDBACK_CHANNEL, channel.id)
|
||||
await interaction.editReply({
|
||||
content: `✔️ Set feedback channel to ${channel.id}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -4,26 +4,39 @@ import {
|
||||
CommandInteraction,
|
||||
TextChannel,
|
||||
} from 'discord.js'
|
||||
|
||||
import { db, DBTableEnum } from '../../db'
|
||||
import { logger } from '../../lib'
|
||||
|
||||
@Discord()
|
||||
export class SetOrderChannel {
|
||||
@Slash({
|
||||
description: 'Set a make an order channel',
|
||||
description: 'Set make an order channel',
|
||||
name: 'set-order-channel',
|
||||
defaultMemberPermissions: 'Administrator',
|
||||
})
|
||||
async setOrderChannel(
|
||||
@SlashOption({
|
||||
name: 'channel',
|
||||
description: 'channel description',
|
||||
description: 'Make an order channel',
|
||||
type: ApplicationCommandOptionType.Channel,
|
||||
required: true,
|
||||
})
|
||||
channel: TextChannel,
|
||||
interaction: CommandInteraction,
|
||||
) {
|
||||
await db.set(DBTableEnum.MAKE_AN_ORDER_CHANNEL, channel.id)
|
||||
await interaction.reply({ ephemeral: true, content: 'Success.' })
|
||||
await interaction.deferReply({ ephemeral: true })
|
||||
await db
|
||||
.set(DBTableEnum.MAKE_AN_ORDER_CHANNEL, channel.id)
|
||||
.catch(async () => {
|
||||
await interaction.editReply({
|
||||
content: `❌ Failed to set make an order channel`,
|
||||
})
|
||||
return
|
||||
})
|
||||
logger.database(DBTableEnum.MAKE_AN_ORDER_CHANNEL, channel.id)
|
||||
await interaction.editReply({
|
||||
content: `✔️ Set make an order channel to ${channel.id}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ import {
|
||||
CommandInteraction,
|
||||
TextChannel,
|
||||
} from 'discord.js'
|
||||
|
||||
import { db, DBTableEnum } from '../../db'
|
||||
import { logger } from '../../lib'
|
||||
|
||||
@Discord()
|
||||
export class SetPortfolioChannel {
|
||||
@ -16,14 +18,25 @@ export class SetPortfolioChannel {
|
||||
async setPortfolioChannel(
|
||||
@SlashOption({
|
||||
name: 'channel',
|
||||
description: 'channel description',
|
||||
description: 'Portfolio channel',
|
||||
type: ApplicationCommandOptionType.Channel,
|
||||
required: true,
|
||||
})
|
||||
channel: TextChannel,
|
||||
interaction: CommandInteraction,
|
||||
) {
|
||||
await db.set(DBTableEnum.PORTFOLIO_CHANNEL, channel.id)
|
||||
await interaction.reply({ ephemeral: true, content: 'Success.' })
|
||||
await interaction.deferReply({ ephemeral: true })
|
||||
await db
|
||||
.set(DBTableEnum.PORTFOLIO_CHANNEL, channel.id)
|
||||
.catch(async () => {
|
||||
await interaction.editReply({
|
||||
content: `❌ Failed to set portfolio channel`,
|
||||
})
|
||||
return
|
||||
})
|
||||
logger.database(DBTableEnum.PORTFOLIO_CHANNEL, channel.id)
|
||||
await interaction.editReply({
|
||||
content: `✔️ Set portfolio channel to ${channel.id}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -4,7 +4,9 @@ import {
|
||||
CommandInteraction,
|
||||
TextChannel,
|
||||
} from 'discord.js'
|
||||
|
||||
import { db, DBTableEnum } from '../../db'
|
||||
import { logger } from '../../lib'
|
||||
|
||||
@Discord()
|
||||
export class SetPriceChannel {
|
||||
@ -16,14 +18,23 @@ export class SetPriceChannel {
|
||||
async setPriceChannel(
|
||||
@SlashOption({
|
||||
name: 'channel',
|
||||
description: 'channel description',
|
||||
description: 'Price channel',
|
||||
type: ApplicationCommandOptionType.Channel,
|
||||
required: true,
|
||||
})
|
||||
channel: TextChannel,
|
||||
interaction: CommandInteraction,
|
||||
) {
|
||||
await db.set(DBTableEnum.PRICE_CHANNEL, channel.id)
|
||||
await interaction.reply({ ephemeral: true, content: 'Success.' })
|
||||
await interaction.deferReply({ ephemeral: true })
|
||||
await db.set(DBTableEnum.PRICE_CHANNEL, channel.id).catch(async () => {
|
||||
await interaction.editReply({
|
||||
content: `❌ Failed to set price channel`,
|
||||
})
|
||||
return
|
||||
})
|
||||
logger.database(DBTableEnum.PRICE_CHANNEL, channel.id)
|
||||
await interaction.editReply({
|
||||
content: `✔️ Set price channel to ${channel.id}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { Discord, Slash, SlashChoice, SlashOption } from 'discordx'
|
||||
import { ApplicationCommandOptionType, CommandInteraction } from 'discord.js'
|
||||
|
||||
import { db, DBTableEnum } from '../../db'
|
||||
import { Status } from '../../utils/enums.ts'
|
||||
import { Workload } from '../../utils'
|
||||
import { logger } from '../../lib'
|
||||
|
||||
@Discord()
|
||||
export class SetStatus {
|
||||
@ -11,7 +13,7 @@ export class SetStatus {
|
||||
defaultMemberPermissions: 'Administrator',
|
||||
})
|
||||
async setPriceChannel(
|
||||
@SlashChoice(Status.AVAILABLE, Status.BUSY, Status.NOT_AVAILABLE)
|
||||
@SlashChoice(Workload.AVAILABLE, Workload.BUSY, Workload.NOT_AVAILABLE)
|
||||
@SlashOption({
|
||||
name: 'status',
|
||||
description: 'Current workload status',
|
||||
@ -21,10 +23,16 @@ export class SetStatus {
|
||||
status: string,
|
||||
interaction: CommandInteraction,
|
||||
) {
|
||||
await db.set(DBTableEnum.WORKLOAD, status).then(x => console.log(x))
|
||||
await interaction.reply({
|
||||
ephemeral: true,
|
||||
content: `Status set to: ${status}`,
|
||||
await interaction.deferReply({ ephemeral: true })
|
||||
await db.set(DBTableEnum.WORKLOAD, status).catch(async () => {
|
||||
await interaction.editReply({
|
||||
content: `❌ Failed to workload status`,
|
||||
})
|
||||
return
|
||||
})
|
||||
logger.database(DBTableEnum.WORKLOAD, status)
|
||||
await interaction.editReply({
|
||||
content: `✔️ Set workload status to ${status}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import {
|
||||
TextChannel,
|
||||
} from 'discord.js'
|
||||
import { db, DBTableEnum } from '../../db'
|
||||
import { logger } from '../../lib'
|
||||
|
||||
@Discord()
|
||||
export class SetWelcomeChannel {
|
||||
@ -17,22 +18,41 @@ export class SetWelcomeChannel {
|
||||
async setWelcomeChannel(
|
||||
@SlashOption({
|
||||
name: 'channel',
|
||||
description: 'channel description',
|
||||
description: 'Welcome channel',
|
||||
type: ApplicationCommandOptionType.Channel,
|
||||
required: true,
|
||||
})
|
||||
channel: TextChannel,
|
||||
@SlashOption({
|
||||
name: 'role',
|
||||
description: 'Role which will be given to user',
|
||||
description: 'Role which will be given to user on join',
|
||||
type: ApplicationCommandOptionType.Role,
|
||||
required: true,
|
||||
})
|
||||
role: Role,
|
||||
interaction: CommandInteraction,
|
||||
) {
|
||||
await db.set(DBTableEnum.WELCOME_CHANNEL, channel.id)
|
||||
await db.set(DBTableEnum.WELCOME_ROLE, role.id)
|
||||
await interaction.reply({ ephemeral: true, content: 'Success.' })
|
||||
await interaction.deferReply({ ephemeral: true })
|
||||
await db
|
||||
.set(DBTableEnum.WELCOME_CHANNEL, channel.id)
|
||||
.catch(async () => {
|
||||
await interaction.editReply({
|
||||
content: `❌ Failed to welcome channel`,
|
||||
})
|
||||
return
|
||||
})
|
||||
logger.database(DBTableEnum.WELCOME_CHANNEL, channel.id)
|
||||
|
||||
await db.set(DBTableEnum.WELCOME_ROLE, role.id).catch(async () => {
|
||||
await interaction.editReply({
|
||||
content: `❌ Failed to entry role`,
|
||||
})
|
||||
return
|
||||
})
|
||||
logger.database(DBTableEnum.WELCOME_ROLE, role.id)
|
||||
|
||||
await interaction.editReply({
|
||||
content: `✔️ Set welcome channel to ${channel.id}\n✔️ Set entry role to ${role.id}`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,9 @@
|
||||
import { Discord, Slash, SlashChoice, SlashOption } from 'discordx'
|
||||
import {
|
||||
ApplicationCommandOptionType,
|
||||
CommandInteraction,
|
||||
EmbedBuilder,
|
||||
} from 'discord.js'
|
||||
import { ApplicationCommandOptionType, CommandInteraction } from 'discord.js'
|
||||
|
||||
import { db, DBTableEnum } from '../db'
|
||||
import { logger } from '../lib'
|
||||
import { feedbackEmbed } from '../utils'
|
||||
|
||||
@Discord()
|
||||
export class Feedback {
|
||||
@ -16,37 +15,68 @@ export class Feedback {
|
||||
@SlashChoice('⭐', '⭐⭐', '⭐⭐⭐', '⭐⭐⭐⭐', '⭐⭐⭐⭐⭐')
|
||||
@SlashOption({
|
||||
name: 'rating',
|
||||
description: 'Leave you review',
|
||||
description: 'Your review',
|
||||
required: true,
|
||||
type: ApplicationCommandOptionType.String,
|
||||
})
|
||||
rating: string,
|
||||
@SlashOption({
|
||||
name: 'description',
|
||||
description: 'Leave a description about job',
|
||||
description: 'Leave a review about the job',
|
||||
required: true,
|
||||
type: ApplicationCommandOptionType.String,
|
||||
})
|
||||
description: string,
|
||||
interaction: CommandInteraction,
|
||||
) {
|
||||
await interaction.deferReply({ ephemeral: true })
|
||||
|
||||
if (!interaction.guild) return
|
||||
|
||||
const reviewChannelID = await db.get(DBTableEnum.FEEDBACK_CHANNEL)
|
||||
|
||||
const embed = new EmbedBuilder()
|
||||
.setTitle(`⭐ Review by ${interaction.user.username}`)
|
||||
.setDescription(`Author: <@${interaction.user.id}>`)
|
||||
.setThumbnail(interaction.user.avatarURL())
|
||||
.setFields([
|
||||
{ name: 'Evaluation of support work:', value: rating },
|
||||
{ name: 'Commentary:', value: '```' + description + '```' },
|
||||
])
|
||||
|
||||
const channel = await interaction.guild?.channels.fetch(reviewChannelID)
|
||||
|
||||
if (channel?.isTextBased()) {
|
||||
channel.send({ embeds: [embed] })
|
||||
if (!reviewChannelID) {
|
||||
logger.error(
|
||||
'Missing feedback channel in database',
|
||||
'Recreate using /set-feedback-channel',
|
||||
)
|
||||
await interaction.editReply(
|
||||
'❌ Feedback channel is not set. Please try again later',
|
||||
)
|
||||
}
|
||||
|
||||
await interaction.reply({ ephemeral: true, content: 'Review sent!' })
|
||||
const reviewChannel =
|
||||
await interaction.guild.channels.fetch(reviewChannelID)
|
||||
if (!reviewChannel) {
|
||||
logger.error(
|
||||
'Missing feedback discord channel',
|
||||
`Feedback channel id exists in database (${reviewChannelID}) but is not found in channels list`,
|
||||
)
|
||||
await interaction.editReply(
|
||||
'❌ Feedback channel is not set. Please try again later',
|
||||
)
|
||||
}
|
||||
|
||||
if (!reviewChannel || !reviewChannel.isTextBased()) {
|
||||
logger.error(
|
||||
'Missing feedback discord channel',
|
||||
`Feedback channel id exists in database (${reviewChannelID}) but is not a text channel or is not found in channels list`,
|
||||
)
|
||||
await interaction.editReply(
|
||||
'❌ Feedback channel is not set. Please try again later',
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
await reviewChannel.send({
|
||||
embeds: [
|
||||
feedbackEmbed({
|
||||
user: interaction.user,
|
||||
description,
|
||||
rating,
|
||||
}),
|
||||
],
|
||||
})
|
||||
|
||||
await interaction.editReply('✔️ Review sent successfully!')
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
import { Discord, Slash } from 'discordx'
|
||||
import { CommandInteraction, EmbedBuilder } from 'discord.js'
|
||||
import { CommandInteraction } from 'discord.js'
|
||||
|
||||
import { paymentsEmbed } from '../utils'
|
||||
|
||||
@Discord()
|
||||
export class Payments {
|
||||
@ -8,20 +10,7 @@ export class Payments {
|
||||
description: 'See available payments',
|
||||
})
|
||||
async payments(interaction: CommandInteraction) {
|
||||
const embed = new EmbedBuilder().setTitle(`Payments`).setFields([
|
||||
{
|
||||
name: 'Crypto:',
|
||||
value:
|
||||
'Wallet number: `TRdGKNPABvoTrdwHgfUTX65DbqbguTh6cc`\n' +
|
||||
'Crypto name: `USDT`\n' +
|
||||
'Network name: `TRC20`',
|
||||
},
|
||||
{
|
||||
name: 'PayPal:',
|
||||
value: '`khamidalakkhali@gmail.com` | Types: Friends and Family',
|
||||
},
|
||||
])
|
||||
|
||||
await interaction.reply({ embeds: [embed] })
|
||||
await interaction.deferReply()
|
||||
await interaction.editReply({ embeds: [paymentsEmbed()] })
|
||||
}
|
||||
}
|
||||
|
@ -5,13 +5,13 @@ import { CommandInteraction } from 'discord.js'
|
||||
export class Ping {
|
||||
@Slash({
|
||||
name: 'ping',
|
||||
description: 'ping the bot',
|
||||
description: 'Show ping (ms)',
|
||||
defaultMemberPermissions: ['Administrator'],
|
||||
})
|
||||
async ping(interaction: CommandInteraction) {
|
||||
await interaction.deferReply({ ephemeral: true })
|
||||
await interaction.editReply({
|
||||
content: `🏓Pong! ${interaction.client.ws.ping} ms`,
|
||||
content: `🏓 Pong! ${interaction.client.ws.ping} ms`,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
1
src/lib/index.ts
Normal file
1
src/lib/index.ts
Normal file
@ -0,0 +1 @@
|
||||
export * from './logger'
|
15
src/lib/logger.ts
Normal file
15
src/lib/logger.ts
Normal file
@ -0,0 +1,15 @@
|
||||
import c from 'chalk'
|
||||
|
||||
class Logger {
|
||||
error(msg: string, err: string) {
|
||||
console.log(c.red.bold('Error: ') + c.red(msg) + '\n' + err + '\n')
|
||||
}
|
||||
|
||||
database(field: string, value: string) {
|
||||
console.log(
|
||||
c.cyan.bold('Entry: ') + c.cyan(field) + '\n' + value + '\n',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export const logger = new Logger()
|
22
src/utils/embeds/feedback.ts
Normal file
22
src/utils/embeds/feedback.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import { EmbedBuilder, User } from 'discord.js'
|
||||
|
||||
type FeedbackEmbedProps = {
|
||||
user: User
|
||||
rating: string
|
||||
description: string
|
||||
}
|
||||
|
||||
export function feedbackEmbed({
|
||||
user,
|
||||
rating,
|
||||
description,
|
||||
}: FeedbackEmbedProps) {
|
||||
return new EmbedBuilder()
|
||||
.setTitle(`⭐ Review by ${user.username}`)
|
||||
.setDescription(`Author: <@${user.id}>`)
|
||||
.setThumbnail(user.avatarURL())
|
||||
.setFields([
|
||||
{ name: 'Evaluation of work:', value: rating },
|
||||
{ name: 'Commentary:', value: '```' + description + '```' },
|
||||
])
|
||||
}
|
3
src/utils/embeds/index.ts
Normal file
3
src/utils/embeds/index.ts
Normal file
@ -0,0 +1,3 @@
|
||||
export * from './tickets'
|
||||
export * from './feedback'
|
||||
export * from './payments'
|
17
src/utils/embeds/payments.ts
Normal file
17
src/utils/embeds/payments.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { EmbedBuilder } from 'discord.js'
|
||||
|
||||
export function paymentsEmbed() {
|
||||
return new EmbedBuilder().setTitle(`Payments`).setFields([
|
||||
{
|
||||
name: 'Crypto:',
|
||||
value:
|
||||
'Wallet number: `TRdGKNPABvoTrdwHgfUTX65DbqbguTh6cc`\n' +
|
||||
'Crypto name: `USDT`\n' +
|
||||
'Network name: `TRC20`',
|
||||
},
|
||||
{
|
||||
name: 'PayPal:',
|
||||
value: '`khamidalakkhali@gmail.com` | Types: Friends and Family',
|
||||
},
|
||||
])
|
||||
}
|
108
src/utils/embeds/tickets.ts
Normal file
108
src/utils/embeds/tickets.ts
Normal file
@ -0,0 +1,108 @@
|
||||
import {
|
||||
ActionRowBuilder,
|
||||
ButtonBuilder,
|
||||
ButtonStyle,
|
||||
channelMention,
|
||||
EmbedBuilder,
|
||||
MessageActionRowComponentBuilder,
|
||||
} from 'discord.js'
|
||||
import { Workload } from '../enums'
|
||||
|
||||
export function ticketCreateButton() {
|
||||
const createBtn = new ButtonBuilder()
|
||||
.setLabel('Open a ticket')
|
||||
.setEmoji('👉')
|
||||
.setStyle(ButtonStyle.Success)
|
||||
.setCustomId('create-btn')
|
||||
|
||||
return new ActionRowBuilder<MessageActionRowComponentBuilder>().addComponents(
|
||||
createBtn,
|
||||
)
|
||||
}
|
||||
|
||||
type TicketCreateEmbedProps = {
|
||||
pricesChannelId: string
|
||||
bannerURL: string
|
||||
}
|
||||
|
||||
export function ticketCreateEmbed({
|
||||
pricesChannelId,
|
||||
bannerURL,
|
||||
}: TicketCreateEmbedProps) {
|
||||
return new EmbedBuilder()
|
||||
.setTitle(`Create a ticket`)
|
||||
.setDescription(
|
||||
`Hey, want to order a design?
|
||||
Then open a ticket and we'll answer it in a jiffy!
|
||||
Dear customer, before opening a ticket\n
|
||||
I strongly recommend that you familiarize yourself with the prices of basic interfaces - ` +
|
||||
channelMention(pricesChannelId),
|
||||
)
|
||||
.setImage(bannerURL)
|
||||
}
|
||||
|
||||
type TicketWorkloadEmbedProps = {
|
||||
workload: Workload | null
|
||||
}
|
||||
|
||||
export function ticketWorkloadEmbed({ workload }: TicketWorkloadEmbedProps) {
|
||||
return new EmbedBuilder()
|
||||
.setTitle(`Workload status by orders`)
|
||||
.setDescription('**Status:**\n' + getStatusMessage(workload))
|
||||
|
||||
function getStatusMessage(status: Workload | null) {
|
||||
if (!status) return 'No information provided'
|
||||
switch (status) {
|
||||
case Workload.AVAILABLE:
|
||||
return ':green_circle: - Available for orders'
|
||||
case Workload.BUSY:
|
||||
return ':yellow_circle: - Available for orders, but there may be delays'
|
||||
case Workload.NOT_AVAILABLE:
|
||||
return ':red_circle: - Currently unavailable for orders'
|
||||
default:
|
||||
return 'No information provided'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type TicketCreatedEmbedProps = {
|
||||
username: string
|
||||
threadChannelId: string
|
||||
}
|
||||
|
||||
export function ticketCreatedEmbed({
|
||||
username,
|
||||
threadChannelId,
|
||||
}: TicketCreatedEmbedProps) {
|
||||
return new EmbedBuilder()
|
||||
.setTitle(`${username} opened a ticket`)
|
||||
.setDescription(
|
||||
`You successfully opened a ticket. Your ticket: ${channelMention(threadChannelId)}`,
|
||||
)
|
||||
}
|
||||
|
||||
type TicketEntityEmbedProps = {
|
||||
username: string
|
||||
}
|
||||
|
||||
export function ticketEntityEmbed({ username }: TicketEntityEmbedProps) {
|
||||
return new EmbedBuilder()
|
||||
.setTitle(`${username} opened a ticket`)
|
||||
.setDescription(
|
||||
`You successfully opened a ticket. We will be with you soon`,
|
||||
)
|
||||
.setFooter({
|
||||
text: 'You can close ticket by clicking the button below',
|
||||
})
|
||||
}
|
||||
|
||||
export function ticketEntityButton() {
|
||||
const closeBtn = new ButtonBuilder()
|
||||
.setLabel('Close ticket')
|
||||
.setStyle(ButtonStyle.Danger)
|
||||
.setCustomId('close-btn')
|
||||
|
||||
return new ActionRowBuilder<MessageActionRowComponentBuilder>().addComponents(
|
||||
closeBtn,
|
||||
)
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
export enum Status {
|
||||
export enum Workload {
|
||||
AVAILABLE = 'AVAILABLE',
|
||||
BUSY = 'BUSY',
|
||||
NOT_AVAILABLE = 'NOT_AVAILABLE',
|
||||
|
2
src/utils/index.ts
Normal file
2
src/utils/index.ts
Normal file
@ -0,0 +1,2 @@
|
||||
export * from './embeds'
|
||||
export * from './enums'
|
Loading…
Reference in New Issue
Block a user