From 245ef74698637277cb367aedfcc98bb8e54bac1a Mon Sep 17 00:00:00 2001 From: LeonspaceX Date: Sat, 18 Oct 2025 17:32:46 +0800 Subject: [PATCH] first commit --- API文档.md | 855 -------------------------------------------------- LICENSE.txt | 21 ++ README.md | 1 + api_server.py | 218 +++++++++++-- 4 files changed, 209 insertions(+), 886 deletions(-) delete mode 100644 API文档.md create mode 100644 LICENSE.txt create mode 100644 README.md diff --git a/API文档.md b/API文档.md deleted file mode 100644 index de0719e..0000000 --- a/API文档.md +++ /dev/null @@ -1,855 +0,0 @@ -# Sycamore_whisper API 文档 - -** - -## 一、基础信息 - -| 项目 | 内容 | -| ---------- | ------------------------------------------------------------ | -| 响应格式 | 所有 API 响应均为 JSON 格式(除非特殊说明) | -| 成功状态码 | 通常为 200(请求成功)或 201(资源创建成功) | -| 权限说明 | 管理接口需在请求头中携带 Authorization: Bearer {ADMIN_TOKEN} 进行身份验证 | - -## 二、通用说明 - -1. 错误响应均会附带明确错误信息,帮助定位问题; - -2. 图片上传大小限制为 10MB,支持常见图片格式(png, jpg, jpeg, gif, webp); -3. 分页接口默认按 “ID 降序” 排列数据,未指定页码时默认返回第 1 页; -4. 涉及 “帖子 ID”“评论 ID”“举报 ID” 的接口,若 ID 不存在则返回 404 错误。 - -## 三、公共接口(无需管理员权限) - -### 3.1 提交帖子 - -- **URL**: /post - -- **请求方法**: POST - -- **功能描述**: 提交新帖子内容,是否需要审核取决于当前系统审核模式 - -- **请求体(JSON)**: - -``` -{ - "content": "帖子内容" -} -``` - -- **成功响应(201 Created)**: - -``` -{ - "id": 1, // 新创建帖子的ID - "status": "Pending" // 状态:Pending(待审核)/ Pass(直接通过,无需审核) -} -``` - -- **错误响应**: - -- - 400 Bad Request:帖子内容为空 - -- - 403 Forbidden:内容包含违规关键词 - -### 3.2 帖子点赞 / 点踩 - -#### 3.2.1 帖子点赞 - -- **URL**: /up - -- **请求方法**: POST - -- **功能描述**: 给指定 ID 的帖子点赞 - -- **请求体(JSON)**: - -``` -{ - "id": 1 // 帖子ID(必填) -} -``` - -- **成功响应(200 OK)**: - -``` -{ - "status": "OK" -} -``` - -- **错误响应**: - -- - 400 Bad Request:缺少帖子 ID - -- - 404 Not Found:帖子不存在 - -#### 3.2.2 帖子点踩 - -- **URL**: /down - -- **请求方法**: POST - -- **请求体、成功响应、错误响应**:与 “帖子点赞” 接口完全一致 - -### 3.3 发布评论 - -- **URL**: /comment - -- **请求方法**: POST - -- **功能描述**: 给帖子添加顶级评论,或回复已有评论 - -- **请求体(JSON)**: - -``` -{ - "content": "评论内容", - "submission_id": 1, // 关联的帖子ID - "parent_comment_id": 0, // 父评论ID:0=顶级评论,非0=回复指定评论 - "nickname": "匿名用户" // 用户名 -} -``` - -- **成功响应(200 OK)**: - -``` -{ - "id": 1, // 新创建评论的ID - "status": "Pass" -} -``` - -- **错误响应**: - -- - 400 Bad Request:缺少 content/submission_id/parent_comment_id,或 parent_comment_id 指向不存在的评论 - -- - 403 Forbidden:评论内容包含违规关键词 - -- - 404 Not Found:关联的帖子不存在 - -### 3.4 图片上传与访问 - -#### 3.4.1 上传图片 - -- **URL**: /upload_pic - -- **请求方法**: POST - -- **请求体格式**: multipart/form-data,需包含名为file的图片文件 - -- **功能描述**: 上传图片至服务器,返回图片访问 URL - -- **成功响应(201 Created)**: - -``` -{ - "status": "OK", - "url": "/img/230615_abcd1.png" // 图片访问URL -} -``` - -- **错误响应**: - -- - 400 Bad Request:未上传文件、文件格式不支持(非图片)、文件大小超过 10MB - -#### 3.4.2 访问图片 - -- **URL**: /img/{filename} ({filename} 替换为图片文件名,如 230615_abcd1.png) - -- **请求方法**: GET - -- **功能描述**: 通过文件名直接访问图片 - -- **成功响应(200 OK)**: 返回图片文件(如 PNG/JPG 格式) - -- **错误响应**: 403 Forbidden:访问的文件格式不允许(非图片文件) - -### 3.5 举报与状态查询 - -#### 3.5.1 提交举报 - -- **URL**: /report - -- **请求方法**: POST - -- **功能描述**: 举报违规帖子,需提供举报标题和具体理由 - -- **请求体(JSON)**: - -``` -{ - "id": 1, // 被举报帖子的ID(必填) - "title": "举报标题(必填,如“帖子包含广告”)", - "content": "举报内容(必填,详细说明违规点)" -} -``` - -- **成功响应(201 Created)**: - -``` -{ - "id": 1, // 举报记录的ID - "status": "OK" -} -``` - -- **错误响应**: - -- - 400 Bad Request:缺少 id/title/content - -- - 404 Not Found:被举报的帖子不存在 - -#### 3.5.2 获取帖子状态 - -- **URL**: /get/post_state - -- **请求方法**: GET - -- **请求参数(Query)**: id=1(帖子 ID,必填) - -- **功能描述**: 查询指定帖子的审核状态 - -- **成功响应(200 OK)**: - -``` -{ - "status": "Approved" | "Rejected" | "Pending" | "Deleted or Not Found" - // Approved=已通过,Rejected=已拒绝,Pending=待审核,Deleted or Not Found=已删除或不存在 -} -``` - -- **错误响应**: 400 Bad Request:缺少帖子 ID - -#### 3.5.3 获取举报状态 - -- **URL**: /get/report_state - -- **请求方法**: GET - -- **请求参数(Query)**: id=1(举报 ID,必填) - -- **功能描述**: 查询指定举报的处理状态 - -- **成功响应(200 OK)**: - -``` -{ - "status": "Approved" | "Rejected" | "Pending" | "Deleted or Not Found" - // Approved=举报通过(帖子已处理),Rejected=举报驳回,Pending=待处理,Deleted or Not Found=举报记录已删除或不存在 -} -``` - -- **错误响应**: 400 Bad Request:缺少举报 ID - -### 3.6 帖子详情与列表 - -#### 3.6.1 获取帖子详情 - -- **URL**: /get/post_info - -- **请求方法**: GET - -- **请求参数(Query)**: id=1(帖子 ID,必填) - -- **功能描述**: 查询已通过审核的帖子详情(含点赞 / 点踩数) - -- **成功响应(200 OK)**: - -``` -{ - "id": 1, - "content": "帖子内容", - "upvotes": 10, // 点赞数 - "downvotes": 2 // 点踩数 -} -``` - -- **错误响应**: - -- - 400 Bad Request:缺少帖子 ID - -- - 404 Not Found:帖子不存在或未通过审核 - -#### 3.6.2 获取帖子评论 - -- **URL**: /get/comment - -- **请求方法**: GET - -- **请求参数(Query)**: id=1(帖子 ID,必填) - -- **功能描述**: 查询指定帖子的所有评论 - -- **成功响应(200 OK)**: - -``` -[ - { - "id": 1, - "nickname": "用户名", - "content": "评论内容", - "parent_comment_id": 0 // 0=顶级评论,非0=回复评论 - } -] -``` - -- **错误响应**: - -- - 400 Bad Request:缺少帖子 ID - -- - 404 Not Found:帖子不存在或未通过审核 - -#### 3.6.3 分页获取帖子列表 - -- **URL**: /get/10_info - -- **请求方法**: GET - -- **请求参数(Query)**: page=1(页码,可选,默认 1) - -- **功能描述**: 分页返回已通过审核的帖子,每页 10 条,按 ID 降序排列 - -- **成功响应(200 OK)**: - -``` -[ - { - "id": 1, - "content": "帖子内容", - "upvotes": 10, - "downvotes": 2 - } -] -``` - -### 3.7 其他公共接口 - -#### 3.7.1 获取统计信息 - -- **URL**: /get/statics - -- **请求方法**: GET - -- **功能描述**: 查询系统总数据统计(帖子、评论、图片数量) - -- **成功响应(200 OK)**: - -``` -{ - "posts": 100, // 总帖子数 - "comments": 500, // 总评论数 - "images": 50 // 总图片数 -} -``` - -#### 3.7.2 测试接口 - -- **URL**: /test - -- **请求方法**: GET / POST - -- **功能描述**: 验证 API 服务是否正常运行 - -- **成功响应(200 OK)**: API OK!!!(文本格式) - -#### 3.7.3 获取 API 信息 - -- **URL**: /get/api_info - -- **请求方法**: GET - -- **功能描述**: 获取 API 版本、开发者等信息 - -- **成功响应(200 OK)**: 返回包含 API 信息的 HTML 页面 - -#### 3.7.4 茶壶彩蛋 - -- **URL**: /get/teapot - -- **请求方法**: GET - -- **响应**: 418 I'm a teapot - -## 四、管理接口 - -### 4.1 审核模式管理 - -#### 4.1.1 切换审核模式 - -- **URL**: /admin/need_audit - -- **请求方法**: POST - -- **功能描述**: 开启 / 关闭帖子审核模式(开启后新帖子需审核才能通过) - -- **请求体(JSON)**: - -``` -{ - "need_audit": true | false // true=开启审核,false=关闭审核(必填) -} -``` - -- **成功响应(200 OK)**: - -``` -{ - "status": "OK" -} -``` - -- **错误响应**: - -- - 400 Bad Request:缺少 need_audit 参数,或参数不是布尔值 - -- - 401 Unauthorized / 403 Forbidden:管理员权限验证失败 - -#### 4.1.2 获取当前审核模式 - -- **URL**: /admin/get/need_audit - -- **请求方法**: GET - -- **功能描述**: 查询当前系统的帖子审核模式 - -- **成功响应(200 OK)**: - -``` -{ - "status": true | false // true=开启审核,false=关闭审核 -} -``` - -- **错误响应**: 401/403:管理员权限验证失败 - -### 4.2 帖子管理 - -#### 4.2.1 审核通过帖子 - -- **URL**: /admin/approve - -- **请求方法**: POST - -- **功能描述**: 将待审核的帖子标记为 “已通过” - -- **请求体(JSON)**: - -``` -{ - "id": 1 // 待审核帖子的ID(必填) -} -``` - -- **成功响应(200 OK)**: - -``` -{ - "status": "OK" -} -``` - -- **错误响应**: 401/403:权限失败;400:缺少 ID;404:帖子不存在 - -#### 4.2.2 拒绝帖子 - -- **URL**: /admin/disapprove - -- **请求方法**: POST - -- **请求体、成功响应、错误响应**:与 “审核通过帖子” 接口一致 - -#### 4.2.3 重新审核帖子 - -- **URL**: /admin/reaudit - -- **请求方法**: POST - -- **功能描述**: 将已通过审核的帖子重新标记为 “待审核” - -- **请求体、成功响应、错误响应**:与 “审核通过帖子” 接口一致 - -#### 4.2.4 删除帖子 - -- **URL**: /admin/del_post - -- **请求方法**: POST - -- **功能描述**: 永久删除指定帖子 - -- **请求体(JSON)**: - -``` -{ - "id": 1 // 帖子ID(必填) -} -``` - -- **成功响应(200 OK)**: - -``` -{ - "status": "OK" -} -``` - -- **错误响应**: 401/403:权限失败;400:缺少 ID;404:帖子不存在 - -#### 4.2.5 修改帖子 - -- **URL**: /admin/modify_post - -- **请求方法**: POST - -- **功能描述**: 修改指定帖子的内容 - -- **请求体(JSON)**: - -``` -{ - "id": 1, // 帖子ID(必填) - "content": "新的帖子内容(必填)" -} -``` - -- **成功响应(200 OK)**: - -``` -{ - "status": "OK" -} -``` - -- **错误响应**: 401/403:权限失败;400:缺少 ID 或 content;404:帖子不存在 - -### 4.3 评论与图片管理 - -#### 4.3.1 删除评论 - -- **URL**: /admin/del_comment - -- **请求方法**: POST - -- **功能描述**: 永久删除指定评论 - -- **请求体(JSON)**: - -``` -{ - "id": 1 // 评论ID(必填) -} -``` - -- **成功响应(200 OK)**: - -``` -{ - "status": "OK" -} -``` - -- **错误响应**: 401/403:权限失败;400:缺少 ID;404:评论不存在 - -#### 4.3.2 修改评论 - -- **URL**: /admin/modify_comment - -- **请求方法**: POST - -- **功能描述**: 修改指定评论的内容、用户名或父评论 ID - -- **请求体(JSON)**: - -``` -{ - "id": 1, // 评论ID(必填) - "content": "新评论内容(必填)", - "parent_comment_id": 0, // 新的父评论ID(必填) - "nickname": "新用户名(必填)" -} -``` - -- **成功响应(200 OK)**: - -``` -{ - "status": "OK" -} -``` - -- **错误响应**: 401/403:权限失败;400:缺少必填字段;404:评论或父评论不存在 - -#### 4.3.3 删除图片 - -- **URL**: /admin/del_pic - -- **请求方法**: POST - -- **功能描述**: 永久删除服务器上的指定图片 - -- **请求体(JSON)**: - -``` -{ - "filename": "230615_abcd1.png" // 图片文件名(必填) -} -``` - -- **成功响应(200 OK)**: - -``` -{ - "status": "OK" -} -``` - -- **错误响应**: 401/403:权限失败;400:缺少 filename;404:图片不存在 - -### 4.4 举报管理 - -#### 4.4.1 批准举报 - -- **URL**: /admin/approve_report - -- **请求方法**: POST - -- **功能描述**: 批准用户提交的举报,系统将自动删除被举报的违规帖子 - -- **请求体(JSON)**: - -``` -{ - "id": 1 // 举报记录的ID(必填) -} -``` - -- **成功响应(200 OK)**: - -``` -{ - "status": "OK" -} -``` - -- **错误响应**: - -- - 401/403:管理员权限验证失败 - -- - 400 Bad Request:缺少举报 ID - -- - 404 Not Found:举报记录不存在或已处理 - -#### 4.4.2 拒绝举报 - -- **URL**: /admin/reject_report - -- **请求方法**: POST - -- **功能描述**: 驳回用户提交的举报,被举报帖子将保持原有状态 - -- **请求体(JSON)**: - -``` -{ - "id": 1 // 举报记录的ID(必填) -} -``` - -- **成功响应(200 OK)**: - -``` -{ - "status": "OK" -} -``` - -- **错误响应**: 与 “批准举报” 接口完全一致 - -### 4.5 数据备份与恢复 - -#### 4.5.1 创建备份 - -- **URL**: /admin/get/backup - -- **请求方法**: GET - -- **功能描述**: 自动备份系统数据库(含帖子、评论、举报数据)和所有上传图片,生成 ZIP 格式压缩包供下载 - -- **成功响应(200 OK)**: 返回 ZIP 格式的备份文件(文件名为 “backup_YYYYMMDD_HHMMSS.zip”,如 “backup_20240520_143025.zip”) - -- **错误响应**: 401/403:管理员权限验证失败 - -#### 4.5.2 恢复备份 - -- **URL**: /admin/recover - -- **请求方法**: POST - -- **请求体格式**: multipart/form-data,需包含名为backup_file的 ZIP 备份文件 - -- **功能描述**: 从备份文件恢复系统数据(覆盖现有数据库和图片,操作不可逆,建议提前二次备份) - -- **成功响应(200 OK)**: - -``` -{ - "status": "OK" -} -``` - -- **错误响应**: - -- - 401/403:管理员权限验证失败 - -- - 400 Bad Request:未上传文件、文件不是 ZIP 格式或备份文件损坏 - -- - 500 Internal Server Error:恢复过程中出现数据错误 - -### 4.6 管理端数据查询 - -#### 4.6.1 获取待审核帖子 - -- **URL**: /admin/get/pending_posts - -- **请求方法**: GET - -- **功能描述**: 查询系统中所有状态为 “待审核” 的帖子列表(含未通过普通用户查询的待审核内容) - -- **成功响应(200 OK)**: - -``` -[ - { - "id": 1, - "content": "待审核的帖子内容", - "create_time": "2024-05-20 14:20:30", // 帖子创建时间 - "upvotes": 0, - "downvotes": 0 - } -] -``` - -- **错误响应**: 401/403:管理员权限验证失败 - -#### 4.6.2 获取已拒绝帖子 - -- **URL**: /admin/get/reject_posts - -- **请求方法**: GET - -- **功能描述**: 查询系统中所有状态为 “已拒绝” 的帖子列表 - -- **成功响应(200 OK)**: 格式与 “获取待审核帖子” 一致,仅包含 “已拒绝” 状态的帖子 - -- **错误响应**: 401/403:管理员权限验证失败 - -#### 4.6.3 获取图片链接列表 - -- **URL**: /admin/get/pic_links - -- **请求方法**: GET - -- **请求参数(Query)**: page=1(页码,可选,默认 1,每页返回 20 条图片链接) - -- **功能描述**: 查询系统中所有已上传图片的访问 URL 和文件名 - -- **成功响应(200 OK)**: - -``` -[ - "/img/251012_nfXWz.png", - "/img/251012_JrTrD.png", - "/img/251012_sk7ll.png", - "/img/251012_8efux.png", - "/img/251012_zK8fz.jpg", - "/img/251012_B0nkO.jpg", - "/img/251012_nNV3o.png" -] -``` - -- **错误响应**: 401/403:管理员权限验证失败 - -#### 4.6.4 获取待处理举报 - -- **URL**: /admin/get/pending_reports - -- **请求方法**: GET - -- **功能描述**: 查询系统中所有状态为 “待处理” 的举报记录列表 - -- **成功响应(200 OK)**: - -``` -[ - { - "id": 1, // 举报ID - "reported_post_id": 2, // 被举报帖子ID - "reported_post_content": "被举报的帖子内容", // 被举报帖子内容(便于审核) - "title": "举报标题", - "content": "举报详细内容", - "submit_time": "2024-05-20 15:00:45" // 举报提交时间 - } -] -``` - -- **错误响应**: 401/403:管理员权限验证失败 - -### 4.7 管理员工具接口 - -#### 4.7.1 管理员测试接口 - -- **URL**: /admin/test - -- **请求方法**: GET / POST - -- **功能描述**: 验证管理员权限接口是否正常运行(需携带管理员 Token) - -- **成功响应(200 OK)**: Admin API OK!!!(文本格式) - -- **错误响应**: 401/403:管理员权限验证失败 - -#### 4.7.2 获取管理员视角的帖子详情 - -- **URL**: /admin/get/post_info - -- **请求方法**: GET - -- **请求参数(Query)**: id=1(帖子 ID,必填) - -- **功能描述**: 查看指定帖子的完整信息(含创建时间、更新时间、审核状态,不受 “未通过审核” 限制) - -- **成功响应(200 OK)**: - -``` -{ - "id": 1, - "content": "帖子内容", - "status": "Pending", // 帖子状态:Approved/Rejected/Pending - "create_time": "2024-05-20 14:20:30", - "update_time": "2024-05-20 14:20:30", // 最后更新时间(如审核时间、修改时间) - "upvotes": 10, - "downvotes": 2 -} -``` - -- **错误响应**: - -- - 401/403:管理员权限验证失败 - -- - 400 Bad Request:缺少帖子 ID - -- - 404 Not Found:帖子不存在 - -## 五、附录 - -### 5.1 常见状态码说明 - -| 状态码 | 含义 | 适用场景 | -| ------------------------- | -------------- | ------------------------------------------------------ | -| 200 OK | 请求成功 | 点赞、获取数据、修改内容等操作成功 | -| 201 Created | 资源创建成功 | 提交帖子、上传图片、提交举报等创建新资源的操作 | -| 400 Bad Request | 请求参数错误 | 缺少必填字段、参数格式错误(如非布尔值、非数字 ID) | -| 401 Unauthorized | 未授权 | 访问管理接口未携带管理员 Token | -| 403 Forbidden | 权限拒绝 | 携带无效 Token、内容含违规关键词、访问不允许的文件格式 | -| 404 Not Found | 资源不存在 | 帖子 ID / 评论 ID / 举报 ID / 图片文件名不存在 | -| 418 I'm a teapot | 彩蛋状态码 | 访问/get/teapot接口 | -| 500 Internal Server Error | 服务器内部错误 | 备份恢复失败、数据库异常等系统级错误 | diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..563cc6c --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024-2025 libm + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..b4feee2 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +"# TODO:README" diff --git a/api_server.py b/api_server.py index f00e434..eb38108 100644 --- a/api_server.py +++ b/api_server.py @@ -90,30 +90,29 @@ def set_config(key, value): # === 变量 === -BANNED_KEYWORDS = [ - "傻逼", "煞笔", "傻叉", "脑残", "狗东西", - "操你妈", "你妈的", "滚", "神经病", "贱人", "杂种", "王八蛋", - "臭婊子", "蠢货", "白痴", "妈的", - "约吗", "开房", "一夜情", "裸聊", "床照", - "小电影", "嫖娼", "成人网", "🈷吗", - "毒品", "枪支", "赌博", "六合彩", "博彩", "赌球", - "诈骗", "洗钱", "开票", "假证", "网监", - "习近平", "毛泽东", "共产党", "台湾独立", "台独", "法轮功", - "反动", "民主运动", "六四", "政变", - "割腕", "跳楼" +DEFAULT_BANNED_KEYWORDS = [ + "傻逼" ] -ADMIN_TOKEN = "I_Love_SFLS_128936^" +# === 延迟初始化配置 === +DEFAULT_ADMIN_TOKEN = "Sycamore_whisper" +DEFAULT_UPLOAD_FOLDER = "img" +DEFAULT_ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "gif", "webp"} +DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 MB -UPLOAD_FOLDER = "img" -ALLOWED_EXTENSIONS = {"png", "jpg", "jpeg", "gif", "webp"} -MAX_FILE_SIZE = 10 * 1024 * 1024 # 10 MB +CONFIG = {} +INIT = False +NEED_AUDIT = False -if not os.path.exists(UPLOAD_FOLDER): - os.makedirs(UPLOAD_FOLDER) +# 运行时使用的变量,初始为默认值 +ADMIN_TOKEN = DEFAULT_ADMIN_TOKEN +UPLOAD_FOLDER = DEFAULT_UPLOAD_FOLDER +ALLOWED_EXTENSIONS = set(DEFAULT_ALLOWED_EXTENSIONS) +MAX_FILE_SIZE = DEFAULT_MAX_FILE_SIZE +BANNED_KEYWORDS = list(DEFAULT_BANNED_KEYWORDS) DB_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'instance', 'database.db') -IMG_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'img') +IMG_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), UPLOAD_FOLDER) BACKUP_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'backups') os.makedirs(BACKUP_FOLDER, exist_ok=True) @@ -122,6 +121,140 @@ ALLOWED_BACKUP_EXTENSIONS = {'zip'} def allowed_backup_file(filename): return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_BACKUP_EXTENSIONS +def apply_config_to_globals(): + global ADMIN_TOKEN, UPLOAD_FOLDER, ALLOWED_EXTENSIONS, MAX_FILE_SIZE, IMG_FOLDER, BANNED_KEYWORDS + ADMIN_TOKEN = CONFIG.get('ADMIN_TOKEN', DEFAULT_ADMIN_TOKEN) + UPLOAD_FOLDER = CONFIG.get('UPLOAD_FOLDER', DEFAULT_UPLOAD_FOLDER) + ALLOWED_EXTENSIONS = set(CONFIG.get('ALLOWED_EXTENSIONS', DEFAULT_ALLOWED_EXTENSIONS)) + MAX_FILE_SIZE = int(CONFIG.get('MAX_FILE_SIZE', DEFAULT_MAX_FILE_SIZE)) + BANNED_KEYWORDS = list(CONFIG.get('BANNED_KEYWORDS', DEFAULT_BANNED_KEYWORDS)) + IMG_FOLDER = os.path.join(os.path.dirname(os.path.abspath(__file__)), UPLOAD_FOLDER) + os.makedirs(UPLOAD_FOLDER, exist_ok=True) + +def load_config(): + global CONFIG, INIT + try: + import importlib, sys + importlib.invalidate_caches() + if 'config' in sys.modules: + cfg = importlib.reload(sys.modules['config']) + else: + cfg = importlib.import_module('config') + CONFIG = { + 'ADMIN_TOKEN': getattr(cfg, 'ADMIN_TOKEN'), + 'UPLOAD_FOLDER': getattr(cfg, 'UPLOAD_FOLDER'), + 'ALLOWED_EXTENSIONS': set(getattr(cfg, 'ALLOWED_EXTENSIONS')), + 'MAX_FILE_SIZE': int(getattr(cfg, 'MAX_FILE_SIZE')), + 'BANNED_KEYWORDS': list(getattr(cfg, 'BANNED_KEYWORDS', DEFAULT_BANNED_KEYWORDS)), + } + INIT = True + apply_config_to_globals() + except Exception: + INIT = False + CONFIG = {} + +# 启动时尝试加载配置 +load_config() + +# 全部接口在初始化完成前返回 503(仅 /init 允许) +@app.before_request +def gate_uninitialized(): + if request.path == '/init': + return None + global INIT + # 若未初始化,尝试动态加载配置(兼容多进程/热重载场景) + if not INIT: + try: + load_config() + except Exception: + pass + if not INIT: + return jsonify({"status": "Fail", "reason": "Uninitialized"}), 503 + +def write_config_py(token, upload_folder, allowed_exts, max_file_size, banned_keywords=None): + # 归一化扩展名为小写且唯一 + exts = sorted(set(str(e).strip().lower() for e in allowed_exts if str(e).strip())) + # 归一化敏感词为去空格的字符串列表 + banned = banned_keywords if banned_keywords is not None else DEFAULT_BANNED_KEYWORDS + banned = [str(w).strip() for w in banned if str(w).strip()] + content = ( + "# Auto-generated by /init\n" + f"ADMIN_TOKEN = {repr(token)}\n" + f"UPLOAD_FOLDER = {repr(upload_folder)}\n" + f"ALLOWED_EXTENSIONS = {repr(exts)}\n" + f"MAX_FILE_SIZE = {int(max_file_size)}\n" + f"BANNED_KEYWORDS = {repr(banned)}\n" + ) + config_path = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'config.py') + with open(config_path, 'w', encoding='utf-8') as f: + f.write(content) + +@app.route('/init', methods=['POST']) +def init_service(): + global INIT + if INIT: + return jsonify({"status": "Fail", "reason": "Already initialized"}), 403 + data = request.get_json() or {} + required = ["ADMIN_TOKEN", "UPLOAD_FOLDER", "ALLOWED_EXTENSIONS", "MAX_FILE_SIZE"] + missing = [k for k in required if k not in data] + if missing: + return jsonify({"status": "Fail", "reason": f"Missing fields: {', '.join(missing)}"}), 400 + + token = str(data["ADMIN_TOKEN"]) + upload_folder = str(data["UPLOAD_FOLDER"]).strip() + # 接受 list 或 逗号字符串 + exts = data["ALLOWED_EXTENSIONS"] + if isinstance(exts, str): + allowed_exts = [x.strip() for x in exts.split(',')] + elif isinstance(exts, list): + allowed_exts = exts + else: + return jsonify({"status": "Fail", "reason": "ALLOWED_EXTENSIONS must be list or comma string"}), 400 + + try: + max_file_size = int(data["MAX_FILE_SIZE"]) + except Exception: + return jsonify({"status": "Fail", "reason": "MAX_FILE_SIZE must be int"}), 400 + + # 可选的 BANNED_KEYWORDS + bk = data.get("BANNED_KEYWORDS", DEFAULT_BANNED_KEYWORDS) + if isinstance(bk, str): + banned_keywords = [x.strip() for x in bk.split(',') if x.strip()] + elif isinstance(bk, list): + banned_keywords = [str(x).strip() for x in bk if str(x).strip()] + else: + return jsonify({"status": "Fail", "reason": "BANNED_KEYWORDS must be list or comma string"}), 400 + + try: + write_config_py(token, upload_folder, allowed_exts, max_file_size, banned_keywords) + load_config() + initialize_database() + try: + global NEED_AUDIT + NEED_AUDIT = get_config("need_audit", "false").lower() == "true" + except Exception: + NEED_AUDIT = False + return jsonify({"status": "OK"}), 200 + except Exception as e: + return jsonify({"status": "Fail", "reason": str(e)}), 500 + + +# 在服务收到请求且已配置后,确保数据库表创建并加载审核状态 +@app.before_request +def ensure_db_and_audit(): + global NEED_AUDIT + if not getattr(ensure_db_and_audit, "_has_run", False) and INIT: + try: + initialize_database() + try: + NEED_AUDIT = get_config("need_audit", "false").lower() == "true" + except Exception: + NEED_AUDIT = False + except Exception: + pass + finally: + setattr(ensure_db_and_audit, "_has_run", True) + # === 管理端文章状态修改工具函数 === def admin_change_status(submission_id, from_status, to_status): @@ -291,7 +424,7 @@ def serve_image(filename): if not allowed_file(filename): return 'Request not allowed', 403 # 后缀不允许 # 返回图片 - return send_from_directory('img', filename) + return send_from_directory(UPLOAD_FOLDER, filename) @app.route('/report', methods=['POST']) @@ -473,10 +606,6 @@ def return_418(): def return_200(): return 'API OK!!!', 200 -@app.route('/get/api_info', methods=['GET']) -def get_api_info(): - return 'Sycamore_whisper API v1.0.0 Made with ❤️ By Leonxie', 200 - @app.route('/admin/need_audit', methods=['POST']) @require_admin def toggle_audit(): @@ -499,6 +628,36 @@ def get_need_audit(): global NEED_AUDIT return jsonify({"status": NEED_AUDIT}), 200 +# 动态敏感词配置 +@app.route('/admin/get/banned_keywords', methods=['GET']) +@require_admin +def get_banned_keywords(): + return jsonify({"keywords": BANNED_KEYWORDS}), 200 + +@app.route('/admin/banned_keywords', methods=['POST']) +@require_admin +def set_banned_keywords(): + data = request.get_json() or {} + keywords = data.get("BANNED_KEYWORDS", data.get("banned_keywords")) + if keywords is None: + return jsonify({"status": "Fail", "reason": "BANNED_KEYWORDS not found"}), 400 + if isinstance(keywords, str): + new_keywords = [x.strip() for x in keywords.split(',') if x.strip()] + elif isinstance(keywords, list): + new_keywords = [str(x).strip() for x in keywords if str(x).strip()] + else: + return jsonify({"status": "Fail", "reason": "BANNED_KEYWORDS must be list or comma string"}), 400 + + global BANNED_KEYWORDS + BANNED_KEYWORDS = new_keywords + try: + # 重写配置文件,确保重启后仍生效 + write_config_py(ADMIN_TOKEN, UPLOAD_FOLDER, list(ALLOWED_EXTENSIONS), MAX_FILE_SIZE, BANNED_KEYWORDS) + load_config() + return jsonify({"status": "OK"}), 200 + except Exception as e: + return jsonify({"status": "Fail", "reason": str(e)}), 500 + @app.route('/admin/approve', methods=['POST']) @require_admin def admin_approve(): @@ -634,7 +793,7 @@ def admin_del_pic(): return jsonify({"status": "Fail", "reason": "filename not found"}), 400 filename = data["filename"] - file_path = os.path.join(os.getcwd(), "img", filename) + file_path = os.path.join(os.getcwd(), UPLOAD_FOLDER, filename) if not os.path.isfile(file_path): return jsonify({"status": "Fail", "reason": "file not found"}), 404 @@ -832,12 +991,12 @@ def admin_get_pic_links(): page = 1 per_page = 20 - if not os.path.exists('img'): + if not os.path.exists(UPLOAD_FOLDER): return jsonify([]), 200 all_files = sorted( - [f for f in os.listdir('img') if os.path.isfile(os.path.join('img', f))], - key=lambda x: os.path.getmtime(os.path.join('img', x)), + [f for f in os.listdir(UPLOAD_FOLDER) if os.path.isfile(os.path.join(UPLOAD_FOLDER, f))], + key=lambda x: os.path.getmtime(os.path.join(UPLOAD_FOLDER, x)), reverse=True ) @@ -881,7 +1040,4 @@ def initialize_database(): # === 启动 === if __name__ == '__main__': - with app.app_context(): - initialize_database() - NEED_AUDIT = get_config("need_audit", "false").lower() == "true" - app.run(host='0.0.0.0', port=5000) + app.run(host='127.0.0.1', port=5000) # 监听IP&端口,建议监听127.0.0.1并配置反向代理