- Fix reminder duplicate bug: use slot-only keys to prevent multiple reminders when settings change - Add New Jackpot announcement delay setting for groups (default 5 min) - Cancel unpaid purchases after draw completes (prevents payments for past rounds) - Add BotFather commands template file - Update README documentation
224 lines
6.1 KiB
TypeScript
224 lines
6.1 KiB
TypeScript
import { botDatabase } from './database';
|
|
import { logger } from './logger';
|
|
import { GroupSettings, ReminderTime, reminderTimeToMinutes } from '../types/groups';
|
|
|
|
class GroupStateManager {
|
|
async init(): Promise<void> {
|
|
// Database is initialized separately
|
|
logger.info('Group state manager initialized (using SQLite database)');
|
|
}
|
|
|
|
/**
|
|
* Get group settings
|
|
*/
|
|
async getGroup(groupId: number): Promise<GroupSettings | null> {
|
|
return botDatabase.getGroup(groupId);
|
|
}
|
|
|
|
/**
|
|
* Register a new group
|
|
*/
|
|
async registerGroup(
|
|
groupId: number,
|
|
groupTitle: string,
|
|
addedBy: number
|
|
): Promise<GroupSettings> {
|
|
return botDatabase.registerGroup(groupId, groupTitle, addedBy);
|
|
}
|
|
|
|
/**
|
|
* Remove a group
|
|
*/
|
|
async removeGroup(groupId: number): Promise<void> {
|
|
botDatabase.removeGroup(groupId);
|
|
}
|
|
|
|
/**
|
|
* Save group settings
|
|
*/
|
|
async saveGroup(settings: GroupSettings): Promise<void> {
|
|
botDatabase.saveGroup(settings);
|
|
logger.debug('Group settings saved', { groupId: settings.groupId });
|
|
}
|
|
|
|
/**
|
|
* Update a group setting
|
|
*/
|
|
async updateSetting(
|
|
groupId: number,
|
|
setting:
|
|
| 'enabled'
|
|
| 'drawAnnouncements'
|
|
| 'reminders'
|
|
| 'newJackpotAnnouncement'
|
|
| 'ticketPurchaseAllowed'
|
|
| 'reminder1Enabled'
|
|
| 'reminder2Enabled'
|
|
| 'reminder3Enabled',
|
|
value: boolean
|
|
): Promise<GroupSettings | null> {
|
|
return botDatabase.updateGroupSetting(groupId, setting, value);
|
|
}
|
|
|
|
/**
|
|
* Update reminder time for a slot
|
|
*/
|
|
async updateReminderTime(
|
|
groupId: number,
|
|
slot: 1 | 2 | 3,
|
|
time: ReminderTime
|
|
): Promise<GroupSettings | null> {
|
|
return botDatabase.updateReminderTime(groupId, slot, time);
|
|
}
|
|
|
|
/**
|
|
* Update announcement delay
|
|
*/
|
|
async updateAnnouncementDelay(
|
|
groupId: number,
|
|
seconds: number
|
|
): Promise<GroupSettings | null> {
|
|
return botDatabase.updateAnnouncementDelay(groupId, seconds);
|
|
}
|
|
|
|
/**
|
|
* Update new jackpot announcement delay
|
|
*/
|
|
async updateNewJackpotDelay(
|
|
groupId: number,
|
|
minutes: number
|
|
): Promise<GroupSettings | null> {
|
|
return botDatabase.updateNewJackpotDelay(groupId, minutes);
|
|
}
|
|
|
|
/**
|
|
* Get groups with specific feature enabled
|
|
*/
|
|
async getGroupsWithFeature(
|
|
feature: 'enabled' | 'drawAnnouncements' | 'reminders' | 'newJackpotAnnouncement'
|
|
): Promise<GroupSettings[]> {
|
|
if (feature === 'newJackpotAnnouncement') {
|
|
const allGroups = await this.getAllGroups();
|
|
return allGroups.filter(g => g.enabled && g.newJackpotAnnouncement);
|
|
}
|
|
return botDatabase.getGroupsWithFeature(feature as 'enabled' | 'drawAnnouncements' | 'reminders');
|
|
}
|
|
|
|
/**
|
|
* Get all groups
|
|
*/
|
|
async getAllGroups(): Promise<GroupSettings[]> {
|
|
return botDatabase.getAllGroups();
|
|
}
|
|
|
|
/**
|
|
* Get groups that need reminders for a specific draw time
|
|
*/
|
|
async getGroupsNeedingReminders(drawTime: Date): Promise<Array<{
|
|
settings: GroupSettings;
|
|
reminderSlot: 1 | 2 | 3;
|
|
}>> {
|
|
const allGroups = await this.getGroupsWithFeature('reminders');
|
|
const now = new Date();
|
|
const minutesUntilDraw = (drawTime.getTime() - now.getTime()) / (1000 * 60);
|
|
const results: Array<{ settings: GroupSettings; reminderSlot: 1 | 2 | 3 }> = [];
|
|
|
|
for (const group of allGroups) {
|
|
// Check each reminder slot
|
|
if (group.reminder1Enabled) {
|
|
const reminderMinutes = reminderTimeToMinutes(group.reminder1Time);
|
|
if (Math.abs(minutesUntilDraw - reminderMinutes) < 1) {
|
|
results.push({ settings: group, reminderSlot: 1 });
|
|
}
|
|
}
|
|
|
|
if (group.reminder2Enabled) {
|
|
const reminderMinutes = reminderTimeToMinutes(group.reminder2Time);
|
|
if (Math.abs(minutesUntilDraw - reminderMinutes) < 1) {
|
|
results.push({ settings: group, reminderSlot: 2 });
|
|
}
|
|
}
|
|
|
|
if (group.reminder3Enabled) {
|
|
const reminderMinutes = reminderTimeToMinutes(group.reminder3Time);
|
|
if (Math.abs(minutesUntilDraw - reminderMinutes) < 1) {
|
|
results.push({ settings: group, reminderSlot: 3 });
|
|
}
|
|
}
|
|
}
|
|
|
|
return results;
|
|
}
|
|
|
|
/**
|
|
* Add time to a reminder
|
|
*/
|
|
async addReminderTime(
|
|
groupId: number,
|
|
slot: 1 | 2 | 3,
|
|
amount: number,
|
|
unit: 'minutes' | 'hours' | 'days'
|
|
): Promise<GroupSettings | null> {
|
|
const group = await this.getGroup(groupId);
|
|
if (!group) return null;
|
|
|
|
const timeKey = `reminder${slot}Time` as 'reminder1Time' | 'reminder2Time' | 'reminder3Time';
|
|
const currentTime = group[timeKey];
|
|
|
|
// Convert everything to minutes, add, then convert back
|
|
let totalMinutes = reminderTimeToMinutes(currentTime);
|
|
|
|
switch (unit) {
|
|
case 'minutes': totalMinutes += amount; break;
|
|
case 'hours': totalMinutes += amount * 60; break;
|
|
case 'days': totalMinutes += amount * 24 * 60; break;
|
|
}
|
|
|
|
// Ensure minimum of 1 minute
|
|
totalMinutes = Math.max(1, totalMinutes);
|
|
|
|
// Convert back to best unit
|
|
const newTime = this.minutesToReminderTime(totalMinutes);
|
|
return this.updateReminderTime(groupId, slot, newTime);
|
|
}
|
|
|
|
/**
|
|
* Remove time from a reminder
|
|
*/
|
|
async removeReminderTime(
|
|
groupId: number,
|
|
slot: 1 | 2 | 3,
|
|
amount: number,
|
|
unit: 'minutes' | 'hours' | 'days'
|
|
): Promise<GroupSettings | null> {
|
|
return this.addReminderTime(groupId, slot, -amount, unit);
|
|
}
|
|
|
|
/**
|
|
* Convert total minutes to the best ReminderTime representation
|
|
*/
|
|
private minutesToReminderTime(totalMinutes: number): ReminderTime {
|
|
// Use days if evenly divisible and >= 1 day
|
|
if (totalMinutes >= 1440 && totalMinutes % 1440 === 0) {
|
|
return { value: totalMinutes / 1440, unit: 'days' };
|
|
}
|
|
// Use hours if evenly divisible and >= 1 hour
|
|
if (totalMinutes >= 60 && totalMinutes % 60 === 0) {
|
|
return { value: totalMinutes / 60, unit: 'hours' };
|
|
}
|
|
// Use minutes
|
|
return { value: totalMinutes, unit: 'minutes' };
|
|
}
|
|
|
|
/**
|
|
* Shutdown
|
|
*/
|
|
async close(): Promise<void> {
|
|
// Database close is handled separately
|
|
logger.info('Group state manager closed');
|
|
}
|
|
}
|
|
|
|
export const groupStateManager = new GroupStateManager();
|
|
export default groupStateManager;
|