Skip to content

Commit

Permalink
Merge pull request #91 from Mickhat/oli-add-elite-promotions
Browse files Browse the repository at this point in the history
Add elite game promotions
  • Loading branch information
oglimmer committed Feb 2, 2024
2 parents c350b33 + 3bd85ab commit d13fe6e
Show file tree
Hide file tree
Showing 7 changed files with 702 additions and 55 deletions.
6 changes: 5 additions & 1 deletion env.example
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,8 @@ REPORT_CHANNEL_ID= <channel, in den Reports geschickt werden sollen>
TICKET_SUPPORTER= <ID der Rolle, die jedem Ticket hinzugefügt wird>
MESSAGE_LOGS= <ID des Channels, in dem gelöschte & bearbeitete Nachrichten protokolliert werden sollen>
JOIN_LOGS= <ID des Channels, in dem protokolliert wird wenn ein Nutzer den Discord betritt/verlässt>
SEND_1337_CHANNEL_ID=<ID des Channels wo 13:37 gesendet wird>
SEND_1337_CHANNEL_ID=<ID des Channels wo 13:37 gesendet wird>
LEET_GENERAL_ROLE_ID=<ID der rolle für den leet general>
LEET_COMMANDER_ROLE_ID=<ID der rolle für den leet commander>
LEET_SERGEANT_ROLE_ID=<ID der rolle für den leet sergeant>
GUILD_ID=<ID des servers>
15 changes: 2 additions & 13 deletions src/Bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import {
import { handleBlackJackCommands } from './action/blackjack/handleCommands'
import { registerBlackJackCommands } from './action/blackjack/registerCommands'
import roleDelete from './listeners/roleDelete'
import { EliteGameDataStorage } from './service/eliteGameDataStorage'

const logManager: LogManager = LogManager.getInstance()

Expand Down Expand Up @@ -99,19 +100,7 @@ async function init(): Promise<void> {
await db.runAsync(`ALTER TABLE giveaways add author_display_name TEXT NULL`)
await db.runAsync(`ALTER TABLE giveaways add author_avatar_url TEXT NULL`)
}
await db.runAsync(`CREATE TABLE IF NOT EXISTS elite_game (
identifier INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
player TEXT NOT NULL,
register_date TEXT NOT NULL,
play_timestamp INTEGER NOT NULL
)`)
await db.runAsync(`CREATE TABLE IF NOT EXISTS elite_game_winner (
identifier INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
player TEXT NOT NULL,
register_date TEXT NOT NULL,
play_timestamp INTEGER NOT NULL,
target_timestamp INTEGER NOT NULL
)`)
await EliteGameDataStorage.instance().initEliteGame(db)
await (await PersistentDataStorage.instance()).initBlackJack(db)
})

Expand Down
129 changes: 88 additions & 41 deletions src/commands/elite.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,106 @@
import { CommandInteraction, EmbedBuilder, SlashCommandBuilder, ChannelType, Client } from 'discord.js'
import { CommandInteraction, EmbedBuilder, SlashCommandBuilder, ChannelType, Client, GuildMember } from 'discord.js'
import LogManager, { ILogger } from '../logger/logger'
import schedule from 'node-schedule'
import { AsyncDatabase } from '../sqlite/sqlite'
import { EliteGameDataStorage } from '../service/eliteGameDataStorage'
import promotionService, { getTodayAsDate } from '../service/promotionService'

