From ba3dc95de62332601397c3c5b871b24950c487fe Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E?=
Date: Fri, 14 Feb 2025 18:21:42 +0800
Subject: [PATCH 1/2] =?UTF-8?q?(feat)=E4=BD=BF=E7=94=A8aiohttp=E6=9D=A5?=
=?UTF-8?q?=E4=BC=98=E5=8C=96emby=5Fapi?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
core/emby_api.py | 144 ++++++++++++++++++++++-------------------------
1 file changed, 66 insertions(+), 78 deletions(-)
diff --git a/core/emby_api.py b/core/emby_api.py
index b15ff0c..71abe8c 100644
--- a/core/emby_api.py
+++ b/core/emby_api.py
@@ -1,5 +1,6 @@
import logging
-import requests
+import aiohttp
+import asyncio
logger = logging.getLogger(__name__)
@@ -22,7 +23,7 @@ def __init__(self, emby_url: str, emby_api: str, timeout: int = 10):
f"EmbyApi initialized with URL: {self.base_url}, timeout: {self.timeout}"
)
- def _request(self, method: str, path: str, data=None, params=None):
+ async def _request(self, method: str, path: str, data=None, params=None):
"""
内部通用请求方法,用于简化 GET / POST 等请求的异常处理、状态码检查等。
@@ -46,42 +47,28 @@ def _request(self, method: str, path: str, data=None, params=None):
logger.debug(
f"Making {method} request to {url} with params: {params}, data: {data}"
)
- try:
- if method.upper() == "GET":
- response = requests.get(
- url, params=params, timeout=self.timeout, headers=headers
- )
- elif method.upper() == "POST":
- response = requests.post(
- url, params=params, json=data, timeout=self.timeout, headers=headers
+ async with aiohttp.ClientSession(
+ timeout=aiohttp.ClientTimeout(total=self.timeout)
+ ) as session:
+ try:
+ async with session.request(
+ method, url, params=params, json=data, headers=headers
+ ) as response:
+ if response.status != 200:
+ raise Exception(
+ f"Request failed with status code {response.status}"
+ )
+ return await response.json()
+ except aiohttp.ClientError as e:
+ logger.error(
+ f"An error occurred while requesting Emby: {e}", exc_info=True
)
- else:
- raise Exception(f"暂不支持的 HTTP 方法: {method}")
+ raise Exception(f"请求 Emby 时发生错误: {str(e)}")
+ except asyncio.TimeoutError:
+ logger.error("Request to Emby server timed out", exc_info=True)
+ raise Exception("请求 Emby 服务器超时,请稍后重试或检查网络连接。")
- except requests.exceptions.Timeout:
- # 超时异常,抛出中文提示
- logger.error("Request to Emby server timed out", exc_info=True)
- raise Exception("请求 Emby 服务器超时,请稍后重试或检查网络连接。")
- except requests.exceptions.ConnectionError as e:
- # 连接异常
- logger.error(f"Failed to connect to Emby server: {e}", exc_info=True)
- raise Exception(f"无法连接到 Emby 服务器: {str(e)}")
- except requests.exceptions.RequestException as e:
- # 其他 requests 异常
- logger.error(
- f"An unknown error occurred while requesting Emby: {e}", exc_info=True
- )
- raise Exception(f"请求 Emby 时发生未知错误: {str(e)}")
-
- try:
- response.raise_for_status()
- logger.debug(f"Request successful, status code: {response.status_code}")
- except Exception as e:
- logger.error(f"Emby API request failed: {e}", exc_info=True)
- raise Exception(f"Emby API 请求失败")
- return response.json() if response.text else None
-
- def get_user(self, emby_id: str):
+ async def get_user(self, emby_id: str):
"""
根据用户 ID 获取 Emby 用户信息。
:param emby_id: Emby 用户 ID
@@ -90,14 +77,14 @@ def get_user(self, emby_id: str):
path = f"/emby/Users/{emby_id}"
logger.info(f"Getting user with Emby ID: {emby_id}")
try:
- return self._request("GET", path)
+ return await self._request("GET", path)
except Exception as e:
logger.error(
f"Failed to get user with Emby ID {emby_id}: {e}", exc_info=True
)
raise
- def create_user(self, name: str):
+ async def create_user(self, name: str):
"""
在 Emby 中创建新用户。
:param name: 用户名
@@ -107,12 +94,12 @@ def create_user(self, name: str):
data = {"Name": name, "HasPassword": False}
logger.info(f"Creating user with name: {name}")
try:
- return self._request("POST", path, data=data)
+ return await self._request("POST", path, data=data)
except Exception as e:
logger.error(f"Failed to create user with name {name}: {e}", exc_info=True)
raise
- def ban_user(self, emby_id: str):
+ async def ban_user(self, emby_id: str):
"""
禁用 Emby 用户:设置其 Policy,使其无法登录或观看。
:param emby_id: Emby 用户 ID
@@ -144,14 +131,14 @@ def ban_user(self, emby_id: str):
}
logger.info(f"Banning user with Emby ID: {emby_id}")
try:
- return self.update_user_policy(emby_id, data)
+ return await self.update_user_policy(emby_id, data)
except Exception as e:
logger.error(
f"Failed to ban user with Emby ID {emby_id}: {e}", exc_info=True
)
raise
- def set_default_policy(self, emby_id: str):
+ async def set_default_policy(self, emby_id: str):
"""
取消禁用或为新建用户设置默认权限 Policy。
:param emby_id: Emby 用户 ID
@@ -183,7 +170,7 @@ def set_default_policy(self, emby_id: str):
}
logger.info(f"Setting default policy for user with Emby ID: {emby_id}")
try:
- return self.update_user_policy(emby_id, data)
+ return await self.update_user_policy(emby_id, data)
except Exception as e:
logger.error(
f"Failed to set default policy for user with Emby ID {emby_id}: {e}",
@@ -191,7 +178,7 @@ def set_default_policy(self, emby_id: str):
)
raise
- def update_user_policy(self, emby_id: str, policy_data: dict):
+ async def update_user_policy(self, emby_id: str, policy_data: dict):
"""
更新 Emby 用户的 policy 设置,如是否禁用、并发数等。
:param emby_id: Emby 用户 ID
@@ -203,7 +190,7 @@ def update_user_policy(self, emby_id: str, policy_data: dict):
f"Updating user policy for Emby ID: {emby_id} with data: {policy_data}"
)
try:
- return self._request("POST", path, data=policy_data)
+ return await self._request("POST", path, data=policy_data)
except Exception as e:
logger.error(
f"Failed to update user policy for Emby ID {emby_id}: {e}",
@@ -211,7 +198,7 @@ def update_user_policy(self, emby_id: str, policy_data: dict):
)
raise
- def reset_user_password(self, emby_id: str):
+ async def reset_user_password(self, emby_id: str):
"""
重置用户密码(让 Emby 忘记当前密码,此后需要重新设置新密码)。
:param emby_id: Emby 用户 ID
@@ -221,7 +208,7 @@ def reset_user_password(self, emby_id: str):
data = {"ResetPassword": True}
logger.info(f"Resetting password for user with Emby ID: {emby_id}")
try:
- return self._request("POST", path, data=data)
+ return await self._request("POST", path, data=data)
except Exception as e:
logger.error(
f"Failed to reset password for user with Emby ID {emby_id}: {e}",
@@ -229,7 +216,7 @@ def reset_user_password(self, emby_id: str):
)
raise
- def set_user_password(self, emby_id: str, new_pass: str):
+ async def set_user_password(self, emby_id: str, new_pass: str):
"""
设置指定 Emby 用户的新密码。
:param emby_id: Emby 用户 ID
@@ -240,7 +227,7 @@ def set_user_password(self, emby_id: str, new_pass: str):
data = {"ResetPassword": False, "CurrentPw": "", "NewPw": new_pass}
logger.info(f"Setting password for user with Emby ID: {emby_id}")
try:
- return self._request("POST", path, data=data)
+ return await self._request("POST", path, data=data)
except Exception as e:
logger.error(
f"Failed to set password for user with Emby ID {emby_id}: {e}",
@@ -248,7 +235,7 @@ def set_user_password(self, emby_id: str, new_pass: str):
)
raise
- def check_emby_site(self) -> bool:
+ async def check_emby_site(self) -> bool:
"""
检查 Emby 是否可用,仅做简单的 200 检查。
:return: 若状态码为 200 则返回 True,否则抛出异常或返回 False
@@ -256,15 +243,13 @@ def check_emby_site(self) -> bool:
path = "/emby/System/Info"
logger.info("Checking Emby site availability")
try:
- self._request("GET", path)
+ await self._request("GET", path)
return True
except Exception as e:
logger.warning(f"Emby site check failed: {e}", exc_info=True)
- # 这里可以选择记录日志,或者返回 False 让上层自己判断
- # raise 或者 return False 看业务需求
return False
- def count(self):
+ async def count(self):
"""
获取 Emby 中影视的数量汇总。
:return: 包含影视数量信息的 JSON
@@ -272,7 +257,7 @@ def count(self):
path = "/emby/Items/Counts"
logger.info("Getting Emby item counts")
try:
- return self._request("GET", path)
+ return await self._request("GET", path)
except Exception as e:
logger.error(f"Failed to get Emby item counts: {e}", exc_info=True)
raise
@@ -296,7 +281,7 @@ def __init__(self, api_url: str, api_key: str = "", timeout: int = 10):
f"EmbyRouterAPI initialized with URL: {self.api_url}, timeout: {self.timeout}"
)
- def call_api(self, path: str):
+ async def call_api(self, path: str):
"""
路由API通用请求方法。
:param path: API路径
@@ -305,54 +290,57 @@ def call_api(self, path: str):
url = f"{self.api_url}{path}"
headers = {"Authorization": f"Bearer {self.api_key}"} if self.api_key else {}
logger.debug(f"Calling API at {url}")
- try:
- response = requests.get(url, headers=headers, timeout=self.timeout)
- response.raise_for_status() # 如果状态码非 200-299,自动抛出异常
- return response.json()
- except requests.exceptions.Timeout:
- logger.error("Request to router service timed out", exc_info=True)
- raise Exception("请求路由服务超时,请稍后重试或检查网络连接。")
- except requests.exceptions.ConnectionError as e:
- logger.error(f"Failed to connect to router service: {e}", exc_info=True)
- raise Exception(f"无法连接到路由服务: {str(e)}")
- except requests.exceptions.RequestException as e:
- logger.error(
- f"An unknown error occurred while requesting router service: {e}",
- exc_info=True,
- )
- raise Exception(f"请求路由服务时发生错误: {str(e)}")
+ async with aiohttp.ClientSession(
+ timeout=aiohttp.ClientTimeout(total=self.timeout)
+ ) as session:
+ try:
+ async with session.get(url, headers=headers) as response:
+ if response.status != 200:
+ raise Exception(
+ f"Request failed with status code {response.status}"
+ )
+ return await response.json()
+ except aiohttp.ClientError as e:
+ logger.error(
+ f"An error occurred while requesting router service: {e}",
+ exc_info=True,
+ )
+ raise Exception(f"请求路由服务时发生错误: {str(e)}")
+ except asyncio.TimeoutError:
+ logger.error("Request to router service timed out", exc_info=True)
+ raise Exception("请求路由服务超时,请稍后重试或检查网络连接。")
- def query_all_route(self):
+ async def query_all_route(self):
"""
获取所有可用线路。
"""
logger.info("Querying all routes")
try:
- return self.call_api("/api/route")
+ return await self.call_api("/api/route")
except Exception as e:
logger.error(f"Failed to query all routes: {e}", exc_info=True)
raise
- def query_user_route(self, user_id: str):
+ async def query_user_route(self, user_id: str):
"""
获取指定用户当前所选的线路信息。
"""
logger.info(f"Querying user route for user ID: {user_id}")
try:
- return self.call_api(f"/api/route/{user_id}")
+ return await self.call_api(f"/api/route/{user_id}")
except Exception as e:
logger.error(
f"Failed to query user route for user ID {user_id}: {e}", exc_info=True
)
raise
- def update_user_route(self, user_id: str, new_index: str):
+ async def update_user_route(self, user_id: str, new_index: str):
"""
更新用户当前所使用的线路。
"""
logger.info(f"Updating user route for user ID: {user_id} to index: {new_index}")
try:
- return self.call_api(f"/api/route/{user_id}/{new_index}")
+ return await self.call_api(f"/api/route/{user_id}/{new_index}")
except Exception as e:
logger.error(
f"Failed to update user route for user ID {user_id} to index {new_index}: {e}",
From c59f04ae35664328614e2ee4efa489a986f3f6b1 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=9D=8E?=
Date: Mon, 17 Feb 2025 22:38:13 +0800
Subject: [PATCH 2/2] =?UTF-8?q?(feat)=E5=9B=A0#16pr=E4=B8=AD=E5=BC=95?=
=?UTF-8?q?=E5=85=A5=E4=BA=86uv=E5=8C=85=E7=AE=A1=E7=90=86=EF=BC=8C?=
=?UTF-8?q?=E5=9C=A8readme=E6=96=87=E4=BB=B6=E4=B8=AD=E4=BF=AE=E6=94=B9?=
=?UTF-8?q?=E8=BF=90=E8=A1=8C=E9=A1=B9=E7=9B=AE=E9=83=A8=E5=88=86?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
readme.md | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/readme.md b/readme.md
index 382e4d0..87dc3b8 100644
--- a/readme.md
+++ b/readme.md
@@ -16,13 +16,14 @@
- 限时或限量开放注册。
### 安装及运行
+本项目用[uv](https://github.com/astral-sh/uv)管理及运行
```bash
git clone https://github.com/embyplus/embyBot
cp .env.example .env
vim .env
-python3 -m pip install -r requirements.txt
-python3 app.py
+uv sync
+uv run app.py
```
### 配置环境变量
@@ -77,4 +78,4 @@ python3 app.py
### Thanks
- [Pyrogram](https://docs.pyrogram.org/) - Telegram API for Python
- [SQLAlchemy](https://www.sqlalchemy.org/) - Python SQL Toolkit and Object-Relational Mapping
-- [Emby服管理bot by小草](https://github.com/xiaocao666tzh/EmbyBot)
\ No newline at end of file
+- [Emby服管理bot by小草](https://github.com/xiaocao666tzh/EmbyBot)