Добавлен выбор активного диалога кнопкой в Telegram
Этот коммит содержится в:
@@ -1,4 +1,4 @@
|
|||||||
import { Telegraf } from 'telegraf';
|
import { Telegraf, Markup } from 'telegraf';
|
||||||
import { SocksProxyAgent } from 'socks-proxy-agent';
|
import { SocksProxyAgent } from 'socks-proxy-agent';
|
||||||
import {
|
import {
|
||||||
createOperatorMessage,
|
createOperatorMessage,
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
|
|
||||||
let bot = null;
|
let bot = null;
|
||||||
let operatorMessageNotifier = null;
|
let operatorMessageNotifier = null;
|
||||||
|
const activeOperatorConversations = new Map();
|
||||||
let botStatus = {
|
let botStatus = {
|
||||||
enabled: false,
|
enabled: false,
|
||||||
running: false,
|
running: false,
|
||||||
@@ -48,6 +49,37 @@ function buildTelegramOptions(settings) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function getConversationIdFromReply(ctx) {
|
||||||
|
const replyTo = ctx.message?.reply_to_message;
|
||||||
|
const sourceText = replyTo?.text || replyTo?.caption || '';
|
||||||
|
const match = sourceText.match(/Conversation:\s*(conv_[a-zA-Z0-9_-]+)/);
|
||||||
|
return match ? match[1] : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveOperatorAnswer({ ctx, operator, conversationId, text }) {
|
||||||
|
const conversation = getConversationWithVisitor(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
await ctx.reply('Conversation not found.');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const message = createOperatorMessage({
|
||||||
|
conversationId,
|
||||||
|
siteId: conversation.site_id,
|
||||||
|
operatorId: operator.id,
|
||||||
|
body: text,
|
||||||
|
telegramMessageId: String(ctx.message.message_id)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (operatorMessageNotifier) {
|
||||||
|
operatorMessageNotifier({ conversation, message, operator });
|
||||||
|
}
|
||||||
|
|
||||||
|
activeOperatorConversations.set(String(ctx.from.id), conversationId);
|
||||||
|
await ctx.reply(`Answer sent to ${conversation.site_domain || conversation.site_id} / ${conversation.visitor_key}.`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
export function setOperatorMessageNotifier(callback) {
|
export function setOperatorMessageNotifier(callback) {
|
||||||
operatorMessageNotifier = callback;
|
operatorMessageNotifier = callback;
|
||||||
}
|
}
|
||||||
@@ -55,7 +87,8 @@ export function setOperatorMessageNotifier(callback) {
|
|||||||
export function getTelegramBridgeStatus() {
|
export function getTelegramBridgeStatus() {
|
||||||
return {
|
return {
|
||||||
...botStatus,
|
...botStatus,
|
||||||
hasBot: Boolean(bot)
|
hasBot: Boolean(bot),
|
||||||
|
activeTelegramDialogs: activeOperatorConversations.size
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -69,6 +102,7 @@ export async function stopTelegramBridge(reason = 'restart') {
|
|||||||
}
|
}
|
||||||
|
|
||||||
bot = null;
|
bot = null;
|
||||||
|
activeOperatorConversations.clear();
|
||||||
botStatus = {
|
botStatus = {
|
||||||
...botStatus,
|
...botStatus,
|
||||||
running: false,
|
running: false,
|
||||||
@@ -117,14 +151,46 @@ export async function startTelegramBridge({ logger } = {}) {
|
|||||||
|
|
||||||
nextBot.command('status', async (ctx) => {
|
nextBot.command('status', async (ctx) => {
|
||||||
const status = getTelegramBridgeStatus();
|
const status = getTelegramBridgeStatus();
|
||||||
|
const activeConversationId = activeOperatorConversations.get(String(ctx.from.id));
|
||||||
await ctx.reply(
|
await ctx.reply(
|
||||||
`WSChat bridge\n` +
|
`WSChat bridge\n` +
|
||||||
`running: ${status.running ? 'yes' : 'no'}\n` +
|
`running: ${status.running ? 'yes' : 'no'}\n` +
|
||||||
`proxy: ${status.proxyEnabled ? 'enabled' : 'disabled'}\n` +
|
`proxy: ${status.proxyEnabled ? 'enabled' : 'disabled'}\n` +
|
||||||
`bot: ${status.username || 'unknown'}`
|
`bot: ${status.username || 'unknown'}\n` +
|
||||||
|
`active dialog: ${activeConversationId || 'not selected'}`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
nextBot.action(/^answer:(conv_[a-zA-Z0-9_-]+)$/, async (ctx) => {
|
||||||
|
const operator = getOperatorByTelegramUserId(ctx.from.id);
|
||||||
|
if (!operator) {
|
||||||
|
await ctx.answerCbQuery('Send /start first');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const conversationId = ctx.match[1];
|
||||||
|
const conversation = getConversationWithVisitor(conversationId);
|
||||||
|
if (!conversation) {
|
||||||
|
await ctx.answerCbQuery('Conversation not found');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
activeOperatorConversations.set(String(ctx.from.id), conversationId);
|
||||||
|
await ctx.answerCbQuery('Dialog selected');
|
||||||
|
await ctx.reply(
|
||||||
|
`Active dialog selected:\n` +
|
||||||
|
`${conversation.site_domain || conversation.site_id}\n` +
|
||||||
|
`Visitor: ${conversation.visitor_key}\n\n` +
|
||||||
|
`Now send a normal Telegram message here. It will go to this visitor.`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
nextBot.action(/^close_active$/, async (ctx) => {
|
||||||
|
activeOperatorConversations.delete(String(ctx.from.id));
|
||||||
|
await ctx.answerCbQuery('Active dialog cleared');
|
||||||
|
await ctx.reply('Active dialog cleared. Choose another dialog with the Answer button or reply to a notification.');
|
||||||
|
});
|
||||||
|
|
||||||
nextBot.on('text', async (ctx) => {
|
nextBot.on('text', async (ctx) => {
|
||||||
const text = ctx.message?.text || '';
|
const text = ctx.message?.text || '';
|
||||||
if (!text || text.startsWith('/')) return;
|
if (!text || text.startsWith('/')) return;
|
||||||
@@ -135,35 +201,16 @@ export async function startTelegramBridge({ logger } = {}) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const replyTo = ctx.message?.reply_to_message;
|
const replyConversationId = getConversationIdFromReply(ctx);
|
||||||
const sourceText = replyTo?.text || replyTo?.caption || '';
|
const activeConversationId = activeOperatorConversations.get(String(ctx.from.id));
|
||||||
const match = sourceText.match(/Conversation:\s*(conv_[a-zA-Z0-9_-]+)/);
|
const conversationId = replyConversationId || activeConversationId;
|
||||||
|
|
||||||
if (!match) {
|
if (!conversationId) {
|
||||||
await ctx.reply('Reply to a WSChat notification message to answer a visitor.');
|
await ctx.reply('Choose a dialog with the Answer button or reply to a WSChat notification message.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const conversationId = match[1];
|
await saveOperatorAnswer({ ctx, operator, conversationId, text });
|
||||||
const conversation = getConversationWithVisitor(conversationId);
|
|
||||||
if (!conversation) {
|
|
||||||
await ctx.reply('Conversation not found.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const message = createOperatorMessage({
|
|
||||||
conversationId,
|
|
||||||
siteId: conversation.site_id,
|
|
||||||
operatorId: operator.id,
|
|
||||||
body: text,
|
|
||||||
telegramMessageId: String(ctx.message.message_id)
|
|
||||||
});
|
|
||||||
|
|
||||||
if (operatorMessageNotifier) {
|
|
||||||
operatorMessageNotifier({ conversation, message, operator });
|
|
||||||
}
|
|
||||||
|
|
||||||
await ctx.reply(`Answer saved for conversation ${conversationId}.`);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const me = await nextBot.telegram.getMe();
|
const me = await nextBot.telegram.getMe();
|
||||||
@@ -221,13 +268,18 @@ export async function notifyOperatorsAboutVisitorMessage({ site, visitor, conver
|
|||||||
'',
|
'',
|
||||||
escapeHtml(message.body),
|
escapeHtml(message.body),
|
||||||
'',
|
'',
|
||||||
'Reply to this message to save an operator answer.'
|
'Press Answer to select this dialog, or reply to this message directly.'
|
||||||
].join('\n');
|
].join('\n');
|
||||||
|
|
||||||
|
const keyboard = Markup.inlineKeyboard([
|
||||||
|
[Markup.button.callback('Ответить в этот чат', `answer:${conversation.id}`)],
|
||||||
|
[Markup.button.callback('Сбросить активный диалог', 'close_active')]
|
||||||
|
]);
|
||||||
|
|
||||||
let sent = 0;
|
let sent = 0;
|
||||||
for (const operator of operators) {
|
for (const operator of operators) {
|
||||||
try {
|
try {
|
||||||
await bot.telegram.sendMessage(operator.telegram_user_id, text);
|
await bot.telegram.sendMessage(operator.telegram_user_id, text, keyboard);
|
||||||
sent += 1;
|
sent += 1;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger?.warn?.({ operatorId: operator.id, error: error.message }, 'Failed to notify Telegram operator');
|
logger?.warn?.({ operatorId: operator.id, error: error.message }, 'Failed to notify Telegram operator');
|
||||||
|
|||||||
Ссылка в новой задаче
Block a user