RonAdames / telegram_flowise

This is a Telegram Bot 🤖 using Flowise API call giving a lot of posibilities with langchain tecnology.
MIT License
16 stars 7 forks source link

Update index.js to resolve escaped character issue in responses #2

Closed adolfousier closed 5 months ago

adolfousier commented 6 months ago

Fixed MarkdownV2 text formatting and code formatting to ensure proper rendering on Telegram. This update resolves formatting issues for most cases on Telegram with MarkdownV2. Highly recommended.

        // Extract the text from the JSON response
        const responseText = result.text || 'Could not retrieve response text';
        const escapedResponseText = responseText
         .replace(/-/g, '\\-')
         .replace(/[\.|!|#|(|)|\{|\}|=|_|\*]/g, '\\$&'); // Escape dot (.), escape (!), hyphen (-), and curly braces {}

        // Log for outgoing message
        console.log(`Response: ${escapedResponseText}`);

        // Send the response to the Telegram chat
        await axios.post(`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`, {
            chat_id: chatId,
            text: escapedResponseText, // Use the escaped response text
            parse_mode: 'MarkdownV2', // Try using MarkdownV2
        });
adolfousier commented 5 months ago

New update to the code, now it have falloff measure to send plain text if fails to escape character:

const axios = require('axios');
require('dotenv').config();  // Load environment variables from a .env file
const TelegramBot = require('node-telegram-bot-api');

// Telegram bot token (replace it with your own token)
const TELEGRAM_BOT_TOKEN = process.env.TELEGRAM_BOT_TOKEN;
// API URL
const API_URL = process.env.API_URL;

// Function to make the query
async function query(data) {
    try {
        const response = await axios.post(API_URL, data, {
            headers: {
                Authorization: `Bearer ${process.env.API_TOKEN}`,
                'Content-Type': 'application/json',
            },
        });
        return response.data;
    } catch (error) {
        console.error('Error in the query:', error.message);
        throw error;
    }
}

function escapeSpecialCharacters(text) {
    return text
        .replace(/([_*[\]()~`>#+=|{}.!-])/g, '\\$1')
        .replace(/```/g, '\\`\\`\\`')
        .replace(/\./g, '\\.')
        .replace(/@/g, '\\@');
}

function preserveCodeBlocks(text) {
    const codeBlocks = [];
    const preservedText = text.replace(/```[\s\S]*?```/g, (match) => {
        codeBlocks.push(match);
        return `__CODE_BLOCK_${codeBlocks.length - 1}__`;
    });
    return { preservedText, codeBlocks };
}

function restoreCodeBlocks(text, codeBlocks) {
    return text.replace(/__CODE_BLOCK_(\d+)__/g, (_, index) => {
        const block = codeBlocks[parseInt(index)];
        return '```\n' + escapeSpecialCharacters(block.slice(3, -3)) + '\n```';
    });
}

function formatText(text) {
    const { preservedText, codeBlocks } = preserveCodeBlocks(text);

    let formattedText = preservedText
        .replace(/\*\*(.*?)\*\*/g, '*$1*')  // Bold
        .replace(/\*(.*?)\*/g, '_$1_')      // Italic
        .replace(/`(.*?)`/g, '`$1`');       // Inline code

    formattedText = escapeSpecialCharacters(formattedText);

    // Unescape formatting characters
    formattedText = formattedText
        .replace(/\\_/g, '_')
        .replace(/\\\*/g, '*')
        .replace(/\\`/g, '`');

    // Handle email addresses and usernames
    formattedText = formattedText
        .replace(/(\\@[\w-]+)/g, (match) => match.replace('\\@', '@'))
        .replace(/([a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})/g, (match) => 
            match.replace(/\\\./g, '.'));

    // Ensure all periods are escaped, including those at the end of sentences
    formattedText = formattedText.replace(/\./g, '\\.');

    return restoreCodeBlocks(formattedText, codeBlocks);
}

// Function to handle Telegram messages
async function handleTelegramMessage(message) {
    const chatId = message.chat.id;
    const text = message.text;
    try {
        console.log(`Incoming message: ${text}`);
        const result = await query({ question: text });
        const responseText = result.text || 'Could not retrieve response text';
        console.log('Raw response:', responseText);

        const finalText = formatText(responseText);
        console.log(`Processed response: ${finalText}`);

        try {
            const telegramResponse = await axios.post(`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`, {
                chat_id: chatId,
                text: finalText,
                parse_mode: 'MarkdownV2',
            });
            console.log('Telegram API response:', telegramResponse.data);
            console.log(`Sent response to Telegram chat ${chatId}`);
        } catch (markdownError) {
            console.error('Error sending MarkdownV2 message:', markdownError.response.data);
            // Fallback to sending without formatting
            const plainText = responseText.replace(/[_*[\]()~`>#+=|{}.!-]/g, '');
            await axios.post(`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`, {
                chat_id: chatId,
                text: plainText,
            });
            console.log(`Sent plain text response to Telegram chat ${chatId}`);
        }
    } catch (error) {
        console.error('Error handling Telegram message:', error);
        if (error.response) {
            console.error('Error details:', error.response.data);
            console.error('Request data:', error.response.config.data);
        }

        // Send a fallback message without formatting
        try {
            await axios.post(`https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage`, {
                chat_id: chatId,
                text: 'Sorry, there was an error processing your message. Please try again.',
            });
        } catch (fallbackError) {
            console.error('Error sending fallback message:', fallbackError);
        }
    }
}

// Configure the Telegram bot
const bot = new TelegramBot(TELEGRAM_BOT_TOKEN, { polling: true });

// Handle Telegram messages
bot.on('message', handleTelegramMessage);

// Port to listen on (uses the port defined by the PORT environment variable or defaults to port 3000)
const port = process.env.PORT || 3000;

// Startup message
console.log(`Telegram bot started. Listening on port ${port}...`);
RonAdames commented 5 months ago

Bro, thank you for updating and getting better the code.

adolfousier commented 5 months ago

Thank you bro! It was a good starting point but now we have a client that need proper formatting on Telegram and markdownv2 was the best option for me but still have some bugs, I'm fixing another escaped character which I will update soon and push if you want I will keep contributing to your code, thank you!