Skip to content

Commit e466f51

Browse files
authored
2.0
1 parent c378529 commit e466f51

File tree

14 files changed

+710
-307
lines changed

14 files changed

+710
-307
lines changed

.github/workflows/dockerize.yml

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,17 @@ on:
77

88
jobs:
99
build:
10+
if: github.repository == 'fyaz05/FileToLink'
1011
runs-on: ubuntu-latest
1112
steps:
1213
- uses: actions/checkout@v5
13-
14+
1415
- name: Login to Docker Hub
1516
uses: docker/login-action@v3
1617
with:
1718
username: fyaz05
1819
password: ${{ secrets.DOCKER_TOKEN }}
19-
20+
2021
- name: Build and Push
2122
uses: docker/build-push-action@v6
2223
with:

.python-version

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
3.13
1+
3.14

Dockerfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
FROM python:3.13-slim
1+
FROM python:3.14-slim
22

33
ENV PYTHONUNBUFFERED=1 \
44
PYTHONDONTWRITEBYTECODE=1

README.md

Lines changed: 65 additions & 53 deletions
Large diffs are not rendered by default.

Thunder/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,4 @@
33
import time
44

55
StartTime = time.time()
6-
__version__ = "1.9.9"
6+
__version__ = "2.0.0"

Thunder/bot/plugins/admin.py

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,14 @@
2626
MSG_ADMIN_AUTH_LIST_HEADER, MSG_ADMIN_NO_BAN_REASON,
2727
MSG_ADMIN_USER_BANNED, MSG_ADMIN_USER_UNBANNED, MSG_AUTHORIZE_FAILED,
2828
MSG_AUTHORIZE_SUCCESS, MSG_AUTHORIZE_USAGE, MSG_AUTH_USER_INFO,
29-
MSG_BAN_REASON_SUFFIX, MSG_BAN_USAGE, MSG_BUTTON_CLOSE,
30-
MSG_CANNOT_BAN_OWNER, MSG_CHANNEL_BANNED, MSG_CHANNEL_BANNED_REASON_SUFFIX,
31-
MSG_CHANNEL_NOT_BANNED, MSG_CHANNEL_UNBANNED, MSG_DB_ERROR, MSG_DB_STATS,
29+
MSG_BAN_REASON_SUFFIX, MSG_BAN_USAGE, MSG_BROADCAST_USAGE,
30+
MSG_BUTTON_CLOSE, MSG_CANNOT_BAN_OWNER, MSG_CHANNEL_BANNED,
31+
MSG_CHANNEL_BANNED_REASON_SUFFIX, MSG_CHANNEL_NOT_BANNED,
32+
MSG_CHANNEL_UNBANNED, MSG_DB_ERROR, MSG_DB_STATS,
3233
MSG_DEAUTHORIZE_FAILED, MSG_DEAUTHORIZE_SUCCESS,
33-
MSG_DEAUTHORIZE_USAGE, MSG_ERROR_GENERIC, MSG_INVALID_USER_ID,
34-
MSG_LOG_FILE_CAPTION, MSG_LOG_FILE_EMPTY, MSG_LOG_FILE_MISSING,
35-
MSG_NO_AUTH_USERS, MSG_RESTARTING, MSG_SHELL_ERROR,
34+
MSG_DEAUTHORIZE_USAGE, MSG_ERROR_GENERIC, MSG_INVALID_BROADCAST_CMD,
35+
MSG_INVALID_USER_ID, MSG_LOG_FILE_CAPTION, MSG_LOG_FILE_EMPTY,
36+
MSG_LOG_FILE_MISSING, MSG_NO_AUTH_USERS, MSG_RESTARTING, MSG_SHELL_ERROR,
3637
MSG_SHELL_EXECUTING, MSG_SHELL_NO_OUTPUT, MSG_SHELL_OUTPUT,
3738
MSG_SHELL_OUTPUT_STDERR, MSG_SHELL_OUTPUT_STDOUT, MSG_SHELL_USAGE,
3839
MSG_SPEEDTEST_ERROR, MSG_SPEEDTEST_INIT, MSG_SPEEDTEST_RESULT,
@@ -65,7 +66,28 @@ async def get_total_users(client: Client, message: Message):
6566

