diff --git a/backend/api_service/app/routers/accounts.py b/backend/api_service/app/routers/accounts.py index 4aec281..51baece 100644 --- a/backend/api_service/app/routers/accounts.py +++ b/backend/api_service/app/routers/accounts.py @@ -304,7 +304,34 @@ async def list_topics( return error_response("Cookie 解密失败", "COOKIE_ERROR", status_code=400) topics = await _get_super_topics(cookie_str, account.weibo_user_id) - return success_response({"topics": topics, "total": len(topics)}) + return success_response({ + "topics": topics, + "total": len(topics), + "selected_topics": account.selected_topics, + }) + + +@router.put("/{account_id}/topics") +async def save_selected_topics( + account_id: str, + body: dict = Body(...), + user: User = Depends(get_current_user), + db: AsyncSession = Depends(get_db), +): + """保存用户选择的签到超话列表。空列表或 null 表示签到全部。""" + account = await _get_owned_account(account_id, user, db) + selected = body.get("selected_topics") + # null 或空列表都表示全部签到 + if selected and isinstance(selected, list) and len(selected) > 0: + account.selected_topics = selected + else: + account.selected_topics = None + await db.commit() + await db.refresh(account) + return success_response( + _account_to_dict(account), + f"已保存 {len(selected) if selected else 0} 个超话" if selected else "已设为签到全部超话", + ) # ---- MANUAL SIGNIN ---- diff --git a/backend/api_service/app/schemas/account.py b/backend/api_service/app/schemas/account.py index 0333813..9213e06 100644 --- a/backend/api_service/app/schemas/account.py +++ b/backend/api_service/app/schemas/account.py @@ -27,6 +27,7 @@ class AccountResponse(BaseModel): weibo_user_id: str remark: Optional[str] status: str + selected_topics: Optional[list] = None last_checked_at: Optional[datetime] created_at: Optional[datetime] diff --git a/backend/shared/models/account.py b/backend/shared/models/account.py index 1688fa7..eb5a139 100644 --- a/backend/shared/models/account.py +++ b/backend/shared/models/account.py @@ -2,7 +2,7 @@ import uuid -from sqlalchemy import Column, DateTime, ForeignKey, String, Text +from sqlalchemy import Column, DateTime, ForeignKey, JSON, String, Text from sqlalchemy.orm import relationship from sqlalchemy.sql import func @@ -19,6 +19,7 @@ class Account(Base): encrypted_cookies = Column(Text, nullable=False) iv = Column(String(32), nullable=False) status = Column(String(20), default="pending") + selected_topics = Column(JSON, nullable=True) # 用户选择的签到超话列表,null=全部 last_checked_at = Column(DateTime, nullable=True) created_at = Column(DateTime, server_default=func.now()) diff --git a/backend/task_scheduler/app/main.py b/backend/task_scheduler/app/main.py index 234f5f8..2877534 100644 --- a/backend/task_scheduler/app/main.py +++ b/backend/task_scheduler/app/main.py @@ -369,6 +369,7 @@ async def _async_do_signin(account_id: str, cron_expr: str = ""): return {"status": "failed", "reason": "cookie decryption failed"} acc_id = str(account.id) + acc_selected_topics = account.selected_topics # 用户选择的超话列表 except Exception as e: await eng.dispose() raise e @@ -387,6 +388,16 @@ async def _async_do_signin(account_id: str, cron_expr: str = ""): await session.commit() return {"status": "completed", "signed": 0, "message": "no topics"} + # 如果用户选择了特定超话,只签选中的 + if acc_selected_topics and isinstance(acc_selected_topics, list): + selected_cids = {t.get("containerid") for t in acc_selected_topics if t.get("containerid")} + if selected_cids: + topics = [t for t in topics if t["containerid"] in selected_cids] + logger.info(f"📌 按用户选择过滤: {len(topics)} 个超话 (共 {len(selected_cids)} 个已选)") + + if not topics: + return {"status": "completed", "signed": 0, "message": "no selected topics"} + signed = already = failed = 0 log_entries = [] diff --git a/frontend/app.py b/frontend/app.py index 752d79c..32e0fac 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -1085,7 +1085,9 @@ def account_topics(account_id): try: resp = api_request('GET', f'{API_BASE_URL}/api/v1/accounts/{account_id}/topics') data = resp.json() - topics = data.get('data', {}).get('topics', []) if data.get('success') else [] + payload = data.get('data', {}) if data.get('success') else {} + topics = payload.get('topics', []) + selected_topics = payload.get('selected_topics') or [] # 获取账号信息 acc_resp = api_request('GET', f'{API_BASE_URL}/api/v1/accounts/{account_id}') @@ -1096,12 +1098,32 @@ def account_topics(account_id): flash('账号不存在', 'danger') return redirect(url_for('dashboard')) - return render_template('topics.html', account=account, topics=topics, user=session.get('user')) + return render_template('topics.html', account=account, topics=topics, selected_topics=selected_topics, user=session.get('user')) except Exception as e: flash(f'获取超话列表失败: {str(e)}', 'danger') return redirect(url_for('account_detail', account_id=account_id)) +@app.route('/accounts//topics/save', methods=['POST']) +@login_required +def save_topics(account_id): + """保存用户选择的签到超话""" + try: + body = request.json + resp = api_request( + 'PUT', + f'{API_BASE_URL}/api/v1/accounts/{account_id}/topics', + json=body, + ) + data = resp.json() + if data.get('success'): + return jsonify({'success': True, 'message': data.get('message', '保存成功')}) + else: + return jsonify({'success': False, 'message': data.get('message', '保存失败')}), 400 + except Exception as e: + return jsonify({'success': False, 'message': str(e)}), 500 + + @app.route('/accounts//signin-selected', methods=['POST']) @login_required def signin_selected(account_id): diff --git a/frontend/templates/topics.html b/frontend/templates/topics.html index 29868c9..3f9f215 100644 --- a/frontend/templates/topics.html +++ b/frontend/templates/topics.html @@ -4,8 +4,8 @@ {% block extra_css %} {% endblock %} @@ -42,24 +45,35 @@
-

🔥 选择签到超话

-
+

🔥 超话签到管理

+
{{ account.remark or account.weibo_user_id }} · 共 {{ topics|length }} 个超话
← 返回
+
+ 💡 勾选要参与定时签到的超话,点击「保存选择」后,定时任务和手动签到都只签选中的超话。不选则签到全部。 +
+
{% if topics %}
- - + +
+ + +
{% for topic in topics %}