Skip to content

Commit

Permalink
Feature backport
Browse files Browse the repository at this point in the history
  • Loading branch information
MrBazlow committed Apr 25, 2023
1 parent f97348d commit 7a17598
Show file tree
Hide file tree
Showing 15 changed files with 546 additions and 13 deletions.
1 change: 0 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
node_modules
pnpm-lock.yaml
.env
.gitignore
1 change: 0 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
"plugin:@typescript-eslint/strict"
],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint"],
"root": true,
"parserOptions": {
"ecmaVersion": "latest",
Expand Down
9 changes: 9 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
version: "3.9"
services:
bot:
image: "mrbazlow/warbot:latest"
restart: always
volumes:
- ./data:/opt/app/data
env_file:
- ./.env
16 changes: 9 additions & 7 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,21 +18,23 @@
"author": "",
"license": "ISC",
"dependencies": {
"@sapphire/framework": "^4.4.0",
"@sapphire/discord.js-utilities": "^6.0.6",
"@sapphire/framework": "^4.4.1",
"@sapphire/time-utilities": "^1.7.9",
"@sinclair/typebox": "^0.27.8",
"axios": "^1.3.5",
"axios": "^1.3.6",
"axios-cache-interceptor": "^1.0.1",
"discord.js": "^14.9.0",
"dotenv": "^16.0.3",
"table": "^6.8.1"
},
"devDependencies": {
"@types/node": "^18.15.11",
"@typescript-eslint/eslint-plugin": "^5.58.0",
"@typescript-eslint/parser": "^5.58.0",
"eslint": "^8.38.0",
"@types/node": "^18.16.0",
"@typescript-eslint/eslint-plugin": "^5.59.1",
"@typescript-eslint/parser": "^5.59.1",
"eslint": "^8.39.0",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.7",
"prettier": "^2.8.8",
"typescript": "^5.0.4"
}
}
60 changes: 60 additions & 0 deletions src/commands/enlistmenthistory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { TextChannel, codeBlock } from 'discord.js'
import { Command } from '@sapphire/framework'
import { table } from 'table'

import type { TableUserConfig } from 'table'

const CHANNEL_ID = '1097620947713933393'

export class UserCommand extends Command {
public constructor(context: Command.Context, options: Command.Options) {
super(context, {
...options,
name: 'enlistmenthistory',
description: 'Lookup a users verification history'
})
}

public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand((builder) => {
builder //
.setName(this.name)
.setDescription(this.description)
.addStringOption((option) =>
option //
.setName('user')
.setDescription('Discord user to lookup')
.setRequired(true)
)
})
}

public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
interaction.client.logger.info(
`${interaction.user.tag} in #${
interaction.channel instanceof TextChannel ? interaction.channel.name : 'somewhere'
} called enlistmenthistory.`
)

await interaction.deferReply()

const channel = interaction.client.channels.cache.get(CHANNEL_ID)

const user = interaction.options.getString('user') ?? 'mystery user'

if (channel instanceof TextChannel) {
channel.messages
.fetch()
.then((messages) => {
return messages.filter((message) => {
message.content.includes(`Approved by ${user}`)
})
})
.then((filteredMessages) => {
console.log(user)
console.log(filteredMessages)
})
.catch((error) => console.error(error))
}
}
}
106 changes: 106 additions & 0 deletions src/commands/qrf.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { Command } from '@sapphire/framework'
import { createPartitionedMessageRow } from '@sapphire/discord.js-utilities'
import { EmbedBuilder, ButtonBuilder, ButtonStyle } from 'discord.js'
import errorMessage from '../lib/errorMessage.js'
import { regionFriendlyNamesSchema } from '../schema.js'
import { Time } from '@sapphire/time-utilities'
import { Value } from '@sinclair/typebox/value'

export class UserCommandCreateButton extends Command {
public regionFriendlyNames = Value.Create(regionFriendlyNamesSchema)

public constructor(context: Command.Context, options: Command.Options) {
super(context, {
...options,
name: 'qrf',
description: `Create a QRF Alert`
})
}

public override registerApplicationCommands(registry: Command.Registry) {
registry.registerChatInputCommand((builder) => {
builder //
.setName(this.name)
.setDescription(this.description)
.addStringOption((option) =>
option //
.setName('region')
.setDescription('Region to check')
.setAutocomplete(true)
.setRequired(true)
)
.addStringOption((option) =>
option //
.setName('description')
.setDescription('QRF details')
.setRequired(true)
)
.addAttachmentOption((option) =>
option //
.setName('screenshot')
.setDescription('Image to include in your QRF Message')
.setRequired(true)
)
})
}

public override async chatInputRun(interaction: Command.ChatInputCommandInteraction) {
await interaction.deferReply()
const userAvatar = interaction.user.avatarURL() ?? ''
const region = interaction.options.getString('region') as keyof typeof this.regionFriendlyNames
const description = interaction.options.getString('description') ?? ''
const image = interaction.options.getAttachment('screenshot')

try {
if (!Object.hasOwn(this.regionFriendlyNames, region)) {
await interaction.editReply(`"${region}" is not a location in Foxhole`)
setTimeout(() => {
void interaction.deleteReply().catch((error: unknown) => {
interaction.client.logger.error(error)
})
}, Time.Second * 5)
return
}
if (image === null) {
throw new Error('Unable to find image')
}
if (interaction.channel?.isTextBased()) {
const qrfEmbed = new EmbedBuilder()
.setAuthor({ name: interaction.user.tag, iconURL: userAvatar })
.setTitle(`Help ${this.regionFriendlyNames[region]}!`)
.setDescription(description)
.setImage(image.url)
.setColor('#245682')
.setFields(
{ name: 'Status', value: '🔴 ONGOING', inline: true },
{ name: 'OP', value: interaction.user.toString(), inline: true },
{ name: 'Responders', value: interaction.user.toString(), inline: true }
)
.setTimestamp(Date.now())
.setFooter({
text: 'QRF Request',
iconURL:
'https://cdn.discordapp.com/attachments/1025869440451100742/1099895163662372875/rotating_light.png'
})

const submitButton = new ButtonBuilder() //
.setCustomId('qrfSubmit')
.setEmoji('📩')
.setLabel('Submit')
.setStyle(ButtonStyle.Success)
const cancelButton = new ButtonBuilder() //
.setCustomId('qrfCancel')
.setEmoji('🛑')
.setLabel('Cancel')
.setStyle(ButtonStyle.Secondary)

await interaction.editReply({
embeds: [qrfEmbed],
components: createPartitionedMessageRow([submitButton, cancelButton])
})
}
} catch (error) {
void errorMessage(error, interaction)
}
}
}
24 changes: 21 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,31 @@
import * as dotenv from 'dotenv'
import { ApplicationCommandRegistries, RegisterBehavior, SapphireClient } from '@sapphire/framework'
import { GatewayIntentBits } from 'discord.js'
import { TimerManager } from '@sapphire/time-utilities'

