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:
		
							
								
								
									
										3
									
								
								index.ts
									
									
									
									
									
								
							
							
						
						
									
										3
									
								
								index.ts
									
									
									
									
									
								
							@@ -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()
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
									
								
							
							
						
						
									
										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 { 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)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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 {
 | 
					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)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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 {
 | 
					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
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user