Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
d37fcd9
Merge pull request #237 from AUTO-MAS-Project/dev
DLmaster361 Jun 12, 2026
bc0a201
feat(game-sign): 多账号组架构重构 + UI 修复
Jun 15, 2026
475bfd0
feat(game-sign): 实现最终版用户列表布局重构
Jun 16, 2026
53342e6
fix(game-sign): 签到后立即刷新标签云
Jun 16, 2026
b5c3a6d
fix(game-sign): 森空岛合并调用 + 签到结果绑定account_uid + Token修改清空旧结果
Jun 17, 2026
1546723
fix(game-sign): 签到标签响应式修复 + 去重逻辑 + 时间调度 + BOM清理
Jun 18, 2026
564883f
fix(game-sign): 用户列表居中布局 + 签到跳过顺序修正 + 日志语言统一
Jun 18, 2026
1725e05
fix(game-sign): 米游社签到多策略认证 + 角色列表扁平结构兼容 + -5003处理
Jun 18, 2026
44ced40
fix(game-sign): 米游社风控识别 + 橙色标签展示
Jun 18, 2026
4d2187e
chore: v5.4.0-beta.1 版本更新 + 审查修复
Jun 18, 2026
b23c0e0
fix(game-sign): P2/P3 审查修复 + 标题栏标签改为已启用/未启用
Jun 18, 2026
dd06545
fix(game-sign): address PR review feedback
Jun 20, 2026
c68f8fa
Merge branch 'dev' into fix/gamesign
qiyinxi Jun 22, 2026
c0afc2b
fix(game-sign): address PR review feedback
Jun 20, 2026
799db5c
Merge remote-tracking branch 'origin/dev' into fix/gamesign
Jun 23, 2026
6e054b4
fix(game-sign): 修复社区签到结果判定
ClozyA Jun 27, 2026
1e0a19a
Merge branch 'fix/gamesign' of https://github.com/AUTO-MAS-Project/AU…
Jun 28, 2026
e091184
feat(game-sign): add MAS risk patch updates
Jul 1, 2026
2b167bd
docs: update version notes
Jul 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions app/api/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@
from .ocr import router as ocr_router
from .ws_debug import router as ws_debug_router

# 可选补丁:米游社扫码登录(可安全删除以下 2 行及 app/api/qr_login.py)
try:
from .qr_login import router as qr_login_router
except ImportError:
qr_login_router = None

__all__ = [
"core_router",
"info_router",
Expand All @@ -49,4 +55,5 @@
"update_router",
"ocr_router",
"ws_debug_router",
"qr_login_router",
]
110 changes: 110 additions & 0 deletions app/api/qr_login.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
# AUTO-MAS: A Multi-Script, Multi-Config Management and Automation Software
# Copyright © 2024-2025 DLmaster361
# Copyright © 2025-2026 AUTO-MAS Team

# This file is part of AUTO-MAS.

# AUTO-MAS is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
# published by the Free Software Foundation, either version 3 of
# the License, or (at your option) any later version.

# AUTO-MAS is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.

# You should have received a copy of the GNU Affero General Public License
# along with AUTO-MAS. If not, see <https://www.gnu.org/licenses/>.

# Contact: DLmaster_361@163.com

"""
米游社扫码登录 API 路由(可选补丁)

可安全删除本文件,不会影响任何已有功能。
删除后同时移除 main.py 中的 include_router 调用。

流程(Passport 模式):
1. /create → 生成二维码 (ticket, qr_url, device)
2. /check → 轮询状态 (Init/Scanned/Confirmed),确认后返回 cookies_str
3. /save → 将 cookies 保存到账号配置
"""

from fastapi import APIRouter, Body
from pydantic import BaseModel, Field
from app.core import Config
from app.models.schema import OutBase


router = APIRouter(prefix="/api/tools/sign/miyoushe/qr", tags=["扫码登录"])


# ---- 请求/响应模型 ----


class QrCreateOut(OutBase):
ticket: str = Field(default="", description="二维码 ticket")
qr_url: str = Field(default="")
device: str = Field(default="")


class QrCheckIn(BaseModel):
ticket: str
device: str


class QrCheckOut(OutBase):
status: str = Field(default="", description="Init/Scanned/Confirmed/Error")
cookies_str: str = Field(default="", description="确认后返回的完整 cookie 字符串")


class QrSaveIn(BaseModel):
account_uid: str = Field(..., description="MAS 账号组 UUID")
cookie: str