dotenv.config()

if (process.env.DISCORD_TOKEN === undefined) {
console.error('No DISCORD_TOKEN assigned')
process.exit(1)
process.exitCode = 1
process.exit()
}

if (process.env.QRF_CHANNEL === undefined) {
console.error('ERROR: QRF_CHANNEL VARIABLE NOT SET. QRF MESSAGES CANNOT BE SENT')
process.exitCode = 1
process.exit()
}

ApplicationCommandRegistries.setDefaultBehaviorWhenNotIdentical(RegisterBehavior.BulkOverwrite)

const client = new SapphireClient({
intents: [GatewayIntentBits.Guilds],
intents: [
GatewayIntentBits.Guilds,
GatewayIntentBits.MessageContent,
GatewayIntentBits.DirectMessages,
GatewayIntentBits.GuildMessages
],
loadMessageCommandListeners: true
})

Expand All @@ -24,8 +37,13 @@ async function main(): Promise<void> {
} catch (error) {
client.logger.fatal(error)
client.destroy()
process.exit(1)
process.exitCode = 1
process.exit()
}
}

process.on('SIGTERM', () => {
TimerManager.destroy()
})

void main()
35 changes: 35 additions & 0 deletions src/interaction-handlers/handleQRFCancel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { InteractionHandler, InteractionHandlerTypes, PieceContext } from '@sapphire/framework'
import { ButtonInteraction } from 'discord.js'
import errorMessage from '../lib/errorMessage.js'

export class ButtonHandler extends InteractionHandler {
public constructor(ctx: PieceContext, options: InteractionHandler.Options) {
super(ctx, {
...options,
interactionHandlerType: InteractionHandlerTypes.Button
})
}

public override parse(interaction: ButtonInteraction) {
if (interaction.customId !== 'qrfCancel') {
return this.none()
}
const originalPosterId = interaction.message.embeds[0].fields.find(
(field) => field.name === 'OP'
)?.value
if (interaction.user.toString() === originalPosterId) {
return this.some()
}
return this.none()
}

public async run(interaction: ButtonInteraction) {
await interaction.deferReply({ ephemeral: true })
try {
await interaction.message.delete()
await interaction.deleteReply()
} catch (error) {
void errorMessage(error, interaction)
}
}
}
46 changes: 46 additions & 0 deletions src/interaction-handlers/handleQRFIgnore.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { InteractionHandler, InteractionHandlerTypes, PieceContext } from '@sapphire/framework'
import errorMessage from '../lib/errorMessage.js'
import { EmbedBuilder } from 'discord.js'

import type { ButtonInteraction } from 'discord.js'

export class ButtonHandler extends InteractionHandler {
public constructor(ctx: PieceContext, options: InteractionHandler.Options) {
super(ctx, {
...options,
interactionHandlerType: InteractionHandlerTypes.Button
})
}

public override parse(interaction: ButtonInteraction) {
if (interaction.customId !== 'qrfIgnore') {
return this.none()
}
return this.some()
}

public async run(interaction: ButtonInteraction) {
await interaction.deferReply({ ephemeral: true })

try {
const username = interaction.user.toString()
const oldEmbed = interaction.message.embeds[0]
const respondersField = oldEmbed.fields.find((entry) => entry.name === 'Responders')
if (respondersField?.value.includes(username)) {
respondersField.value = respondersField.value.replaceAll(`, ${username}`, '')
respondersField.value = respondersField.value.replaceAll(username, '')
const newEmbed = new EmbedBuilder(oldEmbed.toJSON())
await interaction.message.edit({
embeds: [newEmbed]
})
await interaction.message.thread?.members.remove(
interaction.user.id,
`${username} requested to leave the QRF Event`
)
}
await interaction.deleteReply()
} catch (error) {
void errorMessage(error, interaction)
}
}
}
Loading

0 comments on commit 7a17598

Please sign in to comment.