telegram/command-handlers/tldr-handler.js

const { JSDOM } = require('jsdom');

const { InlineKeyboard } = require('grammy');

const { YA300_API_BASE } = require('../../config.json');

/**
 * TLDR Command
 * @namespace tldr
 * @memberof Commands
 */

/**
 * @typedef {{title: string, summary: { text: string, url: string}[], sharing_url: string}} SummaryJSON
 * @memberof Commands.tldr
 */

/**
 * Request summary from Ya300
 * @param {{ article_url: string }}
 * @returns {Promise<Response>}
 * @memberof Commands.tldr
 */
async function getSummaryURL({ article_url }) {
    return await fetch(
        `${YA300_API_BASE}`,
        {
            method: 'post',
            headers: {
                'Content-Type': 'application/json',
                'Authorization': `OAuth ${process.env.YA300_TOKEN}`
            },
            body: JSON.stringify({ article_url })
        }
    );
}

/**
 * Get summary from Ya300
 * @param {string} summary url
 * @returns {Promise<Response>}
 * @memberof Commands.tldr
 */
async function getSummaryHTML(url) {
    return await fetch(url);
}

/**
 * Parse HTML summary into a JSON
 * @param {Response} response 
 * @returns {SummaryJSON}
 * @memberof Commands.tldr
 */
async function parseSummary(response) {
    const html = await response.text();
    const result = {};
    const $ = require('jquery')((new JSDOM(html)).window);
    const summaryDiv = $('.summary .summary-content .summary-text');

    result.title = summaryDiv.find('.title').text()?.trim();
    result.summary = summaryDiv.find('li.thesis a').get()?.map(thesis => {
        return { text: thesis.text?.trim(), url: thesis.href };
    });
    result.sharing_url = response.url;

    return result;
}

exports.definition = {
    command_name: 'tldr',
    args: [
        {
            name: 'url',
            type: 'string',
            description: 'Ссылка на статью.',
            optional: false
        }
    ],
    is_inline: true,
    description: 'Возвращает краткий персказ сгенерированный YandexGPT'
};

exports.condition = !!process.env.YA300_API_BASE;

/**
 * TLDR Command Handler
 * @param {import('grammy').Context} ctx 
 * @param {import('../telegram-client').TelegramInteraction} interaction 
 * @memberof Commands.tldr
 */
async function handler(ctx, interaction) {
    const text = require('./utils').parseArgs(ctx)[1] || ctx.message?.reply_to_message?.text || ctx.message?.reply_to_message?.caption || '';

    const article_url = text.split(' ').find(words => words.match(/https?:\/\//));

    if (!article_url) {
        return ['Для запроса нужно предоставить ссылку, например https://habr.com/ru/news/729422/'];
    }

    return getSummaryURL({ article_url })
        .then(response => response.json())
        .catch(err => { throw { why: 'badresponse', error: err } })
        .then(({ status, sharing_url }) => {
            interaction.logger.silly(`Received response from Ya300`);
            if (status !== 'success') throw { message: `API responded with status=${status}`, why: 'badstatus' };
            return getSummaryHTML(sharing_url);
        })
        .catch(err => { throw { why: err.why || 'badresponse', error: err.error || err } })
        .then(response => parseSummary(response))
        .then(({ title, summary, sharing_url }) => {
            interaction.logger.silly(`Parsed summary from Ya300`);
            if (!summary) throw { message: 'No title/summary in response', why: 'badparsing' };
            return [
                null,
                `<b>${title}</b>\n${summary.reduce((acc, thesis) => acc += `${thesis.text}<a href="${thesis.url}">🔗</a>\n`, '')}`,
                null,
                { reply_markup: new InlineKeyboard().url('Посмотреть на сайте', sharing_url)}
            ];
        })
        .catch(err => {
            interaction.logger.error(err.message || err.error?.message || 'Error', { error: err.stack || err, args: [text, article_url] });
            switch (err?.why) {
                case 'badparsing':
                    return ['Боту чего-то поплохело, давай на ту ссылку больше не ходить'];
                case 'badstatus':
                    return ['Яндекс просил передать, что там ничего хорошего нет'];
                case 'badresponse':
                    return ['Яндексу поплохело, можешь попробовать позже'];
                default:
                    return [`Что-то совсем не так:\n<code>${err.message}</code>`]
            }
        });
}

exports.tldr = handler;