# ---- 端点 ----


@router.post("/create", summary="创建二维码", response_model=QrCreateOut)
async def qr_create() -> QrCreateOut:
try:
from app.tools.miyoushe_qr import create_qr_login
result = await create_qr_login()
except Exception as e:
return QrCreateOut(code=500, status="error", message=str(e))
if "error" in result:
return QrCreateOut(code=500, status="error", message=result["error"])
return QrCreateOut(ticket=result["ticket"], qr_url=result["qr_url"], device=result["device"])


@router.post("/check", summary="轮询扫码状态", response_model=QrCheckOut)
async def qr_check(body: QrCheckIn = Body(...)) -> QrCheckOut:
"""轮询状态,确认后 cookies 直接从响应头获取"""
try:
from app.tools.miyoushe_qr import check_qr_status
result = await check_qr_status(body.ticket, body.device)
except Exception as e:
return QrCheckOut(code=500, status="error", message=str(e))
if "error" in result:
return QrCheckOut(code=500, status="error", message=result["error"])
return QrCheckOut(
status=result.get("status", ""),
cookies_str=result.get("cookies_str", ""),
)


@router.post("/save", summary="保存 cookie 到账号配置", response_model=OutBase)
async def qr_save(body: QrSaveIn = Body(...)) -> OutBase:
try:
data = {"GameSignAccount": {"MiyousheToken": body.cookie}}
await Config.update_game_sign_account(body.account_uid, data)
result = Config.ToolsConfig._game_sign_result_data
for platform in list(result.keys()):
result[platform] = [g for g in result[platform] if g.get("account_uid") != body.account_uid]
if not result[platform]:
del result[platform]
except Exception as e:
return OutBase(code=500, status="error", message=str(e))
return OutBase(message="米游社 Token 已保存")
229 changes: 227 additions & 2 deletions app/api/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,21 @@


from fastapi import APIRouter, Body
from datetime import datetime
from app.core import Config
from app.models.schema import ToolsGetOut, ToolsConfig, OutBase, ToolsUpdateIn
from app.models.schema import (
ToolsGetOut,
ToolsConfig,
OutBase,
ToolsUpdateIn,
GameSignAccountCreateOut,
GameSignAccountGroupConfig,
GameSignAccountGetIn,
GameSignAccountUpdateIn,
GameSignAccountDeleteIn,
GameSignAccountReorderIn,
GameSignAccountsListOut,
)

router = APIRouter(prefix="/api/tools", tags=["工具设置"])

Expand All @@ -36,7 +49,7 @@
status_code=200,
)
async def get_tools() -> ToolsGetOut:
"""查询工具配置"""
"""获取工具设置"""

try:
data = await Config.get_tools()
Expand Down Expand Up @@ -69,3 +82,215 @@ async def update_tools(script: ToolsUpdateIn = Body(...)) -> OutBase:
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
)
return OutBase()


@router.post(
"/sign",
tags=["Action"],
summary="手动触发游戏社区签到",
response_model=OutBase,
status_code=200,
)
async def manual_game_sign() -> OutBase:
"""手动触发游戏社区签到"""

try:
from app.tools.game_sign import run_all_sign_in, format_sign_results
from app.tools.game_sign import merge_sign_results

results = await run_all_sign_in(force=True)

# 格式化并存储结果
formatted = format_sign_results(results)
# 合并结果(手动签到按 account_uid 替换旧数据)
Config.ToolsConfig._game_sign_result_data = merge_sign_results(
Config.ToolsConfig._game_sign_result_data, formatted, replace=True
)

# 标记今天已签到(仅当所有启用的用户都已签到时标记全局)
today = datetime.now().strftime("%Y-%m-%d")
all_signed = True
for uid, account in Config.ToolsConfig.GameSign_Accounts.items():
if account.get("GameSignAccount", "Enabled"):
if account.get("GameSignAccount", "LastSignDate") != today:
all_signed = False
break
if all_signed:
await Config.ToolsConfig.set("GameSign", "LastSignDate", today)
# 清除计划时间
await Config.ToolsConfig.set("GameSign", "ScheduledTime", "")

except Exception as e:
return OutBase(
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
)
return OutBase()


# ==================== 游戏签到账号组 CRUD ====================


@router.post(
"/sign/account/list",
tags=["GameSign"],
summary="获取所有游戏签到账号组",
response_model=GameSignAccountsListOut,
status_code=200,
)
async def list_game_sign_accounts() -> GameSignAccountsListOut:
"""获取所有游戏签到账号组"""

