├── .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 | [](https://hub.docker.com/r/yym68686/dolinuxradar) 5 | [](https://hub.docker.com/r/yym68686/dolinuxradar) 6 | [](https://hub.docker.com/r/yym68686/dolinuxradar) 7 | 8 |
9 |
10 |
11 |
12 |
标签或发生错误则返回 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 | --------------------------------------------------------------------------------