refactoring and adding capability to recognize more schedules
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed
This commit is contained in:
parent
9df18575fd
commit
180c467826
@ -2,7 +2,7 @@ FROM node:alpine as Build
|
|||||||
ENV NODE_ENV=production
|
ENV NODE_ENV=production
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY [ "yarn.lock", "package.json", "index.ts", "tsconfig.json", "./" ]
|
COPY [ "yarn.lock", "package.json", "index.ts", "./" ]
|
||||||
COPY server ./server
|
COPY server ./server
|
||||||
|
|
||||||
RUN yarn install --production
|
RUN yarn install --production
|
||||||
|
@ -1,107 +1,18 @@
|
|||||||
import { add } from "date-fns"
|
import { GuildScheduledEvent } from "discord.js"
|
||||||
import { DateResolvable, GuildScheduledEvent, GuildScheduledEventCreateOptions, InternalDiscordGatewayAdapterCreator } from "discord.js"
|
import { handleRepeatingEvent } from "../handler/repeatingEvents/repeatingEvents.controller"
|
||||||
|
|
||||||
|
|
||||||
|
const repetitionMarkerIsFound = (desc: string): boolean => desc.includes('$rep')
|
||||||
|
|
||||||
export const name = 'guildScheduledEventUpdate'
|
export const name = 'guildScheduledEventUpdate'
|
||||||
export function execute(oldguildScheduledEvent: GuildScheduledEvent, newguildScheduledEvent: GuildScheduledEvent) {
|
export function execute(oldguildScheduledEvent: GuildScheduledEvent, newguildScheduledEvent: GuildScheduledEvent) {
|
||||||
console.dir(oldguildScheduledEvent)
|
console.dir(oldguildScheduledEvent)
|
||||||
console.dir(newguildScheduledEvent)
|
console.dir(newguildScheduledEvent)
|
||||||
|
|
||||||
if (oldguildScheduledEvent.description && repetitionMarkerFound(oldguildScheduledEvent.description)) {
|
if (oldguildScheduledEvent.description && repetitionMarkerIsFound(oldguildScheduledEvent.description)) {
|
||||||
// valid repeating event
|
// valid repeating event
|
||||||
if (newguildScheduledEvent.status === 'COMPLETED') {
|
if (newguildScheduledEvent.status === 'COMPLETED')
|
||||||
const repetitionInfo = getRepetitonInfo(oldguildScheduledEvent.description)
|
handleRepeatingEvent(oldguildScheduledEvent, newguildScheduledEvent)
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
68
server/handler/repeatingEvents/helper.ts
Normal file
68
server/handler/repeatingEvents/helper.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import add from "date-fns/add"
|
||||||
|
import { DateResolvable, GuildScheduledEvent } from "discord.js"
|
||||||
|
import { findInScheduleTypes } from "../../helper/typeFind"
|
||||||
|
import { RepetitonInfo, supportedSchedule } from "../../types/scheduledEventTypes"
|
||||||
|
|
||||||
|
export 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: supportedSchedule = determineSchedule(repetitionString)
|
||||||
|
const { totalAmount, alreadyOccured } = determineRepetitionCount(repetitionString)
|
||||||
|
return {
|
||||||
|
totalAmount,
|
||||||
|
alreadyOccured,
|
||||||
|
schedule
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function determineSchedule(repetitionLine: string): supportedSchedule {
|
||||||
|
const segments = repetitionLine.split(':')
|
||||||
|
const scheduleSegment = segments[1]
|
||||||
|
const easilyKnownScheduleName = findInScheduleTypes(scheduleSegment)
|
||||||
|
if (easilyKnownScheduleName)
|
||||||
|
return easilyKnownScheduleName
|
||||||
|
else
|
||||||
|
throw new Error('Inferring schedule names is not yet supported')
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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 }
|
||||||
|
}
|
||||||
|
|
||||||
|
export function buildNewRepetitionString(repetitionInfo: RepetitonInfo) {
|
||||||
|
return `$rep:${repetitionInfo.schedule}:${repetitionInfo.alreadyOccured + 1}/${repetitionInfo.totalAmount}`
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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')
|
||||||
|
}
|
||||||
|
|
||||||
|
export 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
|
||||||
|
}
|
28
server/handler/repeatingEvents/repeatingEvents.controller.ts
Normal file
28
server/handler/repeatingEvents/repeatingEvents.controller.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { GuildScheduledEvent, GuildScheduledEventCreateOptions } from "discord.js"
|
||||||
|
import { RepetitonInfo } from "../../types/scheduledEventTypes"
|
||||||
|
import { addRepetitonStringToEventDescription, buildNewRepetitionString, getNewScheduledStart, getRepetitonInfo } from "./helper"
|
||||||
|
|
||||||
|
const needsToBeRepeated = (rInfo: RepetitonInfo): boolean => rInfo.alreadyOccured < rInfo.totalAmount
|
||||||
|
|
||||||
|
export function handleRepeatingEvent(oldguildScheduledEvent: GuildScheduledEvent, newguildScheduledEvent: GuildScheduledEvent) {
|
||||||
|
if (!oldguildScheduledEvent.description) throw new Error('Event has no description -> cant handle this')
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
server/helper/typeFind.ts
Normal file
13
server/helper/typeFind.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { scheduleNames, supportedSchedule } from "../types/scheduledEventTypes";
|
||||||
|
|
||||||
|
export function findInScheduleTypes(inputString: string): supportedSchedule {
|
||||||
|
const maybeScheduleName: unknown = JSON.parse(`"${inputString.toLowerCase()}"`);
|
||||||
|
const scheduleName = scheduleNames.find((validName: supportedSchedule) => validName === maybeScheduleName);
|
||||||
|
if (scheduleName) {
|
||||||
|
// `sheepName` comes from the list of `sheepNames` so the compiler is happy.
|
||||||
|
return scheduleName;
|
||||||
|
}
|
||||||
|
throw new Error('That is not a schedule name.');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
11
server/types/scheduledEventTypes.ts
Normal file
11
server/types/scheduledEventTypes.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
|
||||||
|
export 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: supportedSchedule
|
||||||
|
numberOfDays?: number
|
||||||
|
}
|
||||||
|
export const scheduleNames = ['daily', 'weekly', 'monthly', 'everyTwoWeeks', 'everyNDays']
|
||||||
|
export type supportedSchedule = typeof scheduleNames[number]
|
35
tests/repetition.test.ts
Normal file
35
tests/repetition.test.ts
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import { findInScheduleTypes } from '../server/helper/typeFind'
|
||||||
|
import { supportedSchedule } from '../server/types/scheduledEventTypes'
|
||||||
|
import { getRepetitonInfo } from '../server/handler/repeatingEvents/helper'
|
||||||
|
import { RepetitonInfo } from '../server/types/scheduledEventTypes'
|
||||||
|
describe('Schedule names are parsed correctly', () => {
|
||||||
|
const dailyValue: supportedSchedule = 'daily'
|
||||||
|
const weeklyValue: supportedSchedule = 'weekly'
|
||||||
|
const monthlyValue: supportedSchedule = 'monthly'
|
||||||
|
test('Easy schedule names', () => {
|
||||||
|
expect(findInScheduleTypes('daily')).toEqual(dailyValue)
|
||||||
|
expect(findInScheduleTypes('weekly')).toEqual(weeklyValue)
|
||||||
|
expect(findInScheduleTypes('monthly')).toEqual(monthlyValue)
|
||||||
|
})
|
||||||
|
test('Medium schedule names', () => {
|
||||||
|
expect(findInScheduleTypes('Daily')).toEqual(dailyValue)
|
||||||
|
expect(findInScheduleTypes('Weekly')).toEqual(weeklyValue)
|
||||||
|
expect(findInScheduleTypes('Monthly')).toEqual(monthlyValue)
|
||||||
|
expect(findInScheduleTypes('DAILY')).toEqual(dailyValue)
|
||||||
|
expect(findInScheduleTypes('WEEKLy')).toEqual(weeklyValue)
|
||||||
|
expect(findInScheduleTypes('MONTHly')).toEqual(monthlyValue)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('Parsing of Repetition Info from Description String',()=>{
|
||||||
|
test('Happy Path',()=>{
|
||||||
|
const inputString = '$rep:daily:1/3'
|
||||||
|
const expectedInfo: RepetitonInfo = {
|
||||||
|
totalAmount: 3,
|
||||||
|
alreadyOccured: 1,
|
||||||
|
schedule: 'daily'
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(getRepetitonInfo(inputString)).toEqual(expectedInfo)
|
||||||
|
})
|
||||||
|
|
||||||
|
})
|
@ -1,5 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends":"@tsconfig/recommended/tsconfig.json",
|
"extends":"@tsconfig/recommended/tsconfig.json",
|
||||||
|
"exclude":["node_modules","**/*.test.ts"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
/* Basic Options */
|
/* Basic Options */
|
||||||
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
|
"target": "es6" /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */,
|
||||||
|
Loading…
Reference in New Issue
Block a user