Files
LightningLotto/telegram_bot/src/services/groupState.ts
Michilis 86e2e0a321 Fix reminder scheduling and add group features
- 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
2025-12-08 23:49:54 +00:00

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;