telegram/command-handlers/tinkov-handler.js

const logger = require('../../logger').child({ module: 'tinkov-handler' });

/**
 * Tinkov Command
 * @namespace t
 * @memberof Commands
 */

/**
 * @typedef {{file_id: string, file_unique_id: string, file_name: string}} FileData
 * @memberof Commands.t
 */

/**
 * Checks if a given name includes all the specified words in a pattern.
 *
 * @param {string} name - The name to check.
 * @param {string} pattern - The pattern containing words to check against the name.
 * @returns {boolean} True if the name includes all words in the pattern, false otherwise.
 * @memberof Commands.t
 */
function isSimilar(name, pattern) {
    const words = pattern.split(/[ ,.]+/);
    return words.every(word => name.includes(word));
}

/**
 * Get array of files, that suffice the pattern if specified
 * @param {string?} pattern search pattern 
 * @param {number} n limit
 * @returns {Promise<[string, FileData][]>}
 * @memberof Commands.t
 */
async function getBest(pattern = null, n = 50) {
    const redis = require('../../services/redis').getRedis();
    if (!redis) {
        throw new Error('Storage is offline');
    }

    const names = [];
    try {
        if (pattern == null) {
            const result = await redis.zrange('tinkov:ratings', 0, n - 1, 'REV');
            names.push(...result);
        }
        else {
            const result = await redis.zrange('tinkov:ratings', 0, -1, 'REV')
            names.push(...result.filter(r => isSimilar(r, pattern.toLowerCase())).slice(0, 10))
        }
    }
    catch (err) {
        logger.error('Failed to get a range of names from [tinkov:ratings]', { error: err.stack || err });    
    }
    if (!names.length) return [];

    const data = await redis.hmget('tinkov:data', names)
        .catch(err => {
            logger.error('Failed to get data for names from [tinkov:data]', { error: err.stack || err });
            return [];
        });

    return data.map((d, i) => [names[i], !!d ? JSON.parse(d) : null]).filter(([_, d]) => d != null);
}

/**
 * Increment the rank values since file was used
 * @param {string} file_unique_id 
 * @memberof Commands.t
 */
async function used(file_unique_id) {
    const redis = require('../../services/redis').getRedis();
    if (!redis) {
        logger.error('Attempted to update the rank of a result, but the storage is offline');
        return;
    }
    if (file_unique_id.startsWith('tinkov:')) file_unique_id = file_unique_id.slice('tinkov:'.length);

    const name = await redis.hget('tinkov:map', file_unique_id).catch(err => {
        logger.error(`Could not find the name for the file_unique_id [${file_unique_id}]`, { error: err.stack || err });
        return null;
    });
    if (name == null) return;

    redis.zincrby('tinkov:ratings', 1, name)
        .catch(err => logger.error(`Failed to increment rating of name [${name}] in [tinkov:data]`, { error: err.stack || err }));
}

/**
 * Tinkov Command Handler
 * @param {import('grammy').Context} ctx 
 * @memberof Commands.t
 */
async function tinkov(ctx) {
    let pattern = require('./utils').parseArgs(ctx, 1)[1];

    if (!pattern || !pattern?.length) pattern = null;

    try {
        const results = await getBest(pattern);
        if (!results.length) return ['Ничего не нашлось'];

        return [
            null,
            results.map(r => ({
                type: 'video',
                media: r[1].file_id,
                text: r[0],
                overrides: {
                    caption: null,
                    id: `tinkov:${r[1].file_unique_id}`,
                }
            })),
        ]
    }
    catch (err) {
        this.logger.error('Failed to get files data', { error: err.stack || err });
        return ['<blockquto>я как бы не совсем понимаю как это работает</blockquote>\nИными словами: что-то сломалось']
    }
}

module.exports = { 
    tinkov,
    used
};