Fyaz Mohammed commited on
Commit
7ff1595
·
unverified ·
1 Parent(s): 9794ce8

Refined template structure for better clarity and usability.
Enhanced error messages and logging for improved troubleshooting.
Improved exception handling for message deletion and streaming actions.
Expanded support for additional user and channel exceptions during broadcast operations.
Consolidated streaming logic for file handling.

LICENSE CHANGED
@@ -1,24 +1,201 @@
1
- This is free and unencumbered software released into the public domain.
2
-
3
- Anyone is free to copy, modify, publish, use, compile, sell, or
4
- distribute this software, either in source code form or as a compiled
5
- binary, for any purpose, commercial or non-commercial, and by any
6
- means.
7
-
8
- In jurisdictions that recognize copyright laws, the author or authors
9
- of this software dedicate any and all copyright interest in the
10
- software to the public domain. We make this dedication for the benefit
11
- of the public at large and to the detriment of our heirs and
12
- successors. We intend this dedication to be an overt act of
13
- relinquishment in perpetuity of all present and future rights to this
14
- software under copyright law.
15
-
16
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
- EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
- MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
19
- IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
20
- OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
21
- ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22
- OTHER DEALINGS IN THE SOFTWARE.
23
-
24
- For more information, please refer to <https://unlicense.org>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship, whether in Source or
36
+ Object form, made available under the License, as indicated by a
37
+ copyright notice that is included in or attached to the work
38
+ (an example is provided in the Appendix below).
39
+
40
+ "Derivative Works" shall mean any work, whether in Source or Object
41
+ form, that is based on (or derived from) the Work and for which the
42
+ editorial revisions, annotations, elaborations, or other modifications
43
+ represent, as a whole, an original work of authorship. For the purposes
44
+ of this License, Derivative Works shall not include works that remain
45
+ separable from, or merely link (or bind by name) to the interfaces of,
46
+ the Work and Derivative Works thereof.
47
+
48
+ "Contribution" shall mean any work of authorship, including
49
+ the original version of the Work and any modifications or additions
50
+ to that Work or Derivative Works thereof, that is intentionally
51
+ submitted to Licensor for inclusion in the Work by the copyright owner
52
+ or by an individual or Legal Entity authorized to submit on behalf of
53
+ the copyright owner. For the purposes of this definition, "submitted"
54
+ means any form of electronic, verbal, or written communication sent
55
+ to the Licensor or its representatives, including but not limited to
56
+ communication on electronic mailing lists, source code control systems,
57
+ and issue tracking systems that are managed by, or on behalf of, the
58
+ Licensor for the purpose of discussing and improving the Work, but
59
+ excluding communication that is conspicuously marked or otherwise
60
+ designated in writing by the copyright owner as "Not a Contribution."
61
+
62
+ "Contributor" shall mean Licensor and any individual or Legal Entity
63
+ on behalf of whom a Contribution has been received by Licensor and
64
+ subsequently incorporated within the Work.
65
+
66
+ 2. Grant of Copyright License. Subject to the terms and conditions of
67
+ this License, each Contributor hereby grants to You a perpetual,
68
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69
+ copyright license to reproduce, prepare Derivative Works of,
70
+ publicly display, publicly perform, sublicense, and distribute the
71
+ Work and such Derivative Works in Source or Object form.
72
+
73
+ 3. Grant of Patent License. Subject to the terms and conditions of
74
+ this License, each Contributor hereby grants to You a perpetual,
75
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76
+ (except as stated in this section) patent license to make, have made,
77
+ use, offer to sell, sell, import, and otherwise transfer the Work,
78
+ where such license applies only to those patent claims licensable
79
+ by such Contributor that are necessarily infringed by their
80
+ Contribution(s) alone or by combination of their Contribution(s)
81
+ with the Work to which such Contribution(s) was submitted. If You
82
+ institute patent litigation against any entity (including a
83
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
84
+ or a Contribution incorporated within the Work constitutes direct
85
+ or contributory patent infringement, then any patent licenses
86
+ granted to You under this License for that Work shall terminate
87
+ as of the date such litigation is filed.
88
+
89
+ 4. Redistribution. You may reproduce and distribute copies of the
90
+ Work or Derivative Works thereof in any medium, with or without
91
+ modifications, and in Source or Object form, provided that You
92
+ meet the following conditions:
93
+
94
+ (a) You must give any other recipients of the Work or
95
+ Derivative Works a copy of this License; and
96
+
97
+ (b) You must cause any modified files to carry prominent notices
98
+ stating that You changed the files; and
99
+
100
+ (c) You must retain, in the Source form of any Derivative Works
101
+ that You distribute, all copyright, patent, trademark, and
102
+ attribution notices from the Source form of the Work,
103
+ excluding those notices that do not pertain to any part of
104
+ the Derivative Works; and
105
+
106
+ (d) If the Work includes a "NOTICE" text file as part of its
107
+ distribution, then any Derivative Works that You distribute must
108
+ include a readable copy of the attribution notices contained
109
+ within such NOTICE file, excluding those notices that do not
110
+ pertain to any part of the Derivative Works, in at least one
111
+ of the following places: within a NOTICE text file distributed
112
+ as part of the Derivative Works; within the Source form or
113
+ documentation, if provided along with the Derivative Works; or,
114
+ within a display generated by the Derivative Works, if and
115
+ wherever such third-party notices normally appear. The contents
116
+ of the NOTICE file are for informational purposes only and
117
+ do not modify the License. You may add Your own attribution
118
+ notices within Derivative Works that You distribute, alongside
119
+ or as an addendum to the NOTICE text from the Work, provided
120
+ that such additional attribution notices cannot be construed
121
+ as modifying the License.
122
+
123
+ You may add Your own copyright statement to Your modifications and
124
+ may provide additional or different license terms and conditions
125
+ for use, reproduction, or distribution of Your modifications, or
126
+ for any such Derivative Works as a whole, provided Your use,
127
+ reproduction, and distribution of the Work otherwise complies with
128
+ the conditions stated in this License.
129
+
130
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
131
+ any Contribution intentionally submitted for inclusion in the Work
132
+ by You to the Licensor shall be under the terms and conditions of
133
+ this License, without any additional terms or conditions.
134
+ Notwithstanding the above, nothing herein shall supersede or modify
135
+ the terms of any separate license agreement you may have executed
136
+ with Licensor regarding such Contributions.
137
+
138
+ 6. Trademarks. This License does not grant permission to use the trade
139
+ names, trademarks, service marks, or product names of the Licensor,
140
+ except as required for reasonable and customary use in describing the
141
+ origin of the Work and reproducing the content of the NOTICE file.
142
+
143
+ 7. Disclaimer of Warranty. Unless required by applicable law or
144
+ agreed to in writing, Licensor provides the Work (and each
145
+ Contributor provides its Contributions) on an "AS IS" BASIS,
146
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147
+ implied, including, without limitation, any warranties or conditions
148
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149
+ PARTICULAR PURPOSE. You are solely responsible for determining the
150
+ appropriateness of using or redistributing the Work and assume any
151
+ risks associated with Your exercise of permissions under this License.
152
+
153
+ 8. Limitation of Liability. In no event and under no legal theory,
154
+ whether in tort (including negligence), contract, or otherwise,
155
+ unless required by applicable law (such as deliberate and grossly
156
+ negligent acts) or agreed to in writing, shall any Contributor be
157
+ liable to You for damages, including any direct, indirect, special,
158
+ incidental, or consequential damages of any character arising as a
159
+ result of this License or out of the use or inability to use the
160
+ Work (including but not limited to damages for loss of goodwill,
161
+ work stoppage, computer failure or malfunction, or any and all
162
+ other commercial damages or losses), even if such Contributor
163
+ has been advised of the possibility of such damages.
164
+
165
+ 9. Accepting Warranty or Additional Liability. While redistributing
166
+ the Work or Derivative Works thereof, You may choose to offer,
167
+ and charge a fee for, acceptance of support, warranty, indemnity,
168
+ or other liability obligations and/or rights consistent with this
169
+ License. However, in accepting such obligations, You may act only
170
+ on Your own behalf and on Your sole responsibility, not on behalf
171
+ of any other Contributor, and only if You agree to indemnify,
172
+ defend, and hold each Contributor harmless for any liability
173
+ incurred by, or claims asserted against, such Contributor by reason
174
+ of your accepting any such warranty or additional liability.
175
+
176
+ END OF TERMS AND CONDITIONS
177
+
178
+ APPENDIX: How to apply the Apache License to your work.
179
+
180
+ To apply the Apache License to your work, attach the following
181
+ boilerplate notice, with the fields enclosed by brackets "[]"
182
+ replaced with your own identifying information. (Don't include
183
+ the brackets!) The text should be enclosed in the appropriate
184
+ comment syntax for the file format. We also recommend that a
185
+ file or class name and description of purpose be included on the
186
+ same "printed page" as the copyright notice for easier
187
+ identification within third-party archives.
188
+
189
+ Copyright [yyyy] [name of copyright owner]
190
+
191
+ Licensed under the Apache License, Version 2.0 (the "License");
192
+ you may not use this file except in compliance with the License.
193
+ You may obtain a copy of the License at
194
+
195
+ http://www.apache.org/licenses/LICENSE-2.0
196
+
197
+ Unless required by applicable law or agreed to in writing, software
198
+ distributed under the License is distributed on an "AS IS" BASIS,
199
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200
+ See the License for the specific language governing permissions and
201
+ limitations under the License.
Thunder/__init__.py CHANGED
@@ -3,4 +3,4 @@
3
  import time
4
 
5
  StartTime = time.time()
6
- __version__ = "1.9.3"
 
3
  import time
4
 
5
  StartTime = time.time()
6
+ __version__ = "1.9.4"
Thunder/bot/plugins/callbacks.py CHANGED
@@ -1,7 +1,7 @@
1
  # Thunder/bot/plugins/callbacks.py
2
 
3
  from pyrogram import Client, filters
4
- from pyrogram.errors import MessageNotModified
5
  from pyrogram.types import (CallbackQuery, InlineKeyboardButton,
6
  InlineKeyboardMarkup, LinkPreviewOptions)
7
 
@@ -99,15 +99,23 @@ async def restart_broadcast_callback(client: Client, callback_query: CallbackQue
99
  async def close_panel_callback(client: Client, callback_query: CallbackQuery):
100
  try:
101
  await handle_flood_wait(callback_query.answer)
102
- await handle_flood_wait(callback_query.message.delete)
 
 
 
 
 
 
103
  if callback_query.message.reply_to_message:
104
  try:
105
  reply_msg = callback_query.message.reply_to_message
106
  await handle_flood_wait(reply_msg.delete)
107
- except Exception:
108
- pass
 
 
109
  except Exception as e:
110
- logger.error(f"Error in close panel callback: {e}", exc_info=True)
111
 
112
  @StreamBot.on_callback_query(filters.regex(r"^cancel_"))
113
  async def cancel_broadcast(client: Client, callback_query: CallbackQuery):
 
1
  # Thunder/bot/plugins/callbacks.py
2
 
3
  from pyrogram import Client, filters
4
+ from pyrogram.errors import MessageNotModified, MessageDeleteForbidden
5
  from pyrogram.types import (CallbackQuery, InlineKeyboardButton,
6
  InlineKeyboardMarkup, LinkPreviewOptions)
7
 
 
99
  async def close_panel_callback(client: Client, callback_query: CallbackQuery):
100
  try:
101
  await handle_flood_wait(callback_query.answer)
102
+ try:
103
+ await handle_flood_wait(callback_query.message.delete)
104
+ except MessageDeleteForbidden:
105
+ logger.debug(f"Failed to delete callback query message due to permissions. Message ID: {callback_query.message.id}")
106
+ except Exception as e:
107
+ logger.error(f"Error deleting callback query message: {e}", exc_info=True)
108
+
109
  if callback_query.message.reply_to_message:
110
  try:
111
  reply_msg = callback_query.message.reply_to_message
112
  await handle_flood_wait(reply_msg.delete)
113
+ except MessageDeleteForbidden:
114
+ logger.debug(f"Failed to delete replied message due to permissions. Message ID: {reply_msg.id}")
115
+ except Exception as e:
116
+ logger.error(f"Error deleting replied message: {e}", exc_info=True)
117
  except Exception as e:
118
+ logger.error(f"General error in close panel callback: {e}", exc_info=True)
119
 
120
  @StreamBot.on_callback_query(filters.regex(r"^cancel_"))
121
  async def cancel_broadcast(client: Client, callback_query: CallbackQuery):
Thunder/bot/plugins/stream.py CHANGED
@@ -1,10 +1,11 @@
1
  # Thunder/bot/plugins/stream.py
2
 
3
  import asyncio
 
4
  from typing import Any, Dict, Optional
5
 
6
  from pyrogram import Client, enums, filters
7
- from pyrogram.errors import MessageNotModified
8
  from pyrogram.types import (InlineKeyboardButton, InlineKeyboardMarkup,
9
  LinkPreviewOptions, Message)
10
 
@@ -161,10 +162,16 @@ async def channel_receive_handler(bot: Client, msg: Message):
161
  )
162
  try:
163
  await handle_flood_wait(msg.edit_reply_markup, reply_markup=get_link_buttons(links))
164
- except (MessageNotModified, Exception):
 
 
 
 
 
 
165
  await send_link(msg, links)
166
  except Exception as e:
167
- logger.error(f"Error in channel_receive_handler: {e}", exc_info=True)
168
 
169
  async def process_single(bot: Client, msg: Message, file_msg: Message, status_msg: Message, shortener_val: bool, original_request_msg: Optional[Message] = None):
170
  try:
@@ -219,16 +226,29 @@ async def process_single(bot: Client, msg: Message, file_msg: Message, status_ms
219
  quote=True
220
  )
221
  if status_msg:
222
- await handle_flood_wait(status_msg.delete)
 
 
 
 
 
223
  return links
224
  except Exception as e:
 
225
  if status_msg:
226
- await handle_flood_wait(status_msg.edit_text, MSG_ERROR_PROCESSING_MEDIA)
227
- if str(e):
228
- await notify_own(bot, MSG_CRITICAL_ERROR.format(
229
- error=str(e),
230
- error_id=str(id(e))[:8]
231
- ))
 
 
 
 
 
 
 
232
  return None
233
 
234
  async def process_batch(bot: Client, msg: Message, start_id: int, count: int, status_msg: Message, shortener_val: bool):
 
1
  # Thunder/bot/plugins/stream.py
2
 
3
  import asyncio
4
+ import secrets
5
  from typing import Any, Dict, Optional
6
 
7
  from pyrogram import Client, enums, filters
8
+ from pyrogram.errors import MessageNotModified, MessageDeleteForbidden
9
  from pyrogram.types import (InlineKeyboardButton, InlineKeyboardMarkup,
10
  LinkPreviewOptions, Message)
11
 
 
162
  )
