Skip to content

Commit cdff85b

Browse files
committed
feat(v2.6.4): add daily download quota with admin exemption
1 parent a348800 commit cdff85b

8 files changed

Lines changed: 234 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,20 @@
22

33
所有版本更新记录。
44

5+
## **v2.6.4** (2026-01-05)
6+
7+
### 新增功能
8+
- **每日下载次数限制** - 新增 `daily_download_limit` 配置项
9+
- 基于 SQLite 实现,按用户 QQ 号统计
10+
- 每日 0 点自动重置
11+
- 设置为 0 表示不限制
12+
- **管理员豁免**`admin_list` 中的用户不受限制
13+
14+
### 新增文件
15+
- `core/quota.py` - 下载配额管理器 `DownloadQuotaManager`
16+
17+
---
18+
519
## **v2.6.3** (2026-01-05)
620

721
### 新增功能

README.md

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
<br>
1212
<div align="center">
13-
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/VERSION-v2.6.3-E91E63?style=for-the-badge" alt="Version"></a>
13+
<a href="CHANGELOG.md"><img src="https://img.shields.io/badge/VERSION-v2.6.4-E91E63?style=for-the-badge" alt="Version"></a>
1414
<a href="https://github.com/GEMILUXVII/astrbot_plugin_jm_cosmos/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-AGPL--3.0-009688?style=for-the-badge" alt="License"></a>
1515
<a href="https://www.python.org/"><img src="https://img.shields.io/badge/PYTHON-3.10+-3776AB?style=for-the-badge&logo=python&logoColor=white" alt="Python"></a>
1616
<a href="https://github.com/AstrBotDevs/AstrBot"><img src="https://img.shields.io/badge/AstrBot-Compatible-00BFA5?style=for-the-badge&logo=robot&logoColor=white" alt="AstrBot Compatible"></a>
@@ -257,10 +257,16 @@ pip install -r requirements.txt
257257
| `admin_only` | 仅管理员可用 | `false` |
258258
| `admin_list` | 管理员用户 ID 列表 ||
259259
| `search_page_size` | 搜索结果数量 | `5` |
260+
| `daily_download_limit` | 每日下载次数限制 (0=不限) | `0` |
260261
| `debug_mode` | 调试模式 | `false` |
261262
| `jm_username` | JM账号用户名 | 空(可选) |
262263
| `jm_password` | JM账号密码 | 空(可选) |
263264

265+
> [!NOTE]
266+
> **关于每日下载限制**
267+
>
268+
> 设置 `daily_download_limit` 后,每个用户(按 QQ 号识别)每日下载次数受限。`admin_list` 中的管理员不受此限制。
269+
264270
> [!NOTE]
265271
> **关于 webp 图片格式**
266272
>
@@ -380,7 +386,7 @@ proxy_url: http://127.0.0.1:7890
380386

381387
查看完整更新日志:[CHANGELOG.md](./CHANGELOG.md)
382388

383-
**当前版本:v2.6.3** - 新增文件名显示密码配置项。
389+
**当前版本:v2.6.4** - 新增每日下载次数限制,插件管理员不受此限制
384390

385391
## 注意事项
386392

_conf_schema.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,5 +138,11 @@
138138
"description": "撤回延迟(秒)",
139139
"hint": "发送后多少秒自动撤回,建议30-120",
140140
"default": 60
141+
},
142+
"daily_download_limit": {
143+
"type": "int",
144+
"description": "每日下载次数限制",
145+
"hint": "每个用户每天最多下载次数,0 表示不限制",
146+
"default": 0
141147
}
142148
}

core/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
"""
22
JM-Cosmos II 核心模块
33
4-
提供下载、浏览、认证、配置、打包等核心功能
4+
提供下载、浏览、认证、配置、打包、配额等核心功能
55
"""
66

77
import importlib.util
@@ -11,12 +11,14 @@
1111
from .browser import JMBrowser
1212
from .downloader import DownloadResult, JMDownloadManager
1313
from .packer import JMPacker
14+
from .quota import DownloadQuotaManager
1415

1516
# 集中管理 jmcomic 库的可用性检查
1617
JMCOMIC_AVAILABLE = importlib.util.find_spec("jmcomic") is not None
1718

1819
__all__ = [
1920
"JMCOMIC_AVAILABLE",
21+
"DownloadQuotaManager",
2022
"JMAuthManager",
2123
"JMBrowser",
2224
"JMClientMixin",
@@ -25,3 +27,4 @@
2527
"DownloadResult",
2628
"JMPacker",
2729
]
30+

core/base/config.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,11 @@ def auto_recall_delay(self) -> int:
145145
"""自动撤回延迟(秒)"""
146146
return self.plugin_config.get("auto_recall_delay", 60)
147147

