import { GuildMember, User } from "discord.js"; import IQuota, { userQuotaData, userQuotaRecoveryData } from "../IQuota"; import { apiRequest } from "../execution"; import { database } from "../index"; /** * Quota based on Request message count and function calls */ export default class MessageCount implements IQuota { lookback: number; defaultQuota: number; constructor( defaultQuota: number = 25, lookback: number = 1000 * 60 * 60 * 24 ) { this.defaultQuota = defaultQuota; this.lookback = lookback; } private getUserQuota(id: string) { return database.limits.findUnique({ where: { user: BigInt(id) }, }); } async checkUser( user: User | GuildMember | string, request: apiRequest ): Promise { const userId: string = typeof user === "string" ? user : user.id; const userQuota = await this.getUserQuota(userId); const usedLimit = ( await database.usage.count({ select: { _all: true }, where: { user: BigInt(userId), timestamp: { gte: new Date(request.createdAt.getTime() - this.lookback), }, }, }) )._all; if (userQuota?.vip) return this.createUserQuotaData(Infinity, usedLimit); if (userQuota?.limit) return this.createUserQuotaData(userQuota.limit, usedLimit); return this.createUserQuotaData(this.defaultQuota, usedLimit); } async getUserQuotaRecovery( user: string | User | GuildMember, request: apiRequest, unitCount: number | undefined = 1 ): Promise { const userId = typeof user === "string" ? user : user.id; const [userQuota, nthUseInLimit] = await Promise.all([ this.checkUser(userId, request), database.usage.findFirst({ where: { user: BigInt(userId), timestamp: { gte: new Date(request.createdTimestamp - this.lookback), }, }, orderBy: { timestamp: "asc" }, skip: unitCount - 1, }), ]); return { ...userQuota, recoveryTimestamp: nthUseInLimit ? this.lookback + nthUseInLimit?.timestamp.valueOf() : Infinity, }; } private createUserQuotaData(quota: number, used: number): userQuotaData { const humanReadable = milisecondsToHumanReadable(this.lookback); return { quota: quota, used: used, unitName: "messages and model function calls", toString() { return `${this.used} of ${this.quota} ${this.unitName} in ${humanReadable}`; }, }; } } function milisecondsToHumanReadable(totalMiliseconds: number): string { const negative = totalMiliseconds < 0; if (negative) totalMiliseconds = -totalMiliseconds; const totalSeconds = totalMiliseconds / 1000; const totalMinutes = totalSeconds / 60; const totalHours = totalMinutes / 60; const totalDays = totalHours / 24; const totalYears = totalDays / 365.2425; //const hours = Math.floor(totalHours % 24); const minutes = Math.floor(totalMinutes % 60); const seconds = Math.floor(totalSeconds % 60); const miliseconds = Math.floor(totalMiliseconds % 1000); if (totalYears > 1.01) return `${Math.ceil(totalYears * 100) / 100} years`; if (totalDays > 16) return `${Math.ceil(totalDays * 100) / 100} days`; if (totalDays > 1 && totalDays % 1 === 0) return `${totalDays} days`; if (totalDays > 4 && totalHours % 1 === 0) return `${Math.floor(totalDays)} days ${ Math.ceil((totalHours % 24) * 100) / 100 } hours`; const textHour = totalHours === 0 ? "" : totalHours === 1 ? "1 hour" : `${totalHours} hours`; const textMinutes = minutes === 0 ? "" : minutes === 1 ? "1 minute" : `${minutes} minutes`; const textSeconds = seconds === 0 ? "" : seconds === 1 ? "1 second" : `${seconds} seconds`; const textMiliseconds = miliseconds === 0 ? "" : miliseconds === 1 ? "1 milisecond" : `${Math.ceil(miliseconds)} miliseconds`; const textMilisecondsPrecise = miliseconds === 0 ? "" : miliseconds === 1 ? "1 milisecond" : `${Math.ceil(miliseconds * 1e6) / 1e6} miliseconds`; if (totalHours >= 1) return [textHour, textMinutes].join(" ").trim(); if (totalMinutes >= 1) return [textMinutes, textSeconds].join(" ").trim(); if (totalSeconds >= 10) return [textSeconds, textMiliseconds].join(" ").trim(); if (totalMiliseconds >= 10) return [textMiliseconds].join(" ").trim(); return textMilisecondsPrecise; }