163
  try:
164
  await handle_flood_wait(msg.edit_reply_markup, reply_markup=get_link_buttons(links))
165
+ except MessageNotModified:
166
+ pass
167
+ except MessageDeleteForbidden:
168
+ logger.debug(f"Failed to edit reply markup for message {msg.id} due to permissions. Sending new link instead.")
169
+ await send_link(msg, links)
170
+ except Exception as e:
171
+ logger.error(f"Error editing reply markup for message {msg.id}: {e}", exc_info=True)
172
  await send_link(msg, links)
173
  except Exception as e:
174
+ logger.error(f"Error in channel_receive_handler for message {msg.id}: {e}", exc_info=True)
175
 
176
  async def process_single(bot: Client, msg: Message, file_msg: Message, status_msg: Message, shortener_val: bool, original_request_msg: Optional[Message] = None):
177
  try:
 
226
  quote=True
227
  )
228
  if status_msg:
229
+ try:
230
+ await handle_flood_wait(status_msg.delete)
231
+ except MessageDeleteForbidden:
232
+ logger.debug(f"Failed to delete status message {status_msg.id} due to permissions.")
233
+ except Exception as e:
234
+ logger.error(f"Error deleting status message {status_msg.id}: {e}", exc_info=True)
235
  return links
236
  except Exception as e:
237
+ logger.error(f"Error processing single file for message {file_msg.id}: {e}", exc_info=True)
238
  if status_msg:
239
+ try:
240
+ await handle_flood_wait(status_msg.edit_text, MSG_ERROR_PROCESSING_MEDIA)
241
+ except MessageNotModified:
242
+ pass
243
+ except MessageDeleteForbidden:
244
+ logger.debug(f"Failed to edit status message {status_msg.id} due to permissions.")
245
+ except Exception as edit_err:
246
+ logger.error(f"Error editing status message {status_msg.id} after processing error: {edit_err}", exc_info=True)
247
+
248
+ await notify_own(bot, MSG_CRITICAL_ERROR.format(
249
+ error=str(e),
250
+ error_id=secrets.token_hex(6)
251
+ ))
252
  return None
253
 
254
  async def process_batch(bot: Client, msg: Message, start_id: int, count: int, status_msg: Message, shortener_val: bool):
Thunder/server/exceptions.py CHANGED
@@ -1,7 +1,7 @@
1
  # Thunder/server/exceptions.py
2
 
3
  class InvalidHash(Exception):
4
- message = "Invalid hash"
5
 
6
  class FileNotFound(Exception):
7
- message = "File not found"
 
1
  # Thunder/server/exceptions.py
2
 
3
  class InvalidHash(Exception):
4
+ pass
5
 
6
  class FileNotFound(Exception):
7
+ pass
Thunder/server/stream_routes.py CHANGED
@@ -43,7 +43,7 @@ def parse_media_request(path: str, query: dict) -> tuple[int, str]:
43
  if len(secure_hash) == SECURE_HASH_LENGTH and VALID_HASH_REGEX.match(secure_hash):
44
  return message_id, secure_hash
45
  except ValueError as e:
46
- raise InvalidHash("Invalid message ID format") from e
47
 
48
  match = PATTERN_ID_FIRST.match(clean_path)
49
  if match:
@@ -52,14 +52,16 @@ def parse_media_request(path: str, query: dict) -> tuple[int, str]:
52
  secure_hash = query.get("hash", "").strip()
53
  if len(secure_hash) == SECURE_HASH_LENGTH and VALID_HASH_REGEX.match(secure_hash):
54
  return message_id, secure_hash
 
 
55
  except ValueError as e:
56
- raise InvalidHash("Invalid message ID format") from e
57
 
58
- raise InvalidHash("Invalid URL structure")
59
 
