taslim19
commited on
Commit
·
98e44f0
1
Parent(s):
a0d8280
feat: Add advanced antiflood plugin and fix admin commands
Browse files
DragMusic/core/mongo.py
CHANGED
@@ -8,6 +8,8 @@ LOGGER(__name__).info("Connecting to your Mongo Database...")
|
|
8 |
try:
|
9 |
_mongo_async_ = AsyncIOMotorClient(MONGO_DB_URI)
|
10 |
mongodb = _mongo_async_.Anon
|
|
|
|
|
11 |
LOGGER(__name__).info("Connected to your Mongo Database.")
|
12 |
except:
|
13 |
LOGGER(__name__).error("Failed to connect to your Mongo Database.")
|
|
|
8 |
try:
|
9 |
_mongo_async_ = AsyncIOMotorClient(MONGO_DB_URI)
|
10 |
mongodb = _mongo_async_.Anon
|
11 |
+
flooddb = _mongo_async_.Flood
|
12 |
+
sudoersdb = _mongo_async_.Sudoers
|
13 |
LOGGER(__name__).info("Connected to your Mongo Database.")
|
14 |
except:
|
15 |
LOGGER(__name__).error("Failed to connect to your Mongo Database.")
|
DragMusic/plugins/management/admins.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1 |
from pyrogram import filters
|
2 |
from pyrogram.types import Message
|
|
|
3 |
from pyrogram.errors import PeerIdInvalid
|
4 |
|
5 |
from DragMusic import app
|
6 |
-
from DragMusic.utils.decorators.admins import
|
7 |
|
8 |
# Helper function to extract user info from a message
|
9 |
async def extract_user(message: Message, client):
|
@@ -25,7 +26,7 @@ async def extract_user(message: Message, client):
|
|
25 |
return None
|
26 |
|
27 |
@app.on_message(filters.command("promote") & filters.group)
|
28 |
-
@
|
29 |
async def promote_user(client, message: Message, _):
|
30 |
target_user = await extract_user(message, client)
|
31 |
if not target_user:
|
@@ -38,7 +39,7 @@ async def promote_user(client, message: Message, _):
|
|
38 |
await message.reply_text(f"Failed to promote user. Error: {e}")
|
39 |
|
40 |
@app.on_message(filters.command("demote") & filters.group)
|
41 |
-
@
|
42 |
async def demote_user(client, message: Message, _):
|
43 |
target_user = await extract_user(message, client)
|
44 |
if not target_user:
|
@@ -63,7 +64,7 @@ async def demote_user(client, message: Message, _):
|
|
63 |
@app.on_message(filters.command("adminlist") & filters.group)
|
64 |
async def admin_list(client, message: Message):
|
65 |
admin_list = []
|
66 |
-
async for admin in client.get_chat_members(message.chat.id, filter=
|
67 |
admin_list.append(f"- {admin.user.mention}")
|
68 |
|
69 |
if admin_list:
|
@@ -72,7 +73,7 @@ async def admin_list(client, message: Message):
|
|
72 |
await message.reply_text("No admins found in this chat.")
|
73 |
|
74 |
@app.on_message(filters.command("admincache") & filters.group)
|
75 |
-
@
|
76 |
async def admin_cache(client, message: Message, _):
|
77 |
from DragMusic.utils.decorators.admins import adminlist
|
78 |
if message.chat.id in adminlist:
|
|
|
1 |
from pyrogram import filters
|
2 |
from pyrogram.types import Message
|
3 |
+
from pyrogram.enums import ChatMembersFilter
|
4 |
from pyrogram.errors import PeerIdInvalid
|
5 |
|
6 |
from DragMusic import app
|
7 |
+
from DragMusic.utils.decorators.admins import AdminActual
|
8 |
|
9 |
# Helper function to extract user info from a message
|
10 |
async def extract_user(message: Message, client):
|
|
|
26 |
return None
|
27 |
|
28 |
@app.on_message(filters.command("promote") & filters.group)
|
29 |
+
@AdminActual
|
30 |
async def promote_user(client, message: Message, _):
|
31 |
target_user = await extract_user(message, client)
|
32 |
if not target_user:
|
|
|
39 |
await message.reply_text(f"Failed to promote user. Error: {e}")
|
40 |
|
41 |
@app.on_message(filters.command("demote") & filters.group)
|
42 |
+
@AdminActual
|
43 |
async def demote_user(client, message: Message, _):
|
44 |
target_user = await extract_user(message, client)
|
45 |
if not target_user:
|
|
|
64 |
@app.on_message(filters.command("adminlist") & filters.group)
|
65 |
async def admin_list(client, message: Message):
|
66 |
admin_list = []
|
67 |
+
async for admin in client.get_chat_members(message.chat.id, filter=ChatMembersFilter.ADMINISTRATORS):
|
68 |
admin_list.append(f"- {admin.user.mention}")
|
69 |
|
70 |
if admin_list:
|
|
|
73 |
await message.reply_text("No admins found in this chat.")
|
74 |
|
75 |
@app.on_message(filters.command("admincache") & filters.group)
|
76 |
+
@AdminActual
|
77 |
async def admin_cache(client, message: Message, _):
|
78 |
from DragMusic.utils.decorators.admins import adminlist
|
79 |
if message.chat.id in adminlist:
|
DragMusic/plugins/management/antiflood.py
ADDED
@@ -0,0 +1,186 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import re
|
2 |
+
from collections import defaultdict
|
3 |
+
from datetime import datetime, timedelta
|
4 |
+
|
5 |
+
from pyrogram import filters
|
6 |
+
from pyrogram.types import Message, ChatPermissions
|
7 |
+
|
8 |
+
from DragMusic import app
|
9 |
+
from DragMusic.utils.database import get_flood_settings, update_flood_setting
|
10 |
+
from DragMusic.utils.decorators.admins import AdminActual
|
11 |
+
from DragMusic.utils.formatters import parse_time
|
12 |
+
|
13 |
+
# In-memory storage for flood tracking
|
14 |
+
user_flood_count = defaultdict(lambda: defaultdict(int))
|
15 |
+
user_timed_messages = defaultdict(lambda: defaultdict(list))
|
16 |
+
user_offenses = defaultdict(lambda: defaultdict(int))
|
17 |
+
|
18 |
+
|
19 |
+
@app.on_message(filters.group & ~filters.service, group=11)
|
20 |
+
async def flood_control(client, message: Message):
|
21 |
+
if not message.from_user:
|
22 |
+
return
|
23 |
+
|
24 |
+
chat_id = message.chat.id
|
25 |
+
user_id = message.from_user.id
|
26 |
+
now = datetime.now()
|
27 |
+
|
28 |
+
settings = await get_flood_settings(chat_id)
|
29 |
+
|
30 |
+
# --- Timed Flood Check ---
|
31 |
+
t_limit = settings.get("t_limit", 0)
|
32 |
+
t_duration = settings.get("t_duration", 30)
|
33 |
+
|
34 |
+
if t_limit > 0:
|
35 |
+
user_timed_messages[chat_id][user_id].append(now)
|
36 |
+
# Filter out old messages
|
37 |
+
user_timed_messages[chat_id][user_id] = [
|
38 |
+
t for t in user_timed_messages[chat_id][user_id] if now - t < timedelta(seconds=t_duration)
|
39 |
+
]
|
40 |
+
if len(user_timed_messages[chat_id][user_id]) >= t_limit:
|
41 |
+
user_offenses[chat_id][user_id] += 1
|
42 |
+
return await take_action(client, message, settings, "timed flood")
|
43 |
+
|
44 |
+
# --- Consecutive Flood Check ---
|
45 |
+
limit = settings.get("limit", 0)
|
46 |
+
if limit > 0:
|
47 |
+
if user_id != list(user_flood_count[chat_id].keys())[0] if user_flood_count[chat_id] else None:
|
48 |
+
user_flood_count[chat_id].clear()
|
49 |
+
|
50 |
+
user_flood_count[chat_id][user_id] += 1
|
51 |
+
|
52 |
+
if user_flood_count[chat_id][user_id] >= limit:
|
53 |
+
user_offenses[chat_id][user_id] += 1
|
54 |
+
return await take_action(client, message, settings, "consecutive flood")
|
55 |
+
|
56 |
+
async def take_action(client, message: Message, settings: dict, reason: str):
|
57 |
+
chat_id = message.chat.id
|
58 |
+
user_id = message.from_user.id
|
59 |
+
action = settings.get("action", "ban")
|
60 |
+
duration_str = settings.get("action_duration", "1h")
|
61 |
+
|
62 |
+
# Clear flood messages if enabled
|
63 |
+
if settings.get("clear", False):
|
64 |
+
# This is a placeholder; deleting messages requires storing message_ids
|
65 |
+
pass
|
66 |
+
|
67 |
+
try:
|
68 |
+
if action == "ban":
|
69 |
+
await client.ban_chat_member(chat_id, user_id)
|
70 |
+
await message.reply_text(f"{message.from_user.mention} has been banned for {reason}.")
|
71 |
+
elif action == "kick":
|
72 |
+
await client.ban_chat_member(chat_id, user_id)
|
73 |
+
await client.unban_chat_member(chat_id, user_id)
|
74 |
+
await message.reply_text(f"{message.from_user.mention} has been kicked for {reason}.")
|
75 |
+
elif action == "mute":
|
76 |
+
await client.restrict_chat_member(chat_id, user_id, permissions=ChatPermissions())
|
77 |
+
await message.reply_text(f"{message.from_user.mention} has been muted for {reason}.")
|
78 |
+
elif action in ["tban", "tmute"]:
|
79 |
+
until_date = datetime.now() + parse_time(duration_str)
|
80 |
+
if action == "tban":
|
81 |
+
await client.ban_chat_member(chat_id, user_id, until_date=until_date)
|
82 |
+
await message.reply_text(f"{message.from_user.mention} has been banned for {duration_str} due to {reason}.")
|
83 |
+
else: # tmute
|
84 |
+
await client.restrict_chat_member(chat_id, user_id, permissions=ChatPermissions(), until_date=until_date)
|
85 |
+
await message.reply_text(f"{message.from_user.mention} has been muted for {duration_str} due to {reason}.")
|
86 |
+
|
87 |
+
# Reset counters
|
88 |
+
user_flood_count[chat_id].clear()
|
89 |
+
user_timed_messages[chat_id].clear()
|
90 |
+
except Exception as e:
|
91 |
+
await message.reply_text(f"Failed to take action. Error: {e}")
|
92 |
+
|
93 |
+
# --- Admin Commands ---
|
94 |
+
|
95 |
+
@app.on_message(filters.command("flood") & filters.group)
|
96 |
+
@AdminActual
|
97 |
+
async def get_flood_command(client, message: Message, _):
|
98 |
+
settings = await get_flood_settings(message.chat.id)
|
99 |
+
limit = settings.get('limit')
|
100 |
+
t_limit = settings.get('t_limit')
|
101 |
+
action = settings.get('action')
|
102 |
+
duration = settings.get('action_duration')
|
103 |
+
|
104 |
+
status_limit = f"{limit} consecutive messages" if limit > 0 else "Disabled"
|
105 |
+
status_t_limit = f"{t_limit} messages in {settings.get('t_duration')}s" if t_limit > 0 else "Disabled"
|
106 |
+
|
107 |
+
if action in ["tban", "tmute"]:
|
108 |
+
action_str = f"{action} for {duration}"
|
109 |
+
else:
|
110 |
+
action_str = action
|
111 |
+
|
112 |
+
await message.reply_text(
|
113 |
+
f"**Antiflood Settings:**\n"
|
114 |
+
f"- **Consecutive Flood:** {status_limit}\n"
|
115 |
+
f"- **Timed Flood:** {status_t_limit}\n"
|
116 |
+
f"- **Action:** {action_str}\n"
|
117 |
+
f"- **Clear messages:** {'On' if settings.get('clear') else 'Off'}"
|
118 |
+
)
|
119 |
+
|
120 |
+
@app.on_message(filters.command("setflood") & filters.group)
|
121 |
+
@AdminActual
|
122 |
+
async def set_flood_command(client, message: Message, _):
|
123 |
+
if len(message.command) < 2:
|
124 |
+
return await message.reply_text("Usage: /setflood <number/off>")
|
125 |
+
|
126 |
+
arg = message.command[1].lower()
|
127 |
+
if arg in ["off", "no", "0"]:
|
128 |
+
await update_flood_setting(message.chat.id, "limit", 0)
|
129 |
+
await message.reply_text("Consecutive antiflood has been disabled.")
|
130 |
+
elif arg.isdigit():
|
131 |
+
limit = int(arg)
|
132 |
+
if limit < 3:
|
133 |
+
return await message.reply_text("Flood limit must be at least 3.")
|
134 |
+
await update_flood_setting(message.chat.id, "limit", limit)
|
135 |
+
await message.reply_text(f"Consecutive flood limit set to {limit} messages.")
|
136 |
+
else:
|
137 |
+
await message.reply_text("Invalid argument. Use a number or 'off'.")
|
138 |
+
|
139 |
+
@app.on_message(filters.command("setfloodtimer") & filters.group)
|
140 |
+
@AdminActual
|
141 |
+
async def set_flood_timer_command(client, message: Message, _):
|
142 |
+
args = message.command[1:]
|
143 |
+
if not args or args[0].lower() in ["off", "no", "0"]:
|
144 |
+
await update_flood_setting(message.chat.id, "t_limit", 0)
|
145 |
+
return await message.reply_text("Timed antiflood has been disabled.")
|
146 |
+
|
147 |
+
if len(args) != 2 or not args[0].isdigit() or not re.match(r"\d+s", args[1].lower()):
|
148 |
+
return await message.reply_text("Usage: /setfloodtimer <count> <duration>s (e.g., 10 30s)")
|
149 |
+
|
150 |
+
await update_flood_setting(message.chat.id, "t_limit", int(args[0]))
|
151 |
+
await update_flood_setting(message.chat.id, "t_duration", int(args[1][:-1]))
|
152 |
+
await message.reply_text(f"Timed flood set to {args[0]} messages in {args[1]}.")
|
153 |
+
|
154 |
+
@app.on_message(filters.command("floodmode") & filters.group)
|
155 |
+
@AdminActual
|
156 |
+
async def set_flood_mode_command(client, message: Message, _):
|
157 |
+
args = message.command[1:]
|
158 |
+
if not args:
|
159 |
+
return await message.reply_text("Usage: /floodmode <ban|kick|mute|tban|tmute> [duration]")
|
160 |
+
|
161 |
+
action = args[0].lower()
|
162 |
+
if action not in ["ban", "kick", "mute", "tban", "tmute"]:
|
163 |
+
return await message.reply_text("Invalid action. Use: ban, kick, mute, tban, tmute.")
|
164 |
+
|
165 |
+
await update_flood_setting(message.chat.id, "action", action)
|
166 |
+
|
167 |
+
if action in ["tban", "tmute"]:
|
168 |
+
if len(args) < 2:
|
169 |
+
return await message.reply_text(f"Usage: /floodmode {action} <duration> (e.g., 1h, 3d)")
|
170 |
+
duration_str = args[1]
|
171 |
+
if not parse_time(duration_str):
|
172 |
+
return await message.reply_text("Invalid time format. Use d, h, m, s (e.g., 3d, 10m).")
|
173 |
+
await update_flood_setting(message.chat.id, "action_duration", duration_str)
|
174 |
+
await message.reply_text(f"Antiflood action set to {action} for {duration_str}.")
|
175 |
+
else:
|
176 |
+
await message.reply_text(f"Antiflood action set to {action}.")
|
177 |
+
|
178 |
+
@app.on_message(filters.command("clearflood") & filters.group)
|
179 |
+
@AdminActual
|
180 |
+
async def set_clear_flood_command(client, message: Message, _):
|
181 |
+
if len(message.command) < 2 or message.command[1].lower() not in ["on", "off", "yes", "no"]:
|
182 |
+
return await message.reply_text("Usage: /clearflood <on/off>")
|
183 |
+
|
184 |
+
status = message.command[1].lower() in ["on", "yes"]
|
185 |
+
await update_flood_setting(message.chat.id, "clear", status)
|
186 |
+
await message.reply_text(f"Deleting flood messages is now {'enabled' if status else 'disabled'}.")
|
DragMusic/utils/database.py
CHANGED
@@ -644,3 +644,25 @@ async def remove_banned_user(user_id: int):
|
|
644 |
if not is_gbanned:
|
645 |
return
|
646 |
return await blockeddb.delete_one({"user_id": user_id})
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
644 |
if not is_gbanned:
|
645 |
return
|
646 |
return await blockeddb.delete_one({"user_id": user_id})
|
647 |
+
|
648 |
+
|
649 |
+
# Antiflood Settings
|
650 |
+
async def get_flood_settings(chat_id: int):
|
651 |
+
settings = await flooddb.find_one({"chat_id": chat_id})
|
652 |
+
if not settings:
|
653 |
+
return {
|
654 |
+
"limit": 0, # Consecutive messages, 0 to disable
|
655 |
+
"action": "ban",
|
656 |
+
"t_limit": 0, # Timed message count, 0 to disable
|
657 |
+
"t_duration": 30, # Timed duration in seconds
|
658 |
+
"action_duration": "1h", # Duration for tban/tmute
|
659 |
+
"clear": False, # Whether to delete flooding messages
|
660 |
+
}
|
661 |
+
return settings
|
662 |
+
|
663 |
+
async def update_flood_setting(chat_id: int, key: str, value):
|
664 |
+
await flooddb.update_one(
|
665 |
+
{"chat_id": chat_id},
|
666 |
+
{"$set": {key: value}},
|
667 |
+
upsert=True
|
668 |
+
)
|
DragMusic/utils/decorators/admins.py
CHANGED
@@ -117,44 +117,20 @@ def AdminRightsCheck(mystic):
|
|
117 |
|
118 |
def AdminActual(mystic):
|
119 |
async def wrapper(client, message):
|
120 |
-
if await is_maintenance() is False:
|
121 |
-
if message.from_user.id not in SUDOERS:
|
122 |
-
return await message.reply_text(
|
123 |
-
text=f"{app.mention} ɪs ᴜɴᴅᴇʀ ᴍᴀɪɴᴛᴇɴᴀɴᴄᴇ, ᴠɪsɪᴛ <a href={SUPPORT_CHAT}>sᴜᴘᴘᴏʀᴛ ᴄʜᴀᴛ</a> ғᴏʀ ᴋɴᴏᴡɪɴɢ ᴛʜᴇ ʀᴇᴀsᴏɴ.",
|
124 |
-
disable_web_page_preview=True,
|
125 |
-
)
|
126 |
-
|
127 |
-
try:
|
128 |
-
await message.delete()
|
129 |
-
except:
|
130 |
-
pass
|
131 |
-
|
132 |
try:
|
133 |
language = await get_lang(message.chat.id)
|
134 |
_ = get_string(language)
|
135 |
except:
|
136 |
_ = get_string("en")
|
137 |
-
|
138 |
-
upl = InlineKeyboardMarkup(
|
139 |
-
[
|
140 |
-
[
|
141 |
-
InlineKeyboardButton(
|
142 |
-
text="ʜᴏᴡ ᴛᴏ ғɪx ?",
|
143 |
-
callback_data="DragmousAdmin",
|
144 |
-
),
|
145 |
-
]
|
146 |
-
]
|
147 |
-
)
|
148 |
-
return await message.reply_text(_["general_3"], reply_markup=upl)
|
149 |
if message.from_user.id not in SUDOERS:
|
150 |
try:
|
151 |
-
member = (
|
152 |
-
|
153 |
-
|
154 |
except:
|
155 |
-
return
|
156 |
-
|
157 |
-
return await message.reply(_["general_4"])
|
158 |
return await mystic(client, message, _)
|
159 |
|
160 |
return wrapper
|
|
|
117 |
|
118 |
def AdminActual(mystic):
|
119 |
async def wrapper(client, message):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
120 |
try:
|
121 |
language = await get_lang(message.chat.id)
|
122 |
_ = get_string(language)
|
123 |
except:
|
124 |
_ = get_string("en")
|
125 |
+
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
126 |
if message.from_user.id not in SUDOERS:
|
127 |
try:
|
128 |
+
member = await app.get_chat_member(message.chat.id, message.from_user.id)
|
129 |
+
if not member.privileges.can_promote_members:
|
130 |
+
return await message.reply_text(_["admin_18"])
|
131 |
except:
|
132 |
+
return await message.reply_text("You are not an admin.")
|
133 |
+
|
|
|
134 |
return await mystic(client, message, _)
|
135 |
|
136 |
return wrapper
|
DragMusic/utils/formatters.py
CHANGED
@@ -1,29 +1,26 @@
|
|
1 |
import json
|
2 |
import subprocess
|
|
|
|
|
3 |
|
4 |
|
5 |
def get_readable_time(seconds: int) -> str:
|
6 |
-
|
7 |
-
|
8 |
-
|
9 |
-
|
10 |
-
|
11 |
-
|
12 |
-
|
13 |
-
|
14 |
-
|
15 |
-
|
16 |
-
|
17 |
-
|
18 |
-
|
19 |
-
|
20 |
-
|
21 |
-
|
22 |
-
if len(time_list) == 4:
|
23 |
-
ping_time += time_list.pop() + ", "
|
24 |
-
time_list.reverse()
|
25 |
-
ping_time += ":".join(time_list)
|
26 |
-
return ping_time
|
27 |
|
28 |
|
29 |
def convert_bytes(size: float) -> str:
|
@@ -183,3 +180,19 @@ formats = [
|
|
183 |
"f4a",
|
184 |
"f4b",
|
185 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import json
|
2 |
import subprocess
|
3 |
+
import re
|
4 |
+
from datetime import datetime, timedelta
|
5 |
|
6 |
|
7 |
def get_readable_time(seconds: int) -> str:
|
8 |
+
result = ""
|
9 |
+
(days, remainder) = divmod(seconds, 86400)
|
10 |
+
days = int(days)
|
11 |
+
if days != 0:
|
12 |
+
result += f"{days}d "
|
13 |
+
(hours, remainder) = divmod(remainder, 3600)
|
14 |
+
hours = int(hours)
|
15 |
+
if hours != 0:
|
16 |
+
result += f"{hours}h "
|
17 |
+
(minutes, seconds) = divmod(remainder, 60)
|
18 |
+
minutes = int(minutes)
|
19 |
+
if minutes != 0:
|
20 |
+
result += f"{minutes}m "
|
21 |
+
seconds = int(seconds)
|
22 |
+
result += f"{seconds}s "
|
23 |
+
return result.strip()
|
|
|
|
|
|
|
|
|
|
|
24 |
|
25 |
|
26 |
def convert_bytes(size: float) -> str:
|
|
|
180 |
"f4a",
|
181 |
"f4b",
|
182 |
]
|
183 |
+
|
184 |
+
|
185 |
+
def parse_time(time_str: str) -> timedelta:
|
186 |
+
matches = re.match(r"(\d+)([d|h|m|s])", time_str)
|
187 |
+
if matches:
|
188 |
+
quantity, unit = matches.groups()
|
189 |
+
quantity = int(quantity)
|
190 |
+
if unit == 'd':
|
191 |
+
return timedelta(days=quantity)
|
192 |
+
elif unit == 'h':
|
193 |
+
return timedelta(hours=quantity)
|
194 |
+
elif unit == 'm':
|
195 |
+
return timedelta(minutes=quantity)
|
196 |
+
elif unit == 's':
|
197 |
+
return timedelta(seconds=quantity)
|
198 |
+
return timedelta(minutes=0) # Default or error case
|