Add SQLite database for Telegram bot user/group settings

- Replace Redis/in-memory storage with SQLite for persistence
- Add database.ts service with tables for users, groups, purchases, participants
- Update state.ts and groupState.ts to use SQLite backend
- Fix buyer_name to use display name instead of Telegram ID
- Remove legacy reminder array handlers (now using 3-slot system)
- Add better-sqlite3 dependency, remove ioredis
- Update env.example with BOT_DATABASE_PATH option
- Add data/ directory to .gitignore for database files
This commit is contained in:
Michilis
2025-12-08 22:33:40 +00:00
parent dd6b26c524
commit 13fd2b8989
24 changed files with 3354 additions and 637 deletions

View File

@@ -18,7 +18,7 @@ export const messages = {
ticketNotFound: '❌ Ticket not found.',
fetchTicketDetailsFailed: '❌ Failed to fetch ticket details.',
checkStatusFailed: '❌ Failed to check status',
noPendingPurchase: '❌ No pending purchase. Please start again with /buy',
noPendingPurchase: '❌ No pending purchase. Please start again with /buyticket',
setAddressFirst: '❌ Please set your Lightning Address first.',
},
@@ -42,6 +42,21 @@ You can buy Bitcoin Lightning lottery tickets, and if you win, your prize is pai
Please send your Lightning Address now:`,
needAddressWithOptions: (username?: string) => {
let msg = `Before you can play, I need your Lightning Address to send any winnings.\n\n`;
if (username) {
msg += `🤖 *Quick Setup* - Choose a tip bot below:\n\n`;
msg += `Or send me your Lightning Address to set a custom one.\n`;
msg += `*Example:* \`yourname@getalby.com\``;
} else {
msg += `*Example:* \`yourname@getalby.com\`\n\n`;
msg += `Please send your Lightning Address now:`;
}
return msg;
},
addressSet: (address: string) =>
`✅ Your payout address is set to: \`${address}\`
@@ -60,12 +75,40 @@ Use the menu below to get started! Good luck! 🍀`,
Send me your new Lightning Address to update it:`,
currentAddressWithOptions: (address: string, username?: string) => {
let msg = `⚡ *Your Current Payout Address:*\n\`${address}\`\n\n`;
if (username) {
msg += `🤖 *Quick Options* - Choose a tip bot below:\n\n`;
msg += `Or send me your Lightning Address to set a custom one.`;
} else {
msg += `Send me your new Lightning Address to update it:`;
}
return msg;
},
noAddressSet: `⚡ You don't have a Lightning Address set yet.
Send me your Lightning Address now:
*Example:* \`yourname@getalby.com\``,
noAddressSetWithOptions: (username?: string) => {
let msg = `⚡ You don't have a Lightning Address set yet.\n\n`;
if (username) {
msg += `🤖 *Quick Setup* - Choose a tip bot below:\n\n`;
msg += `Or send me your Lightning Address to set a custom one.\n`;
msg += `*Example:* \`yourname@getalby.com\``;
} else {
msg += `Send me your Lightning Address now:\n\n`;
msg += `*Example:* \`yourname@getalby.com\``;
}
return msg;
},
invalidFormat: `❌ That doesn't look like a valid Lightning Address.
*Format:* \`username@domain.com\`
@@ -73,6 +116,31 @@ Send me your Lightning Address now:
Please try again:`,
verifying: '🔍 Verifying your Lightning Address...',
verificationFailed: (address: string, error?: string) =>
`❌ *Could not verify Lightning Address*
Address: \`${address}\`
${error ? `Error: ${error}\n` : ''}
Please check the address and try again, or choose a different option:`,
verifyingService: (service: string, address: string) =>
`🔍 Verifying your ${service} address: \`${address}\`...`,
serviceNotSetup: (service: string, error?: string) =>
`❌ *${service} address not found*
It looks like you haven't set up ${service} yet, or your username doesn't match.
${error ? `Error: ${error}\n\n` : ''}Please set up ${service} first, or enter a different Lightning Address:`,
noUsername: `❌ You don't have a Telegram username set!
To use 21Tipbot or Bittip, you need a Telegram username.
Please set a username in Telegram settings, or enter a custom Lightning Address:`,
firstTimeSuccess: (address: string) =>
`✅ *Perfect!* I'll use \`${address}\` to send any winnings.
@@ -153,16 +221,14 @@ Confirm this purchase?`,
invoiceCaption: (
ticketCount: number,
totalAmount: string,
paymentRequest: string,
expiryMinutes: number
) =>
`🎟 *${ticketCount} ticket${ticketCount > 1 ? 's' : ''}*
💰 *Amount:* ${totalAmount} sats
⏳ Expires in ${expiryMinutes} minutes`,
\`${paymentRequest}\`
⏳ This invoice expires in ${expiryMinutes} minutes.
I'll notify you when payment is received!`,
invoiceString: (paymentRequest: string) =>
`\`${paymentRequest}\``,
paymentReceived: (ticketNumbers: string, drawTime: string) =>
`🎉 *Payment Received!*
@@ -180,13 +246,13 @@ Good luck! 🍀 I'll notify you after the draw!`,
No payment was received in time. No tickets were issued.
Use /buy to try again.`,
Use /buyticket to try again.`,
invoiceExpiredShort: `❌ *Invoice Expired*
This invoice has expired. No tickets were issued.
Use /buy to try again.`,
Use /buyticket to try again.`,
jackpotUnavailable: '❌ Jackpot is no longer available.',
},
@@ -201,13 +267,13 @@ Use /buy to try again.`,
You haven't purchased any tickets yet!
Use /buy to get started! 🎟`,
Use /buyticket to get started! 🎟`,
notFound: `🧾 *Your Tickets*
No ticket purchases found. Purchase history may have expired.
Use /buy to get new tickets! 🎟`,
Use /buyticket to get new tickets! 🎟`,
tapForDetails: `\nTap a ticket below for details:`,
@@ -266,13 +332,13 @@ ${statusSection}`,
You haven't purchased any tickets yet, so no wins to show!
Use /buy to get started! 🎟`,
Use /buyticket to get started! 🎟`,
noWinsYet: `🏆 *Your Wins*
You haven't won any jackpots yet. Keep playing!
Use /buy to get more tickets! 🎟🍀`,
Use /buyticket to get more tickets! 🎟🍀`,
header: (totalWinnings: string, paidWinnings: string) =>
`🏆 *Your Wins*
@@ -300,20 +366,47 @@ This is the Lightning Jackpot lottery bot! Buy tickets with Bitcoin Lightning, a
5⃣ If you win, sats are sent to your address instantly!
*Commands:*
• /buy — Buy lottery tickets
• /buyticket — Buy lottery tickets
• /tickets — View your tickets
• /wins — View your past wins
• /address — Update Lightning Address
• /menu — Show main menu
• /help — Show this help
• /lottoaddress — Update Lightning Address
• /lottomenu — Show main menu
• /lottohelp — Show this help
• /jackpot — View current jackpot info
*Settings (use ⚙️ Settings button):*
• Set your display name (shown if you win)
• Enable/disable draw reminders
• Enable/disable draw results notifications
• Enable/disable new jackpot alerts
*Tips:*
🎟 Each ticket is one chance to win
💰 Prize pool grows with each ticket sold
⚡ Winnings are paid instantly via Lightning
🔔 You'll be notified after every draw
🔔 You'll be notified after every draw you participate in
Good luck! 🍀`,
groupMessage: `⚡🎰 *Lightning Jackpot Bot - Group Help* 🎰⚡
*User Commands:*
• /jackpot — View current jackpot info
• /buyticket — Buy lottery tickets
• /lottohelp — Show this help
*Admin Commands:*
• /lottosettings — Configure bot settings for this group
*Admin Settings:*
👉 *Bot Enabled* — Enable/disable the bot in this group
👉 *Draw Announcements* — Announce draw winners in this group
👉 *Draw Reminders* — Send reminders before draws
👉 *Ticket Purchases* — Allow /buyticket command in group
💡 *Tip:* For privacy, ticket purchases in groups can be disabled. Users can always buy tickets by messaging the bot directly.
To buy tickets privately, message me directly!`,
},
// ═══════════════════════════════════════════════════════════════════════════
@@ -371,18 +464,104 @@ Good luck next round! 🍀`,
Congratulations to the winner! ⚡
Use /buy to enter the next draw! 🍀`,
Use /buyticket to enter the next draw! 🍀`,
drawReminder: (potSats: string, drawTime: string, timeLeft: string) =>
`⏰ *Draw Reminder!*
drawCompleted: (potSats: number, hasWinner: boolean) =>
hasWinner
? `🎰 *JACKPOT DRAW COMPLETE!* 🎰
🎰 The next Lightning Jackpot draw is coming up!
💰 *Jackpot:* ${potSats.toLocaleString()} sats
🏆 A winner has been selected!
💰 *Current Prize Pool:* ${potSats} sats
🕐 *Draw Time:* ${drawTime}
⏳ *Time Left:* ${timeLeft}
Use /buyticket to enter the next round! 🍀`
: `🎰 *JACKPOT DRAW COMPLETE!* 🎰
Don't miss your chance to win! Use /buy to get your tickets! 🎟`,
No tickets were sold this round.
The jackpot rolls over to the next draw!
Use /buyticket to be the first to enter! 🍀`,
drawReminder: (value: number, unit: string, drawTime: Date, potSats: number) => {
const timeStr = unit === 'days'
? `${value} day${value > 1 ? 's' : ''}`
: unit === 'hours'
? `${value} hour${value > 1 ? 's' : ''}`
: `${value} minute${value > 1 ? 's' : ''}`;
const drawTimeStr = drawTime.toLocaleString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
timeZoneName: 'short',
});
return `⏰ *Draw Reminder!*
🎰 The next Lightning Jackpot draw is in *${timeStr}*!
💰 *Current Prize Pool:* ${potSats.toLocaleString()} sats
🕐 *Draw Time:* ${drawTimeStr}
Don't miss your chance to win! Use /buyticket to get your tickets! 🎟`;
},
newJackpot: (lotteryName: string, ticketPrice: number, drawTime: Date) => {
const drawTimeStr = drawTime.toLocaleString('en-US', {
weekday: 'short',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
timeZoneName: 'short',
});
return `🎉 *NEW JACKPOT STARTED!* 🎉
⚡ *${lotteryName}*
A new lottery round has begun!
🎟 *Ticket Price:* ${ticketPrice} sats
🕐 *Draw Time:* ${drawTimeStr}
Be first to enter! Use /buyticket to buy tickets! 🍀`;
},
},
// ═══════════════════════════════════════════════════════════════════════════
// USER SETTINGS
// ═══════════════════════════════════════════════════════════════════════════
settings: {
overview: (displayName: string, notifications: { drawReminders: boolean; drawResults: boolean; newJackpotAlerts: boolean }) =>
`⚙️ *Your Settings*
👤 *Display Name:* ${displayName}
_(Used when announcing winners)_
*Notifications:*
${notifications.drawReminders ? '✅' : '❌'} Draw Reminders _(15 min before draws)_
${notifications.drawResults ? '✅' : '❌'} Draw Results _(when your tickets are drawn)_
${notifications.newJackpotAlerts ? '✅' : '❌'} New Jackpot Alerts _(when new rounds start)_
Tap buttons below to change settings:`,
enterDisplayName: `👤 *Set Display Name*
Enter your display name (max 20 characters).
This name will be shown if you win!
_Send "Anon" to stay anonymous._`,
nameTooLong: '❌ Display name must be 20 characters or less. Please try again:',
nameUpdated: (name: string) =>
`✅ *Display Name Updated!*
Your display name is now: *${name}*
This will be shown when announcing winners.`,
},
// ═══════════════════════════════════════════════════════════════════════════
@@ -397,16 +576,16 @@ Hello *${groupName}*! I'm the Lightning Jackpot lottery bot.
I can announce lottery draws and remind you when jackpots are coming up!
*Group Admin Commands:*
• /settings — Configure bot settings for this group
• /lottosettings — Configure bot settings for this group
*User Commands:*
• /buy — Buy lottery tickets (in DM)
• /buyticket — Buy lottery tickets
• /jackpot — View current jackpot info
• /help — Get help
• /lottohelp — Get help
To buy tickets, message me directly @LightningLottoBot! 🎟`,
To buy tickets privately, message me directly! 🎟`,
privateChat: '❌ This command only works in groups. Use /menu to see available commands.',
privateChat: '❌ This command only works in groups. Use /lottomenu to see available commands.',
adminOnly: '⚠️ Only group administrators can change these settings.',
@@ -415,19 +594,59 @@ To buy tickets, message me directly @LightningLottoBot! 🎟`,
enabled: boolean;
drawAnnouncements: boolean;
reminders: boolean;
newJackpotAnnouncement?: boolean;
ticketPurchaseAllowed: boolean;
}) =>
`⚙️ *Group Settings*
reminder1Enabled?: boolean;
reminder1Time?: { value: number; unit: string };
reminder2Enabled?: boolean;
reminder2Time?: { value: number; unit: string };
reminder3Enabled?: boolean;
reminder3Time?: { value: number; unit: string };
announcementDelaySeconds?: number;
}) => {
const announceDelay = settings.announcementDelaySeconds ?? 10;
const formatAnnounce = announceDelay === 0
? 'Immediately'
: announceDelay >= 60
? `${announceDelay / 60} min after draw`
: `${announceDelay}s after draw`;
// Format helper for reminder times
const formatTime = (t?: { value: number; unit: string }) => {
if (!t) return '?';
if (t.unit === 'minutes') return `${t.value}m`;
if (t.unit === 'hours') return t.value === 1 ? '1h' : `${t.value}h`;
return t.value === 1 ? '1d' : `${t.value}d`;
};
// Format reminder times (3-tier system)
const r1 = settings.reminder1Enabled !== false;
const r2 = settings.reminder2Enabled === true;
const r3 = settings.reminder3Enabled === true;
const activeReminders: string[] = [];
if (r1) activeReminders.push(formatTime(settings.reminder1Time) || '1h');
if (r2) activeReminders.push(formatTime(settings.reminder2Time) || '1d');
if (r3) activeReminders.push(formatTime(settings.reminder3Time) || '6d');
const formatReminderList = activeReminders.length > 0
? activeReminders.join(', ')
: 'None';
const newJackpot = settings.newJackpotAnnouncement !== false;
return `⚙️ *Group Settings*
📍 *Group:* ${settings.groupTitle}
*Current Configuration:*
${settings.enabled ? '✅' : '❌'} Bot Enabled
${settings.drawAnnouncements ? '✅' : '❌'} Draw Announcements
${settings.reminders ? '✅' : '❌'} Draw Reminders
${newJackpot ? '✅' : '❌'} New Jackpot Announcements
${settings.drawAnnouncements ? '✅' : '❌'} Draw Announcements ${settings.drawAnnouncements ? `_(${formatAnnounce})_` : ''}
${settings.reminders ? '✅' : '❌'} Draw Reminders ${settings.reminders ? `_(${formatReminderList})_` : ''}
${settings.ticketPurchaseAllowed ? '✅' : '❌'} Ticket Purchases in Group
Tap a button below to toggle settings:`,
_Tap buttons to toggle features or adjust times._
_This message will auto-delete in 2 minutes._`;
},
settingUpdated: (setting: string, enabled: boolean) =>
`✅ *${setting}* has been ${enabled ? 'enabled' : 'disabled'}.`,