60
  def select_optimal_client() -> tuple[int, ByteStreamer]:
61
  if not work_loads:
62
- raise web.HTTPInternalServerError(text="No available clients")
63
 
64
  available_clients = [(cid, load) for cid, load in work_loads.items() if load < MAX_CONCURRENT_PER_CLIENT]
65
 
@@ -76,7 +78,7 @@ def parse_range_header(range_header: str, file_size: int) -> tuple[int, int]:
76
 
77
  match = RANGE_REGEX.match(range_header)
78
  if not match:
79
- raise web.HTTPBadRequest(text="Invalid range")
80
 
81
  start = int(match.group("start")) if match.group("start") else 0
82
  end = int(match.group("end")) if match.group("end") else file_size - 1
@@ -125,12 +127,12 @@ async def media_preview(request: web.Request):
125
  return web.Response(text=rendered_page, content_type='text/html')
126
 
127
  except (InvalidHash, FileNotFound) as e:
128
- logger.debug(f"Client error in preview: {type(e).__name__}", exc_info=True)
129
  raise web.HTTPNotFound(text="Resource not found") from e
130
  except Exception as e:
131
  error_id = secrets.token_hex(6)
132
  logger.error(f"Preview error {error_id}: {e}", exc_info=True)
133
- raise web.HTTPInternalServerError(text="Server error") from e
134
 
135
  @routes.get(r"/{path:.+}", allow_head=True)
136
  async def media_delivery(request: web.Request):
@@ -145,14 +147,14 @@ async def media_delivery(request: web.Request):
145
  try:
146
  file_info = await streamer.get_file_info(message_id)
147
  if not file_info.get('unique_id'):
148
- raise FileNotFound("File not found")
149
 
150
  if file_info['unique_id'][:SECURE_HASH_LENGTH] != secure_hash:
151
- raise InvalidHash("Invalid hash")
152
 
153
  file_size = file_info.get('file_size', 0)
154
  if file_size == 0:
155
- raise FileNotFound("File size unavailable")
156
 
157
  range_header = request.headers.get("Range", "")
158
  start, end = parse_range_header(range_header, file_size)
@@ -213,13 +215,13 @@ async def media_delivery(request: web.Request):
213
  except Exception as e:
214
  work_loads[client_id] -= 1
215
  error_id = secrets.token_hex(6)
216
- logger.error(f"Stream error {error_id}: {e}")
217
- raise web.HTTPInternalServerError(text="Server error") from e
218
 
219
  except (InvalidHash, FileNotFound) as e:
220
- logger.debug(f"Client error: {type(e).__name__}", exc_info=True)
221
  raise web.HTTPNotFound(text="Resource not found") from e
222
  except Exception as e:
223
  error_id = secrets.token_hex(6)
224
  logger.error(f"Server error {error_id}: {e}", exc_info=True)
225
- raise web.HTTPInternalServerError(text="Server error") from e
 
43
  if len(secure_hash) == SECURE_HASH_LENGTH and VALID_HASH_REGEX.match(secure_hash):
44
  return message_id, secure_hash
45
  except ValueError as e:
46
+ raise InvalidHash(f"Invalid message ID format in path: {e}") from e
47
 
48
  match = PATTERN_ID_FIRST.match(clean_path)
49
  if match:
 
52
  secure_hash = query.get("hash", "").strip()
53
  if len(secure_hash) == SECURE_HASH_LENGTH and VALID_HASH_REGEX.match(secure_hash):
54
  return message_id, secure_hash
55
+ else:
56
+ raise InvalidHash("Invalid or missing hash in query parameter")
57
  except ValueError as e:
58
+ raise InvalidHash(f"Invalid message ID format in path: {e}") from e
59
 
60
+ raise InvalidHash("Invalid URL structure or missing hash")
61
 
62
  def select_optimal_client() -> tuple[int, ByteStreamer]:
63
  if not work_loads:
64
+ raise web.HTTPInternalServerError(text="No available clients to handle the request. Please try again later.")
65
 
66
  available_clients = [(cid, load) for cid, load in work_loads.items() if load < MAX_CONCURRENT_PER_CLIENT]
67
 
 
78
 
79
  match = RANGE_REGEX.match(range_header)
80
  if not match:
81
+ raise web.HTTPBadRequest(text=f"Invalid range header: {range_header}")
82
 
83
  start = int(match.group("start")) if match.group("start") else 0
84
  end = int(match.group("end")) if match.group("end") else file_size - 1
 
127
  return web.Response(text=rendered_page, content_type='text/html')
128
 
129
  except (InvalidHash, FileNotFound) as e:
130
+ logger.debug(f"Client error in preview: {type(e).__name__} - {e}", exc_info=True)
131
  raise web.HTTPNotFound(text="Resource not found") from e
132
  except Exception as e:
133
  error_id = secrets.token_hex(6)
134
  logger.error(f"Preview error {error_id}: {e}", exc_info=True)
135
+ raise web.HTTPInternalServerError(text=f"Server error occurred: {error_id}") from e
136
 
137
  @routes.get(r"/{path:.+}", allow_head=True)
138
  async def media_delivery(request: web.Request):
 
147
  try:
148
  file_info = await streamer.get_file_info(message_id)
149
  if not file_info.get('unique_id'):
150
+ raise FileNotFound("File unique ID not found in info.")
151
 
152
  if file_info['unique_id'][:SECURE_HASH_LENGTH] != secure_hash:
153
+ raise InvalidHash("Provided hash does not match file's unique ID.")
154
 
155
  file_size = file_info.get('file_size', 0)
156
  if file_size == 0:
157
+ raise FileNotFound("File size is reported as zero or unavailable.")
158
 
159
  range_header = request.headers.get("Range", "")
160
  start, end = parse_range_header(range_header, file_size)
 
215
  except Exception as e:
216
  work_loads[client_id] -= 1
217
  error_id = secrets.token_hex(6)
218
+ logger.error(f"Stream error {error_id}: {e}", exc_info=True) # Ensure exc_info is true
219
+ raise web.HTTPInternalServerError(text=f"Server error during streaming: {error_id}") from e
220
 
221
  except (InvalidHash, FileNotFound) as e:
222
+ logger.debug(f"Client error: {type(e).__name__} - {e}", exc_info=True)
223
  raise web.HTTPNotFound(text="Resource not found") from e