148+
@property
149+
def daily_download_limit(self) -> int:
150+
"""每日下载次数限制,0 表示不限制"""
151+
return self.plugin_config.get("daily_download_limit", 0)
152+
148153
@property
149154
def cookies_file(self) -> Path:
150155
"""Cookies文件路径"""

core/quota.py

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
"""
2+
下载配额管理模块
3+
4+
基于 SQLite 实现每用户每日下载次数限制。
5+
用户标识使用 QQ 号(或其他平台的 user_id)。
6+
"""
7+
8+
import sqlite3
9+
from datetime import date
10+
from pathlib import Path
11+
12+
from astrbot.api import logger
13+
14+
15+
class DownloadQuotaManager:
16+
"""下载配额管理器 - 基于 SQLite"""
17+
18+
def __init__(self, db_path: Path):
19+
"""
20+
初始化配额管理器
21+
22+
Args:
23+
db_path: SQLite 数据库文件路径
24+
"""
25+
self.db_path = db_path
26+
self._init_db()
27+
28+
def _init_db(self):
29+
"""初始化数据库表"""
30+
try:
31+
with self._get_connection() as conn:
32+
conn.execute("""
33+
CREATE TABLE IF NOT EXISTS download_quota (
34+
user_id TEXT NOT NULL,
35+
date TEXT NOT NULL,
36+
count INTEGER DEFAULT 0,
37+
PRIMARY KEY (user_id, date)
38+
)
39+
""")
40+
conn.commit()
41+
except Exception as e:
42+
logger.error(f"初始化配额数据库失败: {e}")
43+
44+
def _get_connection(self) -> sqlite3.Connection:
45+
"""获取数据库连接"""
46+
return sqlite3.connect(self.db_path)
47+
48+
def _get_today(self) -> str:
49+
"""获取今天的日期字符串"""
50+
return date.today().isoformat()
51+
52+
def get_used_count(self, user_id: str) -> int:
53+
"""
54+
获取用户今日已使用次数
55+
56+
Args:
57+
user_id: 用户 QQ 号
58+
59+
Returns:
60+
今日已使用次数
61+
"""
62+
try:
63+
with self._get_connection() as conn:
64+
cursor = conn.execute(
65+
"SELECT count FROM download_quota WHERE user_id = ? AND date = ?",
66+
(str(user_id), self._get_today()),
67+
)
68+
row = cursor.fetchone()
69+
return row[0] if row else 0
70+
except Exception as e:
71+
logger.error(f"查询配额失败: {e}")
72+
return 0
73+
74+
def check_quota(self, user_id: str, limit: int) -> tuple[bool, int, int]:
75+
"""
76+
检查用户是否可以下载
77+
78+
Args:
79+
user_id: 用户 QQ 号
80+
limit: 每日下载限制次数
81+
82+
Returns:
83+
(是否可下载, 已用次数, 限制次数)
84+
"""
85+
if limit <= 0:
86+
return True, 0, 0 # 限制为 0 表示不限制
87+
88+
used = self.get_used_count(user_id)
89+
can_download = used < limit
90+
return can_download, used, limit
91+
92+
def consume_quota(self, user_id: str) -> int:
93+
"""
94+
消耗一次配额
95+
96+
Args:
97+
user_id: 用户 QQ 号
98+
99+
Returns:
100+
消耗后的已用次数
101+
"""
102+
try:
103+
today = self._get_today()
104+
with self._get_connection() as conn:
105+
# 使用 UPSERT 语法,原子操作
106+
conn.execute(
107+
"""
108+
INSERT INTO download_quota (user_id, date, count)
109+
VALUES (?, ?, 1)
110+
ON CONFLICT(user_id, date) DO UPDATE SET count = count + 1
111+
""",
112+
(str(user_id), today),
113+
)
114+
conn.commit()
115+
return self.get_used_count(user_id)
116+
except Exception as e:
117+
logger.error(f"消耗配额失败: {e}")
118+
return 0
119+
120+
def get_remaining(self, user_id: str, limit: int) -> int | None:
121+
"""
122+
获取剩余次数
123+
124+
Args:
125+
user_id: 用户 QQ 号
126+
limit: 每日下载限制次数
127+
128+
Returns:
129+
剩余次数,如果不限制则返回 None
130+
"""
131+
if limit <= 0:
132+
return None
133+
used = self.get_used_count(user_id)
134+
return max(0, limit - used)
135+
136+
def cleanup_old_data(self, days: int = 7):
137+
"""
138+
清理过期数据
139+
140+
Args:
141+
days: 保留最近多少天的数据
142+
"""
143+
try:
144+
with self._get_connection() as conn:
145+
conn.execute(
146+
"DELETE FROM download_quota WHERE date < date('now', ?)",
147+
(f"-{days} days",),
148+
)
149+
conn.commit()
150+
logger.debug(f"已清理 {days} 天前的配额数据")
151+
except Exception as e:
152+
logger.error(f"清理配额数据失败: {e}")

