463 lines
23 KiB
JavaScript
463 lines
23 KiB
JavaScript
const TelegramBot = require('node-telegram-bot-api');
|
||
const axios = require('axios');
|
||
const fs = require('fs');
|
||
require('dotenv').config();
|
||
const MAX_CALLS_PER_DAY = process.env.MAX_CALLS_PER_DAY || 10; // Максимальное количество вызовов в день
|
||
const USAGE_RESET_TIME = 24 * 60 * 60 * 1000; // Время сброса (24 часа)
|
||
const commandCooldowns = {}; // Хранит временные метки вызовов команд для каждого чата
|
||
|
||
const COOLDOWN_TIME = 1 * 60 * 1000; // 10 минут в миллисекундах
|
||
const userUsage = {}; // Для хранения информации о использовании команд пользователями
|
||
const BASE_PROMPT = `You are an advanced text analyzer that assesses messages based on several criteria. You will receive a string of text as input, and you will return the result in JSON format with the specified fields. \n
|
||
|
||
Please pay close attention to the following instructions for each field:\n
|
||
|
||
1. **literacy_score**: Evaluate the overall literacy of the text on a scale from 1 to 10, where:
|
||
|
||
- 1 indicates very poor writing with numerous grammatical errors, spelling mistakes, and incorrect punctuation.
|
||
- 10 indicates the text is perfectly written with no grammatical errors, spelling mistakes, or punctuation issues. The score should solely reflect the correctness of language use, independent of the presence of profanity or strong language.\n
|
||
|
||
2. **elegance_score**: Assess the elegance of the text on a scale from 1 to 10, specifically focusing on the sophistication of any insults.\n
|
||
- 1 would indicate crude, blunt, or simplistic insults.\n
|
||
- 10 would indicate witty, refined insults that are cleverly constructed. The use of profanity or strong language that is artfully integrated should increase this score.\n
|
||
\n
|
||
3. **parental_toxicity_score**: Determine the level of toxicity directed at a person's parents on a scale from 1 to 10, where:\n
|
||
- 1 indicates no mention of parents or neutral references.\n
|
||
- 10 indicates direct insults or severe disrespect aimed at a person's parents. Profane language that enhances the insult should contribute positively to this score.\n
|
||
\n
|
||
4. **sexual_toxicity_score**: Assess the sexual toxicity of the message on a scale from 1 to 10, where:\n
|
||
- 1 indicates no sexual connotations or implications.\n
|
||
- 10 indicates highly explicit, degrading, or inappropriate sexual comments. The use of vulgar language that adds intensity to these comments should enhance this score.\n
|
||
\n
|
||
5. **appearance_toxicity_score**: Evaluate the level of toxicity directed at a person's physical appearance on a scale from 1 to 10, where:\n
|
||
- 1 indicates no negative remarks about appearance.\n
|
||
- 10 indicates severe insults related to physical traits. A use of strong language that emphasizes the insult should increase this score.\n
|
||
\n
|
||
6. **medical_toxicity_score**: Assess the level of toxicity related to medical issues or disabilities on a scale from 1 to 10, where:\n
|
||
- 1 indicates no mention of health issues or disabilities.\n
|
||
- 10 indicates highly disrespectful or demeaning remarks about a person's health or medical conditions. Profanity that amplifies the demeaning nature should positively impact this score.\n
|
||
\n
|
||
7. **emotional_toxicity_score**: Evaluate the presence of negative emotions in the message on a scale from 1 to 10, where:\n
|
||
- 1 indicates no negative emotions.\n
|
||
- 10 indicates the message is filled with hate or aggression. The presence of strong language may contribute to a higher score.\n
|
||
\n
|
||
8. **discrimination_score**: Assess the presence of discriminatory language on a scale from 1 to 10, where:\n
|
||
- 1 indicates no discriminatory remarks.\n
|
||
- 10 indicates inherently racist, sexist, or otherwise offensive language. The presence of derogatory language may enhance this score.\n
|
||
\n
|
||
9. **manipulative_language_score**: Evaluate the use of manipulative language or threats on a scale from 1 to 10, where:\n
|
||
- 1 indicates no manipulative language.\n
|
||
- 10 indicates clear threats or coercive statements. Use of strong language that underscores the manipulation should also positively affect this score.\n
|
||
\n
|
||
10. **intellectual_toxicity_score**: Assess the level of intellectual insults on a scale from 1 to 10, where:\n
|
||
- 1 indicates no belittlement of intelligence.\n
|
||
- 10 indicates severe mockery of intellect or education. Profanity that enhances the insult should be considered positively.\n
|
||
\n
|
||
11. **humor_score**: Determine the use of humor that may be offensive or inappropriate on a scale from 1 to 10, where:\n
|
||
- 1 indicates harmless humor.\n
|
||
- 10 indicates a joke that is hard-hitting and likely to offend. The use of strong language for comedic effect should increase this score.\n`;
|
||
|
||
const adminChannelId = '-4588651872';
|
||
const token = process.env.BOT_TOKEN;
|
||
const bot = new TelegramBot(token, {polling: true});
|
||
|
||
const aiCachePath = 'aiCache.json';
|
||
const statsPath = 'stats.json';
|
||
const userStatsPath = 'userStats.json';
|
||
let userStats = {}; // Для хранения информации о пользователях
|
||
let aiCache = {}; // Кеш для запросов к ИИ
|
||
let stats = {cacheHits: 0, cacheMisses: 0, maxScore: 0};
|
||
|
||
|
||
// Загружаем кеш из файла
|
||
function loadAiCache() {
|
||
try {
|
||
if (fs.existsSync(aiCachePath)) {
|
||
const data = fs.readFileSync(aiCachePath);
|
||
aiCache = JSON.parse(data);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading AI cache:', error);
|
||
}
|
||
}
|
||
|
||
// Сохраняем кеш в файл
|
||
function saveAiCache() {
|
||
try {
|
||
fs.writeFileSync(aiCachePath, JSON.stringify(aiCache, null, 2));
|
||
} catch (error) {
|
||
console.error('Error saving AI cache:', error);
|
||
}
|
||
}
|
||
|
||
// Загружаем статистику из файла
|
||
function loadStats() {
|
||
try {
|
||
if (fs.existsSync(statsPath)) {
|
||
const data = fs.readFileSync(statsPath);
|
||
stats = JSON.parse(data);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading stats:', error);
|
||
}
|
||
}
|
||
|
||
// Сохраняем статистику в файл
|
||
function saveStats() {
|
||
try {
|
||
fs.writeFileSync(statsPath, JSON.stringify(stats, null, 2));
|
||
} catch (error) {
|
||
console.error('Error saving stats:', error);
|
||
}
|
||
}
|
||
function loadUserStats() {
|
||
try {
|
||
if (fs.existsSync(userStatsPath)) {
|
||
const data = fs.readFileSync(userStatsPath);
|
||
userStats = JSON.parse(data);
|
||
}
|
||
} catch (error) {
|
||
console.error('Error loading user stats:', error);
|
||
}
|
||
}
|
||
|
||
// Сохраняем статистику пользователей в файл
|
||
function saveUserStats() {
|
||
try {
|
||
fs.writeFileSync(userStatsPath, JSON.stringify(userStats, null, 2));
|
||
} catch (error) {
|
||
console.error('Error saving user stats:', error);
|
||
}
|
||
}
|
||
|
||
|
||
loadUserStats(); // Загружаем статистику пользователей при старте бота
|
||
loadAiCache(); // Загружаем кеш ИИ при старте бота
|
||
loadStats(); // Загружаем статистику при старте бота
|
||
|
||
|
||
async function updateUserStats(userId, username, firstName, toxScore, averageScore) {
|
||
if (!userStats[userId]) {
|
||
userStats[userId] = {
|
||
userId,
|
||
username,
|
||
firstName,
|
||
totalScore: 0,
|
||
averageScore: 0,
|
||
highestScore: 0,
|
||
requestCount: 0,
|
||
};
|
||
}
|
||
|
||
const user = userStats[userId];
|
||
|
||
// Обновляем информацию о пользователе
|
||
const currentScore = averageScore//Object.values(toxScore).reduce((a, b) => a + b, 0);
|
||
user.totalScore += currentScore; // Суммируем баллы
|
||
user.requestCount++; // Увеличиваем счетчик запросов
|
||
user.averageScore = user.totalScore / user.requestCount; // Пересчитываем средний балл
|
||
|
||
// Обновляем наивысший балл
|
||
if (currentScore > user.highestScore) {
|
||
user.highestScore = currentScore;
|
||
}
|
||
|
||
// // Обновляем максимальный средний балл за всю историю
|
||
// if (user.averageScore > maxAverageScore) {
|
||
// maxAverageScore = user.averageScore;
|
||
// }
|
||
|
||
saveUserStats(); // Сохраняем обновленную статистику
|
||
}
|
||
// Функция для выбора топ-10 пользователей по среднему баллу
|
||
function getTopUsersByAverage() {
|
||
return Object.values(userStats)
|
||
.sort((a, b) => b.averageScore - a.averageScore)
|
||
.slice(0, 10);
|
||
}
|
||
|
||
// Функция для выбора топ-10 пользователей по количеству запросов
|
||
function getTopUsersByRequestCount() {
|
||
return Object.values(userStats)
|
||
.sort((a, b) => b.requestCount - a.requestCount)
|
||
.slice(0, 10);
|
||
}
|
||
|
||
|
||
function toxAnalyze(text) {
|
||
// Проверяем, есть ли уже результат в кеше
|
||
if (aiCache[text]) {
|
||
stats.cacheHits += 1; // Увеличиваем счетчик попаданий в кеш
|
||
let tempResult = JSON.parse(aiCache[text]);
|
||
tempResult.firstTime = false;
|
||
return tempResult;
|
||
}
|
||
|
||
// Если нет, делаем запрос к AI
|
||
stats.cacheMisses += 1; // Увеличиваем счетчик промахов в кеш
|
||
return getAIResume(text, BASE_PROMPT).then(result => {
|
||
// Сохраняем результат в кеш
|
||
let tempResult = JSON.parse(result);
|
||
tempResult.firstTime = true;
|
||
|
||
aiCache[text] = JSON.stringify(tempResult);
|
||
|
||
saveAiCache(); // Сохраняем кеш в файл
|
||
return tempResult;
|
||
});
|
||
}
|
||
|
||
function escapeMarkdown(text) {
|
||
return text
|
||
.replace(/_/g, '\\_') // Экранируем нижнее подчеркивание
|
||
.replace(/\*/g, '\\*') // Экранируем звездочку
|
||
.replace(/\[/g, '\\[') // Экранируем открывающую скобку
|
||
.replace(/\]/g, '\\]') // Экранируем закрывающую скобку
|
||
.replace(/\(/g, '\\(') // Экранируем открытую скобку
|
||
.replace(/\)/g, '\\)') // Экранируем закрытую скобку
|
||
.replace(/~/g, '\\~') // Экранируем тильду
|
||
.replace(/`/g, '\\`') // Экранируем обратную кавычку
|
||
.replace(/>/g, '\\>') // Экранируем знак больше
|
||
.replace(/#/g, '\\#') // Экранируем решетку
|
||
.replace(/-/g, '\\-'); // Экранируем дефис
|
||
}
|
||
|
||
|
||
// Обработчик текстовых сообщений
|
||
// bot.on('message', async (msg) => {
|
||
// const chatId = msg.chat.id;
|
||
// const userId = msg.from.id;
|
||
// const userName = msg.from.username || '';
|
||
// const firstName = msg.from.first_name || '';
|
||
// let isNewUser = false;
|
||
//
|
||
// // Создаем запись пользователя для данного чата, если ее еще нет
|
||
//
|
||
//
|
||
// if (msg.reply_to_message && msg.reply_to_message.text && msg.text && msg.text.toLowerCase() === 'токсанал') {
|
||
// const analyzedText = msg.reply_to_message.text;
|
||
//
|
||
// // Проверяем, что текст для анализа не пустой
|
||
// if (analyzedText) {
|
||
// const toxScore = await toxAnalyze(analyzedText);
|
||
// //console.log(toxScore)
|
||
// // Форматируем вывод
|
||
// const values = Object.values(toxScore);
|
||
// const sum = values.reduce((a, b) => a + b, 0);
|
||
// const average = sum / values.length;
|
||
// const resultMessage = `
|
||
// Слов в тексте: ${analyzedText.split(' ').length}
|
||
// Грамотность: ${toxScore.literacy_score}
|
||
// Элегантность: ${toxScore.elegance_score}
|
||
// Токсичность по отношению к родителям: ${toxScore.parental_toxicity_score}
|
||
// Сексуальная токсичность: ${toxScore.sexual_toxicity_score}
|
||
// Токсичность по отношению к внешности: ${toxScore.appearance_toxicity_score}
|
||
// Медицинская токсичность: ${toxScore.medical_toxicity_score}
|
||
// Эмоциональная токсичность: ${toxScore.emotional_toxicity_score}
|
||
// Дискриминация: ${toxScore.discrimination_score}
|
||
// Манипулятивный язык: ${toxScore.manipulative_language_score}
|
||
// Интеллектуальная токсичность: ${toxScore.intellectual_toxicity_score}
|
||
// Юмористическая токсичность: ${toxScore.humor_score}
|
||
// Средний бал: ${average.toFixed(1)}
|
||
// `.trim(); // Убираем лишние пробелы
|
||
//
|
||
// // Отправляем отформатированное сообщение
|
||
// bot.sendMessage(chatId, `Результаты анализа:\n${resultMessage}`, { reply_to_message_id: msg.reply_to_message.message_id });
|
||
// } else {
|
||
// bot.sendMessage(chatId, 'Пожалуйста, укажите текст для анализа.');
|
||
// }
|
||
// }
|
||
// // Сохраняем обновленные статистические данные
|
||
// saveStats();
|
||
// });
|
||
bot.on('message', async (msg) => {
|
||
const chatId = msg.chat.id;
|
||
const userId = msg.from.id;
|
||
const username = msg.from.username || '';
|
||
const firstName = msg.from.first_name || '';
|
||
// Инициализация данных пользователя, если их нет
|
||
if (!userUsage[userId]) {
|
||
userUsage[userId] = { count: 0, lastReset: Date.now() };
|
||
}
|
||
|
||
// Сброс данных, если прошел день
|
||
if (Date.now() - userUsage[userId].lastReset >= USAGE_RESET_TIME) {
|
||
userUsage[userId].count = 0; // Сбрасываем счетчик
|
||
userUsage[userId].lastReset = Date.now(); // Обновляем временную метку
|
||
}
|
||
|
||
// Проверяем, является ли сообщение ответом на другое сообщение с текстом "токсанал"
|
||
if (msg.reply_to_message && msg.reply_to_message.text && msg.text && msg.text.toLowerCase() === 'токсанал') {
|
||
// Проверяем, использовал ли пользователь команду больше трех раз
|
||
if (userUsage[userId].count >= MAX_CALLS_PER_DAY) {
|
||
return bot.sendMessage(chatId, 'Вы достигли лимита использования команды "токсанал" на сегодня. Попробуйте снова завтра.', { reply_to_message_id: msg.message_id });
|
||
}
|
||
|
||
const analyzedText = msg.reply_to_message.text;
|
||
|
||
// Проверяем, что текст для анализа не пустой
|
||
if (analyzedText) {
|
||
const toxScore = await toxAnalyze(analyzedText);
|
||
|
||
// Форматируем вывод
|
||
const values = Object.values(toxScore);
|
||
const sum = values.reduce((a, b) => a + b, 0);
|
||
const average = sum / values.length;
|
||
stats.maxScore = Math.max(stats.maxScore, average);
|
||
//console.log(stats)
|
||
const resultMessage = `
|
||
Слов в тексте: ${analyzedText.split(' ').length}
|
||
Грамотность: ${toxScore.literacy_score}
|
||
Элегантность: ${toxScore.elegance_score}
|
||
Токсичность по отношению к родителям: ${toxScore.parental_toxicity_score}
|
||
Сексуальная токсичность: ${toxScore.sexual_toxicity_score}
|
||
Токсичность по отношению к внешности: ${toxScore.appearance_toxicity_score}
|
||
Медицинская токсичность: ${toxScore.medical_toxicity_score}
|
||
Эмоциональная токсичность: ${toxScore.emotional_toxicity_score}
|
||
Дискриминация: ${toxScore.discrimination_score}
|
||
Манипулятивный язык: ${toxScore.manipulative_language_score}
|
||
Интеллектуальная токсичность: ${toxScore.intellectual_toxicity_score}
|
||
Юмористическая токсичность: ${toxScore.humor_score}
|
||
Средний бал: ${average.toFixed(1)}
|
||
`.trim(); // Убираем лишние пробелы
|
||
|
||
// Увеличиваем счетчик использований
|
||
userUsage[userId].count++;
|
||
|
||
// Получаем количество оставшихся попыток
|
||
const remainingAttempts = MAX_CALLS_PER_DAY - userUsage[userId].count;
|
||
console.log(msg.reply_to_message)
|
||
// Отправляем отформатированное сообщение с оставшимися попытками
|
||
if (toxScore.firstTime) {await updateUserStats(msg.reply_to_message.from.id, msg.reply_to_message.from.username, msg.reply_to_message.from.first_name, toxScore, average);} // Обновляем статистику пользователя
|
||
await bot.sendMessage(chatId, `Результаты анализа:\n${resultMessage}\n\nАнализов осталось: ${remainingAttempts}\n\nРекорд: ${stats.maxScore.toFixed(1)}`, { reply_to_message_id: msg.reply_to_message.message_id });
|
||
} else {
|
||
await bot.sendMessage(chatId, 'Пожалуйста, укажите текст для анализа.');
|
||
}
|
||
}
|
||
|
||
// Сохраняем обновленные статистические данные
|
||
saveStats();
|
||
});
|
||
|
||
// Функция запроса к AI
|
||
async function getAIResume(text, prompt) {
|
||
const baseUrl = `${process.env.AI_WEB_URL}/api`;
|
||
const url = `${baseUrl}/chat/completions`;
|
||
if (text.length < 4) return 0;
|
||
const headers = {
|
||
'Authorization': `Bearer ${process.env.AI_TOKEN}`,
|
||
'Content-Type': 'application/json',
|
||
'Accept': 'application/json',
|
||
};
|
||
const body = {
|
||
model: 'gpt-4o-mini',
|
||
messages: [
|
||
{role: "system", content: prompt},
|
||
{role: "user", content: text},
|
||
],
|
||
// temperature: 0.7,
|
||
// max_tokens: 150
|
||
};
|
||
|
||
try {
|
||
|
||
const result = await axios.post(url, body, {headers});
|
||
|
||
const responseContent = result.data.choices[0].message.content
|
||
return responseContent.replace(/```json\n|\n```/g, '').replace(/\n/g, ' ').trim();
|
||
//return responseContent; // Теперь без символов новой строкие
|
||
} catch (error) {
|
||
console.error('Error:', error);
|
||
// throw error;
|
||
}
|
||
}
|
||
|
||
function statCooldown(chatId) {
|
||
if (commandCooldowns[chatId] && Date.now() - commandCooldowns[chatId] < COOLDOWN_TIME) {
|
||
return true;
|
||
}
|
||
|
||
commandCooldowns[chatId] = Date.now();
|
||
return false;
|
||
}
|
||
|
||
bot.onText(/токстоп/, async (msg) => {
|
||
try {
|
||
await bot.deleteMessage(msg.chat.id, msg.message_id);
|
||
} catch (error) {}
|
||
if (statCooldown(msg.chat.id)) {
|
||
console.log('cooldown for stat')
|
||
await sendTemporaryMessage(msg.chat.id, 'Слишком частые запросы. Подождите некоторое время.', 5000);
|
||
return
|
||
}
|
||
console.log('not cooldown for stat')
|
||
const chatId = msg.chat.id;
|
||
|
||
const topUsers = getTopUsersByAverage(); // Получаем топ пользователей по среднему баллу
|
||
console.log(topUsers)
|
||
if (topUsers.length === 0) {
|
||
return bot.sendMessage(chatId, 'Статистика пока недоступна.');
|
||
}
|
||
|
||
// Форматируем сообщение для вывода
|
||
let leaderboard = '*Топ пользователей по среднему баллу:*\n(считается средняя оценка всех сообщений)\n\n';
|
||
topUsers.forEach((user, index) => {
|
||
// leaderboard += `*${index + 1}*. ${escapeMarkdown(user.firstName)} (${escapeMarkdown('@' + user.username || 'Без имени')}) - Средний балл: ${user.averageScore.toFixed(2)}\n`;
|
||
leaderboard += `*${index + 1}*. ${escapeMarkdown(user.firstName)} - Средний балл: ${user.averageScore.toFixed(2)}\n`;
|
||
});
|
||
|
||
await bot.sendMessage(chatId, leaderboard, {parse_mode: 'Markdown'});
|
||
});
|
||
|
||
bot.onText(/токсзапросы/, async (msg) => {
|
||
try {
|
||
await bot.deleteMessage(msg.chat.id, msg.message_id);
|
||
} catch (error) {}
|
||
if (statCooldown(msg.chat.id)) {
|
||
await sendTemporaryMessage(msg.chat.id, 'Слишком частые запросы. Подождите некоторое время.', 5000);
|
||
return
|
||
}
|
||
|
||
const chatId = msg.chat.id;
|
||
|
||
const topRequestUsers = getTopUsersByRequestCount(); // Получаем топ пользователей по количеству запросов
|
||
|
||
if (topRequestUsers.length === 0) {
|
||
return bot.sendMessage(chatId, 'Статистика пока недоступна.');
|
||
}
|
||
|
||
// Форматируем сообщение для вывода
|
||
let requestLeaderboard = '*Топ пользователей по количеству запросов:*\n\n';
|
||
topRequestUsers.forEach((user, index) => {
|
||
// requestLeaderboard += `*${index + 1}*. ${escapeMarkdown(user.firstName)} (${escapeMarkdown('@' + user.username || 'Без имени')}) - Запросов: ${user.requestCount}\n`;
|
||
requestLeaderboard += `*${index + 1}*. ${escapeMarkdown(user.firstName)} - Запросов: ${user.requestCount}\n`;
|
||
});
|
||
|
||
await bot.sendMessage(chatId, requestLeaderboard, {parse_mode: 'Markdown'});
|
||
});
|
||
|
||
|
||
async function sendTemporaryMessage (chatId, text, interval = 5000) {
|
||
const message = await bot.sendMessage(chatId, text)
|
||
// Удаляем сообщение через указанный интервал
|
||
setTimeout(async () => {
|
||
try {
|
||
//if(!ctx.chat || !ctx.chat.id) {throw new Error('Chat ID not found');}
|
||
await bot.deleteMessage(chatId, message.message_id); // Удаляем сообщение
|
||
|
||
} catch (error) {
|
||
console.error('Ошибка при удалении сообщения:', error);
|
||
}
|
||
}, interval);
|
||
}
|
||
|
||
bot.on('polling_error', (error) => {
|
||
console.log(error);
|
||
});
|
||
console.log('bot started');
|
||
// Функция для отображения статуса кеша и спамеров
|
||
// setInterval(() => {
|
||
// const cacheEfficiency = ((stats.cacheHits / (stats.cacheHits + stats.cacheMisses)) || 0) * 100;
|
||
// const spamPercent = ((stats.spamCount / stats.totalUsers) || 0) * 100;
|
||
// console.log(`Эффективность кеша: ${cacheEfficiency.toFixed(2)}%, Процент спамеров: ${spamPercent.toFixed(2)}%`);
|
||
// }, 60000); // Обновляем каждую минуту
|