224
  except Exception as e:
225
  error_id = secrets.token_hex(6)
226
  logger.error(f"Server error {error_id}: {e}", exc_info=True)
227
+ raise web.HTTPInternalServerError(text=f"An unexpected server error occurred: {error_id}") from e
Thunder/template/req.html CHANGED
@@ -5,137 +5,117 @@
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>{{ heading }}</title>
7
 
 
8
  <meta name="description" content="Easily share and distribute files">
9
  <meta property="og:title" content="Thunder FileToLink">
10
  <meta property="og:description" content="File Streamer">
11
  <meta property="og:type" content="website">
12
  <meta property="og:image" content="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/FileToLink/live.png">
 
 
13
  <meta name="theme-color" content="#1a1a2e">
14
  <meta name="msapplication-navbutton-color" content="#1a1a2e">
15
  <meta name="apple-mobile-web-app-status-bar-style" content="#1a1a2e">
16
  <meta name="color-scheme" content="dark light">
17
  <meta name="application-name" content="Thunder FileToLink">
18
 
 
19
  <link rel="icon" href="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/FileToLink/Thunder.jpg">
20
  <link rel="apple-touch-icon" href="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/FileToLink/Thunder.jpg">
21
 
 
22
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
23
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
24
  <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css">
25
 
26
- <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/FileToLink/reqstyle.css">
 
27
  </head>
28
  <body>
29
- <div class="loading-overlay" id="loading-overlay">
30
- <div class="loading-spinner">
31
- <div></div>
32
- <div></div>
33
- </div>
 
34
  </div>
35
- <button class="toggle-dark-mode" onclick="app.toggleDarkMode()" aria-label="Toggle Light/Dark Mode" aria-pressed="false" title="Toggle Light/Dark Mode"><i class="fas fa-moon"></i></button>
36
- <div class="bg-animation"><div class="bg-gradient"></div></div>
37
 
38
  <div class="container">
39
  <header class="header">
40
  <div class="file-info">
41
  <h1 id="file-name">{{ file_name }}</h1>
42
- <div class="file-meta"></div>
43
  </div>
44
  </header>
45
 
46
- <main id="main-content" role="main" aria-label="Main content">
47
- <div class="player-container">
48
- <{{ tag }} id="player" playsinline controls preload="metadata" controlsList="nodownload">
49
- <source src="{{ src }}" type="{{ tag }}/mp4">
50
- Your browser does not support the {{ tag }} tag.
51
- </{{ tag }}>
52
- </div>
53
 
54
- <section class="controls-section" aria-label="File options">
55
  <div class="dropdown-container" id="stream-dropdown-container">
56
- <button type="button" class="action-button secondary-button stream-btn" onclick="app.toggleStreamMenu(event)" aria-haspopup="true" aria-expanded="false" aria-controls="stream-menu" id="stream-btn-label">
57
- <i class="fas fa-play-circle" aria-hidden="true"></i><span>Stream</span>
 
58
  </button>
59
- <div class="dropdown-menu" id="stream-menu" aria-labelledby="stream-btn-label">
60
  <div class="stream-menu-header">
61
  <h3>Open with external player</h3>
62
- <button type="button" aria-label="Close menu" onclick="app.closeStreamMenu()" class="stream-menu-close">
63
  <i class="fas fa-times" aria-hidden="true"></i>
64
  </button>
65
  </div>
66
- <div class="stream-menu-content" role="menu">
67
  <div class="stream-category">
68
- <div class="category-heading" role="presentation">
69
- <i class="fas fa-desktop" aria-hidden="true"></i>
70
- <span>Desktop Players</span>
71
- </div>
72
- <div class="player-grid">
73
- <button type="button" class="player-card dropdown-item" tabindex="0" role="menuitem" onclick="app.playOnline('vlc-pc')" title="Open in VLC Player (PC)">
74
- <div class="player-icon">
75
- <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/vlc.png" alt="VLC Player icon" loading="lazy" width="32" height="32">
76
- </div>
77
- <span class="player-name">VLC Player</span>
78
  </button>
79
- <button type="button" class="player-card dropdown-item" tabindex="0" role="menuitem" onclick="app.playOnline('potplayer')" title="Open in PotPlayer">
80
- <div class="player-icon">
81
- <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/FileToLink/pot.png" alt="PotPlayer icon" loading="lazy" width="32" height="32">
82
- </div>
83
- <span class="player-name">PotPlayer</span>
84
  </button>
85
- <button type="button" class="player-card dropdown-item" tabindex="0" role="menuitem" onclick="app.playOnline('mpc')" title="Open in MPC-HC">
86
- <div class="player-icon">
87
- <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/FileToLink/classic.png" alt="MPC-HC icon" loading="lazy" width="32" height="32">
88
- </div>
89
- <span class="player-name">MPC-HC</span>
 
 
 
 
 
 
90
  </button>
91
- <button type="button" class="player-card dropdown-item" tabindex="0" role="menuitem" onclick="app.playOnline('kmpc')" title="Open in KM Player (PC)">
92
- <div class="player-icon">
93
- <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/km.webp" alt="KM Player icon" loading="lazy" width="32" height="32">
94
- </div>
95
  <span class="player-name">KM Player</span>
96
  </button>
97
  </div>
98
  </div>
99
  <div class="stream-category">
100
- <div class="category-heading" role="presentation">
101
- <i class="fas fa-mobile-alt" aria-hidden="true"></i>
102
- <span>Mobile Players</span>
103
- </div>
104
- <div class="player-grid">
105
- <button type="button" class="player-card dropdown-item" tabindex="0" role="menuitem" onclick="app.playOnline('vlc')">
106
- <div class="player-icon">
107
- <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/vlc.png" alt="VLC Mobile icon" loading="lazy" width="32" height="32">
108
- </div>
109
- <span class="player-name">VLC Mobile</span>
110
- </button>
111
- <button type="button" class="player-card dropdown-item" tabindex="0" role="menuitem" onclick="app.playOnline('mxpro')">
112
- <div class="player-icon">
113
- <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/mx.png" alt="MX Pro icon" loading="lazy" width="32" height="32">
114
- </div>
115
- <span class="player-name">MX Pro</span>
116
- </button>
117
- <button type="button" class="player-card dropdown-item" tabindex="0" role="menuitem" onclick="app.playOnline('mx')">
118
- <div class="player-icon">
119
- <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/mx.png" alt="MX Player icon" loading="lazy" width="32" height="32">
120
- </div>
121
- <span class="player-name">MX Player</span>
122
  </button>
