feat: Add Telegram bot with group support
- Full Telegram bot implementation for Lightning Jackpot - Commands: /start, /buy, /tickets, /wins, /address, /jackpot, /help - Lightning invoice generation with QR codes - Payment polling and confirmation notifications - User state management (Redis/in-memory fallback) - Group support with admin settings panel - Configurable draw announcements and reminders - Centralized messages for easy i18n - Docker configuration included
This commit is contained in:
145
telegram_bot/src/services/api.ts
Normal file
145
telegram_bot/src/services/api.ts
Normal file
@@ -0,0 +1,145 @@
|
||||
import axios, { AxiosInstance, AxiosError } from 'axios';
|
||||
import config from '../config';
|
||||
import { logger, logApiCall } from './logger';
|
||||
import {
|
||||
ApiResponse,
|
||||
JackpotNextResponse,
|
||||
BuyTicketsResponse,
|
||||
TicketStatusResponse,
|
||||
} from '../types';
|
||||
|
||||
class ApiClient {
|
||||
private client: AxiosInstance;
|
||||
|
||||
constructor() {
|
||||
this.client = axios.create({
|
||||
baseURL: config.api.baseUrl,
|
||||
timeout: 30000,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
});
|
||||
|
||||
// Response interceptor for logging
|
||||
this.client.interceptors.response.use(
|
||||
(response) => {
|
||||
logApiCall(
|
||||
response.config.url || '',
|
||||
response.config.method?.toUpperCase() || 'GET',
|
||||
response.status
|
||||
);
|
||||
return response;
|
||||
},
|
||||
(error: AxiosError) => {
|
||||
logApiCall(
|
||||
error.config?.url || '',
|
||||
error.config?.method?.toUpperCase() || 'GET',
|
||||
error.response?.status,
|
||||
error.message
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get next jackpot cycle information
|
||||
*/
|
||||
async getNextJackpot(): Promise<JackpotNextResponse | null> {
|
||||
try {
|
||||
const response = await this.client.get<ApiResponse<JackpotNextResponse>>(
|
||||
'/jackpot/next'
|
||||
);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const status = error.response?.status;
|
||||
if (status === 503) {
|
||||
// No active lottery or cycle
|
||||
return null;
|
||||
}
|
||||
}
|
||||
logger.error('Failed to get next jackpot', { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Buy lottery tickets
|
||||
*/
|
||||
async buyTickets(
|
||||
tickets: number,
|
||||
lightningAddress: string,
|
||||
telegramUserId: number
|
||||
): Promise<BuyTicketsResponse> {
|
||||
try {
|
||||
const response = await this.client.post<ApiResponse<BuyTicketsResponse>>(
|
||||
'/jackpot/buy',
|
||||
{
|
||||
tickets,
|
||||
lightning_address: lightningAddress,
|
||||
buyer_name: `TG:${telegramUserId}`,
|
||||
}
|
||||
);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error)) {
|
||||
const errorData = error.response?.data as ApiResponse<unknown>;
|
||||
if (errorData?.error) {
|
||||
throw new Error(errorData.message || errorData.error);
|
||||
}
|
||||
}
|
||||
logger.error('Failed to buy tickets', { error });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get ticket purchase status
|
||||
*/
|
||||
async getTicketStatus(purchaseId: string): Promise<TicketStatusResponse | null> {
|
||||
try {
|
||||
const response = await this.client.get<ApiResponse<TicketStatusResponse>>(
|
||||
`/tickets/${purchaseId}`
|
||||
);
|
||||
return response.data.data;
|
||||
} catch (error) {
|
||||
if (axios.isAxiosError(error) && error.response?.status === 404) {
|
||||
return null;
|
||||
}
|
||||
logger.error('Failed to get ticket status', { error, purchaseId });
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get user's ticket purchases by lightning address
|
||||
* Note: This queries tickets by lightning address pattern matching
|
||||
*/
|
||||
async getUserTickets(
|
||||
telegramUserId: number,
|
||||
limit: number = 10,
|
||||
offset: number = 0
|
||||
): Promise<TicketStatusResponse[]> {
|
||||
// Since the backend doesn't have Telegram-specific endpoints,
|
||||
// we'll need to track purchases locally in state
|
||||
// This is a placeholder for future backend integration
|
||||
return [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Health check
|
||||
*/
|
||||
async healthCheck(): Promise<boolean> {
|
||||
try {
|
||||
await this.client.get('/jackpot/next');
|
||||
return true;
|
||||
} catch (error) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const apiClient = new ApiClient();
|
||||
export default apiClient;
|
||||
|
||||
Reference in New Issue
Block a user