├── .gitignore ├── .python-version ├── Dockerfile ├── README.md ├── bot.py ├── pyproject.toml ├── setup.sh ├── test ├── cloudflare.py ├── cookies.py ├── launch_with_clear.py ├── rss.py └── test_re.py └── uv.lock /.gitignore: -------------------------------------------------------------------------------- 1 | user_configs -------------------------------------------------------------------------------- /.python-version: -------------------------------------------------------------------------------- 1 | 3.11 2 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.11-slim-bullseye AS builder 2 | WORKDIR /app 3 | COPY ./pyproject.toml /app 4 | RUN pip install uv && uv sync 5 | 6 | FROM python:3.11-slim-bullseye 7 | EXPOSE 8080 8 | COPY --from=builder /app/.venv/lib/python3.11/site-packages /usr/local/lib/python3.11/site-packages 9 | COPY --from=builder /app/.venv/bin/activate /usr/local/bin/activate 10 | WORKDIR /app 11 | COPY ./setup.sh /app 12 | RUN apt-get update && apt-get install -y --no-install-recommends git \ 13 | && rm -rf /var/lib/apt/lists/* /tmp/* \ 14 | && chmod +x /app/setup.sh 15 | ENTRYPOINT ["/app/setup.sh"] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # doLinuxRadar 2 | 3 | 4 | [![Docker Pulls](https://img.shields.io/docker/pulls/yym68686/dolinuxradar)](https://hub.docker.com/r/yym68686/dolinuxradar) 5 | [![Docker Image Size (latest by date)](https://img.shields.io/docker/image-size/yym68686/dolinuxradar)](https://hub.docker.com/r/yym68686/dolinuxradar) 6 | [![Docker Image Version (latest by date)](https://img.shields.io/docker/v/yym68686/dolinuxradar)](https://hub.docker.com/r/yym68686/dolinuxradar) 7 | 8 |

9 | 10 | 11 | 12 |

13 | 14 | doLinuxRadar 专门嗅探 linux.do 你感兴趣的话题。机器人使用地址:[@doLinuxRadar](https://t.me/doLinuxRadar) 15 | 16 | ## 使用指南 17 | 18 | 命令列表: 19 | 20 | - `/tags`: 设置监控关键词(空格隔开), 例如: `/tags 免费 linux` 21 | - 关键词支持正则匹配,例如我想匹配openai,但是不想匹配openair,可以使用`/tags (? 标签内的文本,并将其解析为 JSON。 167 | 168 | Args: 169 | target_url (str): 需要 FlareSolverr 抓取的目标 URL。 170 | flare_solverr_url (str, optional): FlareSolverr v1 API 的 URL。 171 | 默认为 "http://localhost:8191/v1"。 172 | 173 | Returns: 174 | dict or None: 解析后的 JSON 对象,如果未找到
 标签或发生错误则返回 None。
175 |     """
176 |     headers = {"Content-Type": "application/json"}
177 |     data = {
178 |         "cmd": "request.get",
179 |         "url": target_url,
180 |         "maxTimeout": 60000
181 |     }
182 |     try:
183 |         response = requests.post(flare_solverr_url, headers=headers, json=data, timeout=70) # 增加超时
184 |         response.raise_for_status() # 检查 HTTP 请求错误
185 |         response_data = response.json()
186 |         html_content = response_data.get("solution", {}).get("response")
187 | 
188 |         if not html_content:
189 |             print("错误:未能从 FlareSolverr 响应中获取 HTML 内容")
190 |             return None
191 | 
192 |         # 使用正则表达式查找 
 标签内的内容
193 |         match = re.search(r"]*>(.*?)
", html_content, re.DOTALL) 194 | 195 | # 检查是否找到匹配项 196 | if match: 197 | extracted_text = match.group(1).strip() # 获取并清理提取的文本 198 | try: 199 | # 尝试解析 JSON 200 | result = json.loads(extracted_text) 201 | return result # 返回解析后的 JSON 对象 202 | except json.JSONDecodeError as e: 203 | print(f"错误:解析 JSON 失败 - {e}") 204 | print(f"提取到的文本内容:\n{extracted_text}") 205 | return None 206 | else: 207 | print("未在响应中找到
 标签内的内容")
208 |             # print(f"原始 HTML 内容:\n{html_content}") # 取消注释以调试
209 |             return None
210 |     except requests.exceptions.RequestException as e:
211 |         print(f"错误:请求 FlareSolverr 时发生错误 - {e}")
212 |         return None
213 |     except Exception as e:
214 |         print(f"发生未知错误:{e}")
215 |         return None
216 | 
217 | from telegram.error import Forbidden, TelegramError
218 | async def is_bot_blocked(bot, user_id: int) -> bool:
219 |     try:
220 |         # 尝试向用户发送一条测试消息
221 |         await bot.send_chat_action(chat_id=user_id, action="typing")
222 |         return False  # 如果成功发送,说明机器人未被封禁
223 |     except Forbidden:
224 |         print("error:", user_id, "已封禁机器人")
225 |         return True  # 如果收到Forbidden错误,说明机器人被封禁
226 |     except TelegramError:
227 |         # 处理其他可能的错误
228 |         return False  # 如果是其他错误,我们假设机器人未被封禁
229 | 
230 | # 这是将被定时执行的函数
231 | async def scheduled_function(context: ContextTypes.DEFAULT_TYPE) -> None:
232 |     """这个函数将每10秒执行一次"""
233 |     url = "https://linux.do/latest.json"
234 |     result = None
235 |     flare_solverr_url = os.environ.get("FLARESOLLVERR_URL", "http://localhost:8191/v1")
236 |     try:
237 |         result = get_and_parse_json(url, flare_solverr_url)["topic_list"]["topics"]
238 |     except Exception as e:
239 |         logging.error("获取数据失败:%s", repr(e))
240 |         return
241 |     # print(json.dumps(result, indent=2, ensure_ascii=False))
242 |     titles = [i["title"].lower() for i in result]
243 |     for chat_id in user_config.config.data.keys():
244 |         chat_id = int(chat_id)
245 |         tags = user_config.get_value(str(chat_id), "tags", default=[])
246 |         if tags == []:
247 |             continue
248 |         re_rule = "|".join(tags)
249 |         pages = user_config.get_value(str(chat_id), "pages", default=[])
250 |         timer = user_config.get_value(str(chat_id), "timer", default=True)
251 | 
252 |         # 编译正则表达式,捕获可能的错误,并忽略大小写
253 |         try:
254 |             compiled_re = re.compile(re_rule, re.IGNORECASE)
255 |         except re.error as e:
256 |             logging.error(f"用户 {chat_id} 的正则表达式模式 '{re_rule}' 无效: {e}")
257 |             continue # 跳过此用户的处理
258 | 
259 |         if timer == False:
260 |             continue
261 |         for index, title in enumerate(titles):
262 |             findall_result = list(set(re.findall(re_rule, title)))
263 |             page_id = result[index]['id']
264 |             url = f"https://linux.do/t/topic/{page_id}"
265 |             if findall_result and page_id not in pages and not await is_bot_blocked(context.bot, chat_id):
266 |                 print(get_time(), tags, chat_id, page_id, title)
267 |                 tag_mess = " ".join([f"#{tag}" for tag in findall_result])
268 |                 message = (
269 |                     f"{tag_mess}\n"
270 |                     f"{title}\n"
271 |                     f"{url}"
272 |                 )
273 |                 await context.bot.send_message(chat_id=chat_id, text=message)
274 |                 user_config.set_value(str(chat_id), "pages", [page_id], append=True)
275 | 
276 | tips_message = (
277 |     "欢迎使用 Linux.do 风向标 bot!\n\n"
278 |     "使用 /tags 免费 公益 来设置含有指定关键词的话题。\n\n"
279 |     "关键词支持正则匹配,例如我想匹配openai,但是不想匹配openair,可以使用/tags (? None:
286 |     """发送使用说明"""
287 |     await update.message.reply_text(tips_message)
288 | 
289 | @AdminAuthorization
290 | async def set_timer(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
291 |     """Add a job to the queue."""
292 |     chat_id = update.effective_message.chat_id
293 |     try:
294 |         # args[0] should contain the time for the timer in seconds
295 |         due = float(context.args[0])
296 |         if due < 0:
297 |             await update.effective_message.reply_text("Sorry we can not go back to future!")
298 |             return
299 | 
300 |         job_removed = remove_job_if_exists(str(chat_id), context)
301 |         context.job_queue.run_repeating(
302 |             scheduled_function,
303 |             interval=due,
304 |             first=1,
305 |             chat_id=chat_id,
306 |             name=str(chat_id)
307 |         )
308 | 
309 |         text = "Timer successfully set!"
310 |         if job_removed:
311 |             text += " Old one was removed."
312 |         await update.effective_message.reply_text(text)
313 | 
314 |     except (IndexError, ValueError):
315 |         await update.effective_message.reply_text("Usage: /set ")
316 | 
317 | async def tags(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
318 |     """设置标签"""
319 |     chat_id = update.effective_message.chat_id
320 |     tags = context.args
321 |     tags = list(set([tag.lower() for tag in tags]))
322 |     user_config.set_value(str(chat_id), "tags", tags, append=False)
323 |     print("UserConfig", user_config.to_json(str(chat_id)))
324 |     if tags == []:
325 |         await update.effective_message.reply_text("📖 关键词已清空!")
326 |     else:
327 |         await update.effective_message.reply_text("📖 监控关键词设置成功!")
328 | 
329 | def remove_job_if_exists(name: str, context: ContextTypes.DEFAULT_TYPE) -> bool:
330 |     """如果存在,则移除指定名称的任务"""
331 |     current_jobs = context.job_queue.get_jobs_by_name(name)
332 |     if not current_jobs:
333 |         return False
334 |     for job in current_jobs:
335 |         job.schedule_removal()
336 |     return True
337 | 
338 | async def unset(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
339 |     """取消定时任务"""
340 |     chat_id = update.message.chat_id
341 |     # job_removed = remove_job_if_exists(str(chat_id), context)
342 |     # text = "成功取消定时任务!" if job_removed else "您没有活动的定时任务。"
343 |     timer_status = user_config.set_timer(str(chat_id))
344 |     text = "已关闭消息推送 📢!" if timer_status == False else "已开启消息推送 📢!"
345 |     await update.message.reply_text(text)
346 | 
347 | async def error(update, context):
348 |     import traceback
349 |     traceback_string = traceback.format_exception(None, context.error, context.error.__traceback__)
350 |     if "telegram.error.TimedOut: Timed out" in traceback_string:
351 |         print('telegram.error.TimedOut: Timed out')
352 |         return
353 |     if "telegram.error.NetworkError: Bad Gateway" in traceback_string:
354 |         print('telegram.error.NetworkError: Bad Gateway')
355 |         return
356 | 
357 | async def post_init(application: Application) -> None:
358 |     await application.bot.set_my_commands([
359 |         BotCommand('tags', '设置监控关键词(空格隔开)'),
360 |         BotCommand('set', '设置嗅探间隔(秒)'),
361 |         BotCommand('unset', '关闭或打开消息推送'),
362 |         BotCommand('start', 'linux.do 风向标使用简介'),
363 |     ])
364 |     await application.bot.set_my_description(tips_message)
365 | 
366 | def main() -> None:
367 |     """运行bot"""
368 |     import os
369 |     BOT_TOKEN = os.environ.get('BOT_TOKEN', None)
370 |     time_out = 600
371 |     application = (
372 |         ApplicationBuilder()
373 |         .token(BOT_TOKEN)
374 |         .concurrent_updates(True)
375 |         .connection_pool_size(65536)
376 |         .get_updates_connection_pool_size(65536)
377 |         .read_timeout(time_out)
378 |         .pool_timeout(time_out)
379 |         .get_updates_read_timeout(time_out)
380 |         .get_updates_write_timeout(time_out)
381 |         .get_updates_pool_timeout(time_out)
382 |         .get_updates_connect_timeout(time_out)
383 |         .rate_limiter(AIORateLimiter(max_retries=5))
384 |         .post_init(post_init)
385 |         .build()
386 |     )
387 | 
388 |     application.add_handler(CommandHandler("start", start))
389 |     application.add_handler(CommandHandler("set", set_timer))
390 |     application.add_handler(CommandHandler("unset", unset))
391 |     application.add_handler(CommandHandler("tags", tags))
392 |     application.add_error_handler(error)
393 | 
394 |     application.run_polling(allowed_updates=Update.ALL_TYPES)
395 | 
396 | if __name__ == "__main__":
397 |     main()


--------------------------------------------------------------------------------
/pyproject.toml:
--------------------------------------------------------------------------------
 1 | [project]
 2 | name = "dolinuxradar"
 3 | version = "0.1.0"
 4 | description = "Add your description here"
 5 | readme = "README.md"
 6 | requires-python = ">=3.11"
 7 | dependencies = [
 8 |     "feedparser>=6.0.11",
 9 |     "python-dotenv>=1.1.0",
10 |     "python-telegram-bot[job-queue,rate-limiter,webhooks]>=22.0",
11 |     "requests>=2.32.3",
12 | ]
13 | 


--------------------------------------------------------------------------------
/setup.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | set -eu
3 | rm -rf /app/doLinuxRadar
4 | git clone --recurse-submodules --depth 1 -b main --quiet https://github.com/yym68686/doLinuxRadar.git
5 | source /usr/local/bin/activate
6 | python -u /app/doLinuxRadar/bot.py


--------------------------------------------------------------------------------
/test/cloudflare.py:
--------------------------------------------------------------------------------
 1 | import re # 导入 re 模块
 2 | import json
 3 | import requests
 4 | 
 5 | # 定义获取并解析 pre 标签内容的函数
 6 | def fetch_and_parse_pre_content(target_url, flare_solverr_url="http://localhost:8191/v1"):
 7 |     """
 8 |     发送请求到 FlareSolverr,获取指定 URL 的内容,
 9 |     提取第一个 
 标签内的文本,并将其解析为 JSON。
10 | 
11 |     Args:
12 |         target_url (str): 需要 FlareSolverr 抓取的目标 URL。
13 |         flare_solverr_url (str, optional): FlareSolverr v1 API 的 URL。
14 |                                           默认为 "http://localhost:8191/v1"。
15 | 
16 |     Returns:
17 |         dict or None: 解析后的 JSON 对象,如果未找到 
 标签或发生错误则返回 None。
18 |     """
19 |     headers = {"Content-Type": "application/json"}
20 |     data = {
21 |         "cmd": "request.get",
22 |         "url": target_url,
23 |         "maxTimeout": 60000
24 |     }
25 |     try:
26 |         response = requests.post(flare_solverr_url, headers=headers, json=data, timeout=70) # 增加超时
27 |         response.raise_for_status() # 检查 HTTP 请求错误
28 |         response_data = response.json()
29 |         html_content = response_data.get("solution", {}).get("response")
30 | 
31 |         if not html_content:
32 |             print("错误:未能从 FlareSolverr 响应中获取 HTML 内容")
33 |             return None
34 | 
35 |         # 使用正则表达式查找 
 标签内的内容
36 |         match = re.search(r"]*>(.*?)
", html_content, re.DOTALL) 37 | 38 | # 检查是否找到匹配项 39 | if match: 40 | extracted_text = match.group(1).strip() # 获取并清理提取的文本 41 | try: 42 | # 尝试解析 JSON 43 | result = json.loads(extracted_text) 44 | return result # 返回解析后的 JSON 对象 45 | except json.JSONDecodeError as e: 46 | print(f"错误:解析 JSON 失败 - {e}") 47 | print(f"提取到的文本内容:\n{extracted_text}") 48 | return None 49 | else: 50 | print("未在响应中找到
 标签内的内容")
51 |             # print(f"原始 HTML 内容:\n{html_content}") # 取消注释以调试
52 |             return None
53 |     except requests.exceptions.RequestException as e:
54 |         print(f"错误:请求 FlareSolverr 时发生错误 - {e}")
55 |         return None
56 |     except Exception as e:
57 |         print(f"发生未知错误:{e}")
58 |         return None
59 | 
60 | # --- 主程序部分 ---
61 | if __name__ == "__main__":
62 |     # 调用函数并传入目标 URL
63 |     target_linux_do_url = "https://linux.do/latest.json"
64 |     parsed_data = fetch_and_parse_pre_content(target_linux_do_url)
65 | 
66 |     # 打印结果
67 |     if parsed_data:
68 |         # 使用 json.dumps 美化输出
69 |         print(json.dumps(parsed_data, indent=4, ensure_ascii=False))
70 |     else:
71 |         print("未能成功获取或解析数据。")


--------------------------------------------------------------------------------
/test/cookies.py:
--------------------------------------------------------------------------------
 1 | import os
 2 | import json
 3 | 
 4 | def get_and_parse_json(url, cf_clearance = None):
 5 |     import httpx
 6 |     headers = {
 7 |         'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36',
 8 |     }
 9 |     cookie_dict = {}
10 |     if cf_clearance:
11 |         cookie_dict["cf_clearance"] = cf_clearance
12 |     try:
13 |         with httpx.Client() as client:
14 |             # 直接将字典传递给 cookies 参数
15 |             response = client.get(url, headers=headers, cookies=cookie_dict)
16 |         response.raise_for_status()
17 |         data = response.json()
18 |         return data
19 | 
20 |     except httpx.HTTPStatusError as e:
21 |         print(f"HTTP 错误: {e}")
22 |     except httpx.RequestError as e:
23 |         print(f"网络请求错误: {e}")
24 |     except json.JSONDecodeError:
25 |         print("JSON 解析错误")
26 |     except Exception as e:
27 |         print(f"发生未知错误: {e}")
28 | 
29 |     return None
30 | 
31 | url = "https://linux.do/latest.json"
32 | 
33 | cf_clearance = os.getenv("CF_CLEARANCE")
34 | print(get_and_parse_json(url, cf_clearance))


--------------------------------------------------------------------------------
/test/launch_with_clear.py:
--------------------------------------------------------------------------------
1 | import os
2 | import subprocess
3 | 
4 | # 清理终端
5 | subprocess.call('clear' if os.name == 'posix' else 'cls', shell=True)
6 | 
7 | # 运行主程序
8 | os.system(os.environ['PYTHONPATH'] + ' bot.py')


--------------------------------------------------------------------------------
/test/rss.py:
--------------------------------------------------------------------------------
 1 | import feedparser # 导入 feedparser 库
 2 | 
 3 | def get_and_parse_rss(url): # 函数名修改为 get_and_parse_rss
 4 |     import httpx
 5 |     try:
 6 |         with httpx.Client() as client:
 7 |             # 直接将字典传递给 cookies 参数
 8 |             response = client.get(url)
 9 |         response.raise_for_status()
10 |         # 使用 feedparser 解析返回的文本
11 |         feed_data = feedparser.parse(response.text)
12 |         return feed_data # 返回解析后的 feed 对象
13 | 
14 |     except httpx.HTTPStatusError as e:
15 |         print(f"HTTP 错误: {e}")
16 |     except httpx.RequestError as e:
17 |         print(f"网络请求错误: {e}")
18 |     except Exception as e:
19 |         print(f"发生未知错误: {e}")
20 | 
21 |     return None
22 | 
23 | url = "https://linux.do/latest.rss"
24 | 
25 | feed = get_and_parse_rss(url) # 调用修改后的函数
26 | 
27 | # 检查是否成功获取并解析 feed
28 | if feed:
29 |     print(f"Feed 标题: {feed.feed.title}") # 打印 Feed 的标题
30 |     print("最新帖子:")
31 |     # 遍历 feed 中的条目 (entries) 并打印标题和链接
32 |     for entry in feed.entries:
33 |         print(f"- {entry.title} ({entry.link})")
34 | else:
35 |     print("无法获取或解析 RSS feed。")
36 | 
37 | # 原来的 print(get_and_parse_json(url)) 被移除


--------------------------------------------------------------------------------
/test/test_re.py:
--------------------------------------------------------------------------------
 1 | import re
 2 | 
 3 | # 测试函数
 4 | test_strings = [
 5 |     "这是一个PT下载站",
 6 |     "GPT是一种人工智能模型",
 7 |     "PT和GPT是不同的概念",
 8 |     "PTPT是重复的PT",
 9 |     "GPT不应该被匹配",
10 |     "PT 种子下载很方便"
11 | ]
12 | 
13 | for string in test_strings:
14 |     pattern = '(?=3.11"
  4 | 
  5 | [[package]]
  6 | name = "aiolimiter"
  7 | version = "1.2.1"
  8 | source = { registry = "https://pypi.org/simple" }
  9 | sdist = { url = "https://files.pythonhosted.org/packages/f1/23/b52debf471f7a1e42e362d959a3982bdcb4fe13a5d46e63d28868807a79c/aiolimiter-1.2.1.tar.gz", hash = "sha256:e02a37ea1a855d9e832252a105420ad4d15011505512a1a1d814647451b5cca9", size = 7185 }
 10 | wheels = [
 11 |     { url = "https://files.pythonhosted.org/packages/f3/ba/df6e8e1045aebc4778d19b8a3a9bc1808adb1619ba94ca354d9ba17d86c3/aiolimiter-1.2.1-py3-none-any.whl", hash = "sha256:d3f249e9059a20badcb56b61601a83556133655c11d1eb3dd3e04ff069e5f3c7", size = 6711 },
 12 | ]
 13 | 
 14 | [[package]]
 15 | name = "anyio"
 16 | version = "4.9.0"
 17 | source = { registry = "https://pypi.org/simple" }
 18 | dependencies = [
 19 |     { name = "idna" },
 20 |     { name = "sniffio" },
 21 |     { name = "typing-extensions", marker = "python_full_version < '3.13'" },
 22 | ]
 23 | sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949 }
 24 | wheels = [
 25 |     { url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916 },
 26 | ]
 27 | 
 28 | [[package]]
 29 | name = "apscheduler"
 30 | version = "3.11.0"
 31 | source = { registry = "https://pypi.org/simple" }
 32 | dependencies = [
 33 |     { name = "tzlocal" },
 34 | ]
 35 | sdist = { url = "https://files.pythonhosted.org/packages/4e/00/6d6814ddc19be2df62c8c898c4df6b5b1914f3bd024b780028caa392d186/apscheduler-3.11.0.tar.gz", hash = "sha256:4c622d250b0955a65d5d0eb91c33e6d43fd879834bf541e0a18661ae60460133", size = 107347 }
 36 | wheels = [
 37 |     { url = "https://files.pythonhosted.org/packages/d0/ae/9a053dd9229c0fde6b1f1f33f609ccff1ee79ddda364c756a924c6d8563b/APScheduler-3.11.0-py3-none-any.whl", hash = "sha256:fc134ca32e50f5eadcc4938e3a4545ab19131435e851abb40b34d63d5141c6da", size = 64004 },
 38 | ]
 39 | 
 40 | [[package]]
 41 | name = "certifi"
 42 | version = "2025.1.31"
 43 | source = { registry = "https://pypi.org/simple" }
 44 | sdist = { url = "https://files.pythonhosted.org/packages/1c/ab/c9f1e32b7b1bf505bf26f0ef697775960db7932abeb7b516de930ba2705f/certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651", size = 167577 }
 45 | wheels = [
 46 |     { url = "https://files.pythonhosted.org/packages/38/fc/bce832fd4fd99766c04d1ee0eead6b0ec6486fb100ae5e74c1d91292b982/certifi-2025.1.31-py3-none-any.whl", hash = "sha256:ca78db4565a652026a4db2bcdf68f2fb589ea80d0be70e03929ed730746b84fe", size = 166393 },
 47 | ]
 48 | 
 49 | [[package]]
 50 | name = "charset-normalizer"
 51 | version = "3.4.1"
 52 | source = { registry = "https://pypi.org/simple" }
 53 | sdist = { url = "https://files.pythonhosted.org/packages/16/b0/572805e227f01586461c80e0fd25d65a2115599cc9dad142fee4b747c357/charset_normalizer-3.4.1.tar.gz", hash = "sha256:44251f18cd68a75b56585dd00dae26183e102cd5e0f9f1466e6df5da2ed64ea3", size = 123188 }
 54 | wheels = [
 55 |     { url = "https://files.pythonhosted.org/packages/72/80/41ef5d5a7935d2d3a773e3eaebf0a9350542f2cab4eac59a7a4741fbbbbe/charset_normalizer-3.4.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:8bfa33f4f2672964266e940dd22a195989ba31669bd84629f05fab3ef4e2d125", size = 194995 },
 56 |     { url = "https://files.pythonhosted.org/packages/7a/28/0b9fefa7b8b080ec492110af6d88aa3dea91c464b17d53474b6e9ba5d2c5/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:28bf57629c75e810b6ae989f03c0828d64d6b26a5e205535585f96093e405ed1", size = 139471 },
 57 |     { url = "https://files.pythonhosted.org/packages/71/64/d24ab1a997efb06402e3fc07317e94da358e2585165930d9d59ad45fcae2/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:f08ff5e948271dc7e18a35641d2f11a4cd8dfd5634f55228b691e62b37125eb3", size = 149831 },
 58 |     { url = "https://files.pythonhosted.org/packages/37/ed/be39e5258e198655240db5e19e0b11379163ad7070962d6b0c87ed2c4d39/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:234ac59ea147c59ee4da87a0c0f098e9c8d169f4dc2a159ef720f1a61bbe27cd", size = 142335 },
 59 |     { url = "https://files.pythonhosted.org/packages/88/83/489e9504711fa05d8dde1574996408026bdbdbd938f23be67deebb5eca92/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fd4ec41f914fa74ad1b8304bbc634b3de73d2a0889bd32076342a573e0779e00", size = 143862 },
 60 |     { url = "https://files.pythonhosted.org/packages/c6/c7/32da20821cf387b759ad24627a9aca289d2822de929b8a41b6241767b461/charset_normalizer-3.4.1-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eea6ee1db730b3483adf394ea72f808b6e18cf3cb6454b4d86e04fa8c4327a12", size = 145673 },
 61 |     { url = "https://files.pythonhosted.org/packages/68/85/f4288e96039abdd5aeb5c546fa20a37b50da71b5cf01e75e87f16cd43304/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:c96836c97b1238e9c9e3fe90844c947d5afbf4f4c92762679acfe19927d81d77", size = 140211 },
 62 |     { url = "https://files.pythonhosted.org/packages/28/a3/a42e70d03cbdabc18997baf4f0227c73591a08041c149e710045c281f97b/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_i686.whl", hash = "sha256:4d86f7aff21ee58f26dcf5ae81a9addbd914115cdebcbb2217e4f0ed8982e146", size = 148039 },
 63 |     { url = "https://files.pythonhosted.org/packages/85/e4/65699e8ab3014ecbe6f5c71d1a55d810fb716bbfd74f6283d5c2aa87febf/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_ppc64le.whl", hash = "sha256:09b5e6733cbd160dcc09589227187e242a30a49ca5cefa5a7edd3f9d19ed53fd", size = 151939 },
 64 |     { url = "https://files.pythonhosted.org/packages/b1/82/8e9fe624cc5374193de6860aba3ea8070f584c8565ee77c168ec13274bd2/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_s390x.whl", hash = "sha256:5777ee0881f9499ed0f71cc82cf873d9a0ca8af166dfa0af8ec4e675b7df48e6", size = 149075 },
 65 |     { url = "https://files.pythonhosted.org/packages/3d/7b/82865ba54c765560c8433f65e8acb9217cb839a9e32b42af4aa8e945870f/charset_normalizer-3.4.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:237bdbe6159cff53b4f24f397d43c6336c6b0b42affbe857970cefbb620911c8", size = 144340 },
 66 |     { url = "https://files.pythonhosted.org/packages/b5/b6/9674a4b7d4d99a0d2df9b215da766ee682718f88055751e1e5e753c82db0/charset_normalizer-3.4.1-cp311-cp311-win32.whl", hash = "sha256:8417cb1f36cc0bc7eaba8ccb0e04d55f0ee52df06df3ad55259b9a323555fc8b", size = 95205 },
 67 |     { url = "https://files.pythonhosted.org/packages/1e/ab/45b180e175de4402dcf7547e4fb617283bae54ce35c27930a6f35b6bef15/charset_normalizer-3.4.1-cp311-cp311-win_amd64.whl", hash = "sha256:d7f50a1f8c450f3925cb367d011448c39239bb3eb4117c36a6d354794de4ce76", size = 102441 },
 68 |     { url = "https://files.pythonhosted.org/packages/0a/9a/dd1e1cdceb841925b7798369a09279bd1cf183cef0f9ddf15a3a6502ee45/charset_normalizer-3.4.1-cp312-cp312-macosx_10_13_universal2.whl", hash = "sha256:73d94b58ec7fecbc7366247d3b0b10a21681004153238750bb67bd9012414545", size = 196105 },
 69 |     { url = "https://files.pythonhosted.org/packages/d3/8c/90bfabf8c4809ecb648f39794cf2a84ff2e7d2a6cf159fe68d9a26160467/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad3e487649f498dd991eeb901125411559b22e8d7ab25d3aeb1af367df5efd7", size = 140404 },
 70 |     { url = "https://files.pythonhosted.org/packages/ad/8f/e410d57c721945ea3b4f1a04b74f70ce8fa800d393d72899f0a40526401f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:c30197aa96e8eed02200a83fba2657b4c3acd0f0aa4bdc9f6c1af8e8962e0757", size = 150423 },
 71 |     { url = "https://files.pythonhosted.org/packages/f0/b8/e6825e25deb691ff98cf5c9072ee0605dc2acfca98af70c2d1b1bc75190d/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2369eea1ee4a7610a860d88f268eb39b95cb588acd7235e02fd5a5601773d4fa", size = 143184 },
 72 |     { url = "https://files.pythonhosted.org/packages/3e/a2/513f6cbe752421f16d969e32f3583762bfd583848b763913ddab8d9bfd4f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc2722592d8998c870fa4e290c2eec2c1569b87fe58618e67d38b4665dfa680d", size = 145268 },
 73 |     { url = "https://files.pythonhosted.org/packages/74/94/8a5277664f27c3c438546f3eb53b33f5b19568eb7424736bdc440a88a31f/charset_normalizer-3.4.1-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffc9202a29ab3920fa812879e95a9e78b2465fd10be7fcbd042899695d75e616", size = 147601 },
 74 |     { url = "https://files.pythonhosted.org/packages/7c/5f/6d352c51ee763623a98e31194823518e09bfa48be2a7e8383cf691bbb3d0/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:804a4d582ba6e5b747c625bf1255e6b1507465494a40a2130978bda7b932c90b", size = 141098 },
 75 |     { url = "https://files.pythonhosted.org/packages/78/d4/f5704cb629ba5ab16d1d3d741396aec6dc3ca2b67757c45b0599bb010478/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_i686.whl", hash = "sha256:0f55e69f030f7163dffe9fd0752b32f070566451afe180f99dbeeb81f511ad8d", size = 149520 },
 76 |     { url = "https://files.pythonhosted.org/packages/c5/96/64120b1d02b81785f222b976c0fb79a35875457fa9bb40827678e54d1bc8/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_ppc64le.whl", hash = "sha256:c4c3e6da02df6fa1410a7680bd3f63d4f710232d3139089536310d027950696a", size = 152852 },
 77 |     { url = "https://files.pythonhosted.org/packages/84/c9/98e3732278a99f47d487fd3468bc60b882920cef29d1fa6ca460a1fdf4e6/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_s390x.whl", hash = "sha256:5df196eb874dae23dcfb968c83d4f8fdccb333330fe1fc278ac5ceeb101003a9", size = 150488 },
 78 |     { url = "https://files.pythonhosted.org/packages/13/0e/9c8d4cb99c98c1007cc11eda969ebfe837bbbd0acdb4736d228ccaabcd22/charset_normalizer-3.4.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:e358e64305fe12299a08e08978f51fc21fac060dcfcddd95453eabe5b93ed0e1", size = 146192 },
 79 |     { url = "https://files.pythonhosted.org/packages/b2/21/2b6b5b860781a0b49427309cb8670785aa543fb2178de875b87b9cc97746/charset_normalizer-3.4.1-cp312-cp312-win32.whl", hash = "sha256:9b23ca7ef998bc739bf6ffc077c2116917eabcc901f88da1b9856b210ef63f35", size = 95550 },
 80 |     { url = "https://files.pythonhosted.org/packages/21/5b/1b390b03b1d16c7e382b561c5329f83cc06623916aab983e8ab9239c7d5c/charset_normalizer-3.4.1-cp312-cp312-win_amd64.whl", hash = "sha256:6ff8a4a60c227ad87030d76e99cd1698345d4491638dfa6673027c48b3cd395f", size = 102785 },
 81 |     { url = "https://files.pythonhosted.org/packages/38/94/ce8e6f63d18049672c76d07d119304e1e2d7c6098f0841b51c666e9f44a0/charset_normalizer-3.4.1-cp313-cp313-macosx_10_13_universal2.whl", hash = "sha256:aabfa34badd18f1da5ec1bc2715cadc8dca465868a4e73a0173466b688f29dda", size = 195698 },
 82 |     { url = "https://files.pythonhosted.org/packages/24/2e/dfdd9770664aae179a96561cc6952ff08f9a8cd09a908f259a9dfa063568/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22e14b5d70560b8dd51ec22863f370d1e595ac3d024cb8ad7d308b4cd95f8313", size = 140162 },
 83 |     { url = "https://files.pythonhosted.org/packages/24/4e/f646b9093cff8fc86f2d60af2de4dc17c759de9d554f130b140ea4738ca6/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:8436c508b408b82d87dc5f62496973a1805cd46727c34440b0d29d8a2f50a6c9", size = 150263 },
 84 |     { url = "https://files.pythonhosted.org/packages/5e/67/2937f8d548c3ef6e2f9aab0f6e21001056f692d43282b165e7c56023e6dd/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2d074908e1aecee37a7635990b2c6d504cd4766c7bc9fc86d63f9c09af3fa11b", size = 142966 },
 85 |     { url = "https://files.pythonhosted.org/packages/52/ed/b7f4f07de100bdb95c1756d3a4d17b90c1a3c53715c1a476f8738058e0fa/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:955f8851919303c92343d2f66165294848d57e9bba6cf6e3625485a70a038d11", size = 144992 },
 86 |     { url = "https://files.pythonhosted.org/packages/96/2c/d49710a6dbcd3776265f4c923bb73ebe83933dfbaa841c5da850fe0fd20b/charset_normalizer-3.4.1-cp313-cp313-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:44ecbf16649486d4aebafeaa7ec4c9fed8b88101f4dd612dcaf65d5e815f837f", size = 147162 },
 87 |     { url = "https://files.pythonhosted.org/packages/b4/41/35ff1f9a6bd380303dea55e44c4933b4cc3c4850988927d4082ada230273/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:0924e81d3d5e70f8126529951dac65c1010cdf117bb75eb02dd12339b57749dd", size = 140972 },
 88 |     { url = "https://files.pythonhosted.org/packages/fb/43/c6a0b685fe6910d08ba971f62cd9c3e862a85770395ba5d9cad4fede33ab/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_i686.whl", hash = "sha256:2967f74ad52c3b98de4c3b32e1a44e32975e008a9cd2a8cc8966d6a5218c5cb2", size = 149095 },
 89 |     { url = "https://files.pythonhosted.org/packages/4c/ff/a9a504662452e2d2878512115638966e75633519ec11f25fca3d2049a94a/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_ppc64le.whl", hash = "sha256:c75cb2a3e389853835e84a2d8fb2b81a10645b503eca9bcb98df6b5a43eb8886", size = 152668 },
 90 |     { url = "https://files.pythonhosted.org/packages/6c/71/189996b6d9a4b932564701628af5cee6716733e9165af1d5e1b285c530ed/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_s390x.whl", hash = "sha256:09b26ae6b1abf0d27570633b2b078a2a20419c99d66fb2823173d73f188ce601", size = 150073 },
 91 |     { url = "https://files.pythonhosted.org/packages/e4/93/946a86ce20790e11312c87c75ba68d5f6ad2208cfb52b2d6a2c32840d922/charset_normalizer-3.4.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fa88b843d6e211393a37219e6a1c1df99d35e8fd90446f1118f4216e307e48cd", size = 145732 },
 92 |     { url = "https://files.pythonhosted.org/packages/cd/e5/131d2fb1b0dddafc37be4f3a2fa79aa4c037368be9423061dccadfd90091/charset_normalizer-3.4.1-cp313-cp313-win32.whl", hash = "sha256:eb8178fe3dba6450a3e024e95ac49ed3400e506fd4e9e5c32d30adda88cbd407", size = 95391 },
 93 |     { url = "https://files.pythonhosted.org/packages/27/f2/4f9a69cc7712b9b5ad8fdb87039fd89abba997ad5cbe690d1835d40405b0/charset_normalizer-3.4.1-cp313-cp313-win_amd64.whl", hash = "sha256:b1ac5992a838106edb89654e0aebfc24f5848ae2547d22c2c3f66454daa11971", size = 102702 },
 94 |     { url = "https://files.pythonhosted.org/packages/0e/f6/65ecc6878a89bb1c23a086ea335ad4bf21a588990c3f535a227b9eea9108/charset_normalizer-3.4.1-py3-none-any.whl", hash = "sha256:d98b1668f06378c6dbefec3b92299716b931cd4e6061f3c875a71ced1780ab85", size = 49767 },
 95 | ]
 96 | 
 97 | [[package]]
 98 | name = "dolinuxradar"
 99 | version = "0.1.0"
100 | source = { virtual = "." }
101 | dependencies = [
102 |     { name = "feedparser" },
103 |     { name = "python-dotenv" },
104 |     { name = "python-telegram-bot", extra = ["job-queue", "rate-limiter", "webhooks"] },
105 |     { name = "requests" },
106 | ]
107 | 
108 | [package.metadata]
109 | requires-dist = [
110 |     { name = "feedparser", specifier = ">=6.0.11" },
111 |     { name = "python-dotenv", specifier = ">=1.1.0" },
112 |     { name = "python-telegram-bot", extras = ["job-queue", "rate-limiter", "webhooks"], specifier = ">=22.0" },
113 |     { name = "requests", specifier = ">=2.32.3" },
114 | ]
115 | 
116 | [[package]]
117 | name = "feedparser"
118 | version = "6.0.11"
119 | source = { registry = "https://pypi.org/simple" }
120 | dependencies = [
121 |     { name = "sgmllib3k" },
122 | ]
123 | sdist = { url = "https://files.pythonhosted.org/packages/ff/aa/7af346ebeb42a76bf108027fe7f3328bb4e57a3a96e53e21fd9ef9dd6dd0/feedparser-6.0.11.tar.gz", hash = "sha256:c9d0407b64c6f2a065d0ebb292c2b35c01050cc0dc33757461aaabdc4c4184d5", size = 286197 }
124 | wheels = [
125 |     { url = "https://files.pythonhosted.org/packages/7c/d4/8c31aad9cc18f451c49f7f9cfb5799dadffc88177f7917bc90a66459b1d7/feedparser-6.0.11-py3-none-any.whl", hash = "sha256:0be7ee7b395572b19ebeb1d6aafb0028dee11169f1c934e0ed67d54992f4ad45", size = 81343 },
126 | ]
127 | 
128 | [[package]]
129 | name = "h11"
130 | version = "0.14.0"
131 | source = { registry = "https://pypi.org/simple" }
132 | sdist = { url = "https://files.pythonhosted.org/packages/f5/38/3af3d3633a34a3316095b39c8e8fb4853a28a536e55d347bd8d8e9a14b03/h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d", size = 100418 }
133 | wheels = [
134 |     { url = "https://files.pythonhosted.org/packages/95/04/ff642e65ad6b90db43e668d70ffb6736436c7ce41fcc549f4e9472234127/h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761", size = 58259 },
135 | ]
136 | 
137 | [[package]]
138 | name = "httpcore"
139 | version = "1.0.8"
140 | source = { registry = "https://pypi.org/simple" }
141 | dependencies = [
142 |     { name = "certifi" },
143 |     { name = "h11" },
144 | ]
145 | sdist = { url = "https://files.pythonhosted.org/packages/9f/45/ad3e1b4d448f22c0cff4f5692f5ed0666658578e358b8d58a19846048059/httpcore-1.0.8.tar.gz", hash = "sha256:86e94505ed24ea06514883fd44d2bc02d90e77e7979c8eb71b90f41d364a1bad", size = 85385 }
146 | wheels = [
147 |     { url = "https://files.pythonhosted.org/packages/18/8d/f052b1e336bb2c1fc7ed1aaed898aa570c0b61a09707b108979d9fc6e308/httpcore-1.0.8-py3-none-any.whl", hash = "sha256:5254cf149bcb5f75e9d1b2b9f729ea4a4b883d1ad7379fc632b727cec23674be", size = 78732 },
148 | ]
149 | 
150 | [[package]]
151 | name = "httpx"
152 | version = "0.28.1"
153 | source = { registry = "https://pypi.org/simple" }
154 | dependencies = [
155 |     { name = "anyio" },
156 |     { name = "certifi" },
157 |     { name = "httpcore" },
158 |     { name = "idna" },
159 | ]
160 | sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406 }
161 | wheels = [
162 |     { url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517 },
163 | ]
164 | 
165 | [[package]]
166 | name = "idna"
167 | version = "3.10"
168 | source = { registry = "https://pypi.org/simple" }
169 | sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490 }
170 | wheels = [
171 |     { url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442 },
172 | ]
173 | 
174 | [[package]]
175 | name = "python-dotenv"
176 | version = "1.1.0"
177 | source = { registry = "https://pypi.org/simple" }
178 | sdist = { url = "https://files.pythonhosted.org/packages/88/2c/7bb1416c5620485aa793f2de31d3df393d3686aa8a8506d11e10e13c5baf/python_dotenv-1.1.0.tar.gz", hash = "sha256:41f90bc6f5f177fb41f53e87666db362025010eb28f60a01c9143bfa33a2b2d5", size = 39920 }
179 | wheels = [
180 |     { url = "https://files.pythonhosted.org/packages/1e/18/98a99ad95133c6a6e2005fe89faedf294a748bd5dc803008059409ac9b1e/python_dotenv-1.1.0-py3-none-any.whl", hash = "sha256:d7c01d9e2293916c18baf562d95698754b0dbbb5e74d457c45d4f6561fb9d55d", size = 20256 },
181 | ]
182 | 
183 | [[package]]
184 | name = "python-telegram-bot"
185 | version = "22.0"
186 | source = { registry = "https://pypi.org/simple" }
187 | dependencies = [
188 |     { name = "httpx" },
189 | ]
190 | sdist = { url = "https://files.pythonhosted.org/packages/61/8c/0bd0d5c6de549ee0ebc2ddf4d49618eec1ece6d25084f3b4ef72bba6590c/python_telegram_bot-22.0.tar.gz", hash = "sha256:acf86f28d86d81cab736177d2988e5bcb27f2248137efd62e02c46e9ba1fe44c", size = 440017 }
191 | wheels = [
192 |     { url = "https://files.pythonhosted.org/packages/15/9f/b8c116f606074c19ec2600a7edc222f158c307ca949de568d67fe2b9d364/python_telegram_bot-22.0-py3-none-any.whl", hash = "sha256:23237f778655e634f08cfebbada96ed3692c2bdd3c20c122e90a6d606d6a4516", size = 673473 },
193 | ]
194 | 
195 | [package.optional-dependencies]
196 | job-queue = [
197 |     { name = "apscheduler" },
198 | ]
199 | rate-limiter = [
200 |     { name = "aiolimiter" },
201 | ]
202 | webhooks = [
203 |     { name = "tornado" },
204 | ]
205 | 
206 | [[package]]
207 | name = "requests"
208 | version = "2.32.3"
209 | source = { registry = "https://pypi.org/simple" }
210 | dependencies = [
211 |     { name = "certifi" },
212 |     { name = "charset-normalizer" },
213 |     { name = "idna" },
214 |     { name = "urllib3" },
215 | ]
216 | sdist = { url = "https://files.pythonhosted.org/packages/63/70/2bf7780ad2d390a8d301ad0b550f1581eadbd9a20f896afe06353c2a2913/requests-2.32.3.tar.gz", hash = "sha256:55365417734eb18255590a9ff9eb97e9e1da868d4ccd6402399eaf68af20a760", size = 131218 }
217 | wheels = [
218 |     { url = "https://files.pythonhosted.org/packages/f9/9b/335f9764261e915ed497fcdeb11df5dfd6f7bf257d4a6a2a686d80da4d54/requests-2.32.3-py3-none-any.whl", hash = "sha256:70761cfe03c773ceb22aa2f671b4757976145175cdfca038c02654d061d6dcc6", size = 64928 },
219 | ]
220 | 
221 | [[package]]
222 | name = "sgmllib3k"
223 | version = "1.0.0"
224 | source = { registry = "https://pypi.org/simple" }
225 | sdist = { url = "https://files.pythonhosted.org/packages/9e/bd/3704a8c3e0942d711c1299ebf7b9091930adae6675d7c8f476a7ce48653c/sgmllib3k-1.0.0.tar.gz", hash = "sha256:7868fb1c8bfa764c1ac563d3cf369c381d1325d36124933a726f29fcdaa812e9", size = 5750 }
226 | 
227 | [[package]]
228 | name = "sniffio"
229 | version = "1.3.1"
230 | source = { registry = "https://pypi.org/simple" }
231 | sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372 }
232 | wheels = [
233 |     { url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235 },
234 | ]
235 | 
236 | [[package]]
237 | name = "tornado"
238 | version = "6.4.2"
239 | source = { registry = "https://pypi.org/simple" }
240 | sdist = { url = "https://files.pythonhosted.org/packages/59/45/a0daf161f7d6f36c3ea5fc0c2de619746cc3dd4c76402e9db545bd920f63/tornado-6.4.2.tar.gz", hash = "sha256:92bad5b4746e9879fd7bf1eb21dce4e3fc5128d71601f80005afa39237ad620b", size = 501135 }
241 | wheels = [
242 |     { url = "https://files.pythonhosted.org/packages/26/7e/71f604d8cea1b58f82ba3590290b66da1e72d840aeb37e0d5f7291bd30db/tornado-6.4.2-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e828cce1123e9e44ae2a50a9de3055497ab1d0aeb440c5ac23064d9e44880da1", size = 436299 },
243 |     { url = "https://files.pythonhosted.org/packages/96/44/87543a3b99016d0bf54fdaab30d24bf0af2e848f1d13d34a3a5380aabe16/tornado-6.4.2-cp38-abi3-macosx_10_9_x86_64.whl", hash = "sha256:072ce12ada169c5b00b7d92a99ba089447ccc993ea2143c9ede887e0937aa803", size = 434253 },
244 |     { url = "https://files.pythonhosted.org/packages/cb/fb/fdf679b4ce51bcb7210801ef4f11fdac96e9885daa402861751353beea6e/tornado-6.4.2-cp38-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a017d239bd1bb0919f72af256a970624241f070496635784d9bf0db640d3fec", size = 437602 },
245 |     { url = "https://files.pythonhosted.org/packages/4f/3b/e31aeffffc22b475a64dbeb273026a21b5b566f74dee48742817626c47dc/tornado-6.4.2-cp38-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c36e62ce8f63409301537222faffcef7dfc5284f27eec227389f2ad11b09d946", size = 436972 },
246 |     { url = "https://files.pythonhosted.org/packages/22/55/b78a464de78051a30599ceb6983b01d8f732e6f69bf37b4ed07f642ac0fc/tornado-6.4.2-cp38-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bca9eb02196e789c9cb5c3c7c0f04fb447dc2adffd95265b2c7223a8a615ccbf", size = 437173 },
247 |     { url = "https://files.pythonhosted.org/packages/79/5e/be4fb0d1684eb822c9a62fb18a3e44a06188f78aa466b2ad991d2ee31104/tornado-6.4.2-cp38-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:304463bd0772442ff4d0f5149c6f1c2135a1fae045adf070821c6cdc76980634", size = 437892 },
248 |     { url = "https://files.pythonhosted.org/packages/f5/33/4f91fdd94ea36e1d796147003b490fe60a0215ac5737b6f9c65e160d4fe0/tornado-6.4.2-cp38-abi3-musllinux_1_2_i686.whl", hash = "sha256:c82c46813ba483a385ab2a99caeaedf92585a1f90defb5693351fa7e4ea0bf73", size = 437334 },
249 |     { url = "https://files.pythonhosted.org/packages/2b/ae/c1b22d4524b0e10da2f29a176fb2890386f7bd1f63aacf186444873a88a0/tornado-6.4.2-cp38-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:932d195ca9015956fa502c6b56af9eb06106140d844a335590c1ec7f5277d10c", size = 437261 },
250 |     { url = "https://files.pythonhosted.org/packages/b5/25/36dbd49ab6d179bcfc4c6c093a51795a4f3bed380543a8242ac3517a1751/tornado-6.4.2-cp38-abi3-win32.whl", hash = "sha256:2876cef82e6c5978fde1e0d5b1f919d756968d5b4282418f3146b79b58556482", size = 438463 },
251 |     { url = "https://files.pythonhosted.org/packages/61/cc/58b1adeb1bb46228442081e746fcdbc4540905c87e8add7c277540934edb/tornado-6.4.2-cp38-abi3-win_amd64.whl", hash = "sha256:908b71bf3ff37d81073356a5fadcc660eb10c1476ee6e2725588626ce7e5ca38", size = 438907 },
252 | ]
253 | 
254 | [[package]]
255 | name = "typing-extensions"
256 | version = "4.13.2"
257 | source = { registry = "https://pypi.org/simple" }
258 | sdist = { url = "https://files.pythonhosted.org/packages/f6/37/23083fcd6e35492953e8d2aaaa68b860eb422b34627b13f2ce3eb6106061/typing_extensions-4.13.2.tar.gz", hash = "sha256:e6c81219bd689f51865d9e372991c540bda33a0379d5573cddb9a3a23f7caaef", size = 106967 }
259 | wheels = [
260 |     { url = "https://files.pythonhosted.org/packages/8b/54/b1ae86c0973cc6f0210b53d508ca3641fb6d0c56823f288d108bc7ab3cc8/typing_extensions-4.13.2-py3-none-any.whl", hash = "sha256:a439e7c04b49fec3e5d3e2beaa21755cadbbdc391694e28ccdd36ca4a1408f8c", size = 45806 },
261 | ]
262 | 
263 | [[package]]
264 | name = "tzdata"
265 | version = "2025.2"
266 | source = { registry = "https://pypi.org/simple" }
267 | sdist = { url = "https://files.pythonhosted.org/packages/95/32/1a225d6164441be760d75c2c42e2780dc0873fe382da3e98a2e1e48361e5/tzdata-2025.2.tar.gz", hash = "sha256:b60a638fcc0daffadf82fe0f57e53d06bdec2f36c4df66280ae79bce6bd6f2b9", size = 196380 }
268 | wheels = [
269 |     { url = "https://files.pythonhosted.org/packages/5c/23/c7abc0ca0a1526a0774eca151daeb8de62ec457e77262b66b359c3c7679e/tzdata-2025.2-py2.py3-none-any.whl", hash = "sha256:1a403fada01ff9221ca8044d701868fa132215d84beb92242d9acd2147f667a8", size = 347839 },
270 | ]
271 | 
272 | [[package]]
273 | name = "tzlocal"
274 | version = "5.3.1"
275 | source = { registry = "https://pypi.org/simple" }
276 | dependencies = [
277 |     { name = "tzdata", marker = "sys_platform == 'win32'" },
278 | ]
279 | sdist = { url = "https://files.pythonhosted.org/packages/8b/2e/c14812d3d4d9cd1773c6be938f89e5735a1f11a9f184ac3639b93cef35d5/tzlocal-5.3.1.tar.gz", hash = "sha256:cceffc7edecefea1f595541dbd6e990cb1ea3d19bf01b2809f362a03dd7921fd", size = 30761 }
280 | wheels = [
281 |     { url = "https://files.pythonhosted.org/packages/c2/14/e2a54fabd4f08cd7af1c07030603c3356b74da07f7cc056e600436edfa17/tzlocal-5.3.1-py3-none-any.whl", hash = "sha256:eb1a66c3ef5847adf7a834f1be0800581b683b5608e74f86ecbcef8ab91bb85d", size = 18026 },
282 | ]
283 | 
284 | [[package]]
285 | name = "urllib3"
286 | version = "2.4.0"
287 | source = { registry = "https://pypi.org/simple" }
288 | sdist = { url = "https://files.pythonhosted.org/packages/8a/78/16493d9c386d8e60e442a35feac5e00f0913c0f4b7c217c11e8ec2ff53e0/urllib3-2.4.0.tar.gz", hash = "sha256:414bc6535b787febd7567804cc015fee39daab8ad86268f1310a9250697de466", size = 390672 }
289 | wheels = [
290 |     { url = "https://files.pythonhosted.org/packages/6b/11/cc635220681e93a0183390e26485430ca2c7b5f9d33b15c74c2861cb8091/urllib3-2.4.0-py3-none-any.whl", hash = "sha256:4e16665048960a0900c702d4a66415956a584919c03361cac9f1df5c5dd7e813", size = 128680 },
291 | ]
292 | 


--------------------------------------------------------------------------------