implement auto repeating events
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
mightypanders 2022-04-12 22:39:47 +02:00
parent 10bc4ae5df
commit 9df18575fd
10 changed files with 211 additions and 10 deletions

View File

@ -3,10 +3,11 @@ import Server from "./server/server"
import { ExtendedClient } from "./server/structures/client" import { ExtendedClient } from "./server/structures/client"
const server = Server.init(config.port) const server = Server.init(config.port)
export const client = new ExtendedClient()
server.start(() => { server.start(() => {
console.log(`Server running on port ${server.getPort()}`) console.log(`Server running on port ${server.getPort()}`)
//const discordAdapter = new DiscordAdapter() //const discordAdapter = new DiscordAdapter()
const client = new ExtendedClient()
client.start() client.start()
}) })

View File

@ -19,12 +19,13 @@
"license": "ISC", "license": "ISC",
"dependencies": { "dependencies": {
"@discordjs/rest": "^0.3.0", "@discordjs/rest": "^0.3.0",
"@types/express": "^4.17.13",
"@types/cors": "^2.8.12",
"@tsconfig/recommended": "^1.0.1", "@tsconfig/recommended": "^1.0.1",
"@types/cors": "^2.8.12",
"@types/express": "^4.17.13",
"axios": "^0.26.0", "axios": "^0.26.0",
"body-parser": "^1.19.0", "body-parser": "^1.19.0",
"cors": "^2.8.5", "cors": "^2.8.5",
"date-fns": "^2.28.0",
"discord-api-types": "^0.27.3", "discord-api-types": "^0.27.3",
"discord.js": "^13.6.0", "discord.js": "^13.6.0",
"dotenv": "^16.0.0", "dotenv": "^16.0.0",

18
server/commands/echo.ts Normal file
View File

@ -0,0 +1,18 @@
import { Command } from '../structures/command'
import { RunOptions } from '../types/commandTypes'
export default new Command({
name: 'echo',
description: 'Echoes a text',
options: [
{
name: 'echo',
description: 'The text to echo',
type: 'STRING',
required: true
}
],
run: async (interaction: RunOptions) => {
console.log('echo called')
interaction.interaction.reply(interaction.toString())
}
})

View File

@ -1,8 +1,35 @@
import { client } from '../..'
import { Command } from '../structures/command' import { Command } from '../structures/command'
import { RunOptions } from '../types/commandTypes'
export default new Command({ export default new Command({
name: 'list', name: 'list',
description: 'Lists upcoming events', description: 'Lists upcoming events',
run: async ({ interaction }) => { options: [
interaction.reply('Hello') {
name: 'count',
description: 'The max amount of events to list',
type: 'INTEGER',
required: false,
minValue: 1,
maxValue: 100,
}
],
run: async (opt: RunOptions) => {
console.dir(opt)
const interactionGuild = opt.interaction.guild
const events = interactionGuild?.scheduledEvents.cache
let output = ''
if (!events?.values()) {
opt.interaction.followUp('No events to list')
return
}
const amount = 0
if (opt.interaction.options.get('count'))
for (const e of events.values()) {
output += e.toString()
output += `\n`
}
opt.interaction.followUp(output)
} }
}) })

View File

@ -1,4 +1,107 @@
import { add } from "date-fns"
import { DateResolvable, GuildScheduledEvent, GuildScheduledEventCreateOptions, InternalDiscordGatewayAdapterCreator } from "discord.js"
export const name = 'guildScheduledEventUpdate' export const name = 'guildScheduledEventUpdate'
export function execute(oldguildScheduledEvent: any,newguildScheduledEvent: any) { export function execute(oldguildScheduledEvent: GuildScheduledEvent, newguildScheduledEvent: GuildScheduledEvent) {
console.log(`${JSON.stringify(oldguildScheduledEvent)} has been Updated to be ${newguildScheduledEvent}`) console.dir(oldguildScheduledEvent)
console.dir(newguildScheduledEvent)
if (oldguildScheduledEvent.description && repetitionMarkerFound(oldguildScheduledEvent.description)) {
// valid repeating event
if (newguildScheduledEvent.status === 'COMPLETED') {
const repetitionInfo = getRepetitonInfo(oldguildScheduledEvent.description)
if (needsToBeRepeated(repetitionInfo)) {
try {
const newRepetitonString = buildNewRepetitionString(repetitionInfo)
const newEventOptions: GuildScheduledEventCreateOptions = {
name: oldguildScheduledEvent.name,
description: addRepetitonStringToEventDescription(oldguildScheduledEvent.description, newRepetitonString),
scheduledStartTime: getNewScheduledStart(oldguildScheduledEvent, repetitionInfo),
privacyLevel: oldguildScheduledEvent.privacyLevel,
entityType: oldguildScheduledEvent.entityType,
channel: oldguildScheduledEvent.channel?.id,
reason: 'Repetition'
}
newguildScheduledEvent.guild?.scheduledEvents.create(newEventOptions)
} catch (err) {
console.error(err)
}
}
}
}
} }
interface RepetitonInfo {
startDate?: Date, // If defined will take precedence over repetitonAmount
endDate?: Date,// If defined will take precedence over repetitonAmount
totalAmount: number,
alreadyOccured: number,
schedule: supportedSchedules
numberOfDays?: number
}
type supportedSchedules = 'daily' | 'weekly' | 'monthly' | 'everyTwoWeeks' | 'everyNDays'
function getRepetitonInfo(description: string): RepetitonInfo {
const lines = description.split(`\n`)
const repetitionString = lines.find(x => x.startsWith('$rep:'))
if (!repetitionString)
throw new Error('Cant find repetition string')
const schedule: supportedSchedules = determineSchedule(repetitionString)
const { totalAmount, alreadyOccured } = determineRepetitionCount(repetitionString)
return {
totalAmount,
alreadyOccured,
schedule
}
}
function repetitionMarkerFound(description: string): boolean {
return description.includes('$rep:')
}
function needsToBeRepeated(rInfo: RepetitonInfo): boolean {
return rInfo.alreadyOccured < rInfo.totalAmount
}
function determineSchedule(description: string): supportedSchedules {
return 'daily'
}
function determineRepetitionCount(description: string): { totalAmount: number; alreadyOccured: number } {
const segments = description.split(':')
const amountSegment = segments[2]
const amounts = amountSegment.split('/')
return { totalAmount: Number(amounts[1]) ?? 0, alreadyOccured: Number(amounts[0]) ?? 0 }
}
function buildNewRepetitionString(repetitionInfo: RepetitonInfo) {
return `$rep:${repetitionInfo.schedule}:${repetitionInfo.alreadyOccured + 1}/${repetitionInfo.totalAmount}`
}
function addRepetitonStringToEventDescription(oldguildScheduledEvent: string, newRepetitonString: string): string | undefined {
const lines = oldguildScheduledEvent.split(`\n`)
const repLineIndex = lines.findIndex(x => x.startsWith('$rep:'))
const newLines = lines.filter((_, index) => repLineIndex !== index)
newLines.push(newRepetitonString)
return newLines.join('\n')
}
function getNewScheduledStart(oldguildScheduledEvent: GuildScheduledEvent<"SCHEDULED" | "ACTIVE" | "COMPLETED" | "CANCELED">, rInfo: RepetitonInfo): DateResolvable {
const oldDate = oldguildScheduledEvent.scheduledStartAt
let daysToAdd = 0
switch (rInfo.schedule) {
case 'daily':
daysToAdd = 1
break
case 'weekly':
daysToAdd = 7
break
default:
throw new Error('No schedule found, cant add days')
}
const duration: Duration = {
days: daysToAdd
}
const newDate = add(oldDate, duration)
return newDate
}

View File

@ -0,0 +1,38 @@
import { CommandInteraction, CommandInteractionOptionResolver } from "discord.js"
import { ExtendedInteraction } from "../types/commandTypes"
import { Event } from '../structures/event'
import { client } from "../.."
/**
export default new Event('interactionCreate', async (interaction) => {
console.log(`Interaction has been created, ${JSON.stringify(interaction)}`)
if (interaction.isCommand()) {
await interaction.deferReply()
const command = client.commands.get(interaction.commandName)
if (!command)
return interaction.followUp('You have used a non existant command')
command.run({
args: interaction.options as CommandInteractionOptionResolver,
client: client,
interaction: interaction as ExtendedInteraction
})
}
})
*/
export const name = 'interactionCreate'
export async function execute(interaction: ExtendedInteraction) {
//console.dir(interaction, { depth: null })
if (interaction.isCommand()) {
console.log(`Interaction is a command.`)
await interaction.deferReply()
const command = client.commands.get(interaction.commandName)
if (!command)
return interaction.followUp('Invalid command')
command.run({
args: interaction.options as CommandInteractionOptionResolver,
client,
interaction
})
}
}

View File

@ -6,7 +6,7 @@ import { config } from "../configuration";
export class ExtendedClient extends Client { export class ExtendedClient extends Client {
private eventFilePath = `${__dirname}/../events` private eventFilePath = `${__dirname}/../events`
private commandFilePath = `${__dirname}/../commands` private commandFilePath = `${__dirname}/../commands`
private commands: Collection<string, CommandType> = new Collection() public commands: Collection<string, CommandType> = new Collection()
public constructor() { public constructor() {
super({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_SCHEDULED_EVENTS] }) super({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_SCHEDULED_EVENTS] })
} }
@ -50,7 +50,7 @@ export class ExtendedClient extends Client {
slashCommands.push(command) slashCommands.push(command)
} }
this.on("ready", (client: Client) => { this.on("ready", (client: Client) => {
console.log(`Ready processing ${client}`) console.log(`Ready processing ${JSON.stringify(client)}`)
console.log(`SlashCommands: ${JSON.stringify(slashCommands)}`) console.log(`SlashCommands: ${JSON.stringify(slashCommands)}`)
const guilds = client.guilds.cache const guilds = client.guilds.cache
this.registerCommands(slashCommands, guilds) this.registerCommands(slashCommands, guilds)

View File

@ -0,0 +1,8 @@
import { ClientEvents } from "discord.js";
export class Event<Key extends keyof ClientEvents>{
constructor(
public event: Key,
public run: (...args: ClientEvents[Key]) => any
) { }
}

View File

@ -4,7 +4,7 @@ import { ExtendedClient } from "../structures/client";
export interface ExtendedInteraction extends CommandInteraction { export interface ExtendedInteraction extends CommandInteraction {
member: GuildMember member: GuildMember
} }
interface RunOptions { export interface RunOptions {
client: ExtendedClient client: ExtendedClient
interaction: ExtendedInteraction interaction: ExtendedInteraction
args: CommandInteractionOptionResolver args: CommandInteractionOptionResolver

View File

@ -1465,6 +1465,11 @@ data-urls@^2.0.0:
whatwg-mimetype "^2.3.0" whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0" whatwg-url "^8.0.0"
date-fns@^2.28.0:
version "2.28.0"
resolved "https://registry.yarnpkg.com/date-fns/-/date-fns-2.28.0.tgz#9570d656f5fc13143e50c975a3b6bbeb46cd08b2"
integrity sha512-8d35hViGYx/QH0icHYCeLmsLmMUheMmTyV9Fcm6gvNwdw31yXXH+O85sOBJ+OLnLQMKZowvpKb6FgMIQjcpvQw==
debug@2.6.9: debug@2.6.9:
version "2.6.9" version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"