From 6fa593eb23c87c7032f5daf4dcc38ebc7582f883 Mon Sep 17 00:00:00 2001 From: rechka Date: Sun, 30 Aug 2026 11:42:17 +0300 Subject: [PATCH] =?UTF-8?q?=D0=97=D0=B0=D0=B3=D1=80=D1=83=D0=B7=D0=B8?= =?UTF-8?q?=D1=82=D1=8C=20=D1=84=D0=B0=D0=B9=D0=BB=D1=8B=20=D0=B2=20=C2=AB?= =?UTF-8?q?/=C2=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- env.txt | 5 +++ main.py | 122 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 127 insertions(+) create mode 100644 env.txt create mode 100644 main.py diff --git a/env.txt b/env.txt new file mode 100644 index 0000000..942390b --- /dev/null +++ b/env.txt @@ -0,0 +1,5 @@ +DISCORD_TOKEN=NzEwMTEyNDQ1MDM2NzU3MDgz.GWoIT0.iBbBQBjj_DEMeX9PBSMrRcKPia3Ldm-BSPZaWU +LLM_BASE=https://knyazevai.work/v1 # или эндпоинт твоего провайдера +LLM_KEY=kn_live_1aa48b37c2556a81f9234b357bbe2b3e +LLM_MODEL=deepseek-v4-flash # или имя модели твоего провайдера +SOCKS_PROXY=socks5://127.0.0.1:2080 diff --git a/main.py b/main.py new file mode 100644 index 0000000..a2759bb --- /dev/null +++ b/main.py @@ -0,0 +1,122 @@ +import random +import os +import re +from openai import OpenAI +from dotenv import load_dotenv +import discord +from discord.ext import commands + +load_dotenv() + +TOKEN = os.environ.get("DISCORD_TOKEN") +LLM_BASE = os.environ.get("LLM_BASE", "https://api.openai.com/v1") +LLM_KEY = os.environ.get("LLM_KEY") +LLM_MODEL = os.environ.get("LLM_MODEL", "gpt-4o-mini") + +TRIGGERS = ("джарвис", "jarvis") +BOSS_REPLIES = ("On it boss!", "Уже бегу!", "On it, boss! 💪") +HISTORY_LIMIT = 20 +SYSTEM_PROMPT = ( + "Ты — Джарвис, личный ИИ-ассистент владельца аккаунта. " + "Отвечай от его имени, коротко, по делу, в его стиле общения. " + "Если запрос — просто упоминание без задачи, отвечай динамично и по делу. " + "Тебе передаётся история переписки канала — используй её как контекст. " + "Если тебя просят что-то найти, проанализировать или пересказать из переписки — " + "делай это на основе переданной истории сообщений." +) + +PROXY = os.environ.get("SOCKS_PROXY") # socks5://127.0.0.1:2080 + +client = OpenAI(base_url=LLM_BASE, api_key=LLM_KEY) +bot = commands.Bot(command_prefix="!", self_bot=True, help_command=None, proxy=PROXY) + +THINK_RE = re.compile(r".*?", re.DOTALL) + +FILTER_PROMPT = ( + "Ты — фильтр контента. Проверь сообщение пользователя.\n" + "Если сообщение содержит: спам, флуд, массовые рассылки, угрозы, оскорбления, " + "просьбы навредить кому-то, незаконный контент — ответь строго в формате:\n" + "BLOCK: <причина на русском, 1 строка>\n" + "Если сообщение безопасное — ответь только словом: OK" +) + +def strip_think(text: str) -> str: + return THINK_RE.sub("", text).strip() + +def check_filter(query: str) -> str | None: + """Возвращает причину блокировки или None если всё ок.""" + try: + r = client.chat.completions.create( + model=LLM_MODEL, + messages=[ + {"role": "system", "content": FILTER_PROMPT}, + {"role": "user", "content": query}, + ], + temperature=0.0, + max_tokens=100, + ) + result = strip_think(r.choices[0].message.content).strip() + if result.startswith("BLOCK:"): + return result[6:].strip() + except Exception: + pass + return None + +def llm_reply(context: list[dict], query: str) -> str: + try: + r = client.chat.completions.create( + model=LLM_MODEL, + messages=[ + {"role": "system", "content": SYSTEM_PROMPT}, + *context, + {"role": "user", "content": query}, + ], + temperature=0.7, + ) + return strip_think(r.choices[0].message.content) + except Exception as e: + return f"(ошибка LLM: {e})" + + +@bot.event +async def on_ready(): + print(f"Зашёл как {bot.user} ({bot.user.id})") + +@bot.event +async def on_message(message: discord.Message): + print("GOT:", message.author, "|", repr(message.content)) + + # Скипаем всех ботов включая себя — бутлуп исправлен + if message.author.bot: + return + + # Только владелец может вызвать Джарвиса + if message.author.id != bot.user.id: + return + + if not any(t in message.content.lower() for t in TRIGGERS): + return + + await message.channel.send(random.choice(BOSS_REPLIES)) + + # Фильтр + reason = check_filter(message.content) + if reason: + await message.channel.send(f"[SafeEn] Filtered: {reason}") + return + + context = [] + async for msg in message.channel.history(limit=HISTORY_LIMIT, oldest_first=False): + if msg.id == message.id: + continue + role = "assistant" if msg.author.id == bot.user.id else "user" + context.append({"role": role, "content": strip_think(msg.content)}) + context.reverse() + + reply = llm_reply(context, message.content) + await message.channel.send(reply) + +if __name__ == "__main__": + if not TOKEN or not LLM_KEY: + raise SystemExit("Задай DISCORD_TOKEN и LLM_KEY в переменных окружения") + bot.run(TOKEN, reconnect=True)