123
- <button type="button" class="player-card dropdown-item" tabindex="0" role="menuitem" onclick="app.playOnline('nplayer')">
124
- <div class="player-icon">
125
- <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/nPlayer.webp" alt="nPlayer icon" loading="lazy" width="32" height="32">
126
- </div>
127
- <span class="player-name">nPlayer</span>
128
  </button>
129
- <button type="button" class="player-card dropdown-item" tabindex="0" role="menuitem" onclick="app.playOnline('splayer')">
130
- <div class="player-icon">
131
- <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/splayer.webp" alt="S Player icon" loading="lazy" width="32" height="32">
132
- </div>
133
- <span class="player-name">S Player</span>
134
  </button>
135
- <button type="button" class="player-card dropdown-item" tabindex="0" role="menuitem" onclick="app.playOnline('km')">
136
- <div class="player-icon">
137
- <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/km.webp" alt="KM Player Mobile icon" loading="lazy" width="32" height="32">
138
- </div>
139
  <span class="player-name">KM Player</span>
140
  </button>
141
  </div>
@@ -143,22 +123,31 @@
143
  </div>
144
  </div>
145
  </div>
146
- <button type="button" class="action-button secondary-button" onclick="app.download()" title="Download the video"><i class="fas fa-cloud-download-alt" aria-hidden="true"></i><span>Download</span></button>
147
- <button type="button" class="action-button secondary-button" onclick="app.copyLink()" title="Copy video link"><i class="fas fa-link" aria-hidden="true"></i><span>Copy Link</span></button>
148
- </section>
 
 
 
 
 
 
149
  </main>
150
  </div>
151
 
152
  <footer>
153
- <p>Made by AI © 2025 | <a href="https://github.com/fyaz05/FileToLink" target="_blank" rel="noopener noreferrer"><i class="fab fa-github" aria-hidden="true"></i> GitHub</a></p>
154
  </footer>
155
 
156
- <div class="toast" id="toast" role="alert" aria-live="polite" aria-hidden="true">
157
- <div class="toast-icon" aria-hidden="true"><i class="fas fa-info-circle"></i></div>
 
 
158
  <div class="toast-message">Action completed.</div>
159
  </div>
160
 
161
- <script src="https://cdn.plyr.io/3.7.8/plyr.polyfilled.js"></script>
162
- <script src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/FileToLink/reqscript.js"></script>
163
  </body>
164
  </html>
 
 
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
  <title>{{ heading }}</title>
7
 
8
+ <!-- Social Meta Tags -->
9
  <meta name="description" content="Easily share and distribute files">
10
  <meta property="og:title" content="Thunder FileToLink">
11
  <meta property="og:description" content="File Streamer">
12
  <meta property="og:type" content="website">
13
  <meta property="og:image" content="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/FileToLink/live.png">
14
+
15
+ <!-- Configuration -->
16
  <meta name="theme-color" content="#1a1a2e">
17
  <meta name="msapplication-navbutton-color" content="#1a1a2e">
18
  <meta name="apple-mobile-web-app-status-bar-style" content="#1a1a2e">
19
  <meta name="color-scheme" content="dark light">
20
  <meta name="application-name" content="Thunder FileToLink">
21
 
22
+ <!-- Icons -->
23
  <link rel="icon" href="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/FileToLink/Thunder.jpg">
24
  <link rel="apple-touch-icon" href="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/FileToLink/Thunder.jpg">
25
 
26
+ <!-- External Resources -->
27
  <link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
28
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.7.2/css/all.min.css">
29
  <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css">
30
 
31
+ <!-- Main Stylesheet -->
32
+ <link rel="stylesheet" href="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/FileToLink/style.css">
33
  </head>
34
  <body>
35
+ <button class="theme-toggle button-base" aria-label="Toggle Light/Dark Mode" aria-pressed="false" title="Toggle Light/Dark Mode">
36
+ <i class="fas fa-moon" aria-hidden="true"></i>
37
+ </button>
38
+
39
+ <div class="background-animation" aria-hidden="true">
40
+ <div class="bg-gradient"></div>
41
  </div>
 
 
42
 
43
  <div class="container">
44
  <header class="header">
45
  <div class="file-info">
46
  <h1 id="file-name">{{ file_name }}</h1>
47
+ <div class="file-meta" role="group" aria-label="File metadata"></div>
48
  </div>
49
  </header>
50
 
51
+ <main id="main-content">
52
+ <section class="player-container" aria-label="Video Player">
53
+ <video id="player" playsinline preload="metadata" controlsList="nodownload" aria-label="Video player">
54
+ <source src="{{ src }}" type="video/mp4" />
55
+ <p>Your browser does not support the video tag. Please <a href="{{ src }}" download>download the video</a> instead.</p>
56
+ </video>
57
+ </section>
58
 
59
+ <nav class="controls-section" aria-label="File options">
60
  <div class="dropdown-container" id="stream-dropdown-container">
61
+ <button type="button" class="action-button button-base secondary-button stream-btn" aria-haspopup="true" aria-expanded="false" aria-controls="stream-menu" id="stream-btn-label">
62
+ <i class="fas fa-external-link-alt" aria-hidden="true"></i>
63
+ <span>Stream</span>
64
  </button>
65
+ <div class="dropdown-menu" id="stream-menu" aria-labelledby="stream-btn-label" role="menu">
66
  <div class="stream-menu-header">
67
  <h3>Open with external player</h3>
68
+ <button type="button" aria-label="Close menu" class="stream-menu-close button-base">
69
  <i class="fas fa-times" aria-hidden="true"></i>
70
  </button>
71
  </div>
72
+ <div class="stream-menu-content">
73
  <div class="stream-category">
74
+ <h4 class="category-heading"><i class="fas fa-mobile-screen-button" aria-hidden="true"></i> Mobile Players</h4>
75
+ <div class="player-grid" role="group" aria-label="Mobile video players">
76
+ <button type="button" class="player-card dropdown-item" role="menuitem" data-player-type="vlc">
77
+ <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/vlc.png" alt="VLC Mobile" loading="lazy" width="32" height="32" class="player-icon">
78
+ <span class="player-name">VLC Mobile</span>
 
 
 
 
 
