Skip to content

Commit

Permalink
Merge pull request #67 from JanosGeo/feat/dates-for-best-and-me
Browse files Browse the repository at this point in the history
Add options to !best and !me to filter by dates
  • Loading branch information
tzhf committed Apr 18, 2024
2 parents cb88269 + 45abfb9 commit 41c28c5
Show file tree
Hide file tree
Showing 4 changed files with 139 additions and 24 deletions.
47 changes: 37 additions & 10 deletions src/main/GameHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ import {
fetchMap,
parseCoordinates,
getRandomCoordsInLand,
getStreamerAvatar
getStreamerAvatar,
parseUserDate
} from './utils/gameHelper'
import { getEmoji, randomCountryFlag, selectFlag } from './lib/flags/flags'

Expand Down Expand Up @@ -545,13 +546,27 @@ export default class GameHandler {
return
}

if (message === settings.getUserStatsCmd) {
const userInfo = this.#db.getUserStats(userId)
if (message.split(' ')[0] === settings.getUserStatsCmd) {
const date = message.split(' ')[1]
const dateInfo = await parseUserDate(date)
if (dateInfo.timeStamp < 0) {
await this.#backend?.sendMessage(`${userstate['display-name']}: ${dateInfo.description}.`)
return
}
const userInfo = this.#db.getUserStats(userId, dateInfo.timeStamp)
if (!userInfo) {
await this.#backend?.sendMessage(`${userstate['display-name']} you've never guessed yet.`)
if (dateInfo.timeStamp === 0) {
await this.#backend?.sendMessage(`${userstate['display-name']} you've never guessed yet.`)
} else {
await this.#backend?.sendMessage(`${userstate['display-name']} no guesses for this time period.`)
}
} else {
await this.#backend?.sendMessage(`
${getEmoji(userInfo.flag)} ${userInfo.username} : Current streak: ${userInfo.streak}.
let msg = `${getEmoji(userInfo.flag)} ${userInfo.username}: `
if (dateInfo.description) {
msg += `Stats for ${dateInfo.description}: `
}
msg += `
Current streak: ${userInfo.streak}.
Best streak: ${userInfo.bestStreak}.
Correct countries: ${userInfo.correctGuesses}/${userInfo.nbGuesses}${
userInfo.nbGuesses > 0
Expand All @@ -561,13 +576,20 @@ export default class GameHandler {
Avg. score: ${Math.round(userInfo.meanScore)}.
Victories: ${userInfo.victories}.
5ks: ${userInfo.perfects}.
`)
`
await this.#backend?.sendMessage(msg)
}
return
}

if (message === settings.getBestStatsCmd) {
const { streak, victories, perfects } = this.#db.getGlobalStats()
if (message.split(' ')[0] === settings.getBestStatsCmd) {
const date = message.split(' ')[1]
const dateInfo = await parseUserDate(date)
if (dateInfo.timeStamp < 0) {
await this.#backend?.sendMessage(`${userstate['display-name']}: ${dateInfo.description}.`)
return
}
const { streak, victories, perfects } = this.#db.getGlobalStats(dateInfo.timeStamp)
if (!streak && !victories && !perfects) {
await this.#backend?.sendMessage('No stats available.')
} else {
Expand All @@ -581,7 +603,12 @@ export default class GameHandler {
if (perfects) {
msg += `5ks: ${perfects.perfects} (${perfects.username}). `
}
await this.#backend?.sendMessage(`Channels best: ${msg}`)
if (!dateInfo.description)
{
await this.#backend?.sendMessage(`Channels best: ${msg}`)
} else {
await this.#backend?.sendMessage(`Best ${dateInfo.description}: ${msg}`)
}
}
return
}
Expand Down
30 changes: 17 additions & 13 deletions src/main/utils/Database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -748,24 +748,24 @@ class db {
})
}

getUserStats(userId: string) {
getUserStats(userId: string, sinceTimestamp: number = 0) {
const stmt = this.#db.prepare(`
SELECT
username,
flag,
COALESCE(current_streak.count, 0) AS current_streak,
COALESCE((SELECT MAX(count) FROM streaks WHERE user_id = :id AND updated_at > users.reset_at), 0) AS best_streak,
(SELECT COUNT(*) FROM guesses WHERE user_id = :id AND created_at > users.reset_at) AS total_guesses,
(SELECT COUNT(*) FROM guesses WHERE user_id = :id AND streak > 0 AND created_at > users.reset_at) AS correct_guesses,
(SELECT COUNT(*) FROM guesses WHERE user_id = :id AND score = 5000 AND created_at > users.reset_at) AS perfects,
(SELECT AVG(score) FROM guesses WHERE user_id = users.id AND created_at > users.reset_at) AS average,
(SELECT COUNT(*) FROM game_winners WHERE user_id = users.id AND created_at > users.reset_at) AS victories
COALESCE((SELECT MAX(count) FROM streaks WHERE user_id = :id AND updated_at > users.reset_at AND updated_at > :since), 0) AS best_streak,
(SELECT COUNT(*) FROM guesses WHERE user_id = :id AND created_at > users.reset_at AND created_at > :since) AS total_guesses,
(SELECT COUNT(*) FROM guesses WHERE user_id = :id AND streak > 0 AND created_at > users.reset_at AND created_at > :since) AS correct_guesses,
(SELECT COUNT(*) FROM guesses WHERE user_id = :id AND score = 5000 AND created_at > users.reset_at AND created_at > :since) AS perfects,
(SELECT AVG(score) FROM guesses WHERE user_id = users.id AND created_at > users.reset_at AND created_at > :since) AS average,
(SELECT COUNT(*) FROM game_winners WHERE user_id = users.id AND created_at > users.reset_at AND created_at > :since) AS victories
FROM users
LEFT JOIN streaks current_streak ON current_streak.id = users.current_streak_id
WHERE users.id = :id
`)

const record = stmt.get({ id: userId }) as
const record = stmt.get({ id: userId, since: sinceTimestamp}) as
| {
username: string
flag: string
Expand Down Expand Up @@ -794,13 +794,14 @@ class db {
: undefined
}

getGlobalStats() {
getGlobalStats(sinceTime: number = 0) {
const streakQuery = this.#db.prepare(`
SELECT users.id, users.username, MAX(streaks.count) AS streak
FROM users, streaks
WHERE NOT users.id = 'BROADCASTER'
AND streaks.user_id = users.id
AND streaks.created_at > users.reset_at
AND streaks.created_at > :since
GROUP BY users.id
ORDER BY streak DESC
`)
Expand All @@ -811,27 +812,30 @@ class db {
WHERE NOT users.id = 'BROADCASTER'
AND users.id = game_winners.user_id
AND game_winners.created_at > users.reset_at
AND game_winners.created_at > :since
GROUP BY users.id
ORDER BY victories DESC
`)

const perfectQuery = this.#db.prepare(`
SELECT users.id, users.username, COUNT(guesses.id) AS perfects
FROM users
LEFT JOIN guesses ON guesses.user_id = users.id AND guesses.created_at > users.reset_at
LEFT JOIN guesses ON guesses.user_id = users.id
AND guesses.created_at > users.reset_at
AND guesses.created_at > :since
WHERE NOT users.id = 'BROADCASTER'
AND guesses.score = 5000
GROUP BY users.id
ORDER BY perfects DESC
`)

const bestStreak = streakQuery.get() as
const bestStreak = streakQuery.get({ since: sinceTime }) as
| { id: string; username: string; streak: number }
| undefined
const mostVictories = victoriesQuery.get() as
const mostVictories = victoriesQuery.get({ since: sinceTime }) as
| { id: string; username: string; victories: number }
| undefined
const mostPerfects = perfectQuery.get() as
const mostPerfects = perfectQuery.get({ since: sinceTime }) as
| { id: string; username: string; perfects: number }
| undefined

Expand Down
49 changes: 48 additions & 1 deletion src/main/utils/gameHelper.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { describe, it, expect } from 'vitest'
import { afterEach, beforeEach, describe, it, expect, vi } from 'vitest'
import * as GameHelper from './gameHelper'

describe('getCountryCode', () => {
Expand Down Expand Up @@ -118,3 +118,50 @@ describe('randomPlonk', () => {
}
})
})

describe('parseUserDate', () => {
beforeEach(() => {
vi.useFakeTimers()
})

afterEach(() => {
vi.useRealTimers()
})

it('Handles undefined date as epoch=0', async () => {
expect(await GameHelper.parseUserDate(undefined)).toEqual({timeStamp: 0})
})

it('Handles empty date as epoch=0', async () => {
expect(await GameHelper.parseUserDate("")).toEqual({timeStamp: 0})
})

it('Parses "day" correctly', async () => {
vi.setSystemTime(new Date("2000-01-17T16:01:23"))
const dateInfo = await GameHelper.parseUserDate("day")
expect(dateInfo.timeStamp).toEqual(GameHelper.dateToUnixTimestamp(new Date("2000-01-17T00:00:00")))
})

it('Parses "week" correctly', async () => {
const monday = "2024-04-15:01:23"
vi.setSystemTime(new Date(monday))
let dateInfo = await GameHelper.parseUserDate("week")
expect(dateInfo.timeStamp).toEqual(GameHelper.dateToUnixTimestamp(new Date("2024-04-15:00:00")))
const sunday = "2024-04-21:01:23"
vi.setSystemTime(new Date(sunday))
dateInfo = await GameHelper.parseUserDate("week")
expect(dateInfo.timeStamp).toEqual(GameHelper.dateToUnixTimestamp(new Date("2024-04-15:00:00")))
})

it('Parses "month" correctly', async () => {
vi.setSystemTime(new Date("2001-01-17T16:01:23"))
const dateInfo = await GameHelper.parseUserDate("month")
expect(dateInfo.timeStamp).toEqual(GameHelper.dateToUnixTimestamp(new Date("2001-01-01T00:00:00")))
})

it('Parses "year" correctly', async () => {
vi.setSystemTime(new Date("2002-04-17T16:01:23"))
const dateInfo = await GameHelper.parseUserDate("year")
expect(dateInfo.timeStamp).toEqual(GameHelper.dateToUnixTimestamp(new Date("2002-01-01T00:00:00")))
})
})
37 changes: 37 additions & 0 deletions src/main/utils/gameHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -223,3 +223,40 @@ export async function getStreamerAvatar(channel: string): Promise<{ avatar: stri
return { avatar: undefined }
}
}

export function dateToUnixTimestamp(date: Date): number
{
return Math.floor(date.getTime() / 1000)
}

/**
* Parses a user-defined string that represents a date into a Unix timestamp. Will set timestamp to > 0 on success
* or == 0 for an empty input, and -1 on unrecognized dates.
*/
export async function parseUserDate(userDateStr: string | undefined): Promise<{ timeStamp: number, description: string | undefined}> {
let timeStamp = -1
let description: string | undefined = "Unsupported date (supported dates: 'day', 'week', 'month', 'year')."
if (!userDateStr) {
timeStamp = 0
description = undefined
}
if (userDateStr === "day" || userDateStr === "today") {
timeStamp = dateToUnixTimestamp(new Date(new Date().setHours(0,0,0,0)))
description = "today"
} else if(userDateStr === "week") {
const lastMidnight = new Date(new Date().setHours(0,0,0,0))
var day = lastMidnight.getDay() || 7; // Convert sunday to 7
lastMidnight.setHours(-24 * (day - 1));
timeStamp = dateToUnixTimestamp(lastMidnight)
description = "this week"
} else if(userDateStr === "month") {
const now = new Date()
timeStamp = dateToUnixTimestamp(new Date(now.getFullYear(), now.getMonth(), 1))
description = "this month"
} else if(userDateStr === "year") {
const now = new Date()
timeStamp = dateToUnixTimestamp(new Date(now.getFullYear(), 0, 1))
description = "this year"
}
return {timeStamp: timeStamp, description: description}
}

0 comments on commit 41c28c5

Please sign in to comment.