Skip to content

Commit 0dce97e

Browse files
authored
Fix: direct dl
1 parent 1758529 commit 0dce97e

3 files changed

Lines changed: 131 additions & 123 deletions

File tree

Thunder/server/stream_routes.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
rf"^([a-zA-Z0-9_-]{{{SECURE_HASH_LENGTH}}})(\d+)(?:/.*)?$")
2626
PATTERN_ID_FIRST = re.compile(r"^(\d+)(?:/.*)?$")
2727
VALID_HASH_REGEX = re.compile(r'^[a-zA-Z0-9_-]+$')
28+
VALID_DISPOSITIONS = {"inline", "attachment"}
2829

2930
CORS_HEADERS = {
3031
"Access-Control-Allow-Origin": "*",
@@ -90,6 +91,11 @@ def select_optimal_client() -> tuple[int, ByteStreamer]:
9091
return client_id, get_streamer(client_id)
9192

9293

94+
def get_content_disposition(request: web.Request) -> str:
95+
disposition = request.query.get("disposition", "attachment").strip().lower()
96+
return disposition if disposition in VALID_DISPOSITIONS else "attachment"
97+
98+
9399
def parse_range_header(range_header: str, file_size: int) -> tuple[int, int]:
94100
if not range_header:
95101
return 0, file_size - 1
@@ -244,11 +250,13 @@ async def media_delivery(request: web.Request):
244250
ext = ext_map.get(ext, ext)
245251
filename = f"file_{secrets.token_hex(4)}.{ext}"
246252

253+
disposition = get_content_disposition(request)
254+
247255
headers = {
248256
"Content-Type": mime_type,
249257
"Content-Length": str(content_length),
250258
"Content-Disposition": (
251-
f"inline; filename*=UTF-8''{quote(filename)}"),
259+
f"{disposition}; filename*=UTF-8''{quote(filename)}"),
252260
"Accept-Ranges": "bytes",
253261
"Cache-Control": "public, max-age=31536000",
254262
"Connection": "keep-alive",

Thunder/utils/file_properties.py

Lines changed: 99 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -1,99 +1,99 @@
1-
# Thunder/utils/file_properties.py
2-
3-
import asyncio
4-
from datetime import datetime as dt
5-
from typing import Any, Optional
6-
7-
from pyrogram.client import Client
8-
from pyrogram.errors import FloodWait
9-
from pyrogram.file_id import FileId
10-
from pyrogram.types import Message
11-
12-
from Thunder.server.exceptions import FileNotFound
13-
from Thunder.utils.logger import logger
14-
15-
16-
def get_media(message: Message) -> Optional[Any]:
17-
for attr in ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note"):
18-
media = getattr(message, attr, None)
19-
if media:
20-
return media
21-
return None
22-
23-
24-
def get_uniqid(message: Message) -> Optional[str]:
25-
media = get_media(message)
26-
return getattr(media, 'file_unique_id', None)
27-
28-
29-
def get_hash(media_msg: Message) -> str:
30-
uniq_id = get_uniqid(media_msg)
31-
return uniq_id[:6] if uniq_id else ''
32-
33-
34-
def get_fsize(message: Message) -> int:
35-
media = get_media(message)
36-
return getattr(media, 'file_size', 0) if media else 0
37-
38-
39-
def parse_fid(message: Message) -> Optional[FileId]:
40-
media = get_media(message)
41-
if media and hasattr(media, 'file_id'):
42-
try:
43-
return FileId.decode(media.file_id)
44-
except Exception:
45-
return None
46-
return None
47-
48-
49-
def get_fname(msg: Message) -> str:
50-
media = get_media(msg)
51-
fname = getattr(media, 'file_name', None) if media else None
52-
53-
if not fname:
54-
ext = "bin"
55-
if media:
56-
media_types = {
57-
"photo": "jpg",
58-
"audio": "mp3",
59-
"voice": "ogg",
60-
"video": "mp4",
61-
"animation": "mp4",
62-
"video_note": "mp4",
63-
"sticker": "webp"
64-
}
65-
66-
# Check which attribute type the message has
67-
for attr, extension in media_types.items():
68-
if getattr(msg, attr, None) is not None:
69-
ext = extension
70-
break
71-
72-
timestamp = dt.now().strftime("%Y%m%d%H%M%S")
73-
fname = f"Thunder File To Link_{timestamp}.{ext}"
74-
75-
return fname
76-
77-
78-
async def get_fids(client: Client, chat_id: int, message_id: int) -> FileId:
79-
try:
80-
try:
81-
msg = await client.get_messages(chat_id, message_id)
82-
except FloodWait as e:
83-
await asyncio.sleep(e.value)
84-
msg = await client.get_messages(chat_id, message_id)
85-
86-
if not msg or getattr(msg, 'empty', False):
87-
raise FileNotFound("Message not found")
88-
89-
media = get_media(msg)
90-
if media:
91-
if not hasattr(media, 'file_id') or not hasattr(media, 'file_unique_id'):
92-
raise FileNotFound("Media metadata incomplete")
93-
return FileId.decode(media.file_id)
94-
95-
raise FileNotFound("No media in message")
96-
97-
except Exception as e:
98-
logger.error(f"Error in get_fids: {e}", exc_info=True)
99-
raise FileNotFound(str(e))
1+
# Thunder/utils/file_properties.py
2+
3+
import asyncio
4+
from datetime import datetime as dt
5+
from typing import Any, Optional
6+
7+
from pyrogram.client import Client
8+
from pyrogram.errors import FloodWait
9+
from pyrogram.file_id import FileId
10+
from pyrogram.types import Message
11+
12+
from Thunder.server.exceptions import FileNotFound
13+
from Thunder.utils.logger import logger
14+
15+
16+
def get_media(message: Message) -> Optional[Any]:
17+
for attr in ("audio", "document", "photo", "sticker", "animation", "video", "voice", "video_note"):
18+
media = getattr(message, attr, None)
19+
if media:
20+
return media
21+
return None
22+
23+
24+
def get_uniqid(message: Message) -> Optional[str]:
25+
media = get_media(message)
26+
return getattr(media, 'file_unique_id', None)
27+
28+
29+
def get_hash(media_msg: Message) -> str:
30+
uniq_id = get_uniqid(media_msg)
31+
return uniq_id[:6] if uniq_id else ''
32+
33+
34+
def get_fsize(message: Message) -> int:
35+
media = get_media(message)
36+
return getattr(media, 'file_size', 0) if media else 0
37+
38+
39+
def parse_fid(message: Message) -> Optional[FileId]:
40+
media = get_media(message)
41+
if media and hasattr(media, 'file_id'):
42+
try:
43+
return FileId.decode(media.file_id)
44+
except Exception:
45+
return None
46+
return None
47+
48+
49+
def get_fname(msg: Message) -> str:
50+
media = get_media(msg)
51+
fname = getattr(media, 'file_name', None) if media else None
52+
53+
if not fname:
54+
ext = "bin"
55+
if media:
56+
media_types = {
57+
"photo": "jpg",
58+
"audio": "mp3",
59+
"voice": "ogg",
60+
"video": "mp4",
61+
"animation": "mp4",
62+
"video_note": "mp4",
63+
"sticker": "webp"
64+
}
65+
66+
# Check which attribute type the message has
67+
for attr, extension in media_types.items():
68+
if getattr(msg, attr, None) is not None:
69+
ext = extension
70+
break
71+
72+
timestamp = dt.now().strftime("%Y%m%d%H%M%S")
73+
fname = f"Thunder File To Link_{timestamp}.{ext}"
74+
75+
return fname
76+
77+
78+
async def get_fids(client: Client, chat_id: int, message_id: int) -> FileId:
79+
try:
80+
try:
81+
msg = await client.get_messages(chat_id, message_id)
82+
except FloodWait as e:
83+
await asyncio.sleep(e.value)
84+
msg = await client.get_messages(chat_id, message_id)
85+
86+
if not msg or getattr(msg, 'empty', False):
87+
raise FileNotFound("Message not found")
88+
89+
media = get_media(msg)
90+
if media:
91+
if not hasattr(media, 'file_id') or not hasattr(media, 'file_unique_id'):
92+
raise FileNotFound("Media metadata incomplete")
93+
return FileId.decode(media.file_id)
94+
95+
raise FileNotFound("No media in message")
96+
97+
except Exception as e:
98+
logger.error(f"Error in get_fids: {e}", exc_info=True)
99+
raise FileNotFound(str(e))

Thunder/utils/render_template.py

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,9 @@
1919
cache_size=200,
2020
auto_reload=False,
2121
optimized=True
22-
)
23-
24-
async def render_page(id: int, secure_hash: str, requested_action: str | None = None) -> str:
22+
)
23+
24+
async def render_page(id: int, secure_hash: str, requested_action: str | None = None) -> str:
2525
try:
2626
try:
2727
message = await StreamBot.get_messages(chat_id=int(Var.BIN_CHANNEL), message_ids=id)
@@ -35,26 +35,26 @@ async def render_page(id: int, secure_hash: str, requested_action: str | None =
3535
file_unique_id = get_uniqid(message)
3636
file_name = get_fname(message)
3737

38-
if not file_unique_id or file_unique_id[:6] != secure_hash:
39-
raise InvalidHash("File unique ID or secure hash mismatch during rendering.")
40-
41-
quoted_filename = urllib.parse.quote(file_name.replace('/', '_'))
42-
src = urllib.parse.urljoin(Var.URL, f'{secure_hash}{id}/{quoted_filename}')
43-
safe_filename = html_module.escape(file_name)
44-
if requested_action == 'stream':
45-
template = template_env.get_template('req.html')
46-
context = {
47-
'heading': f"View {safe_filename}",
48-
'file_name': safe_filename,
49-
'src': src
50-
}
51-
else:
52-
template = template_env.get_template('dl.html')
53-
context = {
54-
'file_name': safe_filename,
55-
'src': src
56-
}
57-
return await template.render_async(**context)
38+
if not file_unique_id or file_unique_id[:6] != secure_hash:
39+
raise InvalidHash("File unique ID or secure hash mismatch during rendering.")
40+
41+
quoted_filename = urllib.parse.quote(file_name.replace('/', '_'))
42+
src = urllib.parse.urljoin(Var.URL, f'{secure_hash}{id}/{quoted_filename}')
43+
safe_filename = html_module.escape(file_name)
44+
if requested_action == 'stream':
45+
template = template_env.get_template('req.html')
46+
context = {
47+
'heading': f"View {safe_filename}",
48+
'file_name': safe_filename,
49+
'src': f"{src}?disposition=inline"
50+
}
51+
else:
52+
template = template_env.get_template('dl.html')
53+
context = {
54+
'file_name': safe_filename,
55+
'src': src
56+
}
57+
return await template.render_async(**context)
5858
except Exception as e:
5959
logger.error(f"Error in render_page for ID {id} and hash {secure_hash}: {e}", exc_info=True)
6060
raise

0 commit comments

Comments
 (0)