6667
@StreamBot.on_message(filters.command("broadcast") & owner_filter)
6768
async def broadcast_handler(client: Client, message: Message):
68-
await broadcast_message(client, message)
69+
mode = "all"
70+
if len(message.command) > 1:
71+
arg = message.command[1].lower().strip()
72+
if arg in ("help", "--help", "-h"):
73+
return await reply(message, text=MSG_BROADCAST_USAGE, parse_mode=ParseMode.MARKDOWN)
74+
if arg == "authorized":
75+
mode = "authorized"
76+
elif arg == "regular":
77+
mode = "regular"
78+
else:
79+
safe_arg = arg.replace("`", "'")
80+
await reply(
81+
message,
82+
text=f"❌ **Invalid argument:** `{safe_arg}`\n\n{MSG_BROADCAST_USAGE}",
83+
parse_mode=ParseMode.MARKDOWN
84+
)
85+
return
86+
87+
if not message.reply_to_message:
88+
return await reply(message, text=MSG_INVALID_BROADCAST_CMD)
89+
90+
await broadcast_message(client, message, mode=mode)
6991

7092

7193
@StreamBot.on_message(filters.command("status") & owner_filter)

Thunder/server/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@
33
from aiohttp import web
44
from .stream_routes import routes
55

6+
67
async def web_server():
7-
web_app = web.Application(client_max_size=30000000)
8+
web_app = web.Application(client_max_size=50 * 1024 * 1024)
89
web_app.add_routes(routes)
910
return web_app

Thunder/server/stream_routes.py

Lines changed: 66 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,13 @@
2626
PATTERN_ID_FIRST = re.compile(r"^(\d+)(?:/.*)?$")
2727
VALID_HASH_REGEX = re.compile(r'^[a-zA-Z0-9_-]+$')
2828

29+
CORS_HEADERS = {
30+
"Access-Control-Allow-Origin": "*",
31+
"Access-Control-Allow-Methods": "GET, HEAD, OPTIONS",
32+
"Access-Control-Allow-Headers": "Range, Content-Type, *",
33+
"Access-Control-Expose-Headers": "Content-Length, Content-Range, Content-Disposition",
34+
}
35+
2936
streamers = {}
3037

3138

@@ -101,7 +108,8 @@ def parse_range_header(range_header: str, file_size: int) -> tuple[int, int]:
101108
raise web.HTTPBadRequest(text=f"Invalid range header: {range_header}")
102109
suffix_len = int(end_str)
103110
if suffix_len <= 0:
104-
raise web.HTTPRequestRangeNotSatisfiable(headers={"Content-Range": f"bytes */{file_size}"})
111+
raise web.HTTPRequestRangeNotSatisfiable(
112+
headers={"Content-Range": f"bytes */{file_size}"})
105113
start = max(file_size - suffix_len, 0)
106114
end = file_size - 1
107115

@@ -114,7 +122,6 @@ def parse_range_header(range_header: str, file_size: int) -> tuple[int, int]:
114122

115123

116124
@routes.get("/", allow_head=True)
117-
118125
async def root_redirect(request):
119126
raise web.HTTPFound("https://github.com/fyaz05/FileToLink")
120127

@@ -126,21 +133,39 @@ async def status_endpoint(request):
126133

127134
workload_distribution = {str(k): v for k, v in sorted(work_loads.items())}
128135

