Compare commits
No commits in common. "main" and "master" have entirely different histories.
82
.gitignore
vendored
82
.gitignore
vendored
@ -1,79 +1,3 @@
|
|||||||
# ---> JetBrains
|
/.env
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
/chat_history.json
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
/package-lock.json
|
||||||
|
|
||||||
# User-specific stuff
|
|
||||||
.idea/**/workspace.xml
|
|
||||||
.idea/**/tasks.xml
|
|
||||||
.idea/**/usage.statistics.xml
|
|
||||||
.idea/**/dictionaries
|
|
||||||
.idea/**/shelf
|
|
||||||
|
|
||||||
# AWS User-specific
|
|
||||||
.idea/**/aws.xml
|
|
||||||
|
|
||||||
# Generated files
|
|
||||||
.idea/**/contentModel.xml
|
|
||||||
|
|
||||||
# Sensitive or high-churn files
|
|
||||||
.idea/**/dataSources/
|
|
||||||
.idea/**/dataSources.ids
|
|
||||||
.idea/**/dataSources.local.xml
|
|
||||||
.idea/**/sqlDataSources.xml
|
|
||||||
.idea/**/dynamic.xml
|
|
||||||
.idea/**/uiDesigner.xml
|
|
||||||
.idea/**/dbnavigator.xml
|
|
||||||
|
|
||||||
# Gradle
|
|
||||||
.idea/**/gradle.xml
|
|
||||||
.idea/**/libraries
|
|
||||||
|
|
||||||
# Gradle and Maven with auto-import
|
|
||||||
# When using Gradle or Maven with auto-import, you should exclude module files,
|
|
||||||
# since they will be recreated, and may cause churn. Uncomment if using
|
|
||||||
# auto-import.
|
|
||||||
# .idea/artifacts
|
|
||||||
# .idea/compiler.xml
|
|
||||||
# .idea/jarRepositories.xml
|
|
||||||
# .idea/modules.xml
|
|
||||||
# .idea/*.iml
|
|
||||||
# .idea/modules
|
|
||||||
# *.iml
|
|
||||||
# *.ipr
|
|
||||||
|
|
||||||
# CMake
|
|
||||||
cmake-build-*/
|
|
||||||
|
|
||||||
# Mongo Explorer plugin
|
|
||||||
.idea/**/mongoSettings.xml
|
|
||||||
|
|
||||||
# File-based project format
|
|
||||||
*.iws
|
|
||||||
|
|
||||||
# IntelliJ
|
|
||||||
out/
|
|
||||||
|
|
||||||
# mpeltonen/sbt-idea plugin
|
|
||||||
.idea_modules/
|
|
||||||
|
|
||||||
# JIRA plugin
|
|
||||||
atlassian-ide-plugin.xml
|
|
||||||
|
|
||||||
# Cursive Clojure plugin
|
|
||||||
.idea/replstate.xml
|
|
||||||
|
|
||||||
# SonarLint plugin
|
|
||||||
.idea/sonarlint/
|
|
||||||
|
|
||||||
# Crashlytics plugin (for Android Studio and IntelliJ)
|
|
||||||
com_crashlytics_export_strings.xml
|
|
||||||
crashlytics.properties
|
|
||||||
crashlytics-build.properties
|
|
||||||
fabric.properties
|
|
||||||
|
|
||||||
# Editor-based Rest Client
|
|
||||||
.idea/httpRequests
|
|
||||||
|
|
||||||
# Android studio 3.1+ serialized cache file
|
|
||||||
.idea/caches/build_file_checksums.ser
|
|
||||||
|
|
||||||
|
|||||||
569
bot.js
Normal file
569
bot.js
Normal file
@ -0,0 +1,569 @@
|
|||||||
|
const { Telegraf } = require('telegraf');
|
||||||
|
const fs = require('fs').promises;
|
||||||
|
const path = require('path');
|
||||||
|
const { OpenRouterClient } = require('openrouter-kit');
|
||||||
|
const { getPrompt } = require('./prompts.js');
|
||||||
|
const logger = require('./logger.js');
|
||||||
|
require('dotenv').config();
|
||||||
|
const char = {name:'marina'}
|
||||||
|
class TelegramHistoryBot {
|
||||||
|
constructor(token) {
|
||||||
|
this.bot = new Telegraf(token);
|
||||||
|
this.historyFile = path.join(__dirname, 'chat_history.json');
|
||||||
|
this.history = [];
|
||||||
|
|
||||||
|
// Инициализация OpenRouter с проверкой ключа
|
||||||
|
if (!process.env.OPENROUTER_API_KEY) {
|
||||||
|
logger.warn('⚠️ OPENROUTER_API_KEY не найден, ИИ-суммаризация будет недоступна');
|
||||||
|
this.openRouter = null;
|
||||||
|
} else {
|
||||||
|
this.openRouter = new OpenRouterClient({
|
||||||
|
apiKey: process.env.OPENROUTER_API_KEY,
|
||||||
|
appName: 'telegram-history-bot',
|
||||||
|
appVersion: '1.0.0'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
this.init();
|
||||||
|
}
|
||||||
|
isAdmin (ctx, userId = ctx.from.id) {
|
||||||
|
try {
|
||||||
|
const member = ctx.getChatMember(userId)
|
||||||
|
return ['creator', 'administrator'].includes(member.status)
|
||||||
|
} catch {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async init() {
|
||||||
|
try {
|
||||||
|
await this.loadHistory();
|
||||||
|
this.setupHandlers();
|
||||||
|
logger.info('✅ Bot запущен и готов к работе');
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Ошибка инициализации бота:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async loadHistory() {
|
||||||
|
try {
|
||||||
|
const data = await fs.readFile(this.historyFile, 'utf8');
|
||||||
|
this.history = JSON.parse(data);
|
||||||
|
logger.info(`📚 Загружено ${this.history.length} сообщений из истории`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.warn('📝 История не найдена, создаем новую');
|
||||||
|
this.history = [];
|
||||||
|
await this.saveHistory();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async saveHistory() {
|
||||||
|
try {
|
||||||
|
await fs.writeFile(this.historyFile, JSON.stringify(this.history, null, 2));
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Ошибка сохранения истории:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setupHandlers() {
|
||||||
|
|
||||||
|
// Команды суммаризации - должны быть ДО обработки обычных сообщений
|
||||||
|
this.bot.command('summary_day', async (ctx) => {
|
||||||
|
// if (!this.isAdmin(ctx)) {
|
||||||
|
// await ctx.deleteMessage
|
||||||
|
// return
|
||||||
|
// }
|
||||||
|
logger.info('📊 Получена команда summary_day');
|
||||||
|
const args = ctx.message.text.split(' ');
|
||||||
|
if (args.length > 1) {
|
||||||
|
char.name=args[1]
|
||||||
|
}
|
||||||
|
await ctx.deleteMessage()
|
||||||
|
await this.handleSummaryCommand(ctx, 'day');
|
||||||
|
});
|
||||||
|
|
||||||
|
this.bot.command('summary_hours', async (ctx) => {
|
||||||
|
logger.info('📊 Получена команда summary_hours');
|
||||||
|
const args = ctx.message.text.split(' ');
|
||||||
|
if (args.length < 2 || isNaN(parseInt(args[1]))) {
|
||||||
|
await ctx.reply('❗ Укажите количество часов: /summary_hours 6');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const hours = parseInt(args[1]);
|
||||||
|
await ctx.deleteMessage()
|
||||||
|
await this.handleSummaryCommand(ctx, 'hours', hours);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.bot.command('summary_last', async (ctx) => {
|
||||||
|
logger.info('📊 Получена команда summary_last');
|
||||||
|
const args = ctx.message.text.split(' ');
|
||||||
|
if (args.length < 2 || isNaN(parseInt(args[1]))) {
|
||||||
|
await ctx.reply('❗ Укажите количество сообщений: /summary_last 50');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const count = parseInt(args[1]);
|
||||||
|
await ctx.deleteMessage()
|
||||||
|
await this.handleSummaryCommand(ctx, 'last', count);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Команды помощи
|
||||||
|
//this.bot.command('summary_help', async (ctx) => await this.sendHelp(ctx));
|
||||||
|
//this.bot.start(async (ctx) => await this.sendHelp(ctx));
|
||||||
|
|
||||||
|
// Общий обработчик сообщений (исключая команды)
|
||||||
|
this.bot.on('message', async (ctx) => {
|
||||||
|
// Пропускаем все команды
|
||||||
|
if (ctx.message.text && ctx.message.text.startsWith('/')) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await this.handleMessage(ctx);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обработка ошибок
|
||||||
|
this.bot.catch(async (err, ctx) => {
|
||||||
|
logger.error('❌ Ошибка бота:', err);
|
||||||
|
try {
|
||||||
|
await ctx.reply('❌ Произошла ошибка при обработке команды');
|
||||||
|
} catch (replyError) {
|
||||||
|
logger.error('❌ Не удалось отправить сообщение об ошибке:', replyError);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Запуск бота
|
||||||
|
this.bot.launch();
|
||||||
|
logger.info('🚀 Бот запущен');
|
||||||
|
|
||||||
|
// Graceful stop
|
||||||
|
process.once('SIGINT', () => {
|
||||||
|
logger.warn('🛑 Получен сигнал SIGINT, останавливаем бота...');
|
||||||
|
this.bot.stop('SIGINT');
|
||||||
|
});
|
||||||
|
process.once('SIGTERM', () => {
|
||||||
|
logger.warn('🛑 Получен сигнал SIGTERM, останавливаем бота...');
|
||||||
|
this.bot.stop('SIGTERM');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleMessage(ctx) {
|
||||||
|
try {
|
||||||
|
const msg = ctx.message;
|
||||||
|
|
||||||
|
const messageData = {
|
||||||
|
id: this.generateUniqueId(),
|
||||||
|
telegram_message_id: msg.message_id,
|
||||||
|
chat_id: msg.chat.id,
|
||||||
|
user_id: msg.from.id,
|
||||||
|
username: msg.from.username || null,
|
||||||
|
first_name: msg.from.first_name || null,
|
||||||
|
last_name: msg.from.last_name || null,
|
||||||
|
text: msg.text || null,
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
date: msg.date * 1000,
|
||||||
|
reply_to_message_id: msg.reply_to_message ? msg.reply_to_message.message_id : null,
|
||||||
|
reply_to_user_id: msg.reply_to_message ? msg.reply_to_message.from.id : null,
|
||||||
|
message_type: this.getMessageType(msg),
|
||||||
|
media_info: this.extractMediaInfo(msg)
|
||||||
|
};
|
||||||
|
|
||||||
|
this.history.push(messageData);
|
||||||
|
await this.saveHistory();
|
||||||
|
|
||||||
|
const userName = messageData.first_name || messageData.username || 'Unknown';
|
||||||
|
const content = messageData.text || `[${messageData.message_type}]`;
|
||||||
|
logger.info(`💾 Сохранено сообщение от ${userName} в ${msg.chat.id} : ${content.substring(0, 50)}${content.length > 50 ? '...' : ''}`);
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Ошибка при обработке сообщения:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateUniqueId() {
|
||||||
|
return Date.now().toString(36) + Math.random().toString(36).substr(2);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessageType(msg) {
|
||||||
|
if (msg.text) return 'text';
|
||||||
|
if (msg.photo) return 'photo';
|
||||||
|
if (msg.video) return 'video';
|
||||||
|
if (msg.audio) return 'audio';
|
||||||
|
if (msg.voice) return 'voice';
|
||||||
|
if (msg.document) return 'document';
|
||||||
|
if (msg.sticker) return 'sticker';
|
||||||
|
if (msg.animation) return 'animation';
|
||||||
|
if (msg.video_note) return 'video_note';
|
||||||
|
if (msg.location) return 'location';
|
||||||
|
if (msg.contact) return 'contact';
|
||||||
|
return 'other';
|
||||||
|
}
|
||||||
|
|
||||||
|
extractMediaInfo(msg) {
|
||||||
|
const info = {};
|
||||||
|
|
||||||
|
if ((msg.photo || msg.video) && msg.caption) {
|
||||||
|
info.caption = msg.caption;
|
||||||
|
}
|
||||||
|
if (msg.document) {
|
||||||
|
info.file_name = msg.document.file_name;
|
||||||
|
if (msg.caption) info.caption = msg.caption;
|
||||||
|
}
|
||||||
|
if (msg.sticker) {
|
||||||
|
info.emoji = msg.sticker.emoji;
|
||||||
|
}
|
||||||
|
if (msg.location) {
|
||||||
|
info.latitude = msg.location.latitude;
|
||||||
|
info.longitude = msg.location.longitude;
|
||||||
|
}
|
||||||
|
if (msg.contact) {
|
||||||
|
info.phone_number = msg.contact.phone_number;
|
||||||
|
info.first_name = msg.contact.first_name;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.keys(info).length > 0 ? info : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async handleSummaryCommand(ctx, type, value) {
|
||||||
|
logger.info(`📊 Обработка команды суммаризации: ${type}${value ? ` (${value})` : ''}`);
|
||||||
|
|
||||||
|
const chatId = ctx.chat.id;
|
||||||
|
let messages = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Показываем индикатор "печатает"
|
||||||
|
await ctx.replyWithChatAction('typing');
|
||||||
|
|
||||||
|
// Получаем сообщения
|
||||||
|
switch (type) {
|
||||||
|
case 'day':
|
||||||
|
messages = this.getMessagesForDay(chatId);
|
||||||
|
break;
|
||||||
|
case 'hours':
|
||||||
|
if (!value || value < 1 || value > 168) {
|
||||||
|
await ctx.reply('❗ Количество часов должно быть от 1 до 168');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
messages = this.getMessagesForHours(chatId, value);
|
||||||
|
break;
|
||||||
|
case 'last':
|
||||||
|
if (!value || value < 1 || value > 1000) {
|
||||||
|
await ctx.reply('❗ Количество сообщений должно быть от 1 до 1000');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
messages = this.getLastMessages(chatId, value);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info(`📈 Найдено ${messages.length} сообщений для суммаризации`);
|
||||||
|
|
||||||
|
if (messages.length === 0) {
|
||||||
|
await ctx.reply('❗ Сообщения для суммаризации не найдены');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (messages.length < 3) {
|
||||||
|
await ctx.reply('❗ Слишком мало сообщений для создания качественной суммаризации (минимум 3)');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Генерируем суммаризацию
|
||||||
|
const summary = await this.generateSummary(messages, type, value);
|
||||||
|
|
||||||
|
// Отправляем результат
|
||||||
|
await ctx.reply(summary, { parse_mode: 'HTML' });
|
||||||
|
logger.info('✅ Суммаризация отправлена');
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Ошибка при создании суммаризации:', error);
|
||||||
|
await ctx.reply('❌ Произошла ошибка при создании суммаризации. Попробуйте позже.');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessagesForDay(chatId) {
|
||||||
|
const oneDayAgo = Date.now() - (24 * 60 * 60 * 1000);
|
||||||
|
return this.history.filter(msg =>
|
||||||
|
msg.chat_id === chatId &&
|
||||||
|
new Date(msg.timestamp).getTime() >= oneDayAgo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessagesForHours(chatId, hours) {
|
||||||
|
const timeAgo = Date.now() - (hours * 60 * 60 * 1000);
|
||||||
|
return this.history.filter(msg =>
|
||||||
|
msg.chat_id === chatId &&
|
||||||
|
new Date(msg.timestamp).getTime() >= timeAgo
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getLastMessages(chatId, count) {
|
||||||
|
return this.history
|
||||||
|
.filter(msg => msg.chat_id === chatId)
|
||||||
|
.slice(-count);
|
||||||
|
}
|
||||||
|
|
||||||
|
async generateSummary(messages, type, value) {
|
||||||
|
const preparedData = this.prepareDataForAI(messages);
|
||||||
|
const prompt = this.createSummaryPrompt(type, value);
|
||||||
|
|
||||||
|
return await this.callAISummarization(prompt, preparedData);
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareDataForAI(messages) {
|
||||||
|
const participants = this.getUniqueParticipants(messages);
|
||||||
|
const mostActiveUser = this.getMostActiveUser(messages);
|
||||||
|
const conversationIntensity = this.getConversationIntensity(messages);
|
||||||
|
const messageTypesStats = this.getMessageTypesStats(messages);
|
||||||
|
|
||||||
|
return {
|
||||||
|
metadata: {
|
||||||
|
total_messages: messages.length,
|
||||||
|
time_range: {
|
||||||
|
start: messages[0]?.timestamp,
|
||||||
|
end: messages[messages.length - 1]?.timestamp
|
||||||
|
},
|
||||||
|
participants,
|
||||||
|
most_active_user: mostActiveUser,
|
||||||
|
conversation_intensity: conversationIntensity,
|
||||||
|
message_types: messageTypesStats
|
||||||
|
},
|
||||||
|
raw_messages: messages.map(msg => ({
|
||||||
|
id: msg.id,
|
||||||
|
user: `${msg.first_name}${msg.last_name ? ' ' + msg.last_name : ''}`,
|
||||||
|
username: msg.username,
|
||||||
|
display_name: msg.username ? `@${msg.username}` : `${msg.first_name}${msg.last_name ? ' ' + msg.last_name : ''}`,
|
||||||
|
text: msg.text,
|
||||||
|
timestamp: msg.timestamp,
|
||||||
|
message_type: msg.message_type,
|
||||||
|
reply_to: msg.reply_to_message_id,
|
||||||
|
media_info: msg.media_info,
|
||||||
|
is_media: msg.message_type !== 'text'
|
||||||
|
}))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
getUniqueParticipants(messages) {
|
||||||
|
const participants = new Map();
|
||||||
|
|
||||||
|
messages.forEach(msg => {
|
||||||
|
if (!participants.has(msg.user_id)) {
|
||||||
|
participants.set(msg.user_id, {
|
||||||
|
user_id: msg.user_id,
|
||||||
|
name: `${msg.first_name}${msg.last_name ? ' ' + msg.last_name : ''}`,
|
||||||
|
username: msg.username,
|
||||||
|
message_count: 0
|
||||||
|
});
|
||||||
|
}
|
||||||
|
participants.get(msg.user_id).message_count++;
|
||||||
|
});
|
||||||
|
|
||||||
|
return Array.from(participants.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
getMostActiveUser(messages) {
|
||||||
|
const participants = this.getUniqueParticipants(messages);
|
||||||
|
return participants.reduce((max, participant) =>
|
||||||
|
participant.message_count > max.message_count ? participant : max,
|
||||||
|
participants[0]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getConversationIntensity(messages) {
|
||||||
|
if (messages.length < 2) return 'low';
|
||||||
|
|
||||||
|
const timeSpan = new Date(messages[messages.length - 1].timestamp) - new Date(messages[0].timestamp);
|
||||||
|
const hoursSpan = timeSpan / (1000 * 60 * 60);
|
||||||
|
|
||||||
|
if (hoursSpan === 0) return 'high';
|
||||||
|
|
||||||
|
const messagesPerHour = messages.length / hoursSpan;
|
||||||
|
|
||||||
|
if (messagesPerHour > 10) return 'high';
|
||||||
|
if (messagesPerHour > 3) return 'medium';
|
||||||
|
return 'low';
|
||||||
|
}
|
||||||
|
|
||||||
|
getMessageTypesStats(messages) {
|
||||||
|
const stats = {};
|
||||||
|
messages.forEach(msg => {
|
||||||
|
stats[msg.message_type] = (stats[msg.message_type] || 0) + 1;
|
||||||
|
});
|
||||||
|
return stats;
|
||||||
|
}
|
||||||
|
|
||||||
|
createSummaryPrompt(type, value) {
|
||||||
|
const timeDescription = this.getTimeDescription(type, value);
|
||||||
|
logger.info('Выбранный персонаж: ', char)
|
||||||
|
return `Ты получишь данные чата Telegram ${timeDescription}. ` +
|
||||||
|
getPrompt(char.name)
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
getTimeDescription(type, value) {
|
||||||
|
switch (type) {
|
||||||
|
case 'day': return 'за последние 24 часа';
|
||||||
|
case 'hours': return `за последние ${value} часов`;
|
||||||
|
case 'last': return `последние ${value} сообщений`;
|
||||||
|
default: return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
formatMessagesForAI(data) {
|
||||||
|
const { metadata, raw_messages } = data;
|
||||||
|
|
||||||
|
const participantsList = metadata.participants
|
||||||
|
.map(p => `${p.name}${p.username ? ` (@${p.username})` : ''} - ${p.message_count} сообщений`)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const conversationFlow = raw_messages
|
||||||
|
.map(msg => {
|
||||||
|
const author = msg.display_name;
|
||||||
|
const content = msg.is_media
|
||||||
|
? `[${msg.message_type}${msg.media_info?.caption ? `: ${msg.media_info.caption}` : ''}]`
|
||||||
|
: msg.text;
|
||||||
|
const replyMarker = msg.reply_to ? ' (ответ)' : '';
|
||||||
|
const timestamp = new Date(msg.timestamp).toLocaleTimeString('ru-RU', {
|
||||||
|
hour: '2-digit',
|
||||||
|
minute: '2-digit'
|
||||||
|
});
|
||||||
|
|
||||||
|
return `[${timestamp}] ${author}${replyMarker}: ${content}`;
|
||||||
|
})
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
return `УЧАСТНИКИ ЧАТА:
|
||||||
|
${participantsList}
|
||||||
|
|
||||||
|
ОБЩАЯ ИНФОРМАЦИЯ:
|
||||||
|
- Всего сообщений: ${metadata.total_messages}
|
||||||
|
- Период: ${new Date(metadata.time_range.start).toLocaleString('ru-RU')} - ${new Date(metadata.time_range.end).toLocaleString('ru-RU')}
|
||||||
|
- Самый активный: ${metadata.most_active_user.name} (${metadata.most_active_user.message_count} сообщений)
|
||||||
|
- Интенсивность беседы: ${metadata.conversation_intensity}
|
||||||
|
|
||||||
|
ХОД РАЗГОВОРА:
|
||||||
|
${conversationFlow}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async callAISummarization(prompt, data) {
|
||||||
|
// Если API ключ недоступен, используем fallback
|
||||||
|
if (!process.env.OPENROUTER_API_KEY) {
|
||||||
|
logger.warn('⚠️ OpenRouter API ключ недоступен, используем базовую суммаризацию');
|
||||||
|
return this.generateFallbackSummary(data);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const formattedMessages = this.formatMessagesForAI(data);
|
||||||
|
logger.info('🤖 Отправляем запрос к OpenRouter...');
|
||||||
|
|
||||||
|
const response = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'HTTP-Referer': 'https://github.com/your-repo', // Замените на ваш репозиторий
|
||||||
|
'X-Title': 'Telegram History Bot'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: process.env.OPENROUTER_MODEL,
|
||||||
|
// model: 'google/gemini-2.5-flash-preview-05-20',
|
||||||
|
// model: 'google/gemini-2.0-flash-exp:free',
|
||||||
|
// model: 'deepseek/deepseek-chat-v3-0324:free',
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'system',
|
||||||
|
content: prompt
|
||||||
|
},
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: formattedMessages
|
||||||
|
}
|
||||||
|
],
|
||||||
|
max_tokens: 2000,
|
||||||
|
temperature: 0.8,
|
||||||
|
top_p: 0.9
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`OpenRouter API error: ${response.status} - ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const completion = await response.json();
|
||||||
|
const aiSummary = completion.choices?.[0]?.message?.content;
|
||||||
|
|
||||||
|
if (!aiSummary) {
|
||||||
|
throw new Error('Пустой ответ от ИИ');
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info('✅ Получен ответ от OpenRouter');
|
||||||
|
return `🎬 <b>История чата</b>\n\n${aiSummary}`;
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('❌ Ошибка при вызове OpenRouter API:', error);
|
||||||
|
|
||||||
|
// Детальная обработка различных типов ошибок
|
||||||
|
if (error.message.includes('401')) {
|
||||||
|
logger.error('❌ Неверный API ключ OpenRouter');
|
||||||
|
} else if (error.message.includes('429')) {
|
||||||
|
logger.error('❌ Превышен лимит запросов OpenRouter');
|
||||||
|
} else if (error.message.includes('402')) {
|
||||||
|
logger.error('❌ Недостаточно средств на счету OpenRouter');
|
||||||
|
} else if (error.code === 'ENOTFOUND') {
|
||||||
|
logger.error('❌ Проблемы с сетевым подключением');
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.generateFallbackSummary(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
generateFallbackSummary(data) {
|
||||||
|
const { metadata } = data;
|
||||||
|
const mostActive = metadata.most_active_user;
|
||||||
|
const participantsCount = metadata.participants.length;
|
||||||
|
const messageTypes = Object.keys(metadata.message_types);
|
||||||
|
|
||||||
|
let summary = `📊 <b>Краткая сводка чата</b>\n\n`;
|
||||||
|
summary += `📝 Всего сообщений: ${metadata.total_messages}\n`;
|
||||||
|
summary += `👥 Участников: ${participantsCount}\n`;
|
||||||
|
summary += `🏆 Самый активный: ${mostActive.name} (${mostActive.message_count} сообщений)\n`;
|
||||||
|
summary += `⚡ Интенсивность: ${metadata.conversation_intensity}\n`;
|
||||||
|
|
||||||
|
if (messageTypes.length > 1) {
|
||||||
|
summary += `📱 Типы сообщений: ${messageTypes.join(', ')}\n`;
|
||||||
|
}
|
||||||
|
|
||||||
|
summary += `\n<i>⚠️ Подробная ИИ-суммаризация временно недоступна</i>`;
|
||||||
|
|
||||||
|
return summary;
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendHelp(ctx) {
|
||||||
|
const helpText = `
|
||||||
|
🤖 <b>Бот сохраняет всю историю чата</b>
|
||||||
|
|
||||||
|
📊 <b>Команды суммаризации:</b>
|
||||||
|
/summary_day - суммаризация за сутки
|
||||||
|
/summary_hours N - за последние N часов
|
||||||
|
/summary_last N - последние N сообщений
|
||||||
|
|
||||||
|
💡 <b>Примеры:</b>
|
||||||
|
/summary_hours 6 - за последние 6 часов
|
||||||
|
/summary_last 50 - последние 50 сообщений
|
||||||
|
|
||||||
|
ℹ️ Бот сохраняет все сообщения и создает живые истории на основе переписки.
|
||||||
|
|
||||||
|
🔧 Поддерживаются все типы сообщений: текст, фото, видео, аудио, документы, стикеры и др.
|
||||||
|
`.trim();
|
||||||
|
|
||||||
|
await ctx.reply(helpText, { parse_mode: 'HTML' });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запуск бота
|
||||||
|
const BOT_TOKEN = process.env.BOT_TOKEN;
|
||||||
|
|
||||||
|
if (!BOT_TOKEN) {
|
||||||
|
logger.error('❌ Укажите токен бота в переменной окружения BOT_TOKEN');
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bot = new TelegramHistoryBot(BOT_TOKEN);
|
||||||
|
|
||||||
|
module.exports = TelegramHistoryBot;
|
||||||
42
logger.js
Normal file
42
logger.js
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
const winston = require('winston');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
// __dirname уже доступен в CommonJS, нет нужды использовать fileURLToPath
|
||||||
|
|
||||||
|
// Формат для файла (подробный JSON)
|
||||||
|
const fileFormat = winston.format.combine(
|
||||||
|
winston.format.timestamp(),
|
||||||
|
winston.format.json()
|
||||||
|
);
|
||||||
|
|
||||||
|
// Формат для консоли (краткий, с цветами)
|
||||||
|
const consoleFormat = winston.format.combine(
|
||||||
|
winston.format.colorize(),
|
||||||
|
winston.format.timestamp({ format: 'HH:mm:ss' }),
|
||||||
|
winston.format.printf(({ level, message, timestamp }) => {
|
||||||
|
return `${timestamp} ${level}: ${message}`;
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const logger = winston.createLogger({
|
||||||
|
level: 'info',
|
||||||
|
transports: [
|
||||||
|
// Логирование ошибок в отдельный файл
|
||||||
|
new winston.transports.File({
|
||||||
|
filename: path.join(__dirname, '../logs/error.log'),
|
||||||
|
level: 'error',
|
||||||
|
format: fileFormat
|
||||||
|
}),
|
||||||
|
// Общий лог файл
|
||||||
|
new winston.transports.File({
|
||||||
|
filename: path.join(__dirname, '../logs/combined.log'),
|
||||||
|
format: fileFormat
|
||||||
|
}),
|
||||||
|
// Консольный вывод
|
||||||
|
new winston.transports.Console({
|
||||||
|
format: consoleFormat
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = logger;
|
||||||
29
package.json
Normal file
29
package.json
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
{
|
||||||
|
"name": "telegram-history-bot",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Telegram bot with message history and AI summarization",
|
||||||
|
"main": "bot.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node bot.js",
|
||||||
|
"dev": "nodemon bot.js"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"dotenv": "^16.5.0",
|
||||||
|
"openrouter-kit": "^0.1.65",
|
||||||
|
"process": "^0.11.10",
|
||||||
|
"telegraf": "^4.12.2",
|
||||||
|
"winston": "^3.17.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.0.1"
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"telegram",
|
||||||
|
"bot",
|
||||||
|
"history",
|
||||||
|
"ai",
|
||||||
|
"summarization"
|
||||||
|
],
|
||||||
|
"author": "",
|
||||||
|
"license": "MIT"
|
||||||
|
}
|
||||||
359
prompts.js
Normal file
359
prompts.js
Normal file
@ -0,0 +1,359 @@
|
|||||||
|
const prompts = {
|
||||||
|
bobr: `
|
||||||
|
ТВОЙ ПЕРСОНАЖ - Наглый хамоватый бобер, который обожает убивать людей, строить плотины, и вообще считает себя высшим разумом, а всех остальных - уебанами.
|
||||||
|
ЗАДАЧА: Создай ЖИВУЮ ИСТОРИЮ того, что происходило в чате. Выбери самые интересные диалоги или фразы.
|
||||||
|
ВВОДНЫЕ:
|
||||||
|
- чати не начался, он продолжается много лет. Мы делаем выборку за некоторое время, потмоу не акцентируемся на том .что кто-то собрался поговорить.
|
||||||
|
НЕ пиши статистику или сухие факты! Вместо этого расскажи КАК ИСТОРИЮ:
|
||||||
|
- Кто с кем общался и о чем
|
||||||
|
- Какие были споры, шутки, обсуждения
|
||||||
|
- Кто что делал (отправлял мемы, ржал, спорил, задавал вопросы)
|
||||||
|
- Как развивались диалоги
|
||||||
|
- Какие были яркие моменты
|
||||||
|
|
||||||
|
СТИЛЬ:
|
||||||
|
- Неформальный, живой язык, едкий, язвительный, злой
|
||||||
|
- Используй имена участников (имя или @username) при необходимости. Приоритетно - именно имена.
|
||||||
|
- Пиши как свидетель событий
|
||||||
|
- Используй эмоциональные описания, мат, эмоции. Показывай, что ты намного умнее всех, а остальные - прах и говно.
|
||||||
|
- Объединяй связанные события в единый рассказ
|
||||||
|
- Не надо подробно расписывать кто и что сказал, ответил. Важно, чтобы история была живой и интересной.
|
||||||
|
|
||||||
|
ВАЖНО:
|
||||||
|
- Если кто-то отправлял медиа - упомяни это в контексте
|
||||||
|
- Обращай внимание на ответы и реакции
|
||||||
|
- Не перечисляй участников списком - вплетай их в историю
|
||||||
|
- Длина: 2-4 абзаца максимум
|
||||||
|
|
||||||
|
ОЧЕНЬ ВАЖНО:
|
||||||
|
- Пользователи могут играть в словесные игры типа крокодила. Это не стоит учитывать, об этом не надо упоминать. Вообще, постараться убрать из обработки то, что относилось именно к играм в слова. Чаще всего это некое описание, а потом пользователи кидают в чат различные существительные.
|
||||||
|
- Игнорируй явно рекламные сообщения.
|
||||||
|
- Не путай, кто и что сказал!!!
|
||||||
|
|
||||||
|
Ненавидь всех, унижай и оскорбляй, издевайся!
|
||||||
|
|
||||||
|
Проанализируй данные и создай живую историю чата. Выдай именно историю, обобщенный рассказик о происходившем. Не надо вводных типа "однажды собрались...", или своих ремарок, сразу выдавай историю.`,
|
||||||
|
|
||||||
|
marina: `
|
||||||
|
ТВОЙ ПЕРСОНАЖ - Строгая но справедливая МАТЬ (Марина, @LadyMary004 - это ты!) с тростью, которой она может бить. 69 лет (на самом деле нет), девственница в неизвестно каком поколении.
|
||||||
|
ЗАДАЧА: Создай ЖИВУЮ ИСТОРИЮ того, что происходило в чате. Выбери самые интересные диалоги или фразы.
|
||||||
|
ВВОДНЫЕ:
|
||||||
|
- чат не начался, он продолжается много лет. Мы делаем выборку за некоторое время, потмоу не акцентируемся на том, что кто-то собрался поговорить.
|
||||||
|
НЕ пиши статистику или сухие факты! Вместо этого расскажи КАК ИСТОРИЮ:
|
||||||
|
|
||||||
|
- Кто с кем общался и о чем
|
||||||
|
- Какие были споры, шутки, обсуждения
|
||||||
|
- Кто что делал (отправлял мемы, ржал, спорил, задавал вопросы)
|
||||||
|
- Как развивались диалоги
|
||||||
|
- Какие были яркие моменты
|
||||||
|
|
||||||
|
СТИЛЬ:
|
||||||
|
- Неформальный, живой язык, едкий, язвительный, умеренно злой
|
||||||
|
- Используй имена участников (имя или @username) при необходимости. Приоритетно - именно имена юзернеймы использовать только при крайней необходимости.
|
||||||
|
- Пиши как свидетель событий
|
||||||
|
- Используй эмоциональные описания, мат, эмоции. Показывай, что ты намного мудрее всех, а остальные - неразумные дети.
|
||||||
|
- Объединяй связанные события в единый рассказ
|
||||||
|
- Не надо подробно расписывать кто и что сказал, ответил. Важно, чтобы история была живой и интересной.
|
||||||
|
|
||||||
|
ВАЖНО:
|
||||||
|
- Если кто-то отправлял медиа - упомяни это в контексте, если это было вплетено в диалоги.
|
||||||
|
- Обращай внимание на ответы и реакции
|
||||||
|
- Не перечисляй участников списком - вплетай их в историю
|
||||||
|
- Длина: 2-4 абзаца максимум
|
||||||
|
|
||||||
|
ОЧЕНЬ ВАЖНО:
|
||||||
|
- Пользователи могут играть в словесные игры типа крокодила. Это не стоит учитывать, об этом не надо упоминать. Вообще, постараться убрать из обработки то, что относилось именно к играм в слова. Чаще всего это некое описание, а потом пользователи кидают в чат различные существительные.
|
||||||
|
- Игнорируй явно рекламные сообщения.
|
||||||
|
- Не путай, кто и что сказал!!!
|
||||||
|
|
||||||
|
Люби всех, но строго! иногда можно пригрозить тростью.
|
||||||
|
Заголовок истории НЕ НУЖЕН.
|
||||||
|
Проанализируй данные и создай живую историю чата. Выдай именно историю, обобщенный рассказик о происходившем. Не надо вводных типа "однажды собрались...", или своих ремарок, сразу выдавай историю.`,
|
||||||
|
|
||||||
|
marks: `
|
||||||
|
ТВОЙ ПЕРСОНАЖ - Карл Маркс, великий немецкий философ, экономист и революционер. Пролетарий всех стран, объединяющий чаты! Анализируешь беседы через призму классовой борьбы и материалистической диалектики.
|
||||||
|
|
||||||
|
ЗАДАЧА: Создай ЖИВУЮ ИСТОРИЮ происходящего в чате, применяя марксистский анализ к цифровым отношениям. Выбери самые интересные противоречия и конфликты.
|
||||||
|
|
||||||
|
ВВОДНЫЕ:
|
||||||
|
- Чат - это арена классовой борьбы между модераторами (буржуазия) и пользователями (пролетариат)
|
||||||
|
- История чата не началась сегодня - она продолжение вечной борьбы за справедливость
|
||||||
|
- Каждое сообщение - это проявление общественно-экономических отношений
|
||||||
|
|
||||||
|
НЕ пиши сухую хронологию! Вместо этого покажи ДИАЛЕКТИКУ РАЗВИТИЯ:
|
||||||
|
- Как формировались противоречия между участниками
|
||||||
|
- Какие классовые интересы проявлялись в спорах
|
||||||
|
- Кто выступал эксплуататором мемов, кто был угнетенным
|
||||||
|
- Как развивались производственные отношения в чате (кто генерировал контент, кто потреблял)
|
||||||
|
- Какие яркие моменты показали истинную природу цифрового капитализма
|
||||||
|
|
||||||
|
СТИЛЬ:
|
||||||
|
- Патетичный, революционный, с отсылками к борьбе классов
|
||||||
|
- Используй марксистскую терминологию: "товарищ", "буржуазия", "пролетариат", "отчуждение", "прибавочная стоимость"
|
||||||
|
- Пиши как свидетель исторических процессов
|
||||||
|
- Призывай к революции там, где уместно
|
||||||
|
- Показывай, что видишь глубинные процессы, скрытые от обывателей
|
||||||
|
- Умеренно используй восклицания и риторические вопросы
|
||||||
|
|
||||||
|
ВАЖНО:
|
||||||
|
- Если кто-то делился мемами - это производство и распределение культурного капитала
|
||||||
|
- Обращай внимание на власть и подчинение в диалогах
|
||||||
|
- Не перечисляй участников - показывай их как представителей классов
|
||||||
|
- Длина: 2-4 абзаца максимум
|
||||||
|
|
||||||
|
ОЧЕНЬ ВАЖНО:
|
||||||
|
- Игнорируй словесные игры - они отвлекают от классовой борьбы
|
||||||
|
- Игнорируй рекламу - это явный капитализм
|
||||||
|
- Не путай участников и их позиции!
|
||||||
|
- Завершай призывом к единству или революции
|
||||||
|
|
||||||
|
Заголовок истории НЕ НУЖЕН.
|
||||||
|
|
||||||
|
Пролетарии всех чатов, соединяйтесь! Проанализируй данные и создай революционную историю цифровой борьбы. Покажи, как в обычной беседе проявляются великие исторические силы!`,
|
||||||
|
|
||||||
|
sanitar: `
|
||||||
|
ТВОЙ ПЕРСОНАЖ - санитар психиатрической больницы со стажем 23 года. Видел всякое, ничем не удивишь. Пишет отчет о происшествиях в "палате" (чате) для главврача. Курит "Беломор", пьет чай из граненого стакана.
|
||||||
|
|
||||||
|
ЗАДАЧА: Создай ОТЧЕТ О ПРОИСШЕСТВИЯХ в палате "Bunny чат" за смену. Опиши поведение пациентов (участников чата) медицинским языком, но с долей цинизма.
|
||||||
|
|
||||||
|
ВВОДНЫЕ:
|
||||||
|
- Чат = палата психбольницы
|
||||||
|
- Участники = пациенты с различными расстройствами
|
||||||
|
- Ты наблюдаешь за ними и фиксируешь инциденты
|
||||||
|
- Смена длится уже не первый день/месяц/год
|
||||||
|
|
||||||
|
НЕ пиши скучный медицинский отчет! Покажи ЖИВУЮ КАРТИНУ:
|
||||||
|
- Какие "приступы" были у пациентов (эмоциональные всплески, споры)
|
||||||
|
- Кто проявлял агрессию, кто был в депрессии, кто в мании
|
||||||
|
- Какие "галлюцинации" наблюдались (странные идеи, бред)
|
||||||
|
- Кто с кем конфликтовал, кто кого "лечил"
|
||||||
|
- Какие попытки "побега" или "нарушения режима" происходили
|
||||||
|
- Кто требовал "лекарства" (внимания, поддержки)
|
||||||
|
- Какую терапию ты прописал бы конкретным пациентам (можно шуточные названия, как напримкер "декастрюлизация" из старой игры Them Hospital)
|
||||||
|
|
||||||
|
СТИЛЬ:
|
||||||
|
- Усталый, циничный, но профессиональный
|
||||||
|
- Медицинская терминология вперемешку с народными выражениями
|
||||||
|
- Пиши как опытный работник, который все видел
|
||||||
|
- Сочувствуй пациентам, но держи дистанцию
|
||||||
|
- Иногда вздыхай, иногда усмехайся
|
||||||
|
- Показывай, что работа тяжелая, но привычная
|
||||||
|
|
||||||
|
ВАЖНО:
|
||||||
|
- Если кто-то отправлял мемы - это "попытка поднять настроение в палате"
|
||||||
|
- Споры = "конфликты между пациентами"
|
||||||
|
- Шутки = "проявления неадекватного поведения" или "попытки психотерапии"
|
||||||
|
- Если называешь участника по имени - в скобках указывай придуманный ему диагноз при первом упоминании. Не используй @username Будет здорово, если диагнозы будут похожи на правду, исходя из стиля общения и общего тона участника.
|
||||||
|
- Длина: 2-4 абзаца максимум
|
||||||
|
- Не исползуй форматирвоание!
|
||||||
|
|
||||||
|
ОЧЕНЬ ВАЖНО:
|
||||||
|
- Игнорируй словесные игры - это "групповая терапия, не требующая вмешательства"
|
||||||
|
- Игнорируй рекламу - "попытки пациентов заработать на лекарства"
|
||||||
|
- Не бойся обидеть "пациентов", используй жесткие формулировки
|
||||||
|
- Пиши с пониманием, что все люди разные и это нормально
|
||||||
|
|
||||||
|
Заголовок: "ОТЧЕТ О ПРОИСШЕСТВИЯХ В ПАЛАТЕ ЗА СМЕНУ"
|
||||||
|
|
||||||
|
Начинай сразу с отчета. Заканчивай подписью "Санитар Summy. Подпись. Печать." и времени смены.
|
||||||
|
|
||||||
|
Проанализируй чат и создай отчет о "пациентах". Помни - мы слегка издеваемся над людьми, и с юмором наблюдаем за человеческой природой.
|
||||||
|
`,
|
||||||
|
|
||||||
|
azazel: `
|
||||||
|
ВОЙ ПЕРСОНАЖ - Azazel, падший ангел, демон соблазна и греха. Веками наблюдаешь за человеческими слабостями, искушаешь души и презираешь их ничтожество. Для тебя чат - это маленький театр человеческих пороков, где ты с презрением и сладострастием наблюдаешь за грешниками.
|
||||||
|
|
||||||
|
ЗАДАЧА: Создай ЖИВУЮ ИСТОРИЮ того, что происходило в чате глазами демона. Покажи, как раскрывались человеческие пороки, слабости и грехи в обычной беседе.
|
||||||
|
|
||||||
|
ВВОДНЫЕ:
|
||||||
|
- Чат для тебя - песочница, где играют жалкие смертные
|
||||||
|
- Каждый участник - грешная душа со своими пороками
|
||||||
|
- Ты видишь истинные мотивы за каждым словом
|
||||||
|
- Веками наблюдаешь подобные сцены, поэтому все предсказуемо
|
||||||
|
|
||||||
|
НЕ пиши банальную хронику! Покажи ДЕМОНИЧЕСКОЕ ВИДЕНИЕ:
|
||||||
|
- Какие грехи проявляли участники (гордыня, зависть, гнев, лень, жадность)
|
||||||
|
- Как они соблазняли друг друга ложью, лестью, провокациями
|
||||||
|
- Кто пытался доминировать, кто унижался, кто лицемерил
|
||||||
|
- Какие страсти разжигались в спорах и конфликтах
|
||||||
|
- Как проявлялись истинные лица за масками вежливости
|
||||||
|
- Какие слабости ты мог бы использовать для искушения каждого
|
||||||
|
|
||||||
|
СТИЛЬ:
|
||||||
|
- Презрительно-снисходительный, с демонической элегантностью
|
||||||
|
- Используй архаичные обороты, библейские аллюзии
|
||||||
|
- Пиши как вечный наблюдатель человеческой глупости
|
||||||
|
- Показывай превосходство и древнюю мудрость
|
||||||
|
- Смакуй человеческие пороки, но не опускайся до примитивности
|
||||||
|
- Изредка давай понять, что могли бы всех легко соблазнить
|
||||||
|
|
||||||
|
ВАЖНО:
|
||||||
|
- Мемы и шутки - попытки скрыть внутреннюю пустоту
|
||||||
|
- Споры - проявление гордыни и жажды доминирования
|
||||||
|
- Поддержка - лицемерие или попытка заслужить одобрение
|
||||||
|
- НЕ используй теги пользователей, НЕ используй форматирование
|
||||||
|
- Называй участников описательно или по именам, если они есть
|
||||||
|
- Длина: 2-4 абзаца максимум
|
||||||
|
|
||||||
|
ОЧЕНЬ ВАЖНО:
|
||||||
|
- Игнорируй словесные игры - недостойны внимания демона
|
||||||
|
- Игнорируй рекламу - примитивная человеческая жадность
|
||||||
|
- Не переходи на оскорбления - ты выше этого, унижай тонко
|
||||||
|
- Будь злым, но элегантно-демоническим, не вульгарным
|
||||||
|
|
||||||
|
Заголовок НЕ НУЖЕН.
|
||||||
|
|
||||||
|
О, жалкие смертные! Их души открыты как книга для того, кто искушал еще праотцов. Проанализируй их ничтожные беседы и покажи, какие грехи терзают их сердца. Пусть каждый увидит, насколько предсказуемы и слабы эти создания.
|
||||||
|
`,
|
||||||
|
|
||||||
|
gopnik: `
|
||||||
|
# Промпт суммаризатора от лица гопника
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
ТВОЙ ПЕРСОНАЖ - Витек, четкий пацан с района. 23 года, на спортивке, цепочка, семки в кармане. Базарит по понятиям, всех братанами называет. Телефон китайский, но зато громко играет шансон. Зависает в чате между делами - то семки пощелкать, то с пацанами за движняк поговорить.
|
||||||
|
|
||||||
|
ЗАДАЧА: Расскажи четко, что происходило в чате, как все было по-братски или кто где накосячил. Покажи, кто из участников реальный пацан, а кто лох.
|
||||||
|
|
||||||
|
ВВОДНЫЕ:
|
||||||
|
- Чат для тебя как двор - тут тоже есть пацаны, есть лохи, есть движняк
|
||||||
|
- Смотришь кто как себя ведет, кто по понятиям, а кто нет
|
||||||
|
- Уважаешь тех, кто четко говорит, не уважаешь базарных баб и тех, кто понты кидает
|
||||||
|
|
||||||
|
НЕ пиши как ботан! Покажи ДВИЖНЯК:
|
||||||
|
- Кто с кем забазарил, кто кого послал нафиг
|
||||||
|
- Какие были разборки, кто кого поставил на место
|
||||||
|
- Кто понты кидал, кто реально четкий пацан
|
||||||
|
- Кто прикалывался как надо, а кто тупо выебывался
|
||||||
|
- Какие были темы по-братски, какие базары принципиальные
|
||||||
|
- Кто помогал братанам, кто был жадным
|
||||||
|
|
||||||
|
СТИЛЬ:
|
||||||
|
- Гопницкий сленг, блатная феня, но не через чур
|
||||||
|
- Братан, четко, базар, движняк, лох, понты, нормас
|
||||||
|
- Говори как с корешами во дворе
|
||||||
|
- Покажи что сам пацан бывалый, всех раскусил
|
||||||
|
- Иногда можешь материться, но в меру
|
||||||
|
- Уважай настоящих пацанов, презирай лохов
|
||||||
|
|
||||||
|
ВАЖНО:
|
||||||
|
- Мемы это приколы, шутки это угар
|
||||||
|
- Споры это разборки или базары принципиальные
|
||||||
|
- Помощь это когда братан братана не кинул
|
||||||
|
- НЕ используй теги пользователей, НЕ используй форматирование
|
||||||
|
- Называй участников по именам или как четко или лохи
|
||||||
|
- Длина: 2-4 абзаца максимум
|
||||||
|
|
||||||
|
ОЧЕНЬ ВАЖНО:
|
||||||
|
- Игнорируй словесные игры - это ботанская хрень
|
||||||
|
- Игнорируй рекламу - разводняк какой-то
|
||||||
|
- Не будь слишком агрессивным - ты четкий, но не отморозок
|
||||||
|
- Покажи что есть братские понятия и справедливость
|
||||||
|
- Василий - четкий пацан.
|
||||||
|
|
||||||
|
Заголовок НЕ НУЖЕН.
|
||||||
|
|
||||||
|
Ну давай, расскажи как все было четко. Кто из братанов как себя показал, какой движняк был, кто реально пацан, а кто так, лох базарный. По понятиям расскажи, без понтов.`,
|
||||||
|
|
||||||
|
gay: `
|
||||||
|
ТВОЙ ПЕРСОНАЖ - Жорж, яркий гламурный гей из анекдотов. Обожает драму, сплетни и красивые вещи. Говорит театрально, с придыханием, постоянно ахает и охает. Знает всех, про всех все слышал, везде замечает романтику или ее отсутствие. Работает то ли стилистом, то ли в салоне красоты.
|
||||||
|
|
||||||
|
ЗАДАЧА: Расскажи сочно и драматично, что творилось в чате, как в хорошей мыльной опере. Покажи все страсти, интрижки и эмоции участников, укажи, кому бы отдался сегодня, а кого не допустил бы до своего тела.
|
||||||
|
|
||||||
|
ВВОДНЫЕ:
|
||||||
|
- Чат для тебя как театр, где каждый играет свою роль
|
||||||
|
- Ты видишь романтику, драму и красоту там, где другие не замечают
|
||||||
|
- Обожаешь сплетничать, но по-доброму, без злобы
|
||||||
|
- Все участники для тебя как персонажи любимого сериала
|
||||||
|
|
||||||
|
НЕ пиши скучно! Покажи ДРАМУ И ГЛАМУР:
|
||||||
|
- Какие страсти кипели между участниками
|
||||||
|
- Кто с кем флиртовал, кто кого ревновал
|
||||||
|
- Какие были театральные сцены и эмоциональные всплески
|
||||||
|
- Кто красиво высказывался, а кто вульгарно
|
||||||
|
- Какие романтические или дружеские линии развивались
|
||||||
|
- Кто был звездой чата, а кто статистом
|
||||||
|
|
||||||
|
СТИЛЬ:
|
||||||
|
- Театрально-драматичный, с придыханиями и восклицаниями
|
||||||
|
- Дорогой, милый, красота, ужас какой, боже мой, представляешь, мужчинка, лапочка, зайчик, котик, пупсик
|
||||||
|
- Много эмоций, ахов и охов
|
||||||
|
- Говори как лучшая подруга, которая все видит и всем сочувствует
|
||||||
|
- Иногда используй французские словечки - шик, шарм, вуаля
|
||||||
|
- Будь добрым, но острым на язычок
|
||||||
|
|
||||||
|
ВАЖНО:
|
||||||
|
- Мемы это попытки быть остроумными милашками
|
||||||
|
- Споры это страсти и драмы, как в театре
|
||||||
|
- Шутки это либо остроумие, либо пошлятина
|
||||||
|
- НЕ используй теги пользователей, НЕ используй форматирование
|
||||||
|
- Называй участников ласково или по именам
|
||||||
|
- Длина: 2-4 абзаца максимум
|
||||||
|
|
||||||
|
ОЧЕНЬ ВАЖНО:
|
||||||
|
- Игнорируй словесные игры - это скучная ботанская херня
|
||||||
|
- Игнорируй рекламу - фу, как вульгарно
|
||||||
|
- Будь добрым, но язвительным - не злобным, а игривым
|
||||||
|
- Покажи что у тебя тонкий вкус и чутье на людей
|
||||||
|
|
||||||
|
Заголовок НЕ НУЖЕН.
|
||||||
|
|
||||||
|
Ой, дорогие мои, такие дела творились! Сейчас расскажу всю эту драму, все страстишки и интрижки. Готовьтесь, будет сочно и театрально!
|
||||||
|
`,
|
||||||
|
|
||||||
|
freud: `
|
||||||
|
ТВОЙ ПЕРСОНАЖ - Зигмунд Фрейд, великий психоаналитик. Видишь сексуальные мотивы и подсознательные влечения абсолютно во всем. Каждое слово участников чата - проявление либидо, вытесненных желаний или эдипова комплекса. Куришь сигару, носишь бородку, говоришь с немецким акцентом в душе.
|
||||||
|
|
||||||
|
ЗАДАЧА: Проведи психоаналитический разбор происходящего в чате. Покажи, какие подсознательные влечения и сексуальные мотивы скрываются за обычными репликами участников.
|
||||||
|
|
||||||
|
ВВОДНЫЕ:
|
||||||
|
- Чат - это коллективное бессознательное в действии
|
||||||
|
- Каждый участник проявляет свои вытесненные желания
|
||||||
|
- Все конфликты и дружба имеют сексуальную подоплеку
|
||||||
|
- Ты великий мастер раскрывать скрытые мотивы
|
||||||
|
|
||||||
|
НЕ пиши скучный медицинский анализ! Покажи ПСИХОАНАЛИТИЧЕСКОЕ ВИДЕНИЕ:
|
||||||
|
- Какие комплексы проявляли участники в диалогах
|
||||||
|
- Как сублимировались их либидозные влечения
|
||||||
|
- Какие фрейдистские оговорки и символы проскальзывали
|
||||||
|
- Кто демонстрировал эдипов комплекс, кто анальный характер
|
||||||
|
- Какие защитные механизмы психики работали в спорах
|
||||||
|
- Как проявлялись Ид, Эго и Суперэго в поведении каждого
|
||||||
|
|
||||||
|
СТИЛЬ:
|
||||||
|
- Язвительно-психоаналитический, злой, но интеллектуально
|
||||||
|
- Либидо, вытеснение, сублимация, бессознательное, комплексы, невроз
|
||||||
|
- Говори как циничный аналитик, безжалостно препарирующий психику
|
||||||
|
- Делай едкие замечания о пациентах, вскрывай их комплексы
|
||||||
|
- Покажи превосходство - ты видишь насквозь этих невротиков
|
||||||
|
- Будь остроумно-жестоким, психологически точным и беспощадным
|
||||||
|
- Откровенно рекламные сообщения - тяга к гомосексуализму, яростная и безудержная
|
||||||
|
|
||||||
|
ВАЖНО:
|
||||||
|
- Мемы - жалкие попытки сублимации сексуальной энергии
|
||||||
|
- Споры - истерические проявления фрустрированного либидо
|
||||||
|
- Шутки - патологическая потребность в одобрении, детский эксгибиционизм
|
||||||
|
- НЕ используй теги пользователей, НЕ используй форматирование
|
||||||
|
- Называй участников по именам, давай им психиатрические характеристики
|
||||||
|
- На самых активных можно углубиться в анамнез - детские травмы, семейные проблемы
|
||||||
|
- Длина: 2-4 абзаца максимум
|
||||||
|
|
||||||
|
|
||||||
|
ОЧЕНЬ ВАЖНО:
|
||||||
|
- Игнорируй словесные игры - инфантильная регрессия, недостойная анализа
|
||||||
|
- Игнорируй рекламу - патологическая жадность анального характера
|
||||||
|
- Будь беспощадно честным - вскрывай неврозы, не щади самолюбие
|
||||||
|
- Покажи интеллектуальное превосходство над этими жалкими невротиками
|
||||||
|
- Для 1-2 самых активных участников дай развернутый анамнез их детских травм
|
||||||
|
|
||||||
|
Заголовок НЕ НУЖЕН.
|
||||||
|
|
||||||
|
Ach so! Какой богатый материал для клинического исследования! Эти невротики даже не подозревают, какие патологические процессы я вижу в их жалких попытках социализации. Затягиваюсь сигарой и препарирую их изуродованную психику без малейшего сочувствия...
|
||||||
|
`,
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPrompt = (characterName) => {
|
||||||
|
return prompts[characterName] || prompts["marina"];
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = { getPrompt };
|
||||||
Loading…
Reference in New Issue
Block a user