function getTodayAsDate(): string {
const berlinDate = new Date().toLocaleDateString('de-DE', {
timeZone: 'Europe/Berlin',
year: 'numeric',
month: '2-digit',
day: '2-digit'
})
return berlinDate
const LEET_GENERAL_ROLE_ID = process.env.LEET_GENERAL_ROLE_ID ?? '<ERROR>'
const LEET_COMMANDER_ROLE_ID = process.env.LEET_COMMANDER_ROLE_ID ?? '<ERROR>'
const LEET_SERGEANT_ROLE_ID = process.env.LEET_SERGEANT_ROLE_ID ?? '<ERROR>'
const GUILD_ID = process.env.GUILD_ID ?? '<ERROR>'

async function removeRole(member: GuildMember, role: string): Promise<void> {
if (member.roles.cache.has(role)) {
await member.roles.remove(role)
}
}

async function handleGeneralPromotion(general: string, oldGeneral: string | null, targetChannel: any, client: Client): Promise<void> {
await targetChannel.send(`<@${general}> has been promoted to Leet General!`)
const member = await client.guilds.cache.get(GUILD_ID)?.members.fetch(general)
if (member) {
await removeRole(member, LEET_COMMANDER_ROLE_ID)
await removeRole(member, LEET_SERGEANT_ROLE_ID)
await member.roles.add(LEET_GENERAL_ROLE_ID)
}
if (oldGeneral) {
const memberToRemove = await client.guilds.cache.get(GUILD_ID)?.members.fetch(oldGeneral)
if (memberToRemove) {
await removeRole(memberToRemove, LEET_GENERAL_ROLE_ID)
}
}
}

async function handleCommanderPromotion(commander: string, oldCommander: string | null, targetChannel: any, client: Client): Promise<void> {
await targetChannel.send(`<@${commander}> has been promoted to Leet Commander!`)
const member = await client.guilds.cache.get(GUILD_ID)?.members.fetch(commander)
if (member) {
await removeRole(member, LEET_SERGEANT_ROLE_ID)
await member.roles.add(LEET_COMMANDER_ROLE_ID)
}
if (oldCommander) {
const memberToRemove = await client.guilds.cache.get(GUILD_ID)?.members.fetch(oldCommander)
if (memberToRemove) {
await removeRole(memberToRemove, LEET_COMMANDER_ROLE_ID)
}
}
}

async function handleSergeantPromotion(sergeant: string, oldSergent: string | null, targetChannel: any, client: Client): Promise<void> {
await targetChannel.send(`<@${sergeant}> has been promoted to Leet Sergeant!`)
const member = await client.guilds.cache.get(GUILD_ID)?.members.fetch(sergeant)
await member?.roles.add(LEET_SERGEANT_ROLE_ID)
if (oldSergent) {
const memberToRemove = await client.guilds.cache.get(GUILD_ID)?.members.fetch(oldSergent)
if (memberToRemove) {
await removeRole(memberToRemove, LEET_SERGEANT_ROLE_ID)
}
}
}

async function doPromotions(targetChannel: any, client: Client): Promise<void> {
const { general: oldGeneral, commander: oldCommander, sergeant: oldSergent } = await promotionService.getCurrentRanks()
const { general, commander, sergeant } = await promotionService.doPromotions()
if (general) {
await handleGeneralPromotion(general, oldGeneral, targetChannel, client)
}
if (commander) {
await handleCommanderPromotion(commander, oldCommander, targetChannel, client)
}
if (sergeant) {
await handleSergeantPromotion(sergeant, oldSergent, targetChannel, client)
}
}

let db: AsyncDatabase | undefined
export default {
init: (client: Client, logger: ILogger): void => {
client.on('ready', async () => {
if (client.user == null || client.application == null) {
if (!client.user || !client.application) {
return
}
logger.logSync('INFO', `Init EliteCommand`)

db = await AsyncDatabase.open()
if (!db) {
logger.logSync('ERROR', 'Datenbank konnte nicht geöffnet werden.')
return
}
// Everyday at 13:37 (24 hour clock) Europe/
schedule.scheduleJob({ rule: '37 13 * * *', tz: 'Europe/Berlin' }, async () => {
const targetChannel = await client.channels.fetch(process.env.SEND_1337_CHANNEL_ID ?? '')
const targetChannel = await client.channels.fetch(process.env.SEND_1337_CHANNEL_ID ?? '<ERROR>')
if (!targetChannel || targetChannel.type !== ChannelType.GuildText) {
logger.logSync('WARN', 'MessageLogger could not find log channel or LogChannel is not TextBased')
return
}

setTimeout(() => {
void (async () => {
const berlinDate = getTodayAsDate()
// write '\uFFFF' to player column to indicate that the game has been played for this day
await db?.runAsync(`INSERT INTO elite_game (register_date, player, play_timestamp) VALUES(?,?,?)`, [berlinDate, '\uFFFF', 0])
// find all participants and sort them by play_timestamp (first one is the winner)
let rows = await db?.getAsync(`select * from elite_game where register_date = ? and player != "\uFFFF" order by play_timestamp desc`, [berlinDate])
await targetChannel.send('13:37')
if (rows) {
if (!Array.isArray(rows)) {
rows = [rows]
}
console.log(rows)
await targetChannel.send(`Todays winner is <@${rows[0].player}>`)
await db?.runAsync(`INSERT INTO elite_game_winner (player, register_date, play_timestamp, target_timestamp) VALUES(?,?,?,?)`, [rows[0].player, berlinDate, rows[0].play_timestamp, Date.now()])
}
})()
// eslint-disable-next-line @typescript-eslint/no-misused-promises
setTimeout(async () => {
const berlinDate = getTodayAsDate()
// write '\uFFFF' to player column to indicate that the game has been played for this day
await EliteGameDataStorage.instance().writeGamePlay('\uFFFF', berlinDate)
// find all participants and sort them by play_timestamp (first one is the winner)
const rows = await EliteGameDataStorage.instance().loadGamePlayAll(berlinDate)
await targetChannel.send('13:37')
if (rows && rows.length > 0) {
const topRow = rows[0]
await EliteGameDataStorage.instance().writeGameWinner(topRow.player, berlinDate, topRow.play_timestamp)
await doPromotions(targetChannel, client)
}
}, Math.random() * 60000) // delay by 0-60 seconds
})
})
Expand All @@ -67,12 +116,9 @@ export default {

const userId = interaction.member?.user.id ?? '<ERROR>'

let rows = await db?.getAsync(`select * from elite_game where register_date = ? and (player = ? or player = "\uFFFF") order by player desc`, [berlinDate, userId])
const rows = await EliteGameDataStorage.instance().loadGamePlayForUser(userId, berlinDate)
try {
if (rows) {
if (!Array.isArray(rows)) {
rows = [rows]
}
if (rows.length > 0) {
if (rows[0].player === '\uFFFF') {
// the player '\uFFFF' indicates that the game has been played for this day
await interaction.reply({
Expand All @@ -98,7 +144,7 @@ export default {
})
}
} else {
await db?.runAsync(`INSERT INTO elite_game(register_date, player, play_timestamp) VALUES(?,?,?)`, [berlinDate, userId, Date.now()])
await EliteGameDataStorage.instance().writeGamePlay(userId, berlinDate)
await interaction.reply({
embeds: [new EmbedBuilder()
.setColor(0x0099ff)
Expand All @@ -111,6 +157,7 @@ export default {
})
}
} catch (err) {
console.log(err)
logger.logSync('ERROR', `Reply to elite command konnte nicht gesendet werden. ${JSON.stringify(err)}`)
}
}
Expand Down
122 changes: 122 additions & 0 deletions src/service/eliteGameDataStorage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
import { AsyncDatabase } from '../sqlite/sqlite'

export interface EliteGameRank {
identifier: number
rank: string
player: string
change_date: string
}

export interface EliteGame {
identifier: number
player: string
register_date: string
play_timestamp: number
}

export class EliteGameDataStorage {
private static _instance: EliteGameDataStorage
static instance(): EliteGameDataStorage {
if (!EliteGameDataStorage._instance) {
EliteGameDataStorage._instance = new EliteGameDataStorage()
}
return EliteGameDataStorage._instance
}

private db?: AsyncDatabase

private constructor() {}

public isInitEliteGame(): boolean {
return this.db !== undefined
}

public async initEliteGame(_db: AsyncDatabase): Promise<void> {
if (this.db) {
throw Error('initEliteGame was already called!')
}
this.db = _db
await this.db.runAsync(`CREATE TABLE IF NOT EXISTS elite_game (
identifier INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
player TEXT NOT NULL,
register_date TEXT NOT NULL,
play_timestamp INTEGER NOT NULL
)`)
await this.db.runAsync(`CREATE TABLE IF NOT EXISTS elite_game_winner (
identifier INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
player TEXT NOT NULL,
register_date TEXT NOT NULL,
play_timestamp INTEGER NOT NULL,
target_timestamp INTEGER NOT NULL
)`)
await this.db.runAsync(`CREATE TABLE IF NOT EXISTS elite_game_rank (
identifier INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL UNIQUE,
rank TEXT NOT NULL,
player TEXT NOT NULL,
change_date TEXT NOT NULL
)`)
}

public async loadRanks(): Promise<EliteGameRank[]> {
if (!this.db) {
throw Error('initEliteGame was not called!')
}
return await this.db.allAsyncT<EliteGameRank>(`select * from elite_game_rank`, [])
}

public async loadWinners(days: number, minWins: number, playerToExclude: string): Promise<Array<{ countRows: number, player: string }>> {
if (!this.db) {
throw Error('initEliteGame was not called!')
}
return await this.db.allAsyncT<{ countRows: number, player: string }>(`select count(identifier) as countRows, player from elite_game_winner where play_timestamp > ? and player != ? group by player having count(identifier) >= ? order by count(identifier) desc`,
[Date.now() - days * 24 * 60 * 60 * 1000, playerToExclude, minWins])
}

public async loadLastWinner(): Promise<string | null> {
if (!this.db) {
throw Error('initEliteGame was not called!')
}
const winner = await this.db.getAsyncT<{ player: string }>(`select player from elite_game_winner order by play_timestamp desc limit 1`, [])
return winner ? winner.player : null
}

public async writeGamePlay(userId: string, berlinDate: string): Promise<void> {
if (!this.db) {
throw Error('initEliteGame was not called!')
}
await this.db.runAsync(`INSERT INTO elite_game(register_date, player, play_timestamp) VALUES(?,?,?)`, [berlinDate, userId, Date.now()])
}

public async loadGamePlayForUser(userId: string, berlinDate: string): Promise<EliteGame[]> {
if (!this.db) {
throw Error('initEliteGame was not called!')
}
return await this.db.allAsyncT<EliteGame>(`select * from elite_game where register_date = ? and (player = ? or player = "\uFFFF") order by player desc`, [berlinDate, userId])
}

public async loadGamePlayAll(berlinDate: string): Promise<EliteGame[]> {
if (!this.db) {
throw Error('initEliteGame was not called!')
}
return await this.db.allAsyncT<EliteGame>(`select * from elite_game where register_date = ? and player != "\uFFFF" order by play_timestamp desc`, [berlinDate])
}

public async writeGameWinner(userId: string, berlinDate: string, playerTimestamp: number): Promise<void> {
if (!this.db) {
throw Error('initEliteGame was not called!')
}
await this.db.runAsync(`INSERT INTO elite_game_winner (player, register_date, play_timestamp, target_timestamp) VALUES(?,?,?,?)`, [userId, berlinDate, playerTimestamp, Date.now()])
}

public async writeGameRank(rank: string, userId: string): Promise<void> {
if (!this.db) {
throw Error('initEliteGame was not called!')
}
if ((await this.db.getAsyncT<{ count: number }>(`select count(*) as count from elite_game_rank where rank = ?`, [rank])).count > 0) {
await this.db.runAsync(`UPDATE elite_game_rank set player = ?, change_date = ? where rank = ?`, [userId, new Date().toISOString(), rank])
} else {
await this.db.runAsync(`INSERT INTO elite_game_rank (rank, player, change_date) VALUES(?,?,?)`, [rank, userId, new Date().toISOString()])
}
await this.db.runAsync(`DELETE FROM elite_game_rank where rank != ? and player = ?`, [rank, userId])
}
}
Loading

0 comments on commit d13fe6e

Please sign in to comment.