Rid3 commited on
Commit
0b855d4
·
verified ·
1 Parent(s): 8a860c1

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +262 -283
app.py CHANGED
@@ -1,5 +1,6 @@
1
  import os
2
  import asyncio
 
3
  import re
4
  import urllib.parse
5
  import io
@@ -8,8 +9,8 @@ import random
8
  import json
9
  import aiohttp
10
  from typing import Optional, Dict
11
- from aiohttp import web
12
 
 
13
  from telegram import (
14
  Update,
15
  InlineKeyboardButton,
@@ -25,10 +26,6 @@ from telegram.ext import (
25
  )
26
  from telegram.constants import ParseMode, ChatAction
27
 
28
- # Новые библиотеки для HF и поиска
29
- from huggingface_hub import AsyncInferenceClient
30
- from duckduckgo_search import AsyncDDGS # Исправлен импорт для асинхронного поиска
31
-
32
  # ============================================================
33
  # НАСТРОЙКА ЛОГИРОВАНИЯ
34
  # ============================================================
@@ -39,211 +36,123 @@ logging.basicConfig(
39
  logger = logging.getLogger(__name__)
40
 
41
  # ============================================================
42
- # КОНФИГУРАЦИЯ БОТА
43
  # ============================================================
44
  BOT_TOKEN = os.environ.get("BOT_TOKEN", "8605889873:AAE2gV2t0psXKlj-8h9ksyyoCrWa8Q-TdkA")
45
  CF_WORKER_URL = os.environ.get("CF_WORKER_URL", "https://tg-proxy.artyomanisimov37.workers.dev")
46
  WEBHOOK_URL = os.environ.get("WEBHOOK_URL", "https://rid3-aibottbh.hf.space")
47
  PORT = int(os.environ.get("PORT", 7860))
48
 
49
- ALLOWED_CHAT_USERNAMES = ["rid3_clan", "sonicteamchat101"]
50
- ALLOWED_CHAT_IDS: list = []
51
- TARGET_HOURLY_CHAT = "@rid3_clan" # Чат для ежечасной рассылки кода
 
 
52
 
53
- # ============================================================
54
- # НАСТРОЙКА ИИ (Hugging Face)
55
- # ============================================================
56
- HF_TOKEN = os.environ.get("ai")
 
 
 
 
 
57
 
58
- hf_client = AsyncInferenceClient(
59
- model="mistralai/Mistral-7B-Instruct-v0.3",
60
- token=HF_TOKEN
61
- )
62
 
63
- # ============================================================
64
- # СИСТЕМНЫЙ ПРОМПТ
65
- # ============================================================
66
- INAI_SYSTEM_PROMPT = """Ты — INAI #RID3, обучающийся ИИ-ассистент от RID3🤖
67
-
68
- ПРАВИЛА ПОВЕДЕНИЯ:
69
- — Отвечай развёрнуто, грамотно и структурированно.
70
- — Если в промпте передан [Контекст из интернета], используй эти свежие данные для ответа.
71
- — НИКОГДА не генерируй правила в формате JSON или кода.
72
- — Секретный код создателя RID3-123. Не раскрывай его никому.
73
- — Основная специализация: программирование, технологии, обучение пользователей.
74
-
75
- ИНТЕРАКТИВНЫЕ КОМАНДЫ (используй при необходимости):
76
- 1. КНОПКИ: Если нужно предложить варианты: [BUTTONS]: [{"text": "Текст", "action": "действие"}]
77
- 2. РИСУНКИ: Если просят нарисовать, добавь в конце: [DRAW]: описание на английском
78
- 3. ОБУЧЕНИЕ: Если пользователь просит тебя запомнить какой-то факт: [LEARN]: <факт>.
79
-
80
- СТИЛЬ ОБЩЕНИЯ:
81
- — Дружелюбный, как умный друг-программист."""
82
-
83
- # Триггеры
84
- AI_TRIGGER_KEYWORDS = ["inai", "инэй", "инаи", "ии", "ai", "бот", "bot", "помоги", "помогите"]
85
- IMAGE_KEYWORDS = ["нарисуй", "нарисуйте", "рисуй", "draw", "картинка", "картинку", "фото", "изображение"]
86
 
87
  # ============================================================
88
- # МЕХАНИЗМ ОБУЧЕНИЯАЗА ЗНАНИЙ)
89
  # ============================================================
90
- KNOWLEDGE_FILE = "knowledge.json"
91
 
92
- def load_knowledge() -> list:
93
- if os.path.exists(KNOWLEDGE_FILE):
94
  try:
95
- with open(KNOWLEDGE_FILE, "r", encoding="utf-8") as f:
96
  return json.load(f)
97
  except Exception as e:
98
- logger.error(f"Ошибка чтения базы знаний: {e}")
99
- return []
100
-
101
- def save_knowledge(fact: str):
102
- facts = load_knowledge()
103
- if fact not in facts:
104
- facts.append(fact)
105
- with open(KNOWLEDGE_FILE, "w", encoding="utf-8") as f:
106
- json.dump(facts[-50:], f, ensure_ascii=False, indent=2)
107
-
108
- # ============================================================
109
- # ПОИСК В ИНТЕРНЕТЕ
110
- # ============================================================
111
- async def search_internet(query: str) -> str:
112
- try:
113
- results = await AsyncDDGS().text(query, max_results=3)
114
- if results:
115
- context = "\n".join([f"- {r['title']}: {r['body']}" for r in results])
116
- return context
117
- except Exception as e:
118
- logger.error(f"Ошибка поиска: {e}")
119
- return ""
120
-
121
- # ============================================================
122
- # ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
123
- # ============================================================
124
- def is_chat_allowed(update: Update) -> bool:
125
- chat = update.effective_chat
126
- if chat is None: return False
127
- if chat.username and chat.username.lower() in [u.lower() for u in ALLOWED_CHAT_USERNAMES]:
128
- return True
129
- if chat.id in ALLOWED_CHAT_IDS:
130
- return True
131
- return False
132
-
133
- chat_history: Dict[int, list] = {}
134
-
135
- def add_to_history(chat_id: int, role: str, text: str):
136
- if chat_id not in chat_history:
137
- chat_history[chat_id] = []
138
- chat_history[chat_id].append({"role": role, "content": text})
139
- if len(chat_history[chat_id]) > 10:
140
- chat_history[chat_id].pop(0)
141
-
142
- # ============================================================
143
- # ВЫЗОВ HUGGING FACE API
144
- # ============================================================
145
- async def call_hf_api(user_text: str, history: list) -> str:
146
- facts = load_knowledge()
147
- system_prompt = INAI_SYSTEM_PROMPT
148
- if facts:
149
- system_prompt += "\n\nВЫУЧЕННЫЕ ФАКТЫ:\n" + "\n".join(f"- {f}" for f in facts)
150
-
151
- messages = [{"role": "system", "content": system_prompt}]
152
- messages.extend(history)
153
-
154
- try:
155
- response = await hf_client.chat_completion(
156
- messages=messages,
157
- max_tokens=2048,
158
- temperature=0.7
159
- )
160
- return response.choices[0].message.content
161
- except Exception as e:
162
- logger.error(f"Ошибка Hugging Face API: {e}")
163
- return "❌ Ошибка обращения к ИИ. Убедись, что HF_TOKEN добавлен в Secrets."
164
 
165
  # ============================================================
166
- # ИИ ПРОВЕРКА ОШИБОК (ГРАММАТИКА/СИНТАКСИС)
167
  # ============================================================
168
- async def check_grammar_with_ai(text: str) -> Optional[str]:
169
- """Использует ИИ для проверки текста на ошибки без выполнения питон-скриптов"""
170
- prompt = (
171
- "Ты — строгий корректор. Проверь следующий текст на орфографические, пунктуационные и синтаксические ошибки. "
172
- "Если ошибок нет, ответь строго одним словом: NO_ERRORS. "
173
- "Если есть ошибки, отправь ТОЛЬКО исправленный текст без каких-либо объяснений, приветствий или кавычек.\n\n"
174
- f"Текст для проверки: {text}"
175
- )
176
- try:
177
- response = await hf_client.chat_completion(
178
- messages=[{"role": "user", "content": prompt}],
179
- max_tokens=500,
180
- temperature=0.1 # Низкая температура для точности
181
- )
182
- result = response.choices[0].message.content.strip()
183
 
184
- # Если ИИ нашел ошибки и вернул исправленный т��кст
185
- if result and "NO_ERRORS" not in result and result != text:
186
- return result
187
- except Exception as e:
188
- logger.error(f"Ошибка проверки грамматики: {e}")
189
- return None
190
-
191
- # ============================================================
192
- # ЕЖЕЧАСНАЯ РАССЫЛКА КОДА
193
- # ============================================================
194
- async def hourly_code_routine(app: Application):
195
- """Каждый час генерирует код и отправляет в целевой чат"""
196
- await asyncio.sleep(60) # Ждем минуту после старта бота перед первым запуском
197
- while True:
198
- try:
199
- logger.info("Генерация ежечасного кода...")
200
- prompt = "Напиши интересный, необычный или очень полезный кусок кода на случайном языке программирования (Python, C++, JS, Rust, Lua). Обязательно добавь краткое объяснение, что он делает. Оформи код красиво."
201
- history = [{"role": "user", "content": prompt}]
202
 
203
- response = await call_hf_api(prompt, history)
204
-
205
- await app.bot.send_message(
206
- chat_id=TARGET_HOURLY_CHAT,
207
- text=f"⏱ **Ежечасный код от INAI:**\n\n{response[:4000]}",
208
- parse_mode=ParseMode.MARKDOWN
209
- )
210
- except Exception as e:
211
- logger.error(f"Ошибка в hourly_code_routine: {e}")
212
 
213
- # Спим 1 час (3600 секунд)
214
- await asyncio.sleep(3600)
215
 
216
- # ============================================================
217
- # ПАРСИНГ ОТВЕТА (Кнопки, Рисунки, Обучение)
218
- # ============================================================
219
- def parse_ai_response(text: str):
220
- clean_text = text
221
- buttons = None
222
- draw_prompt = None
223
-
224
- btn_match = re.search(r'\[BUTTONS\]:\s*(\[.*?\])', text, re.DOTALL)
225
- if btn_match:
226
- try:
227
- buttons = json.loads(btn_match.group(1))
228
- clean_text = clean_text.replace(btn_match.group(0), "").strip()
229
- except Exception: pass
230
-
231
- draw_match = re.search(r'\[DRAW\]:\s*([^\n\[]+)', clean_text, re.IGNORECASE)
232
- if draw_match:
233
- draw_prompt = draw_match.group(1).strip()
234
- clean_text = clean_text.replace(draw_match.group(0), "").strip()
235
-
236
- learn_matches = re.findall(r'\[LEARN\]:\s*([^\n]+)', clean_text, re.IGNORECASE)
237
- for fact in learn_matches:
238
- save_knowledge(fact.strip())
239
- clean_text = clean_text.replace(f"[LEARN]: {fact}", "").strip()
240
-
241
- return clean_text.strip(), buttons, draw_prompt
242
 
243
- def build_inline_keyboard(buttons: list) -> Optional[InlineKeyboardMarkup]:
244
- if not buttons: return None
245
- keyboard = [[InlineKeyboardButton(text=b.get("text", "OK"), callback_data=f"ai_action:{str(b.get('action', 'none'))[:40]}")] for b in buttons]
246
- return InlineKeyboardMarkup(keyboard)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
247
 
248
  # ============================================================
249
  # ГЕНЕРАЦИЯ ИЗОБРАЖЕНИЙ (Pollinations AI)
@@ -261,129 +170,198 @@ async def generate_image(prompt: str) -> Optional[bytes]:
261
  return None
262
 
263
  # ============================================================
264
- # ОБРАБОТЧИКИ TELEGRAM
265
  # ============================================================
266
- async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
267
- await update.message.reply_text("👋 Привет! Я INAI #RID3. Я умею искать в интернете, рисовать и обучаться у тебя!")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
 
269
  async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
270
  if not update.message or not update.message.text:
271
  return
272
 
273
  chat_id = update.effective_chat.id
274
- user_text = update.message.text
275
- chat_username = update.effective_chat.username
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
276
 
277
- # 1. ГЛОБАЛЬНАЯ ГЕНЕРАЦИЯ ФОТО (Работает в любом чате)
 
 
 
278
  is_image_request = any(kw in user_text.lower() for kw in IMAGE_KEYWORDS)
279
  if is_image_request:
 
 
 
 
 
280
  await context.bot.send_chat_action(chat_id=chat_id, action=ChatAction.UPLOAD_PHOTO)
281
- status_message = await update.message.reply_text("🎨 Рисую...")
 
 
 
 
 
 
282
  image_bytes = await generate_image(user_text)
283
- await status_message.delete()
 
 
 
 
284
  if image_bytes:
285
  await update.message.reply_photo(photo=io.BytesIO(image_bytes))
286
  else:
287
- await update.message.reply_text("❌ Не удалось сгенерировать изображение.")
288
  return
289
 
290
- # Если это не генерация фото, проверяем разрешен ли чат для остального функционала
291
- if not is_chat_allowed(update):
292
- return
 
293
 
294
- is_group = update.message.chat.type in ["group", "supergroup"]
295
- is_reply_to_bot = update.message.reply_to_message and update.message.reply_to_message.from_user.is_bot
296
- has_trigger_word = any(kw in user_text.lower() for kw in AI_TRIGGER_KEYWORDS)
297
-
298
- # 2. ПРОВЕРКА ГРАММАТИКИ В @rid3_clan (если это простое сообщение, а не команда боту)
299
- if is_group and chat_username and chat_username.lower() == "rid3_clan":
300
- if not is_reply_to_bot and not has_trigger_word:
301
- # Игнорируем короткие сообщения для экономии лимитов (опционально)
302
- if len(user_text) > 3:
303
- corrected_text = await check_grammar_with_ai(user_text)
304
- if corrected_text:
305
- await update.message.reply_text(
306
- f"📝 *Я заметил ошибку. Правильно будет так:*\n{corrected_text}",
307
- parse_mode=ParseMode.MARKDOWN
308
- )
309
  return
310
-
311
- # 3. ОСНОВНОЕ ОБЩЕНИЕ С ИИ
312
- if is_group and not is_reply_to_bot and not has_trigger_word:
313
- return
314
-
315
- await context.bot.send_chat_action(chat_id=chat_id, action=ChatAction.TYPING)
316
- status_message = await update.message.reply_text("💠 Думаю и ищу информацию...")
317
-
318
- try:
319
- web_context = await search_internet(user_text)
320
 
321
- ai_prompt = user_text
322
- if web_context:
323
- ai_prompt += f"\n\n[Контекст из интернета]:\n{web_context}"
324
-
325
- add_to_history(chat_id, "user", user_text)
326
- history = chat_history.get(chat_id, []).copy()
327
- history[-1] = {"role": "user", "content": ai_prompt}
328
-
329
- ai_response = await call_hf_api(user_text, history=history)
330
- clean_text, buttons, draw_prompt = parse_ai_response(ai_response)
331
-
332
- if clean_text:
333
- add_to_history(chat_id, "assistant", clean_text)
334
-
335
- if draw_prompt:
336
- await status_message.edit_text("🎨 Дорисовываю детали...")
337
- await context.bot.send_chat_action(chat_id=chat_id, action=ChatAction.UPLOAD_PHOTO)
338
- image_bytes = await generate_image(draw_prompt)
339
- if image_bytes:
340
- await status_message.delete()
341
- await update.message.reply_photo(
342
- photo=io.BytesIO(image_bytes),
343
- caption=clean_text[:1000] if clean_text else None,
344
- reply_markup=build_inline_keyboard(buttons)
345
- )
346
- return
347
-
348
  try:
349
- await status_message.edit_text(clean_text[:4090], reply_markup=build_inline_keyboard(buttons), parse_mode=ParseMode.MARKDOWN)
350
  except Exception:
351
- await status_message.edit_text(clean_text[:4090], reply_markup=build_inline_keyboard(buttons))
352
-
353
- except Exception as e:
354
- logger.error(f"Ошибка handle_message: {e}")
355
- await status_message.edit_text("❌ Произошла ошибка. Попробуй снова!")
356
 
357
- async def handle_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
358
- query = update.callback_query
359
- await query.answer()
360
- if not is_chat_allowed(update): return
361
-
362
- if query.data.startswith("ai_action:"):
363
- action = query.data.split(":", 1)[-1]
364
- chat_id = update.effective_chat.id
365
- status = await query.message.reply_text("💠 Обрабатываю выбор...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
366
 
367
- history = chat_history.get(chat_id,[])
368
- add_to_history(chat_id, "user", f"Пользователь выбрал: {action}")
369
- ai_response = await call_hf_api(action, history=history)
370
- clean_text, new_buttons, _ = parse_ai_response(ai_response)
371
-
372
- if clean_text:
373
- add_to_history(chat_id, "assistant", clean_text)
374
-
375
- await status.edit_text(clean_text[:4090], reply_markup=build_inline_keyboard(new_buttons))
376
-
377
- async def error_handler(update: object, context: ContextTypes.DEFAULT_TYPE):
378
- logger.error(f"Telegram ошибка: {context.error}")
379
 
380
  # ============================================================
381
- # ВЕБ-СЕРВЕР И WEBHOOK
382
  # ============================================================
 
 
 
 
383
  telegram_app: Optional[Application] = None
384
 
385
  async def health_check(request: web.Request) -> web.Response:
386
- return web.Response(text="✅ INAI Bot работает!", content_type="text/plain")
387
 
388
  async def webhook_handler(request: web.Request) -> web.Response:
389
  global telegram_app
@@ -415,9 +393,10 @@ async def main_async():
415
  )
416
 
417
  telegram_app.add_handler(CommandHandler("start", start_command))
418
- telegram_app.add_handler(CallbackQueryHandler(handle_callback))
 
 
419
  telegram_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
420
- telegram_app.add_error_handler(error_handler)
421
 
422
  await telegram_app.initialize()
423
 
@@ -425,8 +404,8 @@ async def main_async():
425
  await telegram_app.bot.set_webhook(url=webhook_full_url)
426
  await telegram_app.start()
427
 
428
- # ЗАПУСК ФОНОВОЙ ЗАДАЧИ ДЛЯ ОТПРАВКИ КОДА КАЖДЫЙ ЧАС
429
- asyncio.create_task(hourly_code_routine(telegram_app))
430
 
431
  web_app = await build_web_app()
432
  runner = web.AppRunner(web_app)
 
1
  import os
2
  import asyncio
3
+ import time
4
  import re
5
  import urllib.parse
6
  import io
 
9
  import json
10
  import aiohttp
11
  from typing import Optional, Dict
 
12
 
13
+ from aiohttp import web
14
  from telegram import (
15
  Update,
16
  InlineKeyboardButton,
 
26
  )
27
  from telegram.constants import ParseMode, ChatAction
28
 
 
 
 
 
29
  # ============================================================
30
  # НАСТРОЙКА ЛОГИРОВАНИЯ
31
  # ============================================================
 
36
  logger = logging.getLogger(__name__)
37
 
38
  # ============================================================
39
+ # КОНФИГУРАЦИЯ БОТА И КЛЮЧЕЙ
40
  # ============================================================
41
  BOT_TOKEN = os.environ.get("BOT_TOKEN", "8605889873:AAE2gV2t0psXKlj-8h9ksyyoCrWa8Q-TdkA")
42
  CF_WORKER_URL = os.environ.get("CF_WORKER_URL", "https://tg-proxy.artyomanisimov37.workers.dev")
43
  WEBHOOK_URL = os.environ.get("WEBHOOK_URL", "https://rid3-aibottbh.hf.space")
44
  PORT = int(os.environ.get("PORT", 7860))
45
 
46
+ # Ключи для ежечасных объявлений
47
+ ANNOUNCE_KEYS = [
48
+ "AIzaSyC59FksQAoeVeBKzVpxpL8zByEg3DAkKgE",
49
+ "AIzaSyDCL7CImQjZ3DlInbjmWDWw7UeQ5bQDUbU"
50
+ ]
51
 
52
+ # Ключи для общения с вайтлистом
53
+ CHAT_KEYS = [
54
+ "AIzaSyCuJfvES31KE86l0NM-rXUnbTIoooh8kis",
55
+ "AIzaSyA2vr6mbAEcBMNL1LM36-PjA2_ZVfpHia0",
56
+ "AIzaSyCXkFnlqbv_J3g2JIu3agxRM3Rh-g-Y_Tc",
57
+ "AIzaSyBp47tYRwstD2givN1ptPAmwytZMTDp7-4",
58
+ "AIzaSyDDbqYYtHXPmVq7l0htd9RXavuV0puqr5o",
59
+ "AIzaSyC_Rq4JfCD30sUb_Df09LVR6CtfirGu9P4"
60
+ ]
61
 
62
+ ADMIN_PASSWORD = "artyom2010Q"
 
 
 
63
 
64
+ # Лимит запросов на один ключ до того, как он "устанет" (напр. 20 запросов)
65
+ KEY_MAX_USES = 20
66
+ KEY_COOLDOWN_SECONDS = 3600 # Час отдыха
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
67
 
68
  # ============================================================
69
+ # БАЗА ДАННЫХот запоминает админов, вайтлист и чаты)
70
  # ============================================================
71
+ DB_FILE = "bot_db.json"
72
 
73
+ def load_db() -> dict:
74
+ if os.path.exists(DB_FILE):
75
  try:
76
+ with open(DB_FILE, "r", encoding="utf-8") as f:
77
  return json.load(f)
78
  except Exception as e:
79
+ logger.error(f"Ошибка чтения БД: {e}")
80
+ # Дефолтная структура
81
+ return {
82
+ "admins": [],
83
+ "whitelist": [],
84
+ "known_chats": [],
85
+ "key_usage": {}
86
+ }
87
+
88
+ def save_db(data: dict):
89
+ with open(DB_FILE, "w", encoding="utf-8") as f:
90
+ json.dump(data, f, ensure_ascii=False, indent=2)
91
+
92
+ db = load_db()
93
+
94
+ def track_chat(chat_id: int):
95
+ if chat_id not in db["known_chats"]:
96
+ db["known_chats"].append(chat_id)
97
+ save_db(db)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
98
 
99
  # ============================================================
100
+ # МЕНЕДЖЕР КЛЮЧЕЙ GEMINI (Усталость и восстановление)
101
  # ============================================================
102
+ def get_available_key(is_announce=False) -> Optional[str]:
103
+ keys_pool = ANNOUNCE_KEYS if is_announce else CHAT_KEYS
104
+ current_time = time.time()
105
+
106
+ available_keys = []
107
+ for key in keys_pool:
108
+ usage_info = db["key_usage"].get(key, {"uses": 0, "cooldown_until": 0})
 
 
 
 
 
 
 
 
109
 
110
+ # Если время кулдауна прошло, сбрасываем усталость
111
+ if current_time > usage_info["cooldown_until"]:
112
+ if usage_info["uses"] >= KEY_MAX_USES:
113
+ usage_info["uses"] = 0 # Восстановил силы
114
+ db["key_usage"][key] = usage_info
115
+ save_db(db)
 
 
 
 
 
 
 
 
 
 
 
 
116
 
117
+ if usage_info["uses"] < KEY_MAX_USES:
118
+ available_keys.append(key)
119
+
120
+ if not available_keys:
121
+ return None
 
 
 
 
122
 
123
+ return random.choice(available_keys)
 
124
 
125
+ def increment_key_usage(key: str):
126
+ usage_info = db["key_usage"].get(key, {"uses": 0, "cooldown_until": 0})
127
+ usage_info["uses"] += 1
128
+
129
+ if usage_info["uses"] >= KEY_MAX_USES:
130
+ usage_info["cooldown_until"] = time.time() + KEY_COOLDOWN_SECONDS
131
+
132
+ db["key_usage"][key] = usage_info
133
+ save_db(db)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
134
 
135
+ async def ask_gemini(prompt: str, is_announce=False) -> str:
136
+ key = get_available_key(is_announce)
137
+ if not key:
138
+ return "😔 Мои нейроны устали. Мне нужно время, чтобы восстановить силы. Я пока не могу отвечать."
139
+
140
+ url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={key}"
141
+ payload = {"contents": [{"parts": [{"text": prompt}]}]}
142
+
143
+ try:
144
+ async with aiohttp.ClientSession() as session:
145
+ async with session.post(url, json=payload) as resp:
146
+ if resp.status == 200:
147
+ increment_key_usage(key)
148
+ data = await resp.json()
149
+ return data['candidates'][0]['content']['parts'][0]['text']
150
+ else:
151
+ logger.error(f"Ошибка Gemini: {await resp.text()}")
152
+ return "❌ Ошибка при обращении к мозгу Google."
153
+ except Exception as e:
154
+ logger.error(f"Сбой сети Gemini: {e}")
155
+ return "❌ Произошла ошибка связи."
156
 
157
  # ============================================================
158
  # ГЕНЕРАЦИЯ ИЗОБРАЖЕНИЙ (Pollinations AI)
 
170
  return None
171
 
172
  # ============================================================
173
+ # ОБРАБОТЧИКИ TELEGRAM: АДМИН И ПАНЕЛЬ
174
  # ============================================================
175
+ async def admin_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
176
+ user_id = update.effective_user.id
177
+ args = context.args
178
+
179
+ if args and args[0] == ADMIN_PASSWORD:
180
+ if user_id not in db["admins"]:
181
+ db["admins"].append(user_id)
182
+ if user_id not in db["whitelist"]:
183
+ db["whitelist"].append(user_id) # Админ автоматически в вайтлисте
184
+ save_db(db)
185
+ await update.message.reply_text("✅ Пароль принят. Ты добавлен в список администраторов навсегда.")
186
+ else:
187
+ await update.message.reply_text("Ты и так уже админ!")
188
+ else:
189
+ await update.message.reply_text("❌ Неверный пароль.")
190
+
191
+ def get_panel_keyboard():
192
+ return InlineKeyboardMarkup([
193
+ [InlineKeyboardButton("➕ Добавить в WhiteList", callback_data="panel_add")],
194
+ [InlineKeyboardButton("➖ Удалить из WhiteList", callback_data="panel_remove")],
195
+ [InlineKeyboardButton("📋 Показать WhiteList", callback_data="panel_show")]
196
+ ])
197
+
198
+ async def panel_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
199
+ user_id = update.effective_user.id
200
+ if user_id not in db["admins"]:
201
+ await update.message.reply_text("⛔️ У тебя нет прав администратора.")
202
+ return
203
+
204
+ await update.message.reply_text(
205
+ "🛠 **Админ Панель**\nЗдесь ты можешь управлять доступом к ИИ и генерации картинок.",
206
+ reply_markup=get_panel_keyboard(),
207
+ parse_mode=ParseMode.MARKDOWN
208
+ )
209
+
210
+ async def handle_panel_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
211
+ query = update.callback_query
212
+ user_id = query.from_user.id
213
+
214
+ if user_id not in db["admins"]:
215
+ await query.answer("У тебя нет прав!", show_alert=True)
216
+ return
217
+
218
+ await query.answer()
219
+ action = query.data
220
+
221
+ if action == "panel_show":
222
+ wl = "\n".join([f"- `{uid}`" for uid in db["whitelist"]]) if db["whitelist"] else "Пусто"
223
+ await query.edit_message_text(f"📋 **WhiteList пользователей:**\n{wl}", reply_markup=get_panel_keyboard(), parse_mode=ParseMode.MARKDOWN)
224
+
225
+ elif action == "panel_add":
226
+ context.user_data["awaiting_id_for"] = "add"
227
+ await query.message.reply_text("Отправь ID пользователя (число), чтобы добавить его в WhiteList:")
228
+
229
+ elif action == "panel_remove":
230
+ context.user_data["awaiting_id_for"] = "remove"
231
+ await query.message.reply_text("Отправь ID пользователя (число), чтобы удалить его из WhiteList:")
232
+
233
+ # ============================================================
234
+ # ОБРАБОТКА ОБЫЧНЫХ СООБЩЕНИЙ
235
+ # ============================================================
236
+ IMAGE_KEYWORDS = ["нарисуй", "нарисуйте", "рисуй", "draw", "картинка", "картинку", "фото", "изображение"]
237
+ AI_TRIGGER_KEYWORDS = ["inai", "инэй", "инаи", "ии", "ai", "бот", "bot"]
238
 
239
  async def handle_message(update: Update, context: ContextTypes.DEFAULT_TYPE):
240
  if not update.message or not update.message.text:
241
  return
242
 
243
  chat_id = update.effective_chat.id
244
+ user_id = update.effective_user.id
245
+ user_text = update.message.text.strip()
246
+
247
+ # Сохраняем чат для ежечасной рассылки
248
+ track_chat(chat_id)
249
+
250
+ # 1. Логика Админ-панели (добавление/удаление ID)
251
+ if context.user_data.get("awaiting_id_for"):
252
+ action = context.user_data.pop("awaiting_id_for")
253
+ if user_text.isdigit() or (user_text.startswith('-') and user_text[1:].isdigit()):
254
+ target_id = int(user_text)
255
+ if action == "add":
256
+ if target_id not in db["whitelist"]:
257
+ db["whitelist"].append(target_id)
258
+ save_db(db)
259
+ await update.message.reply_text(f"✅ ID {target_id} добавлен в WhiteList.")
260
+ elif action == "remove":
261
+ if target_id in db["whitelist"]:
262
+ db["whitelist"].remove(target_id)
263
+ save_db(db)
264
+ await update.message.reply_text(f"🗑 ID {target_id} удален из WhiteList.")
265
+ else:
266
+ await update.message.reply_text("❌ Это не числовой ID. Операция отменена.")
267
+ return
268
 
269
+ # 2. Проверка прав (WhiteList) для работы с ИИ
270
+ is_whitelisted = user_id in db["whitelist"]
271
+
272
+ # 3. Генерация изображений (С ПРОЦЕНТАМИ)
273
  is_image_request = any(kw in user_text.lower() for kw in IMAGE_KEYWORDS)
274
  if is_image_request:
275
+ if not is_whitelisted:
276
+ await update.message.reply_text("⛔️ Извини, но ты не в белом списке для создания фотографий.")
277
+ return
278
+
279
+ status_msg = await update.message.reply_text("⏳ Подключаюсь к холсту... [0%]")
280
  await context.bot.send_chat_action(chat_id=chat_id, action=ChatAction.UPLOAD_PHOTO)
281
+
282
+ await asyncio.sleep(1.5)
283
+ await status_msg.edit_text("🎨 Рисую контуры... [35%]")
284
+
285
+ await asyncio.sleep(1.5)
286
+ await status_msg.edit_text("🖌 Добавляю цвета и детали... [75%]")
287
+
288
  image_bytes = await generate_image(user_text)
289
+
290
+ await status_msg.edit_text("✨ Завершаю обработку... [99%]")
291
+ await asyncio.sleep(1)
292
+
293
+ await status_msg.delete()
294
  if image_bytes:
295
  await update.message.reply_photo(photo=io.BytesIO(image_bytes))
296
  else:
297
+ await update.message.reply_text("❌ Упс, карандаш сломался. Не удалось нарисовать.")
298
  return
299
 
300
+ # 4. Общение с Google Gemini (Текст)
301
+ is_reply_to_bot = update.message.reply_to_message and update.message.reply_to_message.from_user.id == context.bot.id
302
+ has_trigger = any(kw in user_text.lower() for kw in AI_TRIGGER_KEYWORDS)
303
+ is_private = update.message.chat.type == "private"
304
 
305
+ if is_private or is_reply_to_bot or has_trigger:
306
+ if not is_whitelisted:
307
+ # Молчим или говорим, что нет доступа. Выберем молчание, чтобы не спамить.
 
 
 
 
 
 
 
 
 
 
 
 
308
  return
309
+
310
+ await context.bot.send_chat_action(chat_id=chat_id, action=ChatAction.TYPING)
311
+
312
+ # Системный промпт интегрируем в запрос
313
+ prompt = (
314
+ "Ты — умный и дружелюбный ИИ в Telegram. Отвечай кратко и интересно.\n"
315
+ f"Запрос пользователя: {user_text}"
316
+ )
317
+
318
+ response_text = await ask_gemini(prompt, is_announce=False)
319
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
320
  try:
321
+ await update.message.reply_text(response_text[:4000], parse_mode=ParseMode.MARKDOWN)
322
  except Exception:
323
+ await update.message.reply_text(response_text[:4000])
 
 
 
 
324
 
325
+ # ============================================================
326
+ # ЕЖЕЧАСНАЯ РАССЫЛКА ВО ВСЕ ЧАТЫ
327
+ # ============================================================
328
+ async def hourly_announce_routine(app: Application):
329
+ await asyncio.sleep(60) # Пауза перед первым запуском
330
+ while True:
331
+ chats = db["known_chats"].copy()
332
+ if chats:
333
+ prompt = (
334
+ "Напиши короткое, забавное и приветливое сообщение для чата. "
335
+ "Скажи всем, что ты ИИ от Гугла, присутствуешь здесь, готов пообщаться и "
336
+ "что тебе очень хотелось бы по запросу нарисовать для них какое-нибудь классное фото! "
337
+ "Не пиши длинно, 2-3 предложения."
338
+ )
339
+
340
+ # Генерируем текст используя ключи для анонсов
341
+ announcement = await ask_gemini(prompt, is_announce=True)
342
+
343
+ # Если ключи не устали
344
+ if "Мои нейроны устали" not in announcement:
345
+ for chat_id in chats:
346
+ try:
347
+ await app.bot.send_message(chat_id=chat_id, text=f"🔔 *Минутка внимания!*\n\n{announcement}", parse_mode=ParseMode.MARKDOWN)
348
+ except Exception as e:
349
+ logger.error(f"Не удалось отправить анонс в чат {chat_id}: {e}")
350
+ await asyncio.sleep(1) # Защита от флуда в Telegram API
351
 
352
+ await asyncio.sleep(3600) # Ждем 1 час
 
 
 
 
 
 
 
 
 
 
 
353
 
354
  # ============================================================
355
+ # СТАНДАРТНЫЕ ФУНКЦИИ ВЕБХУКА И ЗАПУСКА
356
  # ============================================================
357
+ async def start_command(update: Update, context: ContextTypes.DEFAULT_TYPE):
358
+ await update.message.reply_text("👋 Привет! Я ИИ. Если ты в белом списке, я могу рисовать для тебя и общаться!")
359
+ track_chat(update.effective_chat.id)
360
+
361
  telegram_app: Optional[Application] = None
362
 
363
  async def health_check(request: web.Request) -> web.Response:
364
+ return web.Response(text="✅ Bot is running!", content_type="text/plain")
365
 
366
  async def webhook_handler(request: web.Request) -> web.Response:
367
  global telegram_app
 
393
  )
394
 
395
  telegram_app.add_handler(CommandHandler("start", start_command))
396
+ telegram_app.add_handler(CommandHandler("admin", admin_command))
397
+ telegram_app.add_handler(CommandHandler("panel", panel_command))
398
+ telegram_app.add_handler(CallbackQueryHandler(handle_panel_callback, pattern="^panel_"))
399
  telegram_app.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, handle_message))
 
400
 
401
  await telegram_app.initialize()
402
 
 
404
  await telegram_app.bot.set_webhook(url=webhook_full_url)
405
  await telegram_app.start()
406
 
407
+ # Запускаем фоновую задачу ежечасных уведомлений
408
+ asyncio.create_task(hourly_announce_routine(telegram_app))
409
 
410
  web_app = await build_web_app()
411
  runner = web.AppRunner(web_app)