try:
data = await Config.get_game_sign_accounts()
except Exception as e:
return GameSignAccountsListOut(
code=500,
status="error",
message=f"{type(e).__name__}: {str(e)}",
data={},
)
return GameSignAccountsListOut(data=data)


@router.post(
"/sign/account/add",
tags=["GameSign"],
summary="添加游戏签到账号组",
response_model=GameSignAccountCreateOut,
status_code=200,
)
async def add_game_sign_account() -> GameSignAccountCreateOut:
"""添加游戏签到账号组"""

try:
uid, config = await Config.add_game_sign_account()
# toDict() 返回 {"GameSignAccount": {fields}},需提取嵌套字典
raw = await config.toDict()
flat = raw.get("GameSignAccount", raw)
data = GameSignAccountGroupConfig(**flat)
# 新增账号无需清空结果,因为新账号没有历史结果
except Exception as e:
return GameSignAccountCreateOut(
code=500,
status="error",
message=f"{type(e).__name__}: {str(e)}",
accountId="",
data=GameSignAccountGroupConfig(**{}),
)
return GameSignAccountCreateOut(accountId=str(uid), data=data)


@router.post(
"/sign/account/get",
tags=["GameSign"],
summary="获取游戏签到账号组详情",
response_model=GameSignAccountCreateOut,
status_code=200,
)
async def get_game_sign_account(
account: GameSignAccountGetIn = Body(...),
) -> GameSignAccountCreateOut:
"""获取游戏签到账号组详情"""

try:
raw = await Config.get_game_sign_account(account.accountId)
# toDict() 返回 {"GameSignAccount": {fields}},需提取嵌套字典
flat = raw.get("GameSignAccount", raw)
account_data = GameSignAccountGroupConfig(**flat)
except Exception as e:
return GameSignAccountCreateOut(
code=500,
status="error",
message=f"{type(e).__name__}: {str(e)}",
accountId=account.accountId,
data=GameSignAccountGroupConfig(**{}),
)
return GameSignAccountCreateOut(accountId=account.accountId, data=account_data)


@router.post(
"/sign/account/update",
tags=["GameSign"],
summary="更新游戏签到账号组配置",
response_model=OutBase,
status_code=200,
)
async def update_game_sign_account(
account: GameSignAccountUpdateIn = Body(...),
) -> OutBase:
"""更新游戏签到账号组配置"""

try:
# GameSignAccountGroupConfig 是扁平格式,需包装为 {group: {name: value}} 传给 ConfigBase.set
flat_data = account.data.model_dump(exclude_unset=True)
data = {"GameSignAccount": flat_data}
await Config.update_game_sign_account(account.accountId, data)
# Token 变更后只清空该账号的签到结果
token_fields = {"MiyousheToken", "KuroToken", "SklandToken"}
if token_fields & set(flat_data.keys()):
result = Config.ToolsConfig._game_sign_result_data
account_uid = str(account.accountId)
for platform in list(result.keys()):
result[platform] = [
group for group in result[platform]
if group.get("account_uid") != account_uid
]
if not result[platform]:
del result[platform]
except Exception as e:
return OutBase(
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
)
return OutBase()


@router.post(
"/sign/account/delete",
tags=["GameSign"],
summary="删除游戏签到账号组",
response_model=OutBase,
status_code=200,
)
async def delete_game_sign_account(
account: GameSignAccountDeleteIn = Body(...),
) -> OutBase:
"""删除游戏签到账号组"""

try:
await Config.delete_game_sign_account(account.accountId)
# 删除账号后清理该用户的签到结果
account_uid = str(account.accountId)
result = Config.ToolsConfig._game_sign_result_data
for platform in list(result.keys()):
result[platform] = [
group for group in result[platform]
if group.get("account_uid") != account_uid
]
if not result[platform]:
del result[platform]
except Exception as e:
return OutBase(
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
)
return OutBase()


@router.post(
"/sign/account/reorder",
tags=["GameSign"],
summary="调整游戏签到账号组顺序",
response_model=OutBase,
status_code=200,
)
async def reorder_game_sign_accounts(
account: GameSignAccountReorderIn = Body(...),
) -> OutBase:
"""调整游戏签到账号组顺序"""

try:
await Config.reorder_game_sign_accounts(account.order)
except Exception as e:
return OutBase(
code=500, status="error", message=f"{type(e).__name__}: {str(e)}"
)
return OutBase()
Loading
Loading