feat: 钉钉推送、Telegram 推送 #16
Some checks are pending
SyncMirror / sync (push) Waiting to run

This commit is contained in:
GamerNoTitle 2025-06-30 19:03:38 +08:00
parent c9ae81b358
commit 329b0b7812
5 changed files with 536 additions and 138 deletions

1
.python-version Normal file
View File

@ -0,0 +1 @@
3.11

View File

@ -1,6 +1,19 @@
# 使用前请阅读文档https://bili33.top/posts/MHYY-AutoCheckin-Manual-Gen2/
# 有问题请前往Github开启issuehttps://github.com/GamerNoTitle/MHYY/issues
notifications:
# Server酱
serverchan:
key: ""
# 钉钉机器人
# 安全关键词需要包含 `MHYY`,否则会被钉钉机器人拒绝
dingtalk:
webhook_url: "https://oapi.dingtalk.com/robot/send?access_token=2b350a189512e1484d54e8b588c163f787ec9c2798725490f92f63d42b1e2b17"
# Telegram机器人
telegram:
bot_token: "6689586847:AAH0lg1uuXiZMsou9WNYif7qqrbCSdZWWj0"
chat_id: "1762053255"
######## 以下为账号配置项,可以多账号,详情请参考文档 ########
accounts:
# 第一个账号

413
main.py
View File

