feat: implement feedback collection system with quest-based interaction
This commit is contained in:
82
src/telegram/handlers.ts
Normal file
82
src/telegram/handlers.ts
Normal file
@@ -0,0 +1,82 @@
|
||||
import type { Context } from 'telegraf';
|
||||
import type { FeedbackStore } from '../stores/feedback';
|
||||
import type { SessionStore } from '../stores/session';
|
||||
import path from 'node:path';
|
||||
import { QuestAdapter } from '../quest/adapter';
|
||||
import { buildChoicesKeyboard } from './keyboards';
|
||||
|
||||
export function onCallback(rootDir: string, sessions: SessionStore, feedback?: FeedbackStore) {
|
||||
return async (ctx: Context) => {
|
||||
const chatId = String(ctx.chat?.id ?? '');
|
||||
if (!chatId || !('data' in (ctx.callbackQuery ?? {})))
|
||||
return;
|
||||
const data = (ctx.callbackQuery as any).data as string;
|
||||
if (!data?.startsWith('q:'))
|
||||
return;
|
||||
|
||||
const idx = Number(data.split(':')[1] || '-1');
|
||||
if (Number.isNaN(idx) || idx < 0)
|
||||
return;
|
||||
|
||||
// restore adapter at current node
|
||||
const adapter = new QuestAdapter(path.join(rootDir, 'scenarios'));
|
||||
const sess = sessions.get(chatId);
|
||||
if (sess)
|
||||
adapter.moveTo(sess.currentNode);
|
||||
|
||||
// capture selected choice text for analytics
|
||||
const before = adapter.getTextAndChoices();
|
||||
const selected = before.choices.find(c => c.idx === idx)?.text || '';
|
||||
const fromNode = adapter.getCurrentNodeId();
|
||||
adapter.choose(idx);
|
||||
|
||||
// render
|
||||
const render = adapter.getTextAndChoices();
|
||||
if ('message' in (ctx.callbackQuery as any)) {
|
||||
const msg = (ctx.callbackQuery as any).message;
|
||||
await ctx.telegram.editMessageText(
|
||||
msg.chat.id,
|
||||
msg.message_id,
|
||||
undefined,
|
||||
render.text,
|
||||
render.choices.length ? { ...buildChoicesKeyboard(render.choices) } : undefined,
|
||||
).catch(async () => {
|
||||
// fallback if cannot edit
|
||||
await ctx.reply(render.text, render.choices.length ? buildChoicesKeyboard(render.choices) : undefined);
|
||||
});
|
||||
}
|
||||
else {
|
||||
await ctx.reply(render.text, render.choices.length ? buildChoicesKeyboard(render.choices) : undefined);
|
||||
}
|
||||
|
||||
// persist session
|
||||
sessions.upsert({ chatId, currentNode: adapter.getCurrentNodeId(), updatedAt: Date.now() });
|
||||
|
||||
// store event
|
||||
if (feedback && selected)
|
||||
feedback.addEvent(chatId, fromNode, selected);
|
||||
|
||||
try {
|
||||
await ctx.answerCbQuery();
|
||||
}
|
||||
catch {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export function onStart(rootDir: string, sessions: SessionStore) {
|
||||
return async (ctx: Context) => {
|
||||
const chatId = String(ctx.chat?.id ?? '');
|
||||
if (!chatId)
|
||||
return;
|
||||
|
||||
const adapter = new QuestAdapter(path.join(rootDir, 'scenarios'));
|
||||
const render = adapter.getTextAndChoices();
|
||||
|
||||
await ctx.reply(render.text, render.choices.length ? buildChoicesKeyboard(render.choices) : undefined);
|
||||
|
||||
// persist session
|
||||
sessions.upsert({ chatId, currentNode: adapter.getCurrentNodeId(), updatedAt: Date.now() });
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user