mirror of
https://github.com/Memo-2023/mana-monorepo.git
synced 2026-05-14 20:21:09 +02:00
✨ feat(matrix-bots): enhance stats and todo bots
- Add credit commands to todo-bot - Enhance stats-bot with improved metrics - Add Umami analytics improvements
This commit is contained in:
parent
e8c3b97f8f
commit
087d34c552
4 changed files with 151 additions and 19 deletions
|
|
@ -155,25 +155,45 @@ Daten von Umami Analytics (self-hosted).`;
|
|||
|
||||
private async sendStats(roomId: string) {
|
||||
await this.sendMessage(roomId, '📊 Lade Statistiken...');
|
||||
const report = await this.analyticsService.generateStatsOverview();
|
||||
await this.sendMessage(roomId, report);
|
||||
try {
|
||||
const report = await this.analyticsService.generateStatsOverview();
|
||||
await this.sendMessage(roomId, report);
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to generate stats overview:', error);
|
||||
await this.sendMessage(roomId, `❌ Fehler beim Laden der Statistiken: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async sendToday(roomId: string) {
|
||||
await this.sendMessage(roomId, '📊 Lade heutige Statistiken...');
|
||||
const report = await this.analyticsService.generateDailyReport();
|
||||
await this.sendMessage(roomId, report);
|
||||
try {
|
||||
const report = await this.analyticsService.generateDailyReport();
|
||||
await this.sendMessage(roomId, report);
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to generate daily report:', error);
|
||||
await this.sendMessage(roomId, `❌ Fehler beim Laden: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async sendWeek(roomId: string) {
|
||||
await this.sendMessage(roomId, '📊 Lade Wochenstatistiken...');
|
||||
const report = await this.analyticsService.generateWeeklyReport();
|
||||
await this.sendMessage(roomId, report);
|
||||
try {
|
||||
const report = await this.analyticsService.generateWeeklyReport();
|
||||
await this.sendMessage(roomId, report);
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to generate weekly report:', error);
|
||||
await this.sendMessage(roomId, `❌ Fehler beim Laden: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async sendRealtime(roomId: string) {
|
||||
const report = await this.analyticsService.generateRealtimeReport();
|
||||
await this.sendMessage(roomId, report);
|
||||
try {
|
||||
const report = await this.analyticsService.generateRealtimeReport();
|
||||
await this.sendMessage(roomId, report);
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to generate realtime report:', error);
|
||||
await this.sendMessage(roomId, `❌ Fehler beim Laden: ${error instanceof Error ? error.message : String(error)}`);
|
||||
}
|
||||
}
|
||||
|
||||
private async sendUsers(roomId: string) {
|
||||
|
|
|
|||
|
|
@ -30,11 +30,18 @@ export class UmamiService implements OnModuleInit {
|
|||
}
|
||||
|
||||
async onModuleInit() {
|
||||
await this.authenticate();
|
||||
try {
|
||||
await this.authenticate();
|
||||
} catch (error) {
|
||||
this.logger.warn('Initial Umami auth failed, will retry on first request');
|
||||
}
|
||||
}
|
||||
|
||||
private async authenticate(): Promise<void> {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000);
|
||||
|
||||
const response = await fetch(`${this.apiUrl}/api/auth/login`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
|
|
@ -42,10 +49,13 @@ export class UmamiService implements OnModuleInit {
|
|||
username: this.username,
|
||||
password: this.password,
|
||||
}),
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Auth failed: ${response.status}`);
|
||||
throw new Error(`Umami auth failed: ${response.status}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
|
@ -53,34 +63,51 @@ export class UmamiService implements OnModuleInit {
|
|||
this.logger.log('Umami authenticated successfully');
|
||||
} catch (error) {
|
||||
this.logger.error('Failed to authenticate with Umami:', error);
|
||||
this.accessToken = null;
|
||||
throw error instanceof Error ? error : new Error('Umami authentication failed');
|
||||
}
|
||||
}
|
||||
|
||||
private async request<T>(endpoint: string): Promise<T | null> {
|
||||
private async request<T>(endpoint: string, retryCount = 0): Promise<T | null> {
|
||||
if (!this.accessToken) {
|
||||
await this.authenticate();
|
||||
}
|
||||
|
||||
if (!this.accessToken) {
|
||||
throw new Error('Umami nicht authentifiziert - prüfe UMAMI_API_URL und Credentials');
|
||||
}
|
||||
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 10000); // 10s timeout
|
||||
|
||||
const response = await fetch(`${this.apiUrl}${endpoint}`, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${this.accessToken}`,
|
||||
},
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
if (response.status === 401) {
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
if (response.status === 401 && retryCount < 1) {
|
||||
this.accessToken = null;
|
||||
await this.authenticate();
|
||||
return this.request(endpoint);
|
||||
return this.request(endpoint, retryCount + 1);
|
||||
}
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Request failed: ${response.status}`);
|
||||
throw new Error(`Umami API Fehler: ${response.status}`);
|
||||
}
|
||||
|
||||
return response.json();
|
||||
} catch (error) {
|
||||
if (error instanceof Error && error.name === 'AbortError') {
|
||||
this.logger.error(`Umami request timeout: ${endpoint}`);
|
||||
throw new Error('Umami API Timeout - Server nicht erreichbar?');
|
||||
}
|
||||
this.logger.error(`Umami request failed: ${endpoint}`, error);
|
||||
return null;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import {
|
|||
TranscriptionModule,
|
||||
SessionModule,
|
||||
CreditModule,
|
||||
GiftModule,
|
||||
TodoApiService,
|
||||
I18nModule,
|
||||
} from '@manacore/bot-services';
|
||||
|
|
@ -25,6 +26,7 @@ const todoApiServiceProvider = {
|
|||
TranscriptionModule.forRoot(),
|
||||
SessionModule.forRoot({ storageMode: 'redis' }),
|
||||
CreditModule.forRoot(),
|
||||
GiftModule.forRoot(),
|
||||
I18nModule.forRoot(),
|
||||
],
|
||||
providers: [MatrixService, todoApiServiceProvider],
|
||||
|
|
|
|||
|
|
@ -6,11 +6,16 @@ import {
|
|||
MatrixRoomEvent,
|
||||
KeywordCommandDetector,
|
||||
COMMON_KEYWORDS,
|
||||
handleCreditCommand,
|
||||
handleGiftCommand,
|
||||
type CreditCommandsHost,
|
||||
type GiftCommandsHost,
|
||||
} from '@manacore/matrix-bot-common';
|
||||
import {
|
||||
TranscriptionService,
|
||||
SessionService,
|
||||
CreditService,
|
||||
GiftService,
|
||||
TodoApiService,
|
||||
Task as ApiTask,
|
||||
I18nService,
|
||||
|
|
@ -26,7 +31,12 @@ const TASK_CREATE_CREDITS = 0.02;
|
|||
type Task = ApiTask;
|
||||
|
||||
@Injectable()
|
||||
export class MatrixService extends BaseMatrixService {
|
||||
export class MatrixService extends BaseMatrixService implements CreditCommandsHost, GiftCommandsHost {
|
||||
// Expose services for credit and gift commands mixins
|
||||
public creditService: CreditService;
|
||||
public giftService: GiftService;
|
||||
public i18nService: I18nService;
|
||||
public sessionService: SessionService;
|
||||
private readonly keywordDetector = new KeywordCommandDetector(
|
||||
[
|
||||
...COMMON_KEYWORDS,
|
||||
|
|
@ -55,6 +65,9 @@ export class MatrixService extends BaseMatrixService {
|
|||
{ keywords: ['login', 'anmelden'], command: 'login' },
|
||||
{ keywords: ['logout', 'abmelden'], command: 'logout' },
|
||||
{ keywords: ['sprache', 'language', 'lang'], command: 'language' },
|
||||
{ keywords: ['credits', 'guthaben', 'kontostand'], command: 'credits' },
|
||||
{ keywords: ['packages', 'pakete', 'preise'], command: 'packages' },
|
||||
{ keywords: ['kaufen', 'buy'], command: 'buy' },
|
||||
],
|
||||
{ partialMatch: true }
|
||||
);
|
||||
|
|
@ -63,13 +76,59 @@ export class MatrixService extends BaseMatrixService {
|
|||
configService: ConfigService,
|
||||
private todoApiService: TodoApiService,
|
||||
private transcriptionService: TranscriptionService,
|
||||
private sessionService: SessionService,
|
||||
private creditService: CreditService,
|
||||
private i18nService: I18nService
|
||||
sessionService: SessionService,
|
||||
creditService: CreditService,
|
||||
giftService: GiftService,
|
||||
i18nService: I18nService
|
||||
) {
|
||||
super(configService);
|
||||
// Assign to public properties for credit and gift commands mixins
|
||||
this.sessionService = sessionService;
|
||||
this.creditService = creditService;
|
||||
this.giftService = giftService;
|
||||
this.i18nService = i18nService;
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// CreditCommandsHost interface implementation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Send a credit message (delegates to protected sendMessage)
|
||||
*/
|
||||
async sendCreditMessage(roomId: string, message: string): Promise<void> {
|
||||
await this.sendMessage(roomId, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a credit reply (delegates to protected sendReply)
|
||||
*/
|
||||
async sendCreditReply(roomId: string, event: MatrixRoomEvent, message: string): Promise<void> {
|
||||
await this.sendReply(roomId, event, message);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// GiftCommandsHost interface implementation
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Send a gift message (delegates to protected sendMessage)
|
||||
*/
|
||||
async sendGiftMessage(roomId: string, message: string): Promise<void> {
|
||||
await this.sendMessage(roomId, message);
|
||||
}
|
||||
|
||||
/**
|
||||
* Send a gift reply (delegates to protected sendReply)
|
||||
*/
|
||||
async sendGiftReply(roomId: string, event: MatrixRoomEvent, message: string): Promise<void> {
|
||||
await this.sendReply(roomId, event, message);
|
||||
}
|
||||
|
||||
// ============================================================================
|
||||
// Private helpers
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* Check if user is logged in and has a valid token for API access
|
||||
*/
|
||||
|
|
@ -164,6 +223,20 @@ export class MatrixService extends BaseMatrixService {
|
|||
'language',
|
||||
'sprache',
|
||||
'lang',
|
||||
// Credit commands
|
||||
'credits',
|
||||
'guthaben',
|
||||
'packages',
|
||||
'pakete',
|
||||
'buy',
|
||||
'kaufen',
|
||||
// Gift commands
|
||||
'geschenk',
|
||||
'gift',
|
||||
'einloesen',
|
||||
'redeem',
|
||||
'meine-geschenke',
|
||||
'my-gifts',
|
||||
];
|
||||
|
||||
protected async handleTextMessage(
|
||||
|
|
@ -333,6 +406,16 @@ export class MatrixService extends BaseMatrixService {
|
|||
command: string,
|
||||
args: string
|
||||
) {
|
||||
// Handle credit commands first (credits, packages, buy)
|
||||
if (await handleCreditCommand(this, roomId, event, userId, command, args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Handle gift commands (geschenk, einloesen, meine-geschenke)
|
||||
if (await handleGiftCommand(this, roomId, event, userId, command, args)) {
|
||||
return;
|
||||
}
|
||||
|
||||
switch (command) {
|
||||
case 'help':
|
||||
case 'hilfe':
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue