Skip to content

Commit e8f180f

Browse files
committed
feat: implement login functionality with auth manager
- Add core/auth.py with JMAuthManager class - Add /jmlogin, /jmlogout, /jmstatus commands - Add jm_username and jm_password config options - Update help message with login commands
1 parent c26c0d1 commit e8f180f

5 files changed

Lines changed: 232 additions & 5 deletions

File tree

_conf_schema.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,5 +108,17 @@
108108
"description": "调试模式",
109109
"hint": "开启后会输出详细的调试日志",
110110
"default": false
111+
},
112+
"jm_username": {
113+
"type": "string",
114+
"description": "JM账号用户名",
115+
"hint": "填写后可自动登录,解锁收藏夹等功能(可选)",
116+
"default": ""
117+
},
118+
"jm_password": {
119+
"type": "string",
120+
"description": "JM账号密码",
121+
"hint": "配合用户名使用,支持自动登录(可选)",
122+
"default": ""
111123
}
112124
}

core/__init__.py

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

7+
from .auth import JMAuthManager
78
from .base import JMClientMixin, JMConfigManager
89
from .browser import JMBrowser
910
from .downloader import DownloadResult, JMDownloadManager
1011
from .packer import JMPacker
1112

1213
__all__ = [
14+
"JMAuthManager",
1315
"JMBrowser",
1416
"JMClientMixin",
1517
"JMConfigManager",

core/auth.py

Lines changed: 127 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
"""
2+
JMComic 认证管理模块
3+
4+
提供登录、登出、会话管理等认证功能。
5+
"""
6+
7+
from astrbot.api import logger
8+
9+
from .base import JMClientMixin, JMConfigManager
10+
11+
try:
12+
JMCOMIC_AVAILABLE = True
13+
except ImportError:
14+
JMCOMIC_AVAILABLE = False
15+
16+
17+
class JMAuthManager(JMClientMixin):
18+
"""JMComic 认证管理器"""
19+
20+
def __init__(self, config_manager: JMConfigManager):
21+
"""
22+
初始化认证管理器
23+
24+
Args:
25+
config_manager: 配置管理器实例
26+
"""
27+
self.config = config_manager
28+
self._logged_in = False
29+
self._username: str | None = None
30+
self._client = None
31+
32+
@property
33+
def is_logged_in(self) -> bool:
34+
"""检查是否已登录"""
35+
return self._logged_in
36+
37+
@property
38+
def current_user(self) -> str | None:
39+
"""获取当前登录用户名"""
40+
return self._username if self._logged_in else None
41+
42+
def get_client(self):
43+
"""获取已认证的客户端(如果已登录)"""
44+
if self._logged_in and self._client is not None:
45+
return self._client
46+
return self._build_client()
47+
48+
async def login(self, username: str, password: str) -> tuple[bool, str]:
49+
"""
50+
异步登录
51+
52+
Args:
53+
username: 用户名
54+
password: 密码
55+
56+
Returns:
57+
(成功与否, 消息)
58+
"""
59+
if not self.is_available():
60+
return False, "jmcomic 库未安装"
61+
62+
try:
63+
result = await self._run_sync(self._login_sync, username, password)
64+
return result
65+
except Exception as e:
66+
logger.error(f"登录失败: {e}")
67+
return False, f"登录失败: {str(e)}"
68+
69+
def _login_sync(self, username: str, password: str) -> tuple[bool, str]:
70+
"""同步登录"""
71+
try:
72+
option = self._get_option()
73+
if option is None:
74+
return False, "无法创建配置"
75+
76+
client = option.build_jm_client()
77+
client.login(username, password)
78+
79+
# 保存登录状态
80+
self._logged_in = True
81+
self._username = username
82+
self._client = client
83+
84+
logger.info(f"用户 {username} 登录成功")
85+
return True, f"登录成功,欢迎 {username}!"
86+
87+
except Exception as e:
88+
error_msg = str(e)
89+
logger.error(f"登录失败: {error_msg}")
90+
91+
# 解析常见错误
92+
if "password" in error_msg.lower() or "用户名" in error_msg:
93+
return False, "用户名或密码错误"
94+
elif "network" in error_msg.lower() or "connect" in error_msg.lower():
95+
return False, "网络连接失败,请稍后重试"
96+
97+
return False, f"登录失败: {error_msg}"
98+
99+
def logout(self) -> tuple[bool, str]:
100+
"""
101+
登出
102+
103+
Returns:
104+
(成功与否, 消息)
105+
"""
106+
if not self._logged_in:
107+
return False, "当前未登录"
108+
109+
username = self._username
110+
self._logged_in = False
111+
self._username = None
112+
self._client = None
113+
114+
logger.info(f"用户 {username} 已登出")
115+
return True, f"已登出账号 {username}"
116+
117+
def get_login_status(self) -> dict:
118+
"""
119+
获取登录状态
120+
121+
Returns:
122+
登录状态信息字典
123+
"""
124+
return {
125+
"logged_in": self._logged_in,
126+
"username": self._username,
127+
}

main.py

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

14-
from .core import JMBrowser, JMConfigManager, JMDownloadManager, JMPacker
14+
from .core import JMAuthManager, JMBrowser, JMConfigManager, JMDownloadManager, JMPacker
1515
from .utils import MessageFormatter
1616

1717
# 插件名称常量
@@ -53,6 +53,9 @@ def __init__(self, context: Context, config: AstrBotConfig = None):
5353
# 初始化浏览查询器
5454
self.browser = JMBrowser(self.config_manager)
5555

56+
# 初始化认证管理器
57+
self.auth_manager = JMAuthManager(self.config_manager)
58+
5659
# 调试模式
5760
self.debug_mode = self.config_manager.debug_mode
5861
if self.debug_mode:
@@ -395,3 +398,82 @@ async def ranking_command(
395398
except Exception as e:
396399
logger.error(f"获取排行榜失败: {e}")
397400
yield event.plain_result(MessageFormatter.format_error("network", str(e)))
401+
402+
@filter.command("jmlogin")
403+
async def login_command(
404+
self, event: AstrMessageEvent, username: str = None, password: str = None
405+
):
406+
"""
407+
登录JM账号
408+
409+
用法: /jmlogin <用户名> <密码>
410+
示例: /jmlogin myuser mypass
411+
"""
412+
# 权限检查
413+
has_perm, error_msg = self._check_permission(event)
414+
if not has_perm:
415+
yield event.plain_result(error_msg)
416+
return
417+
418+
# 参数检查
419+
if username is None or password is None:
420+
yield event.plain_result(
421+
"❌ 请提供用户名和密码\n用法: /jmlogin <用户名> <密码>\n示例: /jmlogin myuser mypass"
422+
)
423+
return
424+
425+
try:
426+
yield event.plain_result("🔐 正在登录...")
427+
428+
success, message = await self.auth_manager.login(username, password)
429+
430+
if success:
431+
yield event.plain_result(f"✅ {message}")
432+
else:
433+
yield event.plain_result(f"❌ {message}")
434+
435+
except Exception as e:
436+
logger.error(f"登录失败: {e}")
437+
yield event.plain_result(MessageFormatter.format_error("network", str(e)))
438+
439+
@filter.command("jmlogout")
440+
async def logout_command(self, event: AstrMessageEvent):
441+
"""
442+
登出JM账号
443+
444+
用法: /jmlogout
445+
"""
446+
# 权限检查
447+
has_perm, error_msg = self._check_permission(event)
448+
if not has_perm:
449+
yield event.plain_result(error_msg)
450+
return
451+
452+
success, message = self.auth_manager.logout()
453+
454+
if success:
455+
yield event.plain_result(f"✅ {message}")
456+
else:
457+
yield event.plain_result(f"❌ {message}")
458+
459+
@filter.command("jmstatus")
460+
async def status_command(self, event: AstrMessageEvent):
461+
"""
462+
查看登录状态
463+
464+
用法: /jmstatus
465+
"""
466+
# 权限检查
467+
has_perm, error_msg = self._check_permission(event)
468+
if not has_perm:
469+
yield event.plain_result(error_msg)
470+
return
471+
472+
status = self.auth_manager.get_login_status()
473+
474+
if status["logged_in"]:
475+
yield event.plain_result(f"✅ 已登录\n👤 用户名: {status['username']}")
476+
else:
477+
yield event.plain_result(
478+
"❌ 当前未登录\n💡 使用 /jmlogin <用户名> <密码> 登录"
479+
)

utils/formatter.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,16 +218,20 @@ def format_help() -> str:
218218
/jmrank - 查看排行榜
219219
/jmhelp - 显示此帮助信息
220220
221+
【账号命令】
222+
/jmlogin <用户名> <密码> - 登录JM账号
223+
/jmlogout - 登出账号
224+
/jmstatus - 查看登录状态
225+
221226
【使用示例】
222227
/jm 123456 - 下载ID为123456的本子
223228
/jms 标签名 - 搜索包含该标签的漫画
224-
/jmi 123456 - 查看本子详细信息
225229
/jmrank week - 查看周排行榜
226-
/jmrank month 2 - 查看月排行榜第2页
230+
/jmlogin user pw - 登录账号
227231
228232
【说明】
229233
• 下载的文件将自动打包发送
230-
支持群聊和私聊使用"""
234+
登录后可访问收藏夹等功能"""
231235

232236
@staticmethod
233237
def format_error(error_type: str, detail: str = "") -> str:

0 commit comments

Comments
 (0)