feat(ask): 键盘常态提供 ✏️ 输入自定义答案 按钮
- 每个带选项的提问键盘固定追加第三行 ✏️ 输入自定义答案 - 点击后该题进入待文本状态(textWanted),丢弃该题已点选的按钮项 - 下一条文本消息自动填充最先待文本的题;带 1:/2: 前缀的作答同样可清除待文本状态 - 已实测:✏️ → 发送 111 → custom 答案正确回流
This commit is contained in:
+21
-2
@@ -1464,6 +1464,7 @@ export class TelegramBridge {
|
|||||||
at: Date.now(),
|
at: Date.now(),
|
||||||
kbSel: new Map(),
|
kbSel: new Map(),
|
||||||
textAnswers: new Map(),
|
textAnswers: new Map(),
|
||||||
|
textWanted: new Set(),
|
||||||
};
|
};
|
||||||
this.pendingAsks.set(String(chatId), pending);
|
this.pendingAsks.set(String(chatId), pending);
|
||||||
void this.deliverAskPrompt(Number(chatId), pending);
|
void this.deliverAskPrompt(Number(chatId), pending);
|
||||||
@@ -1492,6 +1493,8 @@ export class TelegramBridge {
|
|||||||
text: `${j + 1}. ${o.label}`.slice(0, 48),
|
text: `${j + 1}. ${o.label}`.slice(0, 48),
|
||||||
callback_data: `${ASK_CB}${i}:${j}`,
|
callback_data: `${ASK_CB}${i}:${j}`,
|
||||||
}]),
|
}]),
|
||||||
|
// Standing third choice: free-text answer, always available.
|
||||||
|
[{ text: '✏️ 输入自定义答案', callback_data: `${ASK_CB}${i}:custom` }],
|
||||||
...(q.multiSelect ? [[{ text: '✅ 提交所选', callback_data: `${ASK_CB}${i}:ok` }]] : []),
|
...(q.multiSelect ? [[{ text: '✅ 提交所选', callback_data: `${ASK_CB}${i}:ok` }]] : []),
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
@@ -1557,8 +1560,15 @@ export class TelegramBridge {
|
|||||||
parts.push([null, line]);
|
parts.push([null, line]);
|
||||||
}
|
}
|
||||||
if (parts.some(([idx]) => idx === null)) {
|
if (parts.some(([idx]) => idx === null)) {
|
||||||
await this.enqueueNotice(chatId, `共 ${questions.length} 个问题,请每行用 1:/2: 前缀分别作答`);
|
// ✏️ free-text route: a question awaiting text absorbs the whole message.
|
||||||
return;
|
const target = [...pending.textWanted].sort((a, b) => a - b)[0];
|
||||||
|
if (target !== undefined) {
|
||||||
|
parts = [[target, text.trim()]];
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
await this.enqueueNotice(chatId, `共 ${questions.length} 个问题,请每行用 1:/2: 前缀分别作答`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const [idx, raw] of parts) {
|
for (const [idx, raw] of parts) {
|
||||||
@@ -1588,6 +1598,7 @@ export class TelegramBridge {
|
|||||||
else {
|
else {
|
||||||
pending.textAnswers.set(idx, { id: q.id, selected: [], custom: cleaned });
|
pending.textAnswers.set(idx, { id: q.id, selected: [], custom: cleaned });
|
||||||
}
|
}
|
||||||
|
pending.textWanted.delete(idx);
|
||||||
}
|
}
|
||||||
// Keyboard selections take precedence per question; resolve when complete.
|
// Keyboard selections take precedence per question; resolve when complete.
|
||||||
if (!this.finalizeAskIfComplete(chatId, pending)) {
|
if (!this.finalizeAskIfComplete(chatId, pending)) {
|
||||||
@@ -1612,6 +1623,14 @@ export class TelegramBridge {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const opts = Array.isArray(q.options) ? q.options : [];
|
const opts = Array.isArray(q.options) ? q.options : [];
|
||||||
|
if (action === 'custom') {
|
||||||
|
// Free-text path: discard button selections for this question and wait
|
||||||
|
// for the user's next text message (routed via textWanted).
|
||||||
|
pending.kbSel.delete(qi);
|
||||||
|
pending.textWanted.add(qi);
|
||||||
|
await this.client.answerCallbackQuery(cq.id, '请直接发送你的答案文本');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (action === 'ok') {
|
if (action === 'ok') {
|
||||||
if (!pending.kbSel.get(qi)?.size) {
|
if (!pending.kbSel.get(qi)?.size) {
|
||||||
await this.client.answerCallbackQuery(cq.id, '请先点选选项');
|
await this.client.answerCallbackQuery(cq.id, '请先点选选项');
|
||||||
|
|||||||
+22
-2
@@ -101,6 +101,8 @@ interface TgPendingAsk {
|
|||||||
kbSel: Map<number, Set<number>>
|
kbSel: Map<number, Set<number>>
|
||||||
/** question index → answer parsed from a text reply (letters or custom). */
|
/** question index → answer parsed from a text reply (letters or custom). */
|
||||||
textAnswers: Map<number, { id?: string; selected: string[]; custom?: string }>
|
textAnswers: Map<number, { id?: string; selected: string[]; custom?: string }>
|
||||||
|
/** questions the user chose to answer with free text (✏️ button): the next text reply fills them. */
|
||||||
|
textWanted: Set<number>
|
||||||
}
|
}
|
||||||
|
|
||||||
/** chatId → pending approval request mirrored to Telegram. */
|
/** chatId → pending approval request mirrored to Telegram. */
|
||||||
@@ -1636,6 +1638,7 @@ export class TelegramBridge {
|
|||||||
at: Date.now(),
|
at: Date.now(),
|
||||||
kbSel: new Map(),
|
kbSel: new Map(),
|
||||||
textAnswers: new Map(),
|
textAnswers: new Map(),
|
||||||
|
textWanted: new Set(),
|
||||||
}
|
}
|
||||||
this.pendingAsks.set(String(chatId), pending)
|
this.pendingAsks.set(String(chatId), pending)
|
||||||
void this.deliverAskPrompt(Number(chatId), pending)
|
void this.deliverAskPrompt(Number(chatId), pending)
|
||||||
@@ -1672,6 +1675,8 @@ export class TelegramBridge {
|
|||||||
text: `${j + 1}. ${o.label}`.slice(0, 48),
|
text: `${j + 1}. ${o.label}`.slice(0, 48),
|
||||||
callback_data: `${ASK_CB}${i}:${j}`,
|
callback_data: `${ASK_CB}${i}:${j}`,
|
||||||
}]),
|
}]),
|
||||||
|
// Standing third choice: free-text answer, always available.
|
||||||
|
[{ text: '✏️ 输入自定义答案', callback_data: `${ASK_CB}${i}:custom` }],
|
||||||
...(q.multiSelect ? [[{ text: '✅ 提交所选', callback_data: `${ASK_CB}${i}:ok` }]] : []),
|
...(q.multiSelect ? [[{ text: '✅ 提交所选', callback_data: `${ASK_CB}${i}:ok` }]] : []),
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
@@ -1734,8 +1739,14 @@ export class TelegramBridge {
|
|||||||
else parts.push([null, line])
|
else parts.push([null, line])
|
||||||
}
|
}
|
||||||
if (parts.some(([idx]) => idx === null)) {
|
if (parts.some(([idx]) => idx === null)) {
|
||||||
await this.enqueueNotice(chatId, `共 ${questions.length} 个问题,请每行用 1:/2: 前缀分别作答`)
|
// ✏️ free-text route: a question awaiting text absorbs the whole message.
|
||||||
return
|
const target = [...pending.textWanted].sort((a, b) => a - b)[0]
|
||||||
|
if (target !== undefined) {
|
||||||
|
parts = [[target, text.trim()]]
|
||||||
|
} else {
|
||||||
|
await this.enqueueNotice(chatId, `共 ${questions.length} 个问题,请每行用 1:/2: 前缀分别作答`)
|
||||||
|
return
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for (const [idx, raw] of parts) {
|
for (const [idx, raw] of parts) {
|
||||||
@@ -1764,6 +1775,7 @@ export class TelegramBridge {
|
|||||||
} else {
|
} else {
|
||||||
pending.textAnswers.set(idx, { id: q.id, selected: [], custom: cleaned })
|
pending.textAnswers.set(idx, { id: q.id, selected: [], custom: cleaned })
|
||||||
}
|
}
|
||||||
|
pending.textWanted.delete(idx)
|
||||||
}
|
}
|
||||||
// Keyboard selections take precedence per question; resolve when complete.
|
// Keyboard selections take precedence per question; resolve when complete.
|
||||||
if (!this.finalizeAskIfComplete(chatId, pending)) {
|
if (!this.finalizeAskIfComplete(chatId, pending)) {
|
||||||
@@ -1789,6 +1801,14 @@ export class TelegramBridge {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const opts = Array.isArray(q.options) ? q.options : []
|
const opts = Array.isArray(q.options) ? q.options : []
|
||||||
|
if (action === 'custom') {
|
||||||
|
// Free-text path: discard button selections for this question and wait
|
||||||
|
// for the user's next text message (routed via textWanted).
|
||||||
|
pending.kbSel.delete(qi)
|
||||||
|
pending.textWanted.add(qi)
|
||||||
|
await this.client.answerCallbackQuery(cq.id, '请直接发送你的答案文本')
|
||||||
|
return
|
||||||
|
}
|
||||||
if (action === 'ok') {
|
if (action === 'ok') {
|
||||||
if (!pending.kbSel.get(qi)?.size) {
|
if (!pending.kbSel.get(qi)?.size) {
|
||||||
await this.client.answerCallbackQuery(cq.id, '请先点选选项')
|
await this.client.answerCallbackQuery(cq.id, '请先点选选项')
|
||||||
|
|||||||
Reference in New Issue
Block a user