implement auto repeating events
	
		
			
	
		
	
	
		
	
		
			All checks were successful
		
		
	
	
		
			
				
	
				ci/woodpecker/push/woodpecker Pipeline was successful
				
			
		
		
	
	
				
					
				
			
		
			All checks were successful
		
		
	
	ci/woodpecker/push/woodpecker Pipeline was successful
				
			This commit is contained in:
		
							
								
								
									
										18
									
								
								server/commands/echo.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								server/commands/echo.ts
									
									
									
									
									
										Normal 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())
 | 
			
		||||
	}
 | 
			
		||||
})
 | 
			
		||||
@@ -1,8 +1,35 @@
 | 
			
		||||
import { client } from '../..'
 | 
			
		||||
import { Command } from '../structures/command'
 | 
			
		||||
import { RunOptions } from '../types/commandTypes'
 | 
			
		||||
export default new Command({
 | 
			
		||||
	name: 'list',
 | 
			
		||||
	description: 'Lists upcoming events',
 | 
			
		||||
	run: async ({ interaction }) => {
 | 
			
		||||
		interaction.reply('Hello')
 | 
			
		||||
	options: [
 | 
			
		||||
		{
 | 
			
		||||
			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)
 | 
			
		||||
	}
 | 
			
		||||
})
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +1,107 @@
 | 
			
		||||
import { add } from "date-fns"
 | 
			
		||||
import { DateResolvable, GuildScheduledEvent, GuildScheduledEventCreateOptions, InternalDiscordGatewayAdapterCreator } from "discord.js"
 | 
			
		||||
 | 
			
		||||
export const name = 'guildScheduledEventUpdate'
 | 
			
		||||
export function execute(oldguildScheduledEvent: any,newguildScheduledEvent: any) {
 | 
			
		||||
	console.log(`${JSON.stringify(oldguildScheduledEvent)} has been Updated to be ${newguildScheduledEvent}`)
 | 
			
		||||
export function execute(oldguildScheduledEvent: GuildScheduledEvent, newguildScheduledEvent: GuildScheduledEvent) {
 | 
			
		||||
	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
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										38
									
								
								server/events/interactionCreate.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								server/events/interactionCreate.ts
									
									
									
									
									
										Normal 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
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -6,7 +6,7 @@ import { config } from "../configuration";
 | 
			
		||||
export class ExtendedClient extends Client {
 | 
			
		||||
	private eventFilePath = `${__dirname}/../events`
 | 
			
		||||
	private commandFilePath = `${__dirname}/../commands`
 | 
			
		||||
	private commands: Collection<string, CommandType> = new Collection()
 | 
			
		||||
	public commands: Collection<string, CommandType> = new Collection()
 | 
			
		||||
	public constructor() {
 | 
			
		||||
		super({ intents: [Intents.FLAGS.GUILDS, Intents.FLAGS.GUILD_SCHEDULED_EVENTS] })
 | 
			
		||||
	}
 | 
			
		||||
@@ -50,7 +50,7 @@ export class ExtendedClient extends Client {
 | 
			
		||||
				slashCommands.push(command)
 | 
			
		||||
			}
 | 
			
		||||
			this.on("ready", (client: Client) => {
 | 
			
		||||
				console.log(`Ready processing ${client}`)
 | 
			
		||||
				console.log(`Ready processing ${JSON.stringify(client)}`)
 | 
			
		||||
				console.log(`SlashCommands: ${JSON.stringify(slashCommands)}`)
 | 
			
		||||
				const guilds = client.guilds.cache
 | 
			
		||||
				this.registerCommands(slashCommands, guilds)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								server/structures/event.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								server/structures/event.ts
									
									
									
									
									
										Normal 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
 | 
			
		||||
	) { }
 | 
			
		||||
}
 | 
			
		||||
@@ -4,7 +4,7 @@ import { ExtendedClient } from "../structures/client";
 | 
			
		||||
export interface ExtendedInteraction extends CommandInteraction {
 | 
			
		||||
	member: GuildMember
 | 
			
		||||
}
 | 
			
		||||
interface RunOptions {
 | 
			
		||||
export interface RunOptions {
 | 
			
		||||
	client: ExtendedClient
 | 
			
		||||
	interaction: ExtendedInteraction
 | 
			
		||||
	args: CommandInteractionOptionResolver
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user