diff --git a/nodebb_sdk.py b/nodebb_sdk.py new file mode 100644 index 0000000..84af2ea --- /dev/null +++ b/nodebb_sdk.py @@ -0,0 +1,149 @@ +import requests +from typing import Dict, Any +from loguru import logger + + +class NodeBBAPIError(Exception): + """自定义 NodeBB API 错误异常""" + def __init__(self, message: str, status_code: int = None, response_text: str = None): + super().__init__(message) + self.status_code = status_code + self.response_text = response_text + + def __str__(self): + if self.status_code: + return f"{self.status_code}: {super().__str__()} | Response: {self.response_text}" + return super().__str__() + +class NodeBBClient: + """ + 一个用于与 NodeBB v3 API 交互的客户端 SDK。 + """ + def __init__(self, base_url: str, api_token: str): + """ + 初始化 NodeBB 客户端。 + + :param base_url: 你的 NodeBB 论坛 URL (例如: "https://forum.example.com") + :param api_token: 你在 NodeBB 后台生成的 API Bearer Token + """ + if not base_url or not api_token: + raise ValueError("base_url 和 api_token 不能为空") + + # 确保 URL 末尾没有斜杠,方便拼接 + self.base_url = base_url.rstrip('/') + self._headers = { + "Authorization": f"Bearer {api_token}", + "Content-Type": "application/json" + } + + def _post(self, endpoint: str, payload: Dict[str, Any]) -> Dict[str, Any]: + """ + 一个内部辅助方法,用于发送 POST 请求。 + + :param endpoint: API 的端点 (例如: "/api/v3/topics") + :param payload: 要发送的 JSON 数据 + :return: API 响应的 JSON 字典 + :raises NodeBBAPIError: 如果 API 请求失败 + """ + api_url = f"{self.base_url}{endpoint}" + try: + response = requests.post(api_url, headers=self._headers, json=payload, timeout=15) + # 检查 HTTP 状态码,如果不是 2xx,则抛出异常 + response.raise_for_status() + return response.json() + except requests.exceptions.HTTPError as e: + # 捕获 HTTP 错误,并用我们的自定义异常包装它 + raise NodeBBAPIError( + message=f"API 请求返回了一个错误", + status_code=e.response.status_code, + response_text=e.response.text + ) from e + except requests.exceptions.RequestException as e: + # 捕获其他请求相关的错误 (如网络问题) + raise NodeBBAPIError(f"请求失败: {e}") from e + + def create_topic(self, cid: int, title: str, content: str) -> Dict[str, Any]: + """ + 在指定板块创建一个新的主题 (发帖)。 + + :param cid: 板块 ID (Category ID) + :param title: 主题标题 + :param content: 主题内容 (支持 Markdown) + :return: 成功时返回 API 响应的 JSON 数据 + :raises NodeBBAPIError: 如果 API 请求失败 + """ + logger.info(f"SDK: 正在向板块 (cid={cid}) 发布新主题: '{title}'...") + payload = { + "cid": cid, + "title": title, + "content": content + } + response_data = self._post("/api/v3/topics", payload) + + logger.success("✅ SDK: 主题发布成功!") + logger.info(f" - 主题 ID (tid): {response_data['response']['tid']}") + logger.info(f" - 访问链接: {self.base_url}/topic/{response_data['response']['slug']}") + return response_data['response'] + + def reply_to_topic(self, tid: int, content: str) -> Dict[str, Any]: + """ + 回复一个已存在的主题 (跟帖)。 + + :param tid: 主题 ID (Topic ID) + :param content: 回复内容 (支持 Markdown) + :return: 成功时返回 API 响应的 JSON 数据 + :raises NodeBBAPIError: 如果 API 请求失败 + """ + logger.info(f"SDK: 正在回复主题 (tid={tid})...") + endpoint = f"/api/v3/topics/{tid}" + payload = { + "content": content + } + response_data = self._post(endpoint, payload) + + logger.success("✅ SDK: 回复成功!") + logger.info(f" - 新帖子 ID (pid): {response_data['response']['pid']}") + logger.info(f" - 访问链接: {self.base_url}/post/{response_data['response']['pid']}") + return response_data['response'] + +# --- 以下是如何使用这个 SDK 的示例 --- +if __name__ == "__main__": + # --- 配置区 --- + NODEBB_FORUM_URL = "https://your-nodebb-forum.com" # 你的 NodeBB 论坛 URL + NODEBB_API_TOKEN = "your_api_bearer_token_here" # 你的 API Bearer Token + # --- 配置区结束 --- + + # 1. 检查配置是否已填写 + if "your-nodebb-forum.com" in NODEBB_FORUM_URL or "your_api_bearer_token_here" in NODEBB_API_TOKEN: + logger.error("❌ 请先在脚本中修改 'NODEBB_FORUM_URL' 和 'NODEBB_API_TOKEN' 的值。") + else: + # 2. 创建 NodeBBClient 实例 + try: + client = NodeBBClient(base_url=NODEBB_FORUM_URL, api_token=NODEBB_API_TOKEN) + + # --- 使用示例:发帖并立即回复 --- + + # 3. 定义帖子内容 + category_id = 2 # 目标板块ID + topic_title = "来自 NodeBB SDK 的测试" + topic_content = "这是一个使用 `NodeBBClient` 类发送的主题。代码结构更清晰了!" + + # 4. 使用 try...except 来捕获潜在的 API 错误 + try: + # 5. 调用 create_topic 方法 + new_topic = client.create_topic(cid=category_id, title=topic_title, content=topic_content) + + # 6. 从成功的响应中获取新主题的ID + new_topic_id = new_topic['tid'] + + logger.success(f"\n新主题已创建 (tid={new_topic_id}),现在对其进行回复...") + + # 7. 调用 reply_to_topic 方法 + client.reply_to_topic(tid=new_topic_id, content="这是来自 SDK 的自动沙发回复!🎉") + + except NodeBBAPIError as e: + logger.error("\n--- 操作失败 ---") + logger.error(f"❌ 发生 API 错误: {e}") + + except ValueError as e: + logger.error(f"❌ 初始化客户端失败: {e}") \ No newline at end of file