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并配置反向代理