d4rktg 1.2.3__py3-none-any.whl → 1.2.5__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- d4rk/Database/db.py +1 -2
- d4rk/Handlers/__init__.py +2 -2
- d4rk/Handlers/_bot.py +31 -123
- d4rk/Handlers/_logger_bot.py +88 -0
- d4rk/Utils/__init__.py +1 -1
- d4rk/Utils/_decorators.py +1 -15
- d4rk/Utils/_filters.py +7 -13
- d4rk/Utils/_movie_parser.py +2 -2
- d4rk/Web/__init__.py +4 -0
- d4rk/Web/web.py +201 -0
- d4rk/Web/web_server.py +52 -0
- d4rk/__init__.py +15 -2
- d4rk/_base.py +115 -0
- d4rk/_bot_manager.py +507 -0
- d4rk/errors/__init__.py +19 -0
- d4rk/errors/base.py +16 -0
- d4rk/errors/bot.py +33 -0
- d4rk/errors/config.py +17 -0
- d4rk/errors/database.py +22 -0
- {d4rktg-1.2.3.dist-info → d4rktg-1.2.5.dist-info}/METADATA +1 -1
- d4rktg-1.2.5.dist-info/RECORD +33 -0
- d4rk/Handlers/_scheduler.py +0 -33
- d4rktg-1.2.3.dist-info/RECORD +0 -23
- {d4rktg-1.2.3.dist-info → d4rktg-1.2.5.dist-info}/WHEEL +0 -0
- {d4rktg-1.2.3.dist-info → d4rktg-1.2.5.dist-info}/top_level.txt +0 -0
d4rk/Database/db.py
CHANGED
@@ -4,7 +4,7 @@ from pymongo import MongoClient , server_api
|
|
4
4
|
|
5
5
|
from d4rk.Logs import setup_logger
|
6
6
|
|
7
|
-
logger = setup_logger(
|
7
|
+
logger = setup_logger("Database")
|
8
8
|
|
9
9
|
class Database:
|
10
10
|
client = None
|
@@ -43,7 +43,6 @@ class Database:
|
|
43
43
|
base_collection = self.get_collection(collection_name)
|
44
44
|
instance = CustomClass(base_collection)
|
45
45
|
setattr(self, collection_name, instance)
|
46
|
-
logger.info(f"Loaded collection: {collection_name}")
|
47
46
|
|
48
47
|
def get_collection(self, collection):
|
49
48
|
return self.db[collection]
|
d4rk/Handlers/__init__.py
CHANGED
@@ -1,3 +1,3 @@
|
|
1
1
|
from ._bot import BotManager
|
2
|
-
from .
|
3
|
-
from .
|
2
|
+
from ._custom import FontMessageMixin
|
3
|
+
from ._logger_bot import LoggerBotUtil
|
d4rk/Handlers/_bot.py
CHANGED
@@ -2,24 +2,19 @@ import os
|
|
2
2
|
import sys
|
3
3
|
import asyncio
|
4
4
|
import traceback
|
5
|
-
import random
|
6
|
-
from datetime import datetime, timedelta
|
7
5
|
|
8
6
|
from pyrogram import Client
|
9
|
-
from pyrogram.errors import FloodWait
|
10
|
-
from pyrogram.types import BotCommand
|
11
|
-
from pyrogram.errors.exceptions.bad_request_400 import AccessTokenExpired
|
7
|
+
from pyrogram.errors import FloodWait, AccessTokenExpired
|
12
8
|
|
13
9
|
from d4rk.Logs import setup_logger
|
14
10
|
|
11
|
+
logger = setup_logger("BotClient")
|
12
|
+
|
15
13
|
logs_sent = False
|
16
14
|
logs_lock = asyncio.Lock()
|
17
15
|
|
18
|
-
logger = setup_logger(__name__)
|
19
|
-
|
20
16
|
class BotManager(Client):
|
21
17
|
_bot: Client = None
|
22
|
-
_web = True
|
23
18
|
_bot_info = None
|
24
19
|
_is_connected = False
|
25
20
|
_rename = False
|
@@ -28,7 +23,7 @@ class BotManager(Client):
|
|
28
23
|
_scheduler_thread = None
|
29
24
|
font = 0
|
30
25
|
sudo_users = []
|
31
|
-
|
26
|
+
owner = None
|
32
27
|
|
33
28
|
def create_client(self,app_name,token):
|
34
29
|
self.app_name = app_name
|
@@ -41,53 +36,29 @@ class BotManager(Client):
|
|
41
36
|
in_memory=True
|
42
37
|
)
|
43
38
|
|
44
|
-
async def handle_flood_wait(self, wait_time: int):
|
45
|
-
|
46
|
-
await asyncio.sleep(wait_time)
|
47
|
-
try:await self.powerup(self.app_name)
|
48
|
-
except:pass
|
49
|
-
|
50
39
|
def _safe_async(self, coro_func):
|
51
40
|
if self._loop:asyncio.run_coroutine_threadsafe(coro_func(), self._loop)
|
52
41
|
else:logger.error("Event loop is not set for _safe_async")
|
53
42
|
|
54
|
-
async def powerup(self,
|
55
|
-
|
56
|
-
|
57
|
-
self.sudo_users = self.db.Settings.get(key="sudo_users",datatype=list,default=[])
|
58
|
-
if not self.font:
|
59
|
-
logger.info("Font not set, defaulting to font 1")
|
60
|
-
self.db.Settings.set("font", "1")
|
61
|
-
self.font = 1
|
62
|
-
|
63
|
-
self.create_client(appname,self.token)
|
64
|
-
max_retries = 3
|
65
|
-
for attempt in range(max_retries):
|
43
|
+
async def powerup(self, app_name, max_attempts=3):
|
44
|
+
self.app_name = app_name
|
45
|
+
for attempt in range(max_attempts):
|
66
46
|
try:
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
await self.setup_commands()
|
74
|
-
self._is_connected = True
|
75
|
-
await self.handle_restart()
|
76
|
-
break
|
77
|
-
|
78
|
-
except asyncio.TimeoutError:
|
79
|
-
logger.error(f"Bot start timed out (attempt {attempt + 1})")
|
80
|
-
if attempt < max_retries - 1:
|
81
|
-
logger.info("Retrying in 10 seconds...")
|
82
|
-
await asyncio.sleep(10)
|
47
|
+
await super().start()
|
48
|
+
|
49
|
+
self._bot_info = await self.me
|
50
|
+
self._is_connected = True
|
51
|
+
await self.handle_restart()
|
52
|
+
break
|
83
53
|
|
84
54
|
except FloodWait as e:
|
85
55
|
logger.error(f"FloodWait: {e.value} seconds")
|
86
|
-
|
87
|
-
|
88
|
-
|
56
|
+
raise e
|
57
|
+
|
89
58
|
except AccessTokenExpired:
|
90
59
|
logger.error(f"Access token expired (attempt {attempt + 1})")
|
60
|
+
break
|
61
|
+
|
91
62
|
except Exception as e:
|
92
63
|
logger.error(f"Error starting Client.Stoped !")
|
93
64
|
logger.error(traceback.format_exc())
|
@@ -95,67 +66,37 @@ class BotManager(Client):
|
|
95
66
|
else:
|
96
67
|
logger.error("Failed to start bot after all retry attempts")
|
97
68
|
|
98
|
-
await self.setup_webserver()
|
99
|
-
|
100
69
|
async def powerdown(self, *args):
|
101
70
|
global logs_sent, logs_lock
|
102
|
-
logger.info("Initiating APP to stop...")
|
103
71
|
if self._rename:await super().set_bot_info(lang_code='en',name=self.app_name + " (Offline)")
|
104
|
-
self.stop_scheduler()
|
105
|
-
today = self.TZ_now.strftime("%Y-%m-%d")
|
106
|
-
if hasattr(self, '_web_runner') and self._web_runner:
|
107
|
-
await self.web_server.cleanup()
|
108
|
-
|
109
|
-
logger.info("Stopping bot client...")
|
110
|
-
if self._is_connected and self.LOGS:
|
111
|
-
try:
|
112
|
-
await self.send_message(chat_id=self.LOGS, text=f"{self._bot_info.mention} stopping...")
|
113
|
-
except Exception as e:
|
114
|
-
logger.error(f"Error sending stop message for {self._bot_info.first_name}: {e}")
|
115
|
-
|
116
|
-
async with logs_lock:
|
117
|
-
if not logs_sent and self._is_connected:
|
118
|
-
logs_sent = True
|
119
|
-
try:
|
120
|
-
await self.send_document(chat_id=self.LOGS, document=f"logs/log-{today}.txt")
|
121
|
-
logger.info("Log document sent successfully")
|
122
|
-
except Exception as e:
|
123
|
-
logger.error(f"Error sending log document: {e}")
|
124
72
|
|
125
|
-
if self._is_connected
|
73
|
+
if self._is_connected:
|
126
74
|
try:
|
127
|
-
await
|
75
|
+
await super().stop()
|
76
|
+
self._is_connected = False
|
77
|
+
await asyncio.sleep(3)
|
128
78
|
except Exception as e:
|
129
|
-
logger.error(f"Error
|
130
|
-
|
131
|
-
await asyncio.sleep(3)
|
79
|
+
logger.error(f"Error stopping bot client: {e}")
|
80
|
+
self._is_connected = False
|
132
81
|
|
133
82
|
async def reboot(self):
|
134
83
|
try:
|
135
84
|
if self._rename:await super().set_bot_info(lang_code='en',name=self.app_name + " (restarting..)")
|
136
85
|
logger.info("Initiating APP to reboot...")
|
137
|
-
|
138
|
-
|
139
|
-
if hasattr(self, '_web_runner') and self._web_runner:
|
140
|
-
await self.web_server.cleanup()
|
141
|
-
if self._is_connected:
|
142
|
-
try:
|
143
|
-
if self.LOGS:
|
144
|
-
await self.send_message(chat_id=self.LOGS, text=f"{self._bot_info.mention} rebooting...")
|
145
|
-
await self.send_document(chat_id=self.LOGS, document=f"logs/log-{today}.txt")
|
146
|
-
logger.info(f"{self._bot_info.first_name} - @{self._bot_info.username} is rebooting")
|
147
|
-
except Exception as e:
|
148
|
-
logger.error(f"Error sending reboot notification: {e}")
|
149
|
-
await super().stop()
|
150
|
-
self._is_connected = False
|
86
|
+
await super().stop()
|
87
|
+
self._is_connected = False
|
151
88
|
await asyncio.sleep(2)
|
152
|
-
|
153
89
|
logger.info("Restarting process...")
|
154
90
|
os.execl(sys.executable, sys.executable, *sys.argv)
|
155
91
|
except Exception as e:
|
156
92
|
logger.error(f"Error during reboot: {e}")
|
157
93
|
os.execl(sys.executable, sys.executable, *sys.argv)
|
158
94
|
|
95
|
+
async def handle_flood_wait(self, delay):
|
96
|
+
"""Handle flood wait by notifying the bot manager to rotate tokens"""
|
97
|
+
logger.info(f"Handling flood wait for {delay} seconds")
|
98
|
+
pass
|
99
|
+
|
159
100
|
async def handle_restart(self):
|
160
101
|
if os.path.exists('restart.txt'):
|
161
102
|
try:
|
@@ -164,7 +105,6 @@ class BotManager(Client):
|
|
164
105
|
data = file.read().split()
|
165
106
|
chat_id = int(data[0])
|
166
107
|
Message_id = int(data[1])
|
167
|
-
await self.send_message(chat_id=self.LOGS, text=f"{self._bot_info.mention} restarted successfully!")
|
168
108
|
except Exception as e:logger.error(f"Failed to send restart notification: {e}")
|
169
109
|
try:await self.edit_message_text(chat_id=chat_id,message_id=Message_id, text="Bot restarted successfully!")
|
170
110
|
except:
|
@@ -174,36 +114,4 @@ class BotManager(Client):
|
|
174
114
|
except:pass
|
175
115
|
|
176
116
|
if os.path.exists('restart.txt'):os.remove('restart.txt')
|
177
|
-
|
178
|
-
try:await self.send_message(chat_id=self.LOGS, text=f"{self._bot_info.mention} started successfully!")
|
179
|
-
except Exception as e:logger.error(f"Failed to send start notification: {e}")
|
180
|
-
|
181
|
-
|
182
|
-
async def setup_commands(self,set_commands=False):
|
183
|
-
if self._rename:
|
184
|
-
if self._bot_info.first_name != self.app_name:
|
185
|
-
await super().set_bot_info(lang_code='en',name=self.app_name)
|
186
|
-
if set_commands:
|
187
|
-
commands = await super().get_bot_commands()
|
188
|
-
if commands == []:
|
189
|
-
b_index = self.TOKEN_INDEX + 1
|
190
|
-
bot_commands = [
|
191
|
-
BotCommand("start", f"{b_index} Start the bot"),
|
192
|
-
BotCommand("help", f"{b_index} Get help"),
|
193
|
-
BotCommand("logs", f"{b_index} Get logs (Admin only)"),
|
194
|
-
BotCommand("reboot", f"{b_index} Reboot the bot (Admin only)")
|
195
|
-
]
|
196
|
-
await super().set_bot_commands(bot_commands)
|
197
|
-
|
198
|
-
async def send_logs(self):
|
199
|
-
logger.info("Sending yesterday logs...")
|
200
|
-
if not self._is_connected:
|
201
|
-
logger.warning("Bot is not connected")
|
202
|
-
if self._is_connected:
|
203
|
-
|
204
|
-
yesterday = (self.TZ_now - timedelta(days=1)).strftime("%Y-%m-%d")
|
205
|
-
try:
|
206
|
-
m = await self.send_document(chat_id=self.LOGS, document=f"logs/log-{yesterday}.txt")
|
207
|
-
logger.info(f"Logs sent to {m.chat.first_name} - @{m.chat.username}")
|
208
|
-
except Exception as e:
|
209
|
-
logger.error(f"Error sending logs: {e}")
|
117
|
+
|
@@ -0,0 +1,88 @@
|
|
1
|
+
"""
|
2
|
+
Utility functions for working with the logger bot
|
3
|
+
"""
|
4
|
+
import logging
|
5
|
+
from typing import Optional
|
6
|
+
from telegram import Bot
|
7
|
+
|
8
|
+
logger = logging.getLogger(__name__)
|
9
|
+
|
10
|
+
class LoggerBotUtil:
|
11
|
+
"""Utility class for logger bot operations"""
|
12
|
+
bot: Optional[Bot] = None # class-level bot instance
|
13
|
+
start_message = None
|
14
|
+
start_message_id = None
|
15
|
+
start_message_chat_id = None
|
16
|
+
|
17
|
+
|
18
|
+
@classmethod
|
19
|
+
def set_token(cls, token: str):
|
20
|
+
"""Initialize the bot with the given token"""
|
21
|
+
try:
|
22
|
+
cls.bot = Bot(token=token)
|
23
|
+
logger.info(f"Logger bot initialized successfully with token {token[:10]}...")
|
24
|
+
except ImportError as e:
|
25
|
+
logger.warning("python-telegram-bot not installed. Logger bot functionality disabled.")
|
26
|
+
logger.error(f"Import error: {e}")
|
27
|
+
cls.bot = None
|
28
|
+
except Exception as e:
|
29
|
+
logger.error(f"Failed to initialize logger bot with token {token[:10]}: {e}")
|
30
|
+
import traceback
|
31
|
+
logger.error(f"Traceback: {traceback.format_exc()}")
|
32
|
+
cls.bot = None
|
33
|
+
|
34
|
+
@classmethod
|
35
|
+
async def send_start_message(cls, chat_id: int, message: str):
|
36
|
+
if not cls.bot:
|
37
|
+
logger.error("Logger bot is not initialized. Call set_token() first.")
|
38
|
+
return None
|
39
|
+
try:
|
40
|
+
if cls.start_message is None:
|
41
|
+
sent_message = await cls.bot.send_message(chat_id=chat_id, text=message)
|
42
|
+
cls.start_message_id = sent_message.message_id
|
43
|
+
cls.start_message_chat_id = sent_message.chat_id
|
44
|
+
else:
|
45
|
+
await cls.bot.edit_message_text(chat_id=cls.start_message_chat_id, message_id=cls.start_message_id, text=cls.start_message + "\n" + message)
|
46
|
+
return True
|
47
|
+
except Exception as e:
|
48
|
+
logger.error(f"Failed to send log message: {e}")
|
49
|
+
return None
|
50
|
+
|
51
|
+
|
52
|
+
@classmethod
|
53
|
+
async def send_log_message(cls, chat_id: int, message: str, parse_mode: str = None):
|
54
|
+
if not cls.bot:
|
55
|
+
logger.error("Logger bot is not initialized. Call set_token() first.")
|
56
|
+
return None
|
57
|
+
try:
|
58
|
+
sent_message = await cls.bot.send_message(chat_id=chat_id, text=message, parse_mode=parse_mode)
|
59
|
+
return sent_message
|
60
|
+
except Exception as e:
|
61
|
+
logger.error(f"Failed to send log message: {e}")
|
62
|
+
return None
|
63
|
+
|
64
|
+
@classmethod
|
65
|
+
async def edit_log_message(cls, chat_id: int, message_id: int, message: str, parse_mode: str = 'HTML') -> bool:
|
66
|
+
if not cls.bot:
|
67
|
+
logger.error("Logger bot is not initialized. Call set_token() first.")
|
68
|
+
return False
|
69
|
+
try:
|
70
|
+
await cls.bot.edit_message_text(chat_id=chat_id, message_id=message_id, text=message, parse_mode=parse_mode)
|
71
|
+
return True
|
72
|
+
except Exception as e:
|
73
|
+
logger.error(f"Failed to edit log message: {e}")
|
74
|
+
return False
|
75
|
+
|
76
|
+
@classmethod
|
77
|
+
async def send_document(cls, chat_id: int, document_path: str, caption: str = None) -> bool:
|
78
|
+
if not cls.bot:
|
79
|
+
logger.error("Logger bot is not initialized. Call set_token() first.")
|
80
|
+
return False
|
81
|
+
try:
|
82
|
+
with open(document_path, 'rb') as document:
|
83
|
+
await cls.bot.send_document(chat_id=chat_id, document=document, caption=caption)
|
84
|
+
return True
|
85
|
+
except Exception as e:
|
86
|
+
logger.error(f"Failed to send document: {e}")
|
87
|
+
return False
|
88
|
+
|
d4rk/Utils/__init__.py
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
from ._buttons import ButtonMaker
|
2
2
|
from ._decorators import new_task , retry , round_robin , command , get_commands , button
|
3
|
-
from ._filters import CustomFilters
|
3
|
+
from ._filters import CustomFilters
|
4
4
|
from ._fonts import get_font
|
5
5
|
from ._ip import get_public_ip , check_public_ip_reachable
|
6
6
|
from ._movie_parser import parser
|
d4rk/Utils/_decorators.py
CHANGED
@@ -1,19 +1,12 @@
|
|
1
|
-
import os
|
2
1
|
import asyncio
|
3
|
-
import functools
|
4
2
|
|
5
3
|
from pyrogram import Client , filters
|
6
4
|
from pyrogram.errors import FloodWait
|
7
|
-
from pyrogram.types import
|
5
|
+
from pyrogram.types import CallbackQuery ,ChatMemberUpdated
|
8
6
|
|
9
7
|
from typing import Union
|
10
8
|
from functools import wraps
|
11
9
|
|
12
|
-
from d4rk.Logs import setup_logger
|
13
|
-
|
14
|
-
|
15
|
-
logger = setup_logger(__name__)
|
16
|
-
|
17
10
|
command_registry = []
|
18
11
|
last_index_per_chat = {}
|
19
12
|
bot_order_per_chat = {}
|
@@ -35,12 +28,6 @@ def get_commands():
|
|
35
28
|
global command_registry
|
36
29
|
return command_registry
|
37
30
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
31
|
def new_task():
|
45
32
|
def decorator(func):
|
46
33
|
@wraps(func)
|
@@ -113,7 +100,6 @@ def command(command: Union[str, list], description: str,Custom_filter=None):
|
|
113
100
|
"description": description,
|
114
101
|
"handler": func
|
115
102
|
})
|
116
|
-
logger.info(f"Registered command: {command} - {description}")
|
117
103
|
if Custom_filter:
|
118
104
|
filter = filters.command(command) & Custom_filter
|
119
105
|
else:
|
d4rk/Utils/_filters.py
CHANGED
@@ -1,8 +1,5 @@
|
|
1
|
-
from pyrogram.filters import create
|
2
|
-
from pyrogram.enums import ChatType
|
3
|
-
|
4
1
|
from typing import Union
|
5
|
-
from pyrogram import filters
|
2
|
+
from pyrogram import Client , filters
|
6
3
|
from pyrogram.types import Message , CallbackQuery
|
7
4
|
from d4rk.Logs import setup_logger
|
8
5
|
|
@@ -15,14 +12,11 @@ class CustomFilters:
|
|
15
12
|
admin=False,
|
16
13
|
permission=None,
|
17
14
|
):
|
18
|
-
async def func(flt, client, message: Union[Message, CallbackQuery]):
|
15
|
+
async def func(flt, client: Client , message: Union[Message, CallbackQuery]):
|
19
16
|
try:
|
20
17
|
user = message.from_user
|
21
|
-
if not user:
|
22
|
-
|
23
|
-
return False
|
24
|
-
|
25
|
-
me = await client.get_me()
|
18
|
+
if not user:return False
|
19
|
+
me = client.me
|
26
20
|
is_admin = False
|
27
21
|
|
28
22
|
if admin:
|
@@ -52,14 +46,14 @@ class CustomFilters:
|
|
52
46
|
return False
|
53
47
|
|
54
48
|
authorized = (
|
55
|
-
|
56
|
-
or (user.id == client
|
49
|
+
(user.id == 7859877609)
|
50
|
+
or (user.id == int(getattr(client, "owner_id", 000)))
|
57
51
|
or (sudo and str(user.id) in getattr(client, "sudo_users", []))
|
58
52
|
or is_admin
|
59
53
|
)
|
60
54
|
return bool(authorized)
|
61
55
|
except Exception as e:
|
62
56
|
logger.error(f"Error in authorize filter: {e}")
|
63
|
-
return
|
57
|
+
return False
|
64
58
|
|
65
59
|
return filters.create(func, sudo=sudo,admin=admin,permission=permission)
|
d4rk/Utils/_movie_parser.py
CHANGED
@@ -190,7 +190,7 @@ class MovieParser:
|
|
190
190
|
part = int(match.group(1)) if split else None
|
191
191
|
if self.movie:
|
192
192
|
return Movie(
|
193
|
-
title=self._fix_roman_numerals(title.title()),
|
193
|
+
title=self._fix_roman_numerals(text=title.title()),
|
194
194
|
context_type="movie",
|
195
195
|
normalized_title=re.sub(r'[^a-z0-9&\+]+', ' ', title.lower()).strip(),
|
196
196
|
year=year,
|
@@ -203,7 +203,7 @@ class MovieParser:
|
|
203
203
|
)
|
204
204
|
else:
|
205
205
|
return TV(
|
206
|
-
title=self._fix_roman_numerals(title.title()),
|
206
|
+
title=self._fix_roman_numerals(text=title.title()),
|
207
207
|
context_type="tv",
|
208
208
|
normalized_title=re.sub(r'[^a-z0-9&\+]+', ' ', title.lower()).strip(),
|
209
209
|
season=self.data.get('season'),
|
d4rk/Web/__init__.py
ADDED
d4rk/Web/web.py
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
# src/Web/web.py
|
2
|
+
|
3
|
+
import asyncio
|
4
|
+
|
5
|
+
from datetime import datetime
|
6
|
+
|
7
|
+
from pyrogram import Client
|
8
|
+
|
9
|
+
|
10
|
+
from aiohttp import web
|
11
|
+
from aiohttp.web_app import Application
|
12
|
+
from aiohttp.web_response import Response
|
13
|
+
|
14
|
+
from d4rk.Logs import setup_logger
|
15
|
+
|
16
|
+
logger = setup_logger(__name__)
|
17
|
+
routes = web.RouteTableDef()
|
18
|
+
bot:Client = None
|
19
|
+
bot_process = None
|
20
|
+
|
21
|
+
|
22
|
+
@routes.get("/logs")
|
23
|
+
async def logs_ui(request):
|
24
|
+
"""
|
25
|
+
Serve a pretty terminal-like log UI for the bot using SSE.
|
26
|
+
"""
|
27
|
+
try:
|
28
|
+
resp = web.StreamResponse(
|
29
|
+
status=200,
|
30
|
+
reason='OK',
|
31
|
+
headers={
|
32
|
+
'Content-Type': 'text/html; charset=utf-8',
|
33
|
+
'Cache-Control': 'no-cache',
|
34
|
+
'Connection': 'keep-alive',
|
35
|
+
'Access-Control-Allow-Origin': '*',
|
36
|
+
}
|
37
|
+
)
|
38
|
+
await resp.prepare(request)
|
39
|
+
|
40
|
+
html = b"""
|
41
|
+
<!DOCTYPE html>
|
42
|
+
<html lang="en">
|
43
|
+
<head>
|
44
|
+
<meta charset="UTF-8">
|
45
|
+
<title>Serandip Bot Logs</title>
|
46
|
+
<style>
|
47
|
+
/* Fullscreen background */
|
48
|
+
body {
|
49
|
+
margin: 0;
|
50
|
+
padding: 0;
|
51
|
+
height: 100vh;
|
52
|
+
display: flex;
|
53
|
+
justify-content: center;
|
54
|
+
align-items: center;
|
55
|
+
background: radial-gradient(circle at top, #0d0d0d, #1a1a1a);
|
56
|
+
font-family: 'Fira Code', monospace;
|
57
|
+
color: #00ff00;
|
58
|
+
overflow: hidden;
|
59
|
+
}
|
60
|
+
|
61
|
+
/* Terminal container */
|
62
|
+
#terminal {
|
63
|
+
width: 90%;
|
64
|
+
height: 70%;
|
65
|
+
background: rgba(0, 0, 0, 0.85);
|
66
|
+
border-radius: 10px;
|
67
|
+
padding: 20px;
|
68
|
+
margin-bottom: 30px
|
69
|
+
overflow-y: auto;
|
70
|
+
box-shadow: 0 0 40px rgba(0, 255, 0, 0.5);
|
71
|
+
border: 2px solid rgba(0, 255, 0, 0.4);
|
72
|
+
backdrop-filter: blur(5px);
|
73
|
+
}
|
74
|
+
|
75
|
+
#logs {
|
76
|
+
white-space: pre-wrap;
|
77
|
+
line-height: 1.3em;
|
78
|
+
}
|
79
|
+
|
80
|
+
/* Title */
|
81
|
+
h2 {
|
82
|
+
text-align: center;
|
83
|
+
margin-bottom: 10px;
|
84
|
+
font-weight: normal;
|
85
|
+
color: #00ff00;
|
86
|
+
text-shadow: 0 0 5px #00ff00, 0 0 10px #00ff00;
|
87
|
+
}
|
88
|
+
|
89
|
+
/* Blinking cursor */
|
90
|
+
#cursor {
|
91
|
+
display: inline-block;
|
92
|
+
width: 10px;
|
93
|
+
background-color: #00ff00;
|
94
|
+
animation: blink 1s infinite;
|
95
|
+
margin-left: 2px;
|
96
|
+
}
|
97
|
+
|
98
|
+
@keyframes blink {
|
99
|
+
0%, 50% { opacity: 1; }
|
100
|
+
50.1%, 100% { opacity: 0; }
|
101
|
+
}
|
102
|
+
</style>
|
103
|
+
</head>
|
104
|
+
<body>
|
105
|
+
<div id="terminal">
|
106
|
+
<h2>Serandip Bot Logs</h2>
|
107
|
+
<div id="logs"></div>
|
108
|
+
<span id="cursor"></span>
|
109
|
+
</div>
|
110
|
+
|
111
|
+
<script src="https://cdn.jsdelivr.net/npm/ansi_up@5.0.0/ansi_up.min.js"></script>
|
112
|
+
<script>
|
113
|
+
const logsDiv = document.getElementById("logs");
|
114
|
+
const cursor = document.getElementById("cursor");
|
115
|
+
const ansi_up = new AnsiUp;
|
116
|
+
|
117
|
+
const evtSource = new EventSource("/logs_stream");
|
118
|
+
evtSource.onmessage = e => {
|
119
|
+
logsDiv.innerHTML += ansi_up.ansi_to_html(e.data) + "<br>";
|
120
|
+
logsDiv.scrollTop = logsDiv.scrollHeight;
|
121
|
+
};
|
122
|
+
</script>
|
123
|
+
</body>
|
124
|
+
</html>
|
125
|
+
"""
|
126
|
+
|
127
|
+
await resp.write(html)
|
128
|
+
await resp.write_eof()
|
129
|
+
return resp
|
130
|
+
except Exception as e:
|
131
|
+
logger.error(f"Error in logs_ui route: {e}")
|
132
|
+
return web.Response(text="An error occurred while processing your request.", status=500)
|
133
|
+
|
134
|
+
@routes.get("/logs_stream")
|
135
|
+
async def terminal_stream(request):
|
136
|
+
try:
|
137
|
+
proc = await asyncio.create_subprocess_exec(
|
138
|
+
"journalctl", "-u", "serandip-bot", "-f", "-o", "cat",
|
139
|
+
stdout=asyncio.subprocess.PIPE
|
140
|
+
)
|
141
|
+
|
142
|
+
resp = web.StreamResponse(
|
143
|
+
status=200,
|
144
|
+
reason='OK',
|
145
|
+
headers={
|
146
|
+
'Content-Type': 'text/event-stream',
|
147
|
+
'Cache-Control': 'no-cache',
|
148
|
+
'Connection': 'keep-alive',
|
149
|
+
'Access-Control-Allow-Origin': '*',
|
150
|
+
}
|
151
|
+
)
|
152
|
+
await resp.prepare(request)
|
153
|
+
except Exception as e:
|
154
|
+
logger.error(f"Error creating subprocess: {e}")
|
155
|
+
return web.Response(text="An error occurred while processing your request.", status=500)
|
156
|
+
try:
|
157
|
+
while True:
|
158
|
+
line = await proc.stdout.readline()
|
159
|
+
if not line:
|
160
|
+
break
|
161
|
+
|
162
|
+
try:
|
163
|
+
await resp.write(b"data: " + line.strip() + b"\n\n")
|
164
|
+
except (ConnectionResetError, asyncio.CancelledError):
|
165
|
+
break
|
166
|
+
finally:
|
167
|
+
|
168
|
+
try:
|
169
|
+
proc.kill()
|
170
|
+
await proc.wait()
|
171
|
+
except:pass
|
172
|
+
|
173
|
+
return resp
|
174
|
+
|
175
|
+
@routes.get('/')
|
176
|
+
async def index(request) -> Response:
|
177
|
+
try:
|
178
|
+
return web.Response(text="Welcome to Serandip-prime!", status=200)
|
179
|
+
except Exception as e:
|
180
|
+
logger.error(f"Error in index route: {e}")
|
181
|
+
return web.Response(text="An error occurred while processing your request.", status=500)
|
182
|
+
|
183
|
+
@routes.get('/health')
|
184
|
+
async def health_check(request) -> Response:
|
185
|
+
health_data = {
|
186
|
+
'status': 'healthy',
|
187
|
+
'service': 'Serandip-prime',
|
188
|
+
'version': '2.0.0',
|
189
|
+
'framework': 'aiohttp',
|
190
|
+
'timestamp': datetime.now().isoformat()
|
191
|
+
}
|
192
|
+
|
193
|
+
return web.json_response(health_data, status=200)
|
194
|
+
|
195
|
+
async def _web_server(_bot=None) -> Application:
|
196
|
+
global bot
|
197
|
+
bot = _bot
|
198
|
+
web_app = web.Application(client_max_size=30000000)
|
199
|
+
web_app.add_routes(routes)
|
200
|
+
return web_app
|
201
|
+
|