@ -8,6 +8,7 @@ import time
import yaml
import logging
# --- Logging Setup ---
if os.environ.get("MHYY_LOGLEVEL", "").upper() == "DEBUG":
loglevel = logging.DEBUG
elif os.environ.get("MHYY_LOGLEVEL", "").upper() == "WARNING":
@ -17,7 +18,6 @@ elif os.environ.get("MHYY_LOGLEVEL", "").upper() == "ERROR":
else:
loglevel = logging.INFO
# 设置日志配置
logging.basicConfig(
level=loglevel,
format="%(asctime)s [%(levelname)s]: %(message)s",
@ -27,42 +27,163 @@ logging.basicConfig(
logger = logging.getLogger()
# --- Config Reading Function ---
def ReadConf(variable_name, default_value=None):
# Try to get the variable from the environment
"""
Reads YAML configuration from environment variable or config.yml.
Assumes the variable_name contains the full YAML content.
"""
env_value = os.environ.get(variable_name)
if env_value:
try:
# Attempt to load from environment variable (assuming it's YAML)
config_data = yaml.load(env_value, Loader=yaml.FullLoader)
logger.debug("Configuration loaded from environment variable.")
return config_data
except yaml.YAMLError as e:
logger.error(
f"Failed to parse YAML from environment variable '{variable_name}': {e}"
)
return default_value # Return default or None if env parsing fails
except Exception as e:
logger.error(
f"An unexpected error occurred reading environment variable '{variable_name}': {e}"
)
return default_value
# If not found in environment, try to read from config.yml
# If not found or failed in environment, try to read from config.yml
try:
with open("config.yml", "r", encoding="utf-8") as config_file:
config_data = yaml.load(config_file, Loader=yaml.FullLoader)
logger.debug("Configuration loaded from config.yml file.")
return config_data
except FileNotFoundError:
logger.warning("config.yml not found.")
return default_value
except yaml.YAMLError as e:
logger.error(f"Failed to parse YAML from config.yml: {e}")
return default_value
except Exception as e:
logger.error(f"An unexpected error occurred reading config.yml: {e}")
return default_value
# --- Sentry Setup ---
sentry_sdk.init(
"https://425d7b4536f94c9fa540fe34dd6609a2@o361988.ingest.sentry.io/6352584",
traces_sample_rate=1.0,
)
conf = ReadConf("MHYY_CONFIG")["accounts"]
# --- Load Configuration ---
full_config = ReadConf("MHYY_CONFIG", {}) # Read the entire config
accounts_conf = full_config.get("accounts")
notification_settings = full_config.get(
"notifications", {}
) # Get notification settings, default to empty dict
if not conf:
logger.error("请正确配置环境变量或者config.yml后再运行本脚本")
if not accounts_conf:
logger.error(
"请正确配置环境变量 MHYY_CONFIG 或者 config.yml 并包含 'accounts' 部分后再运行本脚本!"
)
os._exit(0)
logger.info(f"检测到 {len(conf)} 个账号,正在进行任务……")
logger.info(f"检测到 {len(accounts_conf)} 个账号,正在进行任务……")
# Options
sct_status = os.environ.get("sct") # https://sct.ftqq.com/
sct_key = os.environ.get("sct_key")
sct_url = f"https://sctapi.ftqq.com/{sct_key}.send?title=MHYY-AutoCheckin 自动推送"
def send_notifications(message: str, settings: dict):
"""Sends message to configured notification services."""
if not message or not settings:
logger.debug("No message to send or no notification settings configured.")
return
sct_msg = ""
logger.info("Attempting to send notifications...")
# ServerChan (SCT)
sct_conf = settings.get("serverchan", {})
sct_key = sct_conf.get("key")
if sct_key:
sct_url = f"https://sctapi.ftqq.com/{sct_key}.send"
try:
payload = {"title": "MHYY-AutoCheckin 状态推送", "desp": message}
response = httpx.get(sct_url, params=payload, timeout=10)
response.raise_for_status()
logger.info("ServerChan notification sent successfully.")
except httpx.HTTPStatusError as e:
logger.error(
f"ServerChan HTTP error occurred: {e.response.status_code} - {e.response.text}"
)
except httpx.RequestError as e:
logger.error(f"An error occurred while requesting ServerChan: {e}")
except Exception as e:
logger.error(
f"An unexpected error occurred sending ServerChan notification: {e}"
)
else:
logger.debug("ServerChan not configured.")
# DingTalk
dingtalk_conf = settings.get("dingtalk", {})
dingtalk_webhook_url = dingtalk_conf.get("webhook_url")
if dingtalk_webhook_url:
try:
payload = {"msgtype": "text", "text": {"content": message}}
response = httpx.post(dingtalk_webhook_url, json=payload, timeout=10)
response.raise_for_status()
result = response.json()
if result.get("errcode") == 0:
logger.info("DingTalk notification sent successfully.")
else:
logger.error(
f"DingTalk error: {result.get('errcode')} - {result.get('errmsg')}"
)
except httpx.HTTPStatusError as e:
logger.error(
f"DingTalk HTTP error occurred: {e.response.status_code} - {e.response.text}"
)
except httpx.RequestError as e:
logger.error(f"An error occurred while requesting DingTalk: {e}")
except Exception as e:
logger.error(
f"An unexpected error occurred sending DingTalk notification: {e}"
)
else:
logger.debug("DingTalk not configured.")
telegram_conf = settings.get("telegram", {})
telegram_bot_token = telegram_conf.get("bot_token")
telegram_chat_id = telegram_conf.get("chat_id")
if telegram_bot_token and telegram_chat_id:
telegram_url = f"https://api.telegram.org/bot{telegram_bot_token}/sendMessage"
try:
# Telegram text message parameters
params = {
"chat_id": telegram_chat_id,
"text": message,
# Optional: parse_mode can be 'MarkdownV2', 'HTML', or None
# For simplicity, sending as plain text. Be careful with special characters if using Markdown/HTML.
# "parse_mode": "HTML"
}
response = httpx.get(telegram_url, params=params, timeout=10)
response.raise_for_status() # Raise an exception for bad status codes
result = response.json()
if result.get("ok"):
logger.info("Telegram notification sent successfully.")
else:
logger.error(
f"Telegram error: {result.get('error_code')} - {result.get('description')}"
)
except httpx.HTTPStatusError as e:
logger.error(
f"Telegram HTTP error occurred: {e.response.status_code} - {e.response.text}"
)
except httpx.RequestError as e:
logger.error(f"An error occurred while requesting Telegram: {e}")
except Exception as e:
logger.error(
f"An unexpected error occurred sending Telegram notification: {e}"
)
else:
logger.debug("Telegram not configured.")
class RunError(Exception):
@ -71,9 +192,13 @@ class RunError(Exception):
if __name__ == "__main__":
if not os.environ.get("MHYY_DEBUG", False):
wait_time = random.randint(10, 60) # Random Sleep to Avoid Ban
logger.info(f"为了避免同一时间签到人数太多导致被官方怀疑,开始休眠 {wait_time}")
wait_time = random.randint(10, 11) # Random Sleep to Avoid Ban
logger.info(
f"为了避免同一时间签到人数太多导致被官方怀疑,开始休眠 {wait_time}"
)
time.sleep(wait_time)
version = "5.0.0" # Default version
try:
ver_info = httpx.get(
"https://hyp-api.mihoyo.com/hyp/hyp-connect/api/getGameBranches?game_ids[]=1Z8W5NHUQb&launcher_id=jGHBHlcOq1",
@ -83,10 +208,11 @@ if __name__ == "__main__":
version = json.loads(ver_info)["data"]["game_branches"][0]["main"]["tag"]
logger.info(f"从官方API获取到云·原神最新版本号{version}")
except Exception as e:
version = "5.0.0"
logger.warning(f"获取版本号失败,使用默认版本:{version}")
logger.warning(f"获取版本号失败,使用默认版本:{version}. Error: {e}")
for config in accounts_conf:
notification_msg = "【MHYY】签到状态推送\n\n" # Message container for the current account
for config in conf:
# 各种API的URL
NotificationURL = "https://api-cloudgame.mihoyo.com/hk4e_cg_cn/gamer/api/listNotifications?status=NotificationStatusUnread&type=NotificationTypePopup&is_sort=true"
WalletURL = "https://api-cloudgame.mihoyo.com/hk4e_cg_cn/wallet/wallet/get"
@ -94,19 +220,26 @@ if __name__ == "__main__":
"https://api-cloudgame.mihoyo.com/hk4e_cg_cn/gamer/api/getAnnouncementInfo"
)
if config == "":
# Verify config
raise RunError(
f"请在Settings->Secrets->Actions页面中新建名为config的变量并将你的配置填入后再运行"
)
else:
# Validate account config entry
if not isinstance(config, dict) or "token" not in config:
error_msg = f"跳过无效的账号配置条目: {config}"
logger.error(error_msg)
notification_msg += error_msg + "\n"
send_notifications(
notification_msg, notification_settings
) # Notify about invalid config
continue # Skip this entry
try:
token = config["token"]
client_type = config["type"]
sysver = config["sysver"]
client_type = config.get("type", 5)
sysver = config.get("sysver", "14.0")
deviceid = config["deviceid"]
devicename = config["devicename"]
devicemodel = config["devicemodel"]
appid = config["appid"]
devicename = config.get("devicename", "iPhone 13")
devicemodel = config.get("devicemodel", "iPhone13,3")
appid = config.get("appid", "1953439978")
# Construct headers
headers = {
"x-rpc-combo_token": token,
"x-rpc-client_type": str(client_type),
@ -123,9 +256,12 @@ if __name__ == "__main__":
"Host": "api-cloudgame.mihoyo.com",
"Connection": "Keep-Alive",
"Accept-Encoding": "gzip",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36 Edg/121.0.0.0",
"User-Agent": f"Mozilla/5.0 (iPhone; CPU iPhone OS {sysver} like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
}
bbsid = re.findall(r"oi=[0-9]+", token)[0].replace("oi=", "")
bbsid_match = re.search(r"oi=(\d+)", token)
bbsid = bbsid_match.group(1) if bbsid_match else "N/A"
region = config.get("region", "cn")
if region == "os":
headers["x-rpc-channel"] = "mihoyo"
@ -142,81 +278,154 @@ if __name__ == "__main__":
AnnouncementURL = "https://sg-cg-api.hoyoverse.com/hk4e_global/cg/gamer/api/getAnnouncementInfo"
logger.info(
f"正在进行第 {conf.index(config) + 1} 个账号,服务器为{'CN' if region != 'os' else 'GLOBAL'}……"
f"--- 正在进行第 {accounts_conf.index(config) + 1} 个账号 (BBSID: {bbsid}),服务器为{'CN' if region != 'os' else 'GLOBAL'} ---"
)
notification_msg += (
f"☁️ 云原神签到结果 ({'CN' if region != 'os' else 'GLOBAL'}):\n"
)
notification_msg += (
f"账号 {accounts_conf.index(config) + 1} (BBSID: {bbsid})\n\n"
)
try:
wallet = httpx.get(WalletURL, headers=headers, timeout=60, verify=False)
logger.debug(wallet.text)
if json.loads(wallet.text) == {
"data": None,
"message": "登录已失效,请重新登录",
"retcode": -100,
}:
logger.error(f"当前登录已过期,请重新登陆!返回为:{wallet.text}")
sct_msg += f"当前登录已过期,请重新登陆!返回为:{wallet.text}"
else:
logger.info(
f"你当前拥有免费时长 {json.loads(wallet.text)['data']['free_time']['free_time']} 分钟,畅玩卡状态为 {json.loads(wallet.text)['data']['play_card']['short_msg']},拥有原点 {json.loads(wallet.text)['data']['coin']['coin_num']} 点({int(json.loads(wallet.text)['data']['coin']['coin_num'])/10}分钟)"
wallet_res = httpx.get(
WalletURL, headers=headers, timeout=30, verify=False
)
sct_msg += f"你当前拥有免费时长 {json.loads(wallet.text)['data']['free_time']['free_time']} 分钟,畅玩卡状态为 {json.loads(wallet.text)['data']['play_card']['short_msg']},拥有原点 {json.loads(wallet.text)['data']['coin']['coin_num']} 点({int(json.loads(wallet.text)['data']['coin']['coin_num'])/10}分钟)"
announcement = httpx.get(
AnnouncementURL, headers=headers, timeout=60, verify=False
)
logger.debug(f'获取到公告列表:{json.loads(announcement.text)["data"]}')
res = httpx.get(
NotificationURL, headers=headers, timeout=60, verify=False
)
success, Signed = False, False
logger.debug(res.text)
try:
if list(json.loads(res.text)["data"]["list"]) == []:
success = True
Signed = True
Over = False
elif (
json.loads(json.loads(res.text)["data"]["list"][-1]["msg"])[
"msg"
]
== "每日登录奖励"
):
success = True
Signed = False
Over = False
elif (
json.loads(json.loads(res.text)["data"]["list"][-1]["msg"])[
"over_num"
]
> 0
):
success = True
Signed = False
Over = True
wallet_res.raise_for_status()
wallet_data = wallet_res.json()
logger.debug(f"Wallet response: {wallet_data}")
if wallet_data.get("retcode") == -100:
error_msg = f"当前登录已过期,请重新登陆!返回为:{wallet_data.get('message', 'Unknown error')}"
logger.error(error_msg)
notification_msg += error_msg + "\n"
elif wallet_data.get("retcode") == 0 and wallet_data.get("data"):
free_time = wallet_data["data"]["free_time"]["free_time"]
play_card_msg = wallet_data["data"]["play_card"]["short_msg"]
coin_num = wallet_data["data"]["coin"]["coin_num"]
coin_minutes = int(coin_num) / 10 if coin_num is not None else 0
wallet_status = f"✅ 钱包:免费时长 {free_time} 分钟,畅玩卡状态为 {play_card_msg},拥有原点 {coin_num} 点 ({coin_minutes:.0f}分钟)\n"
logger.info(wallet_status.strip())
notification_msg += wallet_status
else:
success = False
except IndexError:
success = False
if success:
if Signed:
logger.info(f"获取签到情况成功!今天是否已经签到过了呢?")
sct_msg += f"获取签到情况成功!今天是否已经签到过了呢?"
logger.debug(f"完整返回体为:{res.text}")
elif not Signed and Over:
logger.info(
f'获取签到情况成功!当前免费时长已经达到上限!签到情况为{json.loads(res.text)["data"]["list"][0]["msg"]}'
)
sct_msg += f"获取签到情况成功!当前免费时长已经达到上限!签到情况为{json.loads(res.text)['data']['list'][0]['msg']}"
else:
logger.info(f"已经签到过了!")
sct_msg += f"已经签到过了!"
else:
logger.info(f"当前没有签到!请稍后再试!")
sct_msg += f"当前没有签到!请稍后再试!"
if sct_key:
httpx.get(sct_url, params={"desp": sct_msg})
error_msg = f"获取钱包信息失败: {wallet_data.get('retcode')} - {wallet_data.get('message', 'Unknown error')}"
logger.error(error_msg)
notification_msg += error_msg + "\n"
except httpx.HTTPStatusError as e:
error_msg = f"获取钱包信息HTTP错误: {e.response.status_code} - {e.response.text}"
logger.error(error_msg)
notification_msg += error_msg + "\n"
except httpx.RequestError as e:
error_msg = f"请求钱包信息失败: {e}"
logger.error(error_msg)
notification_msg += error_msg + "\n"
except Exception as e:
logger.error(f"执行过程中出错:{str(e)}")
sct_msg += f"执行过程中出错:{str(e)}"
if sct_key:
httpx.get(sct_url, params={"desp": sct_msg})
raise RunError(str(e))
error_msg = f"解析钱包信息出错: {e}"
logger.error(error_msg)
notification_msg += error_msg + "\n"
# --- Check Sign-in Status ---
try:
announcement_res = httpx.get(
AnnouncementURL, headers=headers, timeout=30, verify=False
)
announcement_res.raise_for_status()
# logger.debug(f'Announcement response: {announcement_res.text}') # Too verbose usually
notification_res = httpx.get(
NotificationURL, headers=headers, timeout=30, verify=False
)
notification_res.raise_for_status()
notification_data = notification_res.json()
logger.debug(f"Notification response: {notification_data}")
sign_in_status = "❓ 未知签到状态" # Default status
if notification_data.get("retcode") == 0 and notification_data.get(
"data"
):
notification_list = notification_data["data"].get("list", [])
if not notification_list:
sign_in_status = "✅ 今天似乎已经签到过了!(通知列表为空)"
logger.info(sign_in_status)
notification_msg += sign_in_status + "\n"
else:
# Look for a notification indicating sign-in reward or limit reached
# The logic here was a bit fragile, let's try to be more robust
# Look for specific message patterns if possible, or just check the presence of notifications
last_notification_msg = notification_list[0].get(
"msg"
) # Assume the first one is the latest? Or the last one? Original code used [-1]... let's stick to that for now.
if len(notification_list) > 0:
last_notification_msg = notification_list[-1].get("msg")
try:
# Attempt to parse the 'msg' field which is often a JSON string itself
msg_payload = json.loads(last_notification_msg)
logger.debug(
f"Parsed last notification msg payload: {msg_payload}"
)
if msg_payload.get("msg") == "每日登录奖励":
sign_in_status = f"✅ 获取签到情况成功!{msg_payload.get('msg')}:获得 {msg_payload.get('free_time')} 分钟"
logger.info(sign_in_status)
notification_msg += sign_in_status + "\n"
elif msg_payload.get("over_num", 0) > 0:
sign_in_status = f"✅ 获取签到情况成功!免费时长已达上限,未能获得 {msg_payload.get('free_time')} 分钟 (超出 {msg_payload.get('over_num')} 分钟)"
logger.info(sign_in_status)
notification_msg += sign_in_status + "\n"
else:
# Catch-all for other notification types or unexpected payloads
sign_in_status = f"❓ 获取到其他通知,可能已经签到或状态未知: {last_notification_msg}"
logger.info(sign_in_status)
notification_msg += sign_in_status + "\n"
except json.JSONDecodeError:
# 'msg' is not a JSON string
sign_in_status = f"❓ 获取到非标准通知,可能已经签到或状态未知: {last_notification_msg}"
logger.info(sign_in_status)
notification_msg += sign_in_status + "\n"
except Exception as e:
# Other errors during parsing msg
sign_in_status = f"❌ 解析通知详情时出错: {e}. Raw msg: {last_notification_msg}"
logger.error(sign_in_status)
notification_msg += sign_in_status + "\n"
elif notification_data.get("retcode") != 0:
error_msg = f"获取通知列表失败: {notification_data.get('retcode')} - {notification_data.get('message', 'Unknown error')}"
logger.error(error_msg)
notification_msg += error_msg + "\n"
except httpx.HTTPStatusError as e:
error_msg = f"获取通知列表HTTP错误: {e.response.status_code} - {e.response.text}"
logger.error(error_msg)
notification_msg += error_msg + "\n"
except httpx.RequestError as e:
error_msg = f"请求通知列表失败: {e}"
logger.error(error_msg)
notification_msg += error_msg + "\n"
except Exception as e:
error_msg = f"检查签到状态时出错: {e}"
logger.error(error_msg)
notification_msg += error_msg + "\n"
except KeyError as e:
# This catches missing required keys in account config
error_msg = f"账号配置缺少必需的键: {e}"
logger.error(error_msg)
notification_msg += f"❌ 账号配置错误: {error_msg}\n"
except Exception as e:
# Catch any other unexpected errors during account processing
error_msg = f"处理账号时发生未知错误: {e}"
logger.error(error_msg)
notification_msg += f"❌ 账号处理错误: {error_msg}\n"
if accounts_conf.index(config) < len(accounts_conf) - 1:
notification_msg += "\n---\n\n"
send_notifications(notification_msg, notification_settings)
logger.info("所有任务已经执行完毕!")

11
pyproject.toml Normal file
View File

@ -0,0 +1,11 @@
[project]
name = "mhyy"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"httpx>=0.28.1",
"pyyaml>=6.0.2",
"sentry-sdk>=2.32.0",
]

164
uv.lock Normal file
View File

@ -0,0 +1,164 @@
version = 1
revision = 2
requires-python = ">=3.11"
[[package]]
name = "anyio"
version = "4.9.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "idna" },
{ name = "sniffio" },
{ name = "typing-extensions", marker = "python_full_version < '3.13'" },
]
sdist = { url = "https://files.pythonhosted.org/packages/95/7d/4c1bd541d4dffa1b52bd83fb8527089e097a106fc90b467a7313b105f840/anyio-4.9.0.tar.gz", hash = "sha256:673c0c244e15788651a4ff38710fea9675823028a6f08a5eda409e0c9840a028", size = 190949, upload_time = "2025-03-17T00:02:54.77Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a1/ee/48ca1a7c89ffec8b6a0c5d02b89c305671d5ffd8d3c94acf8b8c408575bb/anyio-4.9.0-py3-none-any.whl", hash = "sha256:9f76d541cad6e36af7beb62e978876f3b41e3e04f2c1fbf0884604c0a9c4d93c", size = 100916, upload_time = "2025-03-17T00:02:52.713Z" },
]
[[package]]
name = "certifi"
version = "2025.6.15"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/73/f7/f14b46d4bcd21092d7d3ccef689615220d8a08fb25e564b65d20738e672e/certifi-2025.6.15.tar.gz", hash = "sha256:d747aa5a8b9bbbb1bb8c22bb13e22bd1f18e9796defa16bab421f7f7a317323b", size = 158753, upload_time = "2025-06-15T02:45:51.329Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/84/ae/320161bd181fc06471eed047ecce67b693fd7515b16d495d8932db763426/certifi-2025.6.15-py3-none-any.whl", hash = "sha256:2e0c7ce7cb5d8f8634ca55d2ba7e6ec2689a2fd6537d8dec1296a477a4910057", size = 157650, upload_time = "2025-06-15T02:45:49.977Z" },
]
[[package]]
name = "h11"
version = "0.16.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/01/ee/02a2c011bdab74c6fb3c75474d40b3052059d95df7e73351460c8588d963/h11-0.16.0.tar.gz", hash = "sha256:4e35b956cf45792e4caa5885e69fba00bdbc6ffafbfa020300e549b208ee5ff1", size = 101250, upload_time = "2025-04-24T03:35:25.427Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/04/4b/29cac41a4d98d144bf5f6d33995617b185d14b22401f75ca86f384e87ff1/h11-0.16.0-py3-none-any.whl", hash = "sha256:63cf8bbe7522de3bf65932fda1d9c2772064ffb3dae62d55932da54b31cb6c86", size = 37515, upload_time = "2025-04-24T03:35:24.344Z" },
]
[[package]]
name = "httpcore"
version = "1.0.9"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "h11" },
]
sdist = { url = "https://files.pythonhosted.org/packages/06/94/82699a10bca87a5556c9c59b5963f2d039dbd239f25bc2a63907a05a14cb/httpcore-1.0.9.tar.gz", hash = "sha256:6e34463af53fd2ab5d807f399a9b45ea31c3dfa2276f15a2c3f00afff6e176e8", size = 85484, upload_time = "2025-04-24T22:06:22.219Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/7e/f5/f66802a942d491edb555dd61e3a9961140fd64c90bce1eafd741609d334d/httpcore-1.0.9-py3-none-any.whl", hash = "sha256:2d400746a40668fc9dec9810239072b40b4484b640a8c38fd654a024c7a1bf55", size = 78784, upload_time = "2025-04-24T22:06:20.566Z" },
]
[[package]]
name = "httpx"
version = "0.28.1"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "anyio" },
{ name = "certifi" },
{ name = "httpcore" },
{ name = "idna" },
]
sdist = { url = "https://files.pythonhosted.org/packages/b1/df/48c586a5fe32a0f01324ee087459e112ebb7224f646c0b5023f5e79e9956/httpx-0.28.1.tar.gz", hash = "sha256:75e98c5f16b0f35b567856f597f06ff2270a374470a5c2392242528e3e3e42fc", size = 141406, upload_time = "2024-12-06T15:37:23.222Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/2a/39/e50c7c3a983047577ee07d2a9e53faf5a69493943ec3f6a384bdc792deb2/httpx-0.28.1-py3-none-any.whl", hash = "sha256:d909fcccc110f8c7faf814ca82a9a4d816bc5a6dbfea25d6591d6985b8ba59ad", size = 73517, upload_time = "2024-12-06T15:37:21.509Z" },
]
[[package]]
name = "idna"
version = "3.10"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/f1/70/7703c29685631f5a7590aa73f1f1d3fa9a380e654b86af429e0934a32f7d/idna-3.10.tar.gz", hash = "sha256:12f65c9b470abda6dc35cf8e63cc574b1c52b11df2c86030af0ac09b01b13ea9", size = 190490, upload_time = "2024-09-15T18:07:39.745Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/76/c6/c88e154df9c4e1a2a66ccf0005a88dfb2650c1dffb6f5ce603dfbd452ce3/idna-3.10-py3-none-any.whl", hash = "sha256:946d195a0d259cbba61165e88e65941f16e9b36ea6ddb97f00452bae8b1287d3", size = 70442, upload_time = "2024-09-15T18:07:37.964Z" },
]
[[package]]
name = "mhyy"
version = "0.1.0"
source = { virtual = "." }
dependencies = [
{ name = "httpx" },
{ name = "pyyaml" },
{ name = "sentry-sdk" },
]
[package.metadata]
requires-dist = [
{ name = "httpx", specifier = ">=0.28.1" },
{ name = "pyyaml", specifier = ">=6.0.2" },
{ name = "sentry-sdk", specifier = ">=2.32.0" },
]
[[package]]
name = "pyyaml"
version = "6.0.2"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/54/ed/79a089b6be93607fa5cdaedf301d7dfb23af5f25c398d5ead2525b063e17/pyyaml-6.0.2.tar.gz", hash = "sha256:d584d9ec91ad65861cc08d42e834324ef890a082e591037abe114850ff7bbc3e", size = 130631, upload_time = "2024-08-06T20:33:50.674Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/f8/aa/7af4e81f7acba21a4c6be026da38fd2b872ca46226673c89a758ebdc4fd2/PyYAML-6.0.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:cc1c1159b3d456576af7a3e4d1ba7e6924cb39de8f67111c735f6fc832082774", size = 184612, upload_time = "2024-08-06T20:32:03.408Z" },
{ url = "https://files.pythonhosted.org/packages/8b/62/b9faa998fd185f65c1371643678e4d58254add437edb764a08c5a98fb986/PyYAML-6.0.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1e2120ef853f59c7419231f3bf4e7021f1b936f6ebd222406c3b60212205d2ee", size = 172040, upload_time = "2024-08-06T20:32:04.926Z" },
{ url = "https://files.pythonhosted.org/packages/ad/0c/c804f5f922a9a6563bab712d8dcc70251e8af811fce4524d57c2c0fd49a4/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d225db5a45f21e78dd9358e58a98702a0302f2659a3c6cd320564b75b86f47c", size = 736829, upload_time = "2024-08-06T20:32:06.459Z" },
{ url = "https://files.pythonhosted.org/packages/51/16/6af8d6a6b210c8e54f1406a6b9481febf9c64a3109c541567e35a49aa2e7/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5ac9328ec4831237bec75defaf839f7d4564be1e6b25ac710bd1a96321cc8317", size = 764167, upload_time = "2024-08-06T20:32:08.338Z" },
{ url = "https://files.pythonhosted.org/packages/75/e4/2c27590dfc9992f73aabbeb9241ae20220bd9452df27483b6e56d3975cc5/PyYAML-6.0.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ad2a3decf9aaba3d29c8f537ac4b243e36bef957511b4766cb0057d32b0be85", size = 762952, upload_time = "2024-08-06T20:32:14.124Z" },
{ url = "https://files.pythonhosted.org/packages/9b/97/ecc1abf4a823f5ac61941a9c00fe501b02ac3ab0e373c3857f7d4b83e2b6/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ff3824dc5261f50c9b0dfb3be22b4567a6f938ccce4587b38952d85fd9e9afe4", size = 735301, upload_time = "2024-08-06T20:32:16.17Z" },
{ url = "https://files.pythonhosted.org/packages/45/73/0f49dacd6e82c9430e46f4a027baa4ca205e8b0a9dce1397f44edc23559d/PyYAML-6.0.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:797b4f722ffa07cc8d62053e4cff1486fa6dc094105d13fea7b1de7d8bf71c9e", size = 756638, upload_time = "2024-08-06T20:32:18.555Z" },
{ url = "https://files.pythonhosted.org/packages/22/5f/956f0f9fc65223a58fbc14459bf34b4cc48dec52e00535c79b8db361aabd/PyYAML-6.0.2-cp311-cp311-win32.whl", hash = "sha256:11d8f3dd2b9c1207dcaf2ee0bbbfd5991f571186ec9cc78427ba5bd32afae4b5", size = 143850, upload_time = "2024-08-06T20:32:19.889Z" },
{ url = "https://files.pythonhosted.org/packages/ed/23/8da0bbe2ab9dcdd11f4f4557ccaf95c10b9811b13ecced089d43ce59c3c8/PyYAML-6.0.2-cp311-cp311-win_amd64.whl", hash = "sha256:e10ce637b18caea04431ce14fabcf5c64a1c61ec9c56b071a4b7ca131ca52d44", size = 161980, upload_time = "2024-08-06T20:32:21.273Z" },
{ url = "https://files.pythonhosted.org/packages/86/0c/c581167fc46d6d6d7ddcfb8c843a4de25bdd27e4466938109ca68492292c/PyYAML-6.0.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:c70c95198c015b85feafc136515252a261a84561b7b1d51e3384e0655ddf25ab", size = 183873, upload_time = "2024-08-06T20:32:25.131Z" },
{ url = "https://files.pythonhosted.org/packages/a8/0c/38374f5bb272c051e2a69281d71cba6fdb983413e6758b84482905e29a5d/PyYAML-6.0.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:ce826d6ef20b1bc864f0a68340c8b3287705cae2f8b4b1d932177dcc76721725", size = 173302, upload_time = "2024-08-06T20:32:26.511Z" },
{ url = "https://files.pythonhosted.org/packages/c3/93/9916574aa8c00aa06bbac729972eb1071d002b8e158bd0e83a3b9a20a1f7/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f71ea527786de97d1a0cc0eacd1defc0985dcf6b3f17bb77dcfc8c34bec4dc5", size = 739154, upload_time = "2024-08-06T20:32:28.363Z" },
{ url = "https://files.pythonhosted.org/packages/95/0f/b8938f1cbd09739c6da569d172531567dbcc9789e0029aa070856f123984/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:9b22676e8097e9e22e36d6b7bda33190d0d400f345f23d4065d48f4ca7ae0425", size = 766223, upload_time = "2024-08-06T20:32:30.058Z" },
{ url = "https://files.pythonhosted.org/packages/b9/2b/614b4752f2e127db5cc206abc23a8c19678e92b23c3db30fc86ab731d3bd/PyYAML-6.0.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:80bab7bfc629882493af4aa31a4cfa43a4c57c83813253626916b8c7ada83476", size = 767542, upload_time = "2024-08-06T20:32:31.881Z" },
{ url = "https://files.pythonhosted.org/packages/d4/00/dd137d5bcc7efea1836d6264f049359861cf548469d18da90cd8216cf05f/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:0833f8694549e586547b576dcfaba4a6b55b9e96098b36cdc7ebefe667dfed48", size = 731164, upload_time = "2024-08-06T20:32:37.083Z" },
{ url = "https://files.pythonhosted.org/packages/c9/1f/4f998c900485e5c0ef43838363ba4a9723ac0ad73a9dc42068b12aaba4e4/PyYAML-6.0.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8b9c7197f7cb2738065c481a0461e50ad02f18c78cd75775628afb4d7137fb3b", size = 756611, upload_time = "2024-08-06T20:32:38.898Z" },
{ url = "https://files.pythonhosted.org/packages/df/d1/f5a275fdb252768b7a11ec63585bc38d0e87c9e05668a139fea92b80634c/PyYAML-6.0.2-cp312-cp312-win32.whl", hash = "sha256:ef6107725bd54b262d6dedcc2af448a266975032bc85ef0172c5f059da6325b4", size = 140591, upload_time = "2024-08-06T20:32:40.241Z" },
{ url = "https://files.pythonhosted.org/packages/0c/e8/4f648c598b17c3d06e8753d7d13d57542b30d56e6c2dedf9c331ae56312e/PyYAML-6.0.2-cp312-cp312-win_amd64.whl", hash = "sha256:7e7401d0de89a9a855c839bc697c079a4af81cf878373abd7dc625847d25cbd8", size = 156338, upload_time = "2024-08-06T20:32:41.93Z" },
{ url = "https://files.pythonhosted.org/packages/ef/e3/3af305b830494fa85d95f6d95ef7fa73f2ee1cc8ef5b495c7c3269fb835f/PyYAML-6.0.2-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:efdca5630322a10774e8e98e1af481aad470dd62c3170801852d752aa7a783ba", size = 181309, upload_time = "2024-08-06T20:32:43.4Z" },
{ url = "https://files.pythonhosted.org/packages/45/9f/3b1c20a0b7a3200524eb0076cc027a970d320bd3a6592873c85c92a08731/PyYAML-6.0.2-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:50187695423ffe49e2deacb8cd10510bc361faac997de9efef88badc3bb9e2d1", size = 171679, upload_time = "2024-08-06T20:32:44.801Z" },
{ url = "https://files.pythonhosted.org/packages/7c/9a/337322f27005c33bcb656c655fa78325b730324c78620e8328ae28b64d0c/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ffe8360bab4910ef1b9e87fb812d8bc0a308b0d0eef8c8f44e0254ab3b07133", size = 733428, upload_time = "2024-08-06T20:32:46.432Z" },
{ url = "https://files.pythonhosted.org/packages/a3/69/864fbe19e6c18ea3cc196cbe5d392175b4cf3d5d0ac1403ec3f2d237ebb5/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:17e311b6c678207928d649faa7cb0d7b4c26a0ba73d41e99c4fff6b6c3276484", size = 763361, upload_time = "2024-08-06T20:32:51.188Z" },
{ url = "https://files.pythonhosted.org/packages/04/24/b7721e4845c2f162d26f50521b825fb061bc0a5afcf9a386840f23ea19fa/PyYAML-6.0.2-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:70b189594dbe54f75ab3a1acec5f1e3faa7e8cf2f1e08d9b561cb41b845f69d5", size = 759523, upload_time = "2024-08-06T20:32:53.019Z" },
{ url = "https://files.pythonhosted.org/packages/2b/b2/e3234f59ba06559c6ff63c4e10baea10e5e7df868092bf9ab40e5b9c56b6/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_aarch64.whl", hash = "sha256:41e4e3953a79407c794916fa277a82531dd93aad34e29c2a514c2c0c5fe971cc", size = 726660, upload_time = "2024-08-06T20:32:54.708Z" },
{ url = "https://files.pythonhosted.org/packages/fe/0f/25911a9f080464c59fab9027482f822b86bf0608957a5fcc6eaac85aa515/PyYAML-6.0.2-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:68ccc6023a3400877818152ad9a1033e3db8625d899c72eacb5a668902e4d652", size = 751597, upload_time = "2024-08-06T20:32:56.985Z" },
{ url = "https://files.pythonhosted.org/packages/14/0d/e2c3b43bbce3cf6bd97c840b46088a3031085179e596d4929729d8d68270/PyYAML-6.0.2-cp313-cp313-win32.whl", hash = "sha256:bc2fa7c6b47d6bc618dd7fb02ef6fdedb1090ec036abab80d4681424b84c1183", size = 140527, upload_time = "2024-08-06T20:33:03.001Z" },
{ url = "https://files.pythonhosted.org/packages/fa/de/02b54f42487e3d3c6efb3f89428677074ca7bf43aae402517bc7cca949f3/PyYAML-6.0.2-cp313-cp313-win_amd64.whl", hash = "sha256:8388ee1976c416731879ac16da0aff3f63b286ffdd57cdeb95f3f2e085687563", size = 156446, upload_time = "2024-08-06T20:33:04.33Z" },
]
[[package]]
name = "sentry-sdk"
version = "2.32.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "certifi" },
{ name = "urllib3" },
]
sdist = { url = "https://files.pythonhosted.org/packages/10/59/eb90c45cb836cf8bec973bba10230ddad1c55e2b2e9ffa9d7d7368948358/sentry_sdk-2.32.0.tar.gz", hash = "sha256:9016c75d9316b0f6921ac14c8cd4fb938f26002430ac5be9945ab280f78bec6b", size = 334932, upload_time = "2025-06-27T08:10:02.89Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/01/a1/fc4856bd02d2097324fb7ce05b3021fb850f864b83ca765f6e37e92ff8ca/sentry_sdk-2.32.0-py2.py3-none-any.whl", hash = "sha256:6cf51521b099562d7ce3606da928c473643abe99b00ce4cb5626ea735f4ec345", size = 356122, upload_time = "2025-06-27T08:10:01.424Z" },
]
[[package]]
name = "sniffio"
version = "1.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/a2/87/a6771e1546d97e7e041b6ae58d80074f81b7d5121207425c964ddf5cfdbd/sniffio-1.3.1.tar.gz", hash = "sha256:f4324edc670a0f49750a81b895f35c3adb843cca46f0530f79fc1babb23789dc", size = 20372, upload_time = "2024-02-25T23:20:04.057Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/e9/44/75a9c9421471a6c4805dbf2356f7c181a29c1879239abab1ea2cc8f38b40/sniffio-1.3.1-py3-none-any.whl", hash = "sha256:2f6da418d1f1e0fddd844478f41680e794e6051915791a034ff65e5f100525a2", size = 10235, upload_time = "2024-02-25T23:20:01.196Z" },
]
[[package]]
name = "typing-extensions"
version = "4.14.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz", hash = "sha256:8676b788e32f02ab42d9e7c61324048ae4c6d844a399eebace3d4979d75ceef4", size = 107423, upload_time = "2025-06-02T14:52:11.399Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/69/e0/552843e0d356fbb5256d21449fa957fa4eff3bbc135a74a691ee70c7c5da/typing_extensions-4.14.0-py3-none-any.whl", hash = "sha256:a1514509136dd0b477638fc68d6a91497af5076466ad0fa6c338e44e359944af", size = 43839, upload_time = "2025-06-02T14:52:10.026Z" },
]
[[package]]
name = "urllib3"
version = "2.5.0"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/15/22/9ee70a2574a4f4599c47dd506532914ce044817c7752a79b6a51286319bc/urllib3-2.5.0.tar.gz", hash = "sha256:3fc47733c7e419d4bc3f6b3dc2b4f890bb743906a30d56ba4a5bfa4bbff92760", size = 393185, upload_time = "2025-06-18T14:07:41.644Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/a7/c2/fe1e52489ae3122415c51f387e221dd0773709bad6c6cdaa599e8a2c5185/urllib3-2.5.0-py3-none-any.whl", hash = "sha256:e6b01673c0fa6a13e374b50871808eb3bf7046c4b125b216f6bf1cc604cff0dc", size = 129795, upload_time = "2025-06-18T14:07:40.39Z" },
]