refactoring and adding capability to recognize more schedules
Some checks failed
ci/woodpecker/push/woodpecker Pipeline failed

This commit is contained in:
mightypanders 2022-04-13 21:38:59 +02:00
parent 9df18575fd
commit 180c467826
8 changed files with 165 additions and 98 deletions

View File

@ -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

View File

@ -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
}

View 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
}

View 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
View 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.');
}

View 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
View 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)
})
})

View File

@ -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'. */,