mirror of
https://github.com/B1ackCloudd/Telegram-Stars-b1ack.git
synced 2026-07-25 02:14:30 +00:00
bot.py
This commit is contained in:
@@ -8,7 +8,7 @@ import queue
|
|||||||
import sys
|
import sys
|
||||||
import random
|
import random
|
||||||
import sqlite3
|
import sqlite3
|
||||||
import webbrowser
|
import webbrowser
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Optional, Dict, List, Union, Any, Set
|
from typing import Optional, Dict, List, Union, Any, Set
|
||||||
|
|
||||||
@@ -34,10 +34,11 @@ CONFIG_FILE = "config.enc"
|
|||||||
ctk.set_appearance_mode("Dark")
|
ctk.set_appearance_mode("Dark")
|
||||||
DEFAULT_ACCENT = "#9D00FF"
|
DEFAULT_ACCENT = "#9D00FF"
|
||||||
|
|
||||||
|
# ОБНОВЛЕННЫЙ ЛОГГЕР ЦВЕТОВ
|
||||||
class QueueHandler(logging.Handler):
|
class QueueHandler(logging.Handler):
|
||||||
def emit(self, record):
|
def emit(self, record):
|
||||||
msg = self.format(record)
|
msg = self.format(record)
|
||||||
log_queue.put(msg)
|
log_queue.put((record.levelname, msg))
|
||||||
|
|
||||||
def setup_logger():
|
def setup_logger():
|
||||||
logger = logging.getLogger("BotLogger")
|
logger = logging.getLogger("BotLogger")
|
||||||
@@ -205,14 +206,12 @@ class TelegramStarsBot:
|
|||||||
|
|
||||||
async def notify_admin(self, message: str):
|
async def notify_admin(self, message: str):
|
||||||
if not self.config.get("admin_notify", False): return
|
if not self.config.get("admin_notify", False): return
|
||||||
|
|
||||||
if self.config.get("bot_token") and self.config.get("admin_id"):
|
if self.config.get("bot_token") and self.config.get("admin_id"):
|
||||||
try:
|
try:
|
||||||
url = f"https://api.telegram.org/bot{self.config['bot_token']}/sendMessage"
|
url = f"https://api.telegram.org/bot{self.config['bot_token']}/sendMessage"
|
||||||
payload = {"chat_id": self.config["admin_id"], "text": f"🤖 {message}", "parse_mode": "HTML"}
|
payload = {"chat_id": self.config["admin_id"], "text": f"🤖 {message}", "parse_mode": "HTML"}
|
||||||
async with aiohttp.ClientSession() as session:
|
async with aiohttp.ClientSession() as session:
|
||||||
async with session.post(url, json=payload) as r:
|
await session.post(url, json=payload)
|
||||||
if r.status != 200: logger.error(f"Notify Error: {r.status}")
|
|
||||||
except Exception as e: logger.error(f"Notify Error: {e}")
|
except Exception as e: logger.error(f"Notify Error: {e}")
|
||||||
else:
|
else:
|
||||||
try:
|
try:
|
||||||
@@ -222,7 +221,7 @@ class TelegramStarsBot:
|
|||||||
|
|
||||||
async def send_stars_reaction(self, channel: str, message_id: Optional[int] = None) -> bool:
|
async def send_stars_reaction(self, channel: str, message_id: Optional[int] = None) -> bool:
|
||||||
if not hasattr(self.client, 'send_paid_reaction'):
|
if not hasattr(self.client, 'send_paid_reaction'):
|
||||||
logger.error("Критическая ошибка: Версия Pyrogram (Pyrofork) не поддерживает платные реакции! Обновите библиотеку: pip install -U pyrofork")
|
logger.error("Ошибка: Обновите библиотеку! pip install -U pyrofork")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -233,12 +232,10 @@ class TelegramStarsBot:
|
|||||||
if message_id is None: return False
|
if message_id is None: return False
|
||||||
|
|
||||||
await self.client.send_paid_reaction(f"@{channel}", message_id, int(self.config["stars_count"]))
|
await self.client.send_paid_reaction(f"@{channel}", message_id, int(self.config["stars_count"]))
|
||||||
|
|
||||||
self.db.log_transaction(int(self.config["stars_count"]), channel)
|
self.db.log_transaction(int(self.config["stars_count"]), channel)
|
||||||
logger.info(f"⭐ Отправлено {self.config['stars_count']} звезд в @{channel}")
|
logger.info(f"✅ Отправлено {self.config['stars_count']} звезд в @{channel}")
|
||||||
await self.notify_admin(f"✅ Отправлено <b>{self.config['stars_count']}</b> ⭐️ в @{channel}")
|
await self.notify_admin(f"✅ Отправлено <b>{self.config['stars_count']}</b> ⭐️ в @{channel}")
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except FloodWait as e:
|
except FloodWait as e:
|
||||||
logger.warning(f"FloodWait: ожидание {e.x} сек.")
|
logger.warning(f"FloodWait: ожидание {e.x} сек.")
|
||||||
await asyncio.sleep(e.x + 2)
|
await asyncio.sleep(e.x + 2)
|
||||||
@@ -249,10 +246,8 @@ class TelegramStarsBot:
|
|||||||
|
|
||||||
async def _process_single_post(self, post: Dict[str, Any]):
|
async def _process_single_post(self, post: Dict[str, Any]):
|
||||||
post_id = post.get("post_id")
|
post_id = post.get("post_id")
|
||||||
|
if not post_id or self.db.is_processed(post_id): return
|
||||||
|
|
||||||
if not post_id or self.db.is_processed(post_id):
|
|
||||||
return
|
|
||||||
|
|
||||||
logger.info(f"🔍 Проверка поста ID: {post_id}")
|
logger.info(f"🔍 Проверка поста ID: {post_id}")
|
||||||
|
|
||||||
if self.config["skip_comments"]:
|
if self.config["skip_comments"]:
|
||||||
@@ -263,13 +258,11 @@ class TelegramStarsBot:
|
|||||||
|
|
||||||
post_content = post.get('post_body_html') or post.get('post_body')
|
post_content = post.get('post_body_html') or post.get('post_body')
|
||||||
if not post_content:
|
if not post_content:
|
||||||
self.db.mark_processed(post_id)
|
self.db.mark_processed(post_id); return
|
||||||
return
|
|
||||||
|
|
||||||
links = TelegramLinkExtractor.extract(post_content)
|
links = TelegramLinkExtractor.extract(post_content)
|
||||||
if not links:
|
if not links:
|
||||||
self.db.mark_processed(post_id)
|
self.db.mark_processed(post_id); return
|
||||||
return
|
|
||||||
|
|
||||||
successful_reactions = 0
|
successful_reactions = 0
|
||||||
for link in links:
|
for link in links:
|
||||||
@@ -288,42 +281,36 @@ class TelegramStarsBot:
|
|||||||
await self.lolz.create_comment(post_id, reply_message)
|
await self.lolz.create_comment(post_id, reply_message)
|
||||||
|
|
||||||
self.db.mark_processed(post_id)
|
self.db.mark_processed(post_id)
|
||||||
logger.info(f"✅ Пост {post_id} полностью обработан.")
|
logger.info(f"✅ Пост {post_id} обработан.")
|
||||||
|
|
||||||
async def process(self):
|
async def process(self):
|
||||||
logger.info(f"🚀 Бот запущен. Страница: {self.start_page}")
|
logger.info(f"🚀 Бот запущен. Страница: {self.start_page}")
|
||||||
self.client = Client("secure_session", api_id=self.config["api_id"], api_hash=self.config["api_hash"])
|
self.client = Client("secure_session", api_id=self.config["api_id"], api_hash=self.config["api_hash"])
|
||||||
|
|
||||||
try:
|
try:
|
||||||
await self.client.start()
|
await self.client.start()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.critical(f"Ошибка авторизации: {e}")
|
logger.error(f"Ошибка авторизации: {e}")
|
||||||
return
|
return
|
||||||
|
|
||||||
while not stop_event.is_set():
|
while not stop_event.is_set():
|
||||||
try:
|
try:
|
||||||
posts, _ = await self.lolz.get_thread_posts(self.config["forum_thread_id"], self.start_page)
|
posts, _ = await self.lolz.get_thread_posts(self.config["forum_thread_id"], self.start_page)
|
||||||
|
|
||||||
if posts:
|
if posts:
|
||||||
logger.info(f"📄 Обработка страницы {self.start_page}...")
|
logger.info(f"📄 Обработка страницы {self.start_page}...")
|
||||||
for post in reversed(posts):
|
for post in reversed(posts):
|
||||||
if stop_event.is_set(): break
|
if stop_event.is_set(): break
|
||||||
await self._process_single_post(post)
|
await self._process_single_post(post)
|
||||||
|
self.start_page += 1
|
||||||
self.start_page += 1 # Переход на следующую страницу
|
logger.info(f"➡️ Переход на страницу: {self.start_page}")
|
||||||
logger.info(f"➡️ Переход к странице {self.start_page}")
|
|
||||||
else:
|
else:
|
||||||
logger.info("💤 Новых постов нет, ожидание...")
|
logger.info(f"💤 Страница {self.start_page} пуста. Ожидание...")
|
||||||
for _ in range(int(self.config["check_interval"])):
|
for _ in range(int(self.config["check_interval"])):
|
||||||
if stop_event.is_set(): break
|
if stop_event.is_set(): break
|
||||||
await asyncio.sleep(1)
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f"Критическая ошибка цикла: {e}")
|
logger.error(f"Ошибка цикла: {e}"); await asyncio.sleep(5)
|
||||||
await asyncio.sleep(5)
|
|
||||||
|
|
||||||
if self.client and self.client.is_connected:
|
if self.client and self.client.is_connected: await self.client.stop()
|
||||||
await self.client.stop()
|
|
||||||
logger.info("🛑 Бот остановлен.")
|
logger.info("🛑 Бот остановлен.")
|
||||||
|
|
||||||
class App(ctk.CTk):
|
class App(ctk.CTk):
|
||||||
@@ -342,7 +329,6 @@ class App(ctk.CTk):
|
|||||||
self._init_sidebar()
|
self._init_sidebar()
|
||||||
self._init_pages()
|
self._init_pages()
|
||||||
self._load_config_to_ui()
|
self._load_config_to_ui()
|
||||||
|
|
||||||
self.after(100, self.update_logs)
|
self.after(100, self.update_logs)
|
||||||
|
|
||||||
def _init_sidebar(self):
|
def _init_sidebar(self):
|
||||||
@@ -351,35 +337,27 @@ class App(ctk.CTk):
|
|||||||
self.sidebar.grid_rowconfigure(7, weight=1)
|
self.sidebar.grid_rowconfigure(7, weight=1)
|
||||||
|
|
||||||
ctk.CTkLabel(self.sidebar, text="B1ack Stars", font=("Impact", 24), text_color=self.accent_color).pack(pady=20)
|
ctk.CTkLabel(self.sidebar, text="B1ack Stars", font=("Impact", 24), text_color=self.accent_color).pack(pady=20)
|
||||||
|
|
||||||
self.btns = {}
|
self.btns = {}
|
||||||
for name, cmd in [("Дашборд", self.show_dashboard),
|
for name, cmd in [("Дашборд", self.show_dashboard), ("Настройки", self.show_settings),
|
||||||
("Настройки", self.show_settings),
|
("Консоль", self.show_console), ("Визуал", self.show_theme)]:
|
||||||
("Консоль", self.show_console),
|
|
||||||
("Визуал", self.show_theme)]:
|
|
||||||
btn = ctk.CTkButton(self.sidebar, text=name, command=cmd, fg_color="transparent",
|
btn = ctk.CTkButton(self.sidebar, text=name, command=cmd, fg_color="transparent",
|
||||||
border_width=1, border_color=self.accent_color)
|
border_width=1, border_color=self.accent_color)
|
||||||
btn.pack(pady=5, padx=10, fill="x")
|
btn.pack(pady=5, padx=10, fill="x")
|
||||||
self.btns[name] = btn
|
self.btns[name] = btn
|
||||||
|
|
||||||
ctk.CTkLabel(self.sidebar, text="CONTROL", font=("Arial", 10, "bold"), text_color="gray").pack(pady=(20,5))
|
ctk.CTkLabel(self.sidebar, text="CONTROL", font=("Arial", 10, "bold"), text_color="gray").pack(pady=(20,5))
|
||||||
|
|
||||||
self.btn_start = ctk.CTkButton(self.sidebar, text="START BOT", fg_color="green", command=self.start_bot)
|
self.btn_start = ctk.CTkButton(self.sidebar, text="START BOT", fg_color="green", command=self.start_bot)
|
||||||
self.btn_start.pack(pady=5, padx=10, fill="x")
|
self.btn_start.pack(pady=5, padx=10, fill="x")
|
||||||
|
|
||||||
self.btn_stop = ctk.CTkButton(self.sidebar, text="STOP", fg_color="#330000", state="disabled", command=self.stop_bot)
|
self.btn_stop = ctk.CTkButton(self.sidebar, text="STOP", fg_color="#330000", state="disabled", command=self.stop_bot)
|
||||||
self.btn_stop.pack(pady=5, padx=10, fill="x")
|
self.btn_stop.pack(pady=5, padx=10, fill="x")
|
||||||
|
|
||||||
# КНОПКА ТЕЛЕГРАМ
|
self.btn_bug = ctk.CTkButton(self.sidebar, text="🐞 Сообщить о баге", fg_color="transparent",
|
||||||
self.btn_bug = ctk.CTkButton(self.sidebar, text="🐞 Сообщить о баге",
|
text_color="#FF5555", hover_color="#331111", command=self.open_support)
|
||||||
fg_color="transparent", text_color="#FF5555",
|
|
||||||
hover_color="#331111", command=self.open_telegram)
|
|
||||||
self.btn_bug.pack(pady=(20, 5), padx=10, fill="x", side="bottom")
|
self.btn_bug.pack(pady=(20, 5), padx=10, fill="x", side="bottom")
|
||||||
|
|
||||||
self.btn_tray = ctk.CTkButton(self.sidebar, text="⬇ В трей", fg_color="gray20", command=self.minimize_to_tray)
|
self.btn_tray = ctk.CTkButton(self.sidebar, text="⬇ В трей", fg_color="gray20", command=self.minimize_to_tray)
|
||||||
self.btn_tray.pack(pady=(5, 20), padx=10, fill="x", side="bottom")
|
self.btn_tray.pack(pady=(5, 20), padx=10, fill="x", side="bottom")
|
||||||
|
|
||||||
def open_telegram(self):
|
def open_support(self):
|
||||||
webbrowser.open("https://t.me/B1ackCloudSupp")
|
webbrowser.open("https://t.me/B1ackCloudSupp")
|
||||||
|
|
||||||
def _init_pages(self):
|
def _init_pages(self):
|
||||||
@@ -405,181 +383,118 @@ class App(ctk.CTk):
|
|||||||
self.frame_settings = ctk.CTkScrollableFrame(self, corner_radius=0, label_text="Настройки")
|
self.frame_settings = ctk.CTkScrollableFrame(self, corner_radius=0, label_text="Настройки")
|
||||||
self.entries = {}
|
self.entries = {}
|
||||||
fields = [
|
fields = [
|
||||||
("api_id", "API ID"),
|
("api_id", "API ID"), ("api_hash", "API Hash"), ("lolz_token", "Lolz Token"),
|
||||||
("api_hash", "API Hash"),
|
("forum_thread_id", "Thread ID"), ("admin_id", "TG User ID"),
|
||||||
("lolz_token", "Lolz Token"),
|
("bot_token", "Bot Token"), ("start_page", "Start Page"),
|
||||||
("forum_thread_id", "Thread ID"),
|
("stars_count", "Кол-во звезд"), ("reply_templates", "Ответы (||)")
|
||||||
("admin_id", "TG User ID"),
|
|
||||||
("bot_token", "Bot Token (из @Botfather)"),
|
|
||||||
("start_page", "Start Page (Def: 1)"),
|
|
||||||
("stars_count", "Количество звезд (1,3,5 итд"),
|
|
||||||
("reply_templates", "Ответы (Разделитель || )")
|
|
||||||
]
|
]
|
||||||
for k, name in fields:
|
for k, name in fields:
|
||||||
ctk.CTkLabel(self.frame_settings, text=name, anchor="w").pack(fill="x", padx=10)
|
ctk.CTkLabel(self.frame_settings, text=name, anchor="w").pack(fill="x", padx=10)
|
||||||
e = ctk.CTkEntry(self.frame_settings)
|
e = ctk.CTkEntry(self.frame_settings); e.pack(fill="x", padx=10, pady=(0, 10)); self.entries[k] = e
|
||||||
e.pack(fill="x", padx=10, pady=(0, 10))
|
|
||||||
self.entries[k] = e
|
|
||||||
|
|
||||||
self.chk_reply = ctk.CTkCheckBox(self.frame_settings, text="Отвечать в теме")
|
self.chk_reply = ctk.CTkCheckBox(self.frame_settings, text="Отвечать в теме")
|
||||||
self.chk_reply.pack(anchor="w", padx=10)
|
self.chk_reply.pack(anchor="w", padx=10)
|
||||||
self.chk_skip = ctk.CTkCheckBox(self.frame_settings, text="Пропускать если есть ответ")
|
self.chk_skip = ctk.CTkCheckBox(self.frame_settings, text="Пропускать с ответом")
|
||||||
self.chk_skip.pack(anchor="w", padx=10, pady=10)
|
self.chk_skip.pack(anchor="w", padx=10, pady=10)
|
||||||
self.chk_notify = ctk.CTkCheckBox(self.frame_settings, text="Уведомлять в Telegram")
|
self.chk_notify = ctk.CTkCheckBox(self.frame_settings, text="Уведомлять в TG")
|
||||||
self.chk_notify.pack(anchor="w", padx=10)
|
self.chk_notify.pack(anchor="w", padx=10)
|
||||||
|
|
||||||
ctk.CTkButton(self.frame_settings, text="Сохранить", command=self.save_config).pack(pady=20)
|
ctk.CTkButton(self.frame_settings, text="Сохранить", command=self.save_config).pack(pady=20)
|
||||||
|
|
||||||
|
# КОНСОЛЬ С ЦВЕТАМИ
|
||||||
self.frame_console = ctk.CTkFrame(self, corner_radius=0)
|
self.frame_console = ctk.CTkFrame(self, corner_radius=0)
|
||||||
self.console = ctk.CTkTextbox(self.frame_console, font=("Consolas", 12), text_color="#00FF00", fg_color="#050505")
|
self.console = ctk.CTkTextbox(self.frame_console, font=("Consolas", 12), fg_color="#050505")
|
||||||
self.console.pack(fill="both", expand=True, padx=10, pady=10)
|
self.console.pack(fill="both", expand=True, padx=10, pady=10)
|
||||||
|
|
||||||
|
self.console._textbox.tag_config("INFO", foreground="#FFFFFF")
|
||||||
|
self.console._textbox.tag_config("ERROR", foreground="#FF4444")
|
||||||
|
self.console._textbox.tag_config("WARNING", foreground="#FFFF00")
|
||||||
|
self.console._textbox.tag_config("SUCCESS", foreground="#00FF00")
|
||||||
|
self.console._textbox.tag_config("STEP", foreground="#55AAFF")
|
||||||
|
|
||||||
self.frame_theme = ctk.CTkFrame(self, corner_radius=0)
|
self.frame_theme = ctk.CTkFrame(self, corner_radius=0)
|
||||||
ctk.CTkLabel(self.frame_theme, text="Настройка цвета интерфейса", font=("Arial", 16)).pack(pady=20)
|
ctk.CTkLabel(self.frame_theme, text="Цвет интерфейса", font=("Arial", 16)).pack(pady=20)
|
||||||
|
self.slider_r = ctk.CTkSlider(self.frame_theme, from_=0, to=255, command=self.update_color_preview); self.slider_r.pack(pady=10)
|
||||||
self.slider_r = ctk.CTkSlider(self.frame_theme, from_=0, to=255, command=self.update_color_preview)
|
self.slider_g = ctk.CTkSlider(self.frame_theme, from_=0, to=255, command=self.update_color_preview); self.slider_g.pack(pady=10)
|
||||||
self.slider_r.pack(pady=10)
|
self.slider_b = ctk.CTkSlider(self.frame_theme, from_=0, to=255, command=self.update_color_preview); self.slider_b.pack(pady=10)
|
||||||
self.slider_g = ctk.CTkSlider(self.frame_theme, from_=0, to=255, command=self.update_color_preview)
|
self.color_preview = ctk.CTkButton(self.frame_theme, text="ПРИМЕНИТЬ", command=self.apply_theme); self.color_preview.pack(pady=20)
|
||||||
self.slider_g.pack(pady=10)
|
|
||||||
self.slider_b = ctk.CTkSlider(self.frame_theme, from_=0, to=255, command=self.update_color_preview)
|
|
||||||
self.slider_b.pack(pady=10)
|
|
||||||
|
|
||||||
self.color_preview = ctk.CTkButton(self.frame_theme, text="ПРИМЕНИТЬ ЦВЕТ", command=self.apply_theme)
|
|
||||||
self.color_preview.pack(pady=20)
|
|
||||||
|
|
||||||
self.show_dashboard()
|
self.show_dashboard()
|
||||||
|
|
||||||
def switch_frame(self, frame):
|
def switch_frame(self, frame):
|
||||||
for f in [self.frame_dash, self.frame_settings, self.frame_console, self.frame_theme]:
|
for f in [self.frame_dash, self.frame_settings, self.frame_console, self.frame_theme]: f.grid_forget()
|
||||||
f.grid_forget()
|
|
||||||
frame.grid(row=0, column=1, sticky="nsew", padx=10, pady=10)
|
frame.grid(row=0, column=1, sticky="nsew", padx=10, pady=10)
|
||||||
|
|
||||||
def show_dashboard(self):
|
def show_dashboard(self): self.switch_frame(self.frame_dash); self.draw_stats()
|
||||||
self.switch_frame(self.frame_dash)
|
|
||||||
self.draw_stats()
|
|
||||||
|
|
||||||
def show_settings(self): self.switch_frame(self.frame_settings)
|
def show_settings(self): self.switch_frame(self.frame_settings)
|
||||||
def show_console(self): self.switch_frame(self.frame_console)
|
def show_console(self): self.switch_frame(self.frame_console)
|
||||||
def show_theme(self): self.switch_frame(self.frame_theme)
|
def show_theme(self): self.switch_frame(self.frame_theme)
|
||||||
|
|
||||||
def update_color_preview(self, _=None):
|
def update_color_preview(self, _=None):
|
||||||
r, g, b = int(self.slider_r.get()), int(self.slider_g.get()), int(self.slider_b.get())
|
r, g, b = int(self.slider_r.get()), int(self.slider_g.get()), int(self.slider_b.get())
|
||||||
hex_col = f"#{r:02x}{g:02x}{b:02x}"
|
self.color_preview.configure(fg_color=f"#{r:02x}{g:02x}{b:02x}")
|
||||||
self.color_preview.configure(fg_color=hex_col)
|
|
||||||
|
|
||||||
def apply_theme(self):
|
def apply_theme(self):
|
||||||
r, g, b = int(self.slider_r.get()), int(self.slider_g.get()), int(self.slider_b.get())
|
r, g, b = int(self.slider_r.get()), int(self.slider_g.get()), int(self.slider_b.get())
|
||||||
self.accent_color = f"#{r:02x}{g:02x}{b:02x}"
|
self.accent_color = f"#{r:02x}{g:02x}{b:02x}"
|
||||||
self.logo_label = self.sidebar.winfo_children()[0]
|
self.sidebar.winfo_children()[0].configure(text_color=self.accent_color)
|
||||||
self.logo_label.configure(text_color=self.accent_color)
|
|
||||||
self.lbl_total_stars.configure(text_color=self.accent_color)
|
self.lbl_total_stars.configure(text_color=self.accent_color)
|
||||||
for name, btn in self.btns.items():
|
for btn in self.btns.values(): btn.configure(border_color=self.accent_color)
|
||||||
btn.configure(border_color=self.accent_color)
|
|
||||||
|
|
||||||
def draw_stats(self):
|
def draw_stats(self):
|
||||||
for widget in self.graph_frame.winfo_children():
|
for w in self.graph_frame.winfo_children(): w.destroy()
|
||||||
widget.destroy()
|
data, total = self.db.get_stats_data(), self.db.get_total_stars()
|
||||||
|
self.lbl_total_stars.configure(text=str(total)); self.lbl_money.configure(text=f"${total * 0.013:.2f}")
|
||||||
data = self.db.get_stats_data()
|
if not data: return
|
||||||
total = self.db.get_total_stars()
|
|
||||||
|
|
||||||
self.lbl_total_stars.configure(text=str(total))
|
|
||||||
self.lbl_money.configure(text=f"${total * 0.013:.2f}")
|
|
||||||
|
|
||||||
dates = [d[0] for d in data]
|
|
||||||
counts = [d[1] for d in data]
|
|
||||||
|
|
||||||
if not dates: return
|
|
||||||
|
|
||||||
fig = plt.Figure(figsize=(5, 4), dpi=100, facecolor="#2b2b2b")
|
fig = plt.Figure(figsize=(5, 4), dpi=100, facecolor="#2b2b2b")
|
||||||
ax = fig.add_subplot(111)
|
ax = fig.add_subplot(111); ax.set_facecolor("#2b2b2b"); ax.plot([d[0] for d in data], [d[1] for d in data], marker='o', color=self.accent_color)
|
||||||
ax.set_facecolor("#2b2b2b")
|
ax.tick_params(colors='white'); canvas = FigureCanvasTkAgg(fig, master=self.graph_frame); canvas.draw(); canvas.get_tk_widget().pack(fill="both", expand=True)
|
||||||
ax.plot(dates, counts, marker='o', color=self.accent_color, linewidth=2)
|
|
||||||
ax.set_title("Активность", color="white")
|
|
||||||
ax.tick_params(axis='x', colors='white', rotation=45)
|
|
||||||
ax.tick_params(axis='y', colors='white')
|
|
||||||
|
|
||||||
canvas = FigureCanvasTkAgg(fig, master=self.graph_frame)
|
|
||||||
canvas.draw()
|
|
||||||
canvas.get_tk_widget().pack(fill="both", expand=True)
|
|
||||||
|
|
||||||
def save_config(self):
|
def save_config(self):
|
||||||
cfg = {k: v.get() for k, v in self.entries.items()}
|
cfg = {k: v.get() for k, v in self.entries.items()}
|
||||||
cfg["enable_reply"] = bool(self.chk_reply.get())
|
cfg.update({"enable_reply": bool(self.chk_reply.get()), "skip_comments": bool(self.chk_skip.get()),
|
||||||
cfg["skip_comments"] = bool(self.chk_skip.get())
|
"admin_notify": bool(self.chk_notify.get()), "check_interval": 30, "api_delay": 5})
|
||||||
cfg["admin_notify"] = bool(self.chk_notify.get())
|
self.security.save_config(cfg); logger.info("✅ Конфигурация сохранена!")
|
||||||
cfg["check_interval"] = 30
|
|
||||||
cfg["api_delay"] = 5
|
|
||||||
self.security.save_config(cfg)
|
|
||||||
logger.info("💾 Конфигурация сохранена!")
|
|
||||||
|
|
||||||
def _load_config_to_ui(self):
|
def _load_config_to_ui(self):
|
||||||
if not self.config: return
|
if not self.config: return
|
||||||
for k, e in self.entries.items():
|
for k, e in self.entries.items():
|
||||||
if k in self.config:
|
if k in self.config: e.insert(0, str(self.config[k]))
|
||||||
e.delete(0, "end")
|
|
||||||
e.insert(0, str(self.config[k]))
|
|
||||||
if self.config.get("enable_reply"): self.chk_reply.select()
|
if self.config.get("enable_reply"): self.chk_reply.select()
|
||||||
if self.config.get("skip_comments"): self.chk_skip.select()
|
if self.config.get("skip_comments"): self.chk_skip.select()
|
||||||
if self.config.get("admin_notify"): self.chk_notify.select()
|
if self.config.get("admin_notify"): self.chk_notify.select()
|
||||||
|
|
||||||
def update_logs(self):
|
def update_logs(self):
|
||||||
while not log_queue.empty():
|
while not log_queue.empty():
|
||||||
msg = log_queue.get()
|
level, msg = log_queue.get()
|
||||||
self.console.insert("end", msg + "\n")
|
tag = level
|
||||||
|
if any(x in msg for x in ["✅", "⭐"]): tag = "SUCCESS"
|
||||||
|
elif any(x in msg for x in ["🚀", "➡️", "📄", "🔍"]): tag = "STEP"
|
||||||
|
self.console.insert("end", msg + "\n", tag)
|
||||||
self.console.see("end")
|
self.console.see("end")
|
||||||
self.after(100, self.update_logs)
|
self.after(100, self.update_logs)
|
||||||
|
|
||||||
def start_bot(self):
|
def start_bot(self):
|
||||||
self.save_config()
|
self.save_config(); self.config = self.security.load_config()
|
||||||
self.config = self.security.load_config()
|
if not self.config.get("api_id"): return
|
||||||
|
stop_event.clear(); self.btn_start.configure(state="disabled", fg_color="gray")
|
||||||
if not self.config.get("api_id"):
|
self.btn_stop.configure(state="normal", fg_color="#FF3333"); self.show_console()
|
||||||
logger.error("❌ Заполните настройки!")
|
threading.Thread(target=self._run_bot_process, daemon=True).start()
|
||||||
return
|
|
||||||
|
|
||||||
stop_event.clear()
|
|
||||||
self.btn_start.configure(state="disabled", fg_color="gray")
|
|
||||||
self.btn_stop.configure(state="normal", fg_color="#FF3333")
|
|
||||||
self.show_console()
|
|
||||||
|
|
||||||
thread = threading.Thread(target=self._run_bot_process, daemon=True)
|
|
||||||
thread.start()
|
|
||||||
|
|
||||||
def _run_bot_process(self):
|
def _run_bot_process(self):
|
||||||
loop = asyncio.new_event_loop()
|
loop = asyncio.new_event_loop(); asyncio.set_event_loop(loop)
|
||||||
asyncio.set_event_loop(loop)
|
bot = TelegramStarsBot(self.config, self.db); loop.run_until_complete(bot.process())
|
||||||
bot = TelegramStarsBot(self.config, self.db)
|
|
||||||
loop.run_until_complete(bot.process())
|
|
||||||
|
|
||||||
def stop_bot(self):
|
def stop_bot(self):
|
||||||
stop_event.set()
|
stop_event.set(); self.btn_start.configure(state="normal", fg_color="green")
|
||||||
self.btn_start.configure(state="normal", fg_color="green")
|
self.btn_stop.configure(state="disabled", fg_color="#330000"); logger.warning("🛑 Остановка...")
|
||||||
self.btn_stop.configure(state="disabled", fg_color="#330000")
|
|
||||||
logger.info("🛑 Остановка...")
|
|
||||||
|
|
||||||
def minimize_to_tray(self):
|
def minimize_to_tray(self):
|
||||||
self.withdraw()
|
self.withdraw(); image = Image.new('RGB', (64, 64), color=(73, 109, 137))
|
||||||
image = Image.new('RGB', (64, 64), color=(73, 109, 137))
|
d = ImageDraw.Draw(image); d.text((10,10), "S", fill=(255,255,0))
|
||||||
d = ImageDraw.Draw(image)
|
menu = pystray.Menu(pystray.MenuItem("Открыть", self.show_window), pystray.MenuItem("Выход", self.quit_app))
|
||||||
d.text((10,10), "S", fill=(255,255,0))
|
self.tray_icon = pystray.Icon("name", image, "B1ackStars", menu); threading.Thread(target=self.tray_icon.run, daemon=True).start()
|
||||||
|
|
||||||
menu = pystray.Menu(
|
|
||||||
pystray.MenuItem("Открыть", self.show_window),
|
|
||||||
pystray.MenuItem("Выход", self.quit_app)
|
|
||||||
)
|
|
||||||
self.tray_icon = pystray.Icon("name", image, "B1ackStars", menu)
|
|
||||||
threading.Thread(target=self.tray_icon.run, daemon=True).start()
|
|
||||||
|
|
||||||
def show_window(self, icon, item):
|
def show_window(self, icon, item): self.tray_icon.stop(); self.after(0, self.deiconify)
|
||||||
self.tray_icon.stop()
|
def quit_app(self, icon, item): self.tray_icon.stop(); self.destroy(); sys.exit()
|
||||||
self.after(0, self.deiconify)
|
|
||||||
|
|
||||||
def quit_app(self, icon, item):
|
|
||||||
self.tray_icon.stop()
|
|
||||||
self.destroy()
|
|
||||||
sys.exit()
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
app = App()
|
app = App(); app.mainloop()
|
||||||
app.mainloop()
|
|
||||||
|
|||||||
Reference in New Issue
Block a user