main.py

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,14 @@
1111
from astrbot.api.event import AstrMessageEvent, filter
1212
from astrbot.api.star import Context, Star, StarTools, register
1313

14-
from .core import JMAuthManager, JMBrowser, JMConfigManager, JMDownloadManager, JMPacker
14+
from .core import (
15+
DownloadQuotaManager,
16+
JMAuthManager,
17+
JMBrowser,
18+
JMConfigManager,
19+
JMDownloadManager,
20+
JMPacker,
21+
)
1522
from .utils import MessageFormatter, generate_album_filename, send_with_recall
1623

1724
# 插件名称常量
@@ -22,7 +29,7 @@
2229
"jm_cosmos2",
2330
"GEMILUXVII",
2431
"JM漫画下载插件 - 支持搜索、下载禁漫天堂的漫画本子,支持加密PDF/ZIP打包",
25-
"2.6.3",
32+
"2.6.4",
2633
"https://github.com/GEMILUXVII/astrbot_plugin_jm_cosmos",
2734
)
2835
class JMCosmosPlugin(Star):
@@ -56,6 +63,9 @@ def __init__(self, context: Context, config: AstrBotConfig = None):
5663
# 初始化认证管理器
5764
self.auth_manager = JMAuthManager(self.config_manager)
5865

66+
# 初始化下载配额管理器
67+
self.quota_manager = DownloadQuotaManager(self.data_dir / "quota.db")
68+
5969
# 调试模式
6070
self.debug_mode = self.config_manager.debug_mode
6171
if self.debug_mode:
@@ -117,6 +127,18 @@ async def download_album_command(
117127
yield event.plain_result(MessageFormatter.format_error("invalid_id"))
118128
return
119129

130+
# 检查下载配额(管理员豁免)
131+
user_id = event.get_sender_id()
132+
limit = self.config_manager.daily_download_limit
133+
is_admin = str(user_id) in self.config_manager.admin_list
134+
if limit > 0 and not is_admin:
135+
can_download, used, total = self.quota_manager.check_quota(user_id, limit)
136+
if not can_download:
137+
yield event.plain_result(
138+
f"❌ 今日下载次数已达上限 ({used}/{total})\n请明天再试"
139+
)
140+
return
141+
120142
try:
121143
# 发送开始下载提示
122144
yield event.plain_result(f"⏳ 开始下载本子 {album_id},请稍候...")
@@ -171,6 +193,10 @@ async def download_album_command(
171193
)
172194

173195
# 发送结果消息
196+
# 下载成功,消耗配额
197+
if limit > 0:
198+
self.quota_manager.consume_quota(user_id)
199+
174200
result_msg = MessageFormatter.format_download_result(result, pack_result)
175201

176202
if (
@@ -260,6 +286,18 @@ async def download_photo_command(
260286
yield event.plain_result("❌ 章节序号必须是数字")
261287
return
262288

289+
# 检查下载配额(管理员豁免)
290+
user_id = event.get_sender_id()
291+
limit = self.config_manager.daily_download_limit
292+
is_admin = str(user_id) in self.config_manager.admin_list
293+
if limit > 0 and not is_admin:
294+
can_download, used, total = self.quota_manager.check_quota(user_id, limit)
295+
if not can_download:
296+
yield event.plain_result(
297+
f"❌ 今日下载次数已达上限 ({used}/{total})\n请明天再试"
298+
)
299+
return
300+
263301
try:
264302
yield event.plain_result(
265303
f"⏳ 正在获取本子 {album_id} 的第 {chapter_idx} 章节信息..."
@@ -317,6 +355,10 @@ async def download_photo_command(
317355
output_name=output_name,
318356
)
319357

358+
# 下载成功,消耗配额
359+
if limit > 0:
360+
self.quota_manager.consume_quota(user_id)
361+
320362
result_msg = MessageFormatter.format_download_result(result, pack_result)
321363

322364
if (

metadata.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: JM-Cosmos II
22
desc: JM漫画下载插件 - 支持搜索、下载禁漫天堂的漫画本子,基于jmcomic库,支持加密PDF/ZIP打包
3-
version: v2.6.3
3+
version: v2.6.4
44
author: GEMILUXVII
55
repo: https://github.com/GEMILUXVII/astrbot_plugin_jm_cosmos

0 commit comments

Comments
 (0)