129-
return web.json_response({
130-
"server": {
131-
"status": "operational",
132-
"version": __version__,
133-
"uptime": get_readable_time(uptime)
134-
},
135-
"telegram_bot": {
136-
"username": f"@{StreamBot.username}",
137-
"active_clients": len(multi_clients)
136+
return web.json_response(
137+
{
138+
"server": {
139+
"status": "operational",
140+
"version": __version__,
141+
"uptime": get_readable_time(uptime)
142+
},
143+
"telegram_bot": {
144+
"username": f"@{StreamBot.username}",
145+
"active_clients": len(multi_clients)
146+
},
147+
"resources": {
148+
"total_workload": total_load,
149+
"workload_distribution": workload_distribution
150+
}
138151
},
139-
"resources": {
140-
"total_workload": total_load,
141-
"workload_distribution": workload_distribution
152+
headers={"Access-Control-Allow-Origin": "*"}
153+
)
154+
155+
156+
@routes.options("/status")
157+
async def status_options(request: web.Request):
158+
return web.Response(headers={
159+
**CORS_HEADERS,
160+
"Access-Control-Max-Age": "86400"
161+
})
142162

143-
}
163+
164+
@routes.options(r"/{path:.+}")
165+
async def media_options(request: web.Request):
166+
return web.Response(headers={
167+
**CORS_HEADERS,
168+
"Access-Control-Max-Age": "86400"
144169
})
145170

146171

@@ -152,15 +177,25 @@ async def media_preview(request: web.Request):
152177

153178
rendered_page = await render_page(
154179
message_id, secure_hash, requested_action='stream')
155-
return web.Response(text=rendered_page, content_type='text/html')
180+
181+
response = web.Response(
182+
text=rendered_page,
183+
content_type='text/html',
184+
headers={
185+
"Access-Control-Allow-Origin": "*",
186+
"Access-Control-Allow-Headers": "Range, Content-Type, *",
187+
"X-Content-Type-Options": "nosniff",
188+
}
189+
)
190+
response.enable_compression()
191+
return response
156192

157193
except (InvalidHash, FileNotFound) as e:
158194
logger.debug(
159195
f"Client error in preview: {type(e).__name__} - {e}",
160196
exc_info=True)
161197
raise web.HTTPNotFound(text="Resource not found") from e
162198
except Exception as e:
163-
164199
error_id = secrets.token_hex(6)
165200
logger.error(f"Preview error {error_id}: {e}", exc_info=True)
166201
raise web.HTTPInternalServerError(
@@ -201,8 +236,13 @@ async def media_delivery(request: web.Request):
201236

202237
mime_type = (
203238
file_info.get('mime_type') or 'application/octet-stream')
204-
filename = (
205-
file_info.get('file_name') or f"file_{secrets.token_hex(4)}")
239+
240+
filename = file_info.get('file_name')
241+
if not filename:
242+
ext = mime_type.split('/')[-1] if '/' in mime_type else 'bin'
243+
ext_map = {'jpeg': 'jpg', 'mpeg': 'mp3', 'octet-stream': 'bin'}
244+
ext = ext_map.get(ext, ext)
245+
filename = f"file_{secrets.token_hex(4)}.{ext}"
206246

207247
headers = {
208248
"Content-Type": mime_type,
@@ -211,7 +251,12 @@ async def media_delivery(request: web.Request):
211251
f"inline; filename*=UTF-8''{quote(filename)}"),
212252
"Accept-Ranges": "bytes",
213253
"Cache-Control": "public, max-age=31536000",
214-
"Connection": "keep-alive"
254+
"Connection": "keep-alive",
255+
"Access-Control-Allow-Origin": "*",
256+
"Access-Control-Allow-Headers": "Range, Content-Type, *",
257+
"Access-Control-Expose-Headers": (
258+
"Content-Length, Content-Range, Content-Disposition"),
259+
"X-Content-Type-Options": "nosniff"
215260
}
216261

217262
if range_header:
@@ -250,6 +295,7 @@ async def stream_generator():
250295
break
251296
finally:
252297
work_loads[client_id] -= 1
298+
253299
return web.Response(
254300
status=206 if range_header else 200,
255301
body=stream_generator(),

0 commit comments

Comments
 (0)