79
  </button>
80
+ <button type="button" class="player-card dropdown-item" role="menuitem" data-player-type="mxpro">
81
+ <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/mx.png" alt="MX Player Pro" loading="lazy" width="32" height="32" class="player-icon">
82
+ <span class="player-name">MX Pro</span>
 
 
83
  </button>
84
+ <button type="button" class="player-card dropdown-item" role="menuitem" data-player-type="mx">
85
+ <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/mx.png" alt="MX Player" loading="lazy" width="32" height="32" class="player-icon">
86
+ <span class="player-name">MX Player</span>
87
+ </button>
88
+ <button type="button" class="player-card dropdown-item" role="menuitem" data-player-type="nplayer">
89
+ <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/nPlayer.webp" alt="nPlayer" loading="lazy" width="32" height="32" class="player-icon">
90
+ <span class="player-name">nPlayer</span>
91
+ </button>
92
+ <button type="button" class="player-card dropdown-item" role="menuitem" data-player-type="splayer">
93
+ <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/splayer.webp" alt="S Player" loading="lazy" width="32" height="32" class="player-icon">
94
+ <span class="player-name">S Player</span>
95
  </button>
96
+ <button type="button" class="player-card dropdown-item" role="menuitem" data-player-type="km">
97
+ <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/km.webp" alt="KM Player" loading="lazy" width="32" height="32" class="player-icon">
 
 
98
  <span class="player-name">KM Player</span>
99
  </button>
100
  </div>
101
  </div>
102
  <div class="stream-category">
103
+ <h4 class="category-heading"><i class="fas fa-desktop" aria-hidden="true"></i> Desktop Players</h4>
104
+ <div class="player-grid" role="group" aria-label="Desktop video players">
105
+ <button type="button" class="player-card dropdown-item" role="menuitem" data-player-type="vlc-pc" title="Open in VLC Player (PC)">
106
+ <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/vlc.png" alt="VLC Player" loading="lazy" width="32" height="32" class="player-icon">
107
+ <span class="player-name">VLC Player</span>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
108
  </button>
109
+ <button type="button" class="player-card dropdown-item" role="menuitem" data-player-type="potplayer" title="Open in PotPlayer">
110
+ <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/FileToLink/pot.png" alt="PotPlayer" loading="lazy" width="32" height="32" class="player-icon">
111
+ <span class="player-name">PotPlayer</span>
 
 
112
  </button>
113
+ <button type="button" class="player-card dropdown-item" role="menuitem" data-player-type="mpc" title="Open in MPC-HC">
114
+ <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/FileToLink/classic.png" alt="MPC-HC" loading="lazy" width="32" height="32" class="player-icon">
115
+ <span class="player-name">MPC-HC</span>
 
 
116
  </button>
117
+ <button type="button" class="player-card dropdown-item" role="menuitem" data-player-type="kmpc" title="Open in KM Player (PC)">
118
+ <img src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/HydroStreamerBot/km.webp" alt="KM Player" loading="lazy" width="32" height="32" class="player-icon">
 
 
119
  <span class="player-name">KM Player</span>
120
  </button>
121
  </div>
 
123
  </div>
124
  </div>
125
  </div>
126
+ <button type="button" class="action-button button-base secondary-button" title="Download the video" aria-label="Download video file">
127
+ <i class="fas fa-cloud-download-alt" aria-hidden="true"></i>
128
+ <span>Download</span>
129
+ </button>
130
+ <button type="button" class="action-button button-base secondary-button" title="Copy video link" aria-label="Copy video link to clipboard">
131
+ <i class="fas fa-copy" aria-hidden="true"></i>
132
+ <span>Copy Link</span>
133
+ </button>
134
+ </nav>
135
  </main>
136
  </div>
137
 
138
  <footer>
139
+ <p><span id="thunder-text">⚡ Thunder</span> © <span id="current-year"></span> | <a href="https://github.com/fyaz05/FileToLink" target="_blank" rel="noopener noreferrer" aria-label="View source code on GitHub"><i class="fab fa-github" aria-hidden="true"></i> <span>GitHub</span></a></p>
140
  </footer>
141
 
142
+ <div class="toast" id="toast" role="alert" aria-live="polite" aria-atomic="true" aria-hidden="true">
143
+ <div class="toast-icon" aria-hidden="true">
144
+ <i class="fas fa-info-circle"></i>
145
+ </div>
146
  <div class="toast-message">Action completed.</div>
147
  </div>
148
 
149
+ <script src="https://cdn.plyr.io/3.7.8/plyr.polyfilled.js" defer></script>
150
+ <script src="https://cdn.jsdelivr.net/gh/fyaz05/Resources@main/FileToLink/script.js" defer></script>
151
  </body>
152
  </html>
153
+
Thunder/utils/broadcast.py CHANGED
@@ -7,7 +7,7 @@ import time
7
  from pyrogram.client import Client
8
  from pyrogram.enums import ParseMode
9
  from pyrogram.errors import (ChatWriteForbidden, PeerIdInvalid, UserDeactivated,
10
- UserIsBlocked)
11
  from pyrogram.types import (InlineKeyboardButton, InlineKeyboardMarkup,
12
  Message)
13
 
@@ -49,8 +49,31 @@ async def broadcast_message(client: Client, message: Message):
49
  stats["success"] += 1
50
  else:
51
  stats["failed"] += 1
52
- except (UserDeactivated, UserIsBlocked, PeerIdInvalid, ChatWriteForbidden) as e:
53
- logger.warning(f"User {user['id']} removed due to: {type(e).__name__}", exc_info=True)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
54
  await db.delete_user(user['id'])
55
  stats["deleted"] += 1
56
  except Exception as e:
 
7
  from pyrogram.client import Client
8
  from pyrogram.enums import ParseMode
9
  from pyrogram.errors import (ChatWriteForbidden, PeerIdInvalid, UserDeactivated,
10
+ UserIsBlocked, ChannelInvalid, InputUserDeactivated)
11
  from pyrogram.types import (InlineKeyboardButton, InlineKeyboardMarkup,
12
  Message)
13
 
 
49
  stats["success"] += 1
50
  else:
51
  stats["failed"] += 1
52
+ except (UserDeactivated, UserIsBlocked, PeerIdInvalid, ChatWriteForbidden, ChannelInvalid, InputUserDeactivated) as e:
53
+ if isinstance(e, ChannelInvalid):
54
+ recipient_type = "Channel"
55
+ reason = "invalid channel"
56
+ elif isinstance(e, InputUserDeactivated):
57
+ recipient_type = "User"
58
+ reason = "deactivated account"
59
+ elif isinstance(e, UserIsBlocked):
60
+ recipient_type = "User"
61
+ reason = "blocked the bot"
62
+ elif isinstance(e, UserDeactivated):
63
+ recipient_type = "User"
64
+ reason = "deactivated account"
65
+ elif isinstance(e, PeerIdInvalid):
66
+ recipient_type = "Recipient"
67
+ reason = "invalid ID"
68
+ elif isinstance(e, ChatWriteForbidden):
69
+ recipient_type = "Chat"
70
+ reason = "write forbidden"
71
+ else:
72
+ recipient_type = "Recipient"
73
+ reason = f"error: {type(e).__name__}"
74
+
75
+ logger.warning(f"{recipient_type} {user['id']} removed due to {reason}", exc_info=True)
76
+
77
  await db.delete_user(user['id'])
78
  stats["deleted"] += 1
79
  except Exception as e:
Thunder/utils/custom_dl.py CHANGED
@@ -37,26 +37,17 @@ class ByteStreamer:
37
  async def stream_file(self, message_id: int, offset: int = 0, limit: int = 0) -> AsyncGenerator[bytes, None]:
38
  message = await self.get_message(message_id)
39
 
40
- if limit > 0:
41
- chunk_offset = offset // (1024 * 1024)
42
- chunk_limit = (limit + 1024 * 1024 - 1) // (1024 * 1024)
43
- while True:
44
- try:
45
- async for chunk in self.client.stream_media(message, offset=chunk_offset, limit=chunk_limit):
46
- yield chunk
47
- break
48
- except FloodWait as e:
49
- logger.debug(f"FloodWait: stream_file, sleep {e.value}s")
50
- await asyncio.sleep(e.value)
51
- else:
52
- while True:
53
- try:
54
- async for chunk in self.client.stream_media(message):
55
- yield chunk
56
- break
57
- except FloodWait as e:
58
- logger.debug(f"FloodWait: stream_file, sleep {e.value}s")
59
- await asyncio.sleep(e.value)
60
 
61
  def get_file_info_sync(self, message: Message) -> Dict[str, Any]:
62
  media = message.document or message.video or message.audio or message.photo
 
37
  async def stream_file(self, message_id: int, offset: int = 0, limit: int = 0) -> AsyncGenerator[bytes, None]:
38
  message = await self.get_message(message_id)
39
 
40
+ chunk_offset = offset // (1024 * 1024)
41
+ chunk_limit = (limit + (1024 * 1024) - 1) // (1024 * 1024) if limit > 0 else 0
42
+
43
+ while True:
44
+ try:
45
+ async for chunk in self.client.stream_media(message, offset=chunk_offset, limit=chunk_limit):
46
+ yield chunk
47
+ break
48
+ except FloodWait as e:
49
+ logger.debug(f"FloodWait: stream_file, sleep {e.value}s")
50
+ await asyncio.sleep(e.value)
 
 
 
 
 
 
 
 
 
51
 
52
  def get_file_info_sync(self, message: Message) -> Dict[str, Any]:
53
  media = message.document or message.video or message.audio or message.photo
Thunder/utils/force_channel.py CHANGED
@@ -36,6 +36,10 @@ async def get_force_info(bot: Client):
36
  async def force_channel_check(client: Client, message: Message):
37
  if not Var.FORCE_CHANNEL_ID:
38
  return True
 
 
 
 
39
  try:
40
  while True:
41
  try:
 
36
  async def force_channel_check(client: Client, message: Message):
37
  if not Var.FORCE_CHANNEL_ID:
38
  return True
39
+
40
+ if message.from_user is None:
41
+ return True
42
+
43
  try:
44
  while True:
45
  try:
Thunder/utils/render_template.py CHANGED
@@ -30,7 +30,7 @@ async def render_page(id: int, secure_hash: str, requested_action: str | None =
30
  file_name = get_fname(message)
31
 
32
  if not file_unique_id or file_unique_id[:6] != secure_hash:
33
- raise InvalidHash
34
 
35
  quoted_filename = urllib.parse.quote(file_name.replace('/', '_'))
36
  src = urllib.parse.urljoin(Var.URL, f'{secure_hash}{id}/{quoted_filename}')
@@ -38,7 +38,6 @@ async def render_page(id: int, secure_hash: str, requested_action: str | None =
38
  if requested_action == 'stream':
39
  template = template_env.get_template('req.html')
40
  context = {
41
- 'tag': "video",
42
  'heading': f"View {safe_filename}",
43
  'file_name': safe_filename,
44
  'src': src
 
30
  file_name = get_fname(message)
31
 
32
  if not file_unique_id or file_unique_id[:6] != secure_hash:
33
+ raise InvalidHash("File unique ID or secure hash mismatch during rendering.")
34
 
35
  quoted_filename = urllib.parse.quote(file_name.replace('/', '_'))
36
  src = urllib.parse.urljoin(Var.URL, f'{secure_hash}{id}/{quoted_filename}')
 
38
  if requested_action == 'stream':
39
  template = template_env.get_template('req.html')
40
  context = {
 
41
  'heading': f"View {safe_filename}",
42
  'file_name': safe_filename,
43
  'src': src
config_sample.env CHANGED
@@ -79,7 +79,7 @@ URL_SHORTENER_SITE="" # Example: "example.com"
79
  NAME="ThunderF2L" # Bot application name
80
 
81
  # Performance settings
82
- SLEEP_THRESHOLD=300 # Sleep time in seconds
83
  WORKERS=8 # Number of worker processes
84
 
85
  # Web server configuration
 
79
  NAME="ThunderF2L" # Bot application name
80
 
81
  # Performance settings
82
+ SLEEP_THRESHOLD=600 # Sleep time in seconds
83
  WORKERS=8 # Number of worker processes
84
 
85
  # Web server configuration