Skip to content

修复部分问题#589

Open
c6ForH66 wants to merge 30 commits intoSamueli924:mainfrom
c6ForH66:main
Open

修复部分问题#589
c6ForH66 wants to merge 30 commits intoSamueli924:mainfrom
c6ForH66:main

Conversation

@c6ForH66
Copy link
Copy Markdown
Contributor

@c6ForH66 c6ForH66 commented Mar 11, 2026

  • 修复api限流方法,解决刷课卡死的问题
  • 视频可以拖进度时修改 is_drag=4 解决部分情况卡死在视频末尾的问题
  • 添加部分用于签到的api

Summary by CodeRabbit

  • New Features

    • Added an automatic sign-in option.
    • Added activity management and pre-sign/sign workflows, including location-based check-ins.
  • Improvements

    • Improved video progress tracking and initial completion handling.
    • Enhanced logging, diagnostics, and HTTP response handling.
    • Refined rate-limiting behavior and retry coordination for more reliable operations.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 11, 2026

📝 Walkthrough

Walkthrough

Added three IntEnum types and a sign-in workflow (get_activity_list, pre_sign, sign_in_normal); refined rate limiter and various API helpers; extended video progress tracking with an _isdrag parameter and adjusted study_video flow; added --auto-sign CLI flag and refactored course processing to a JobProcessor-based flow.

Changes

Cohort / File(s) Summary
Activity Sign-In Workflow
api/base.py
Added SignType, ActivityStatus, ActivityType (IntEnum) and public methods get_activity_list(), pre_sign(), sign_in_normal() to implement activity-based sign-in (supports location distance extraction and different sign types).
Video Progress Tracking
api/base.py
Extended video_progress_log() with _isdrag param (default 3); study_video() now calls it with _isdrag=4 on initial completion path; _refresh_video_status() annotated to return Optional[dict].
Core API & Helpers
api/base.py
Refactored rate limiter to compute base wait and optionally add randomness; get_fid() defaults to 1024 if missing; get_course_point()/get_job_list() gain more verbose tracing; get_enc() builds multiline MD5 input; defensive HTTP checks and logging improved.
CLI & Processing Flow
main.py
Added --auto-sign CLI flag; replaced legacy course processing loop with JobProcessor-based workflow; added explanatory comments around retry_thread() and minor formatting tweaks.

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Client as Chaoxing Client
    participant API as Client API Layer
    participant Activity as Activity Endpoint

    User->>Client: Trigger sign-in for course
    Client->>API: get_activity_list(course)
    API->>Activity: Fetch activities
    Activity-->>API: Return list
    API-->>Client: Activities returned

    Client->>API: pre_sign(course, activity_id)
    API->>Activity: Pre-sign request
    Activity-->>API: Pre-sign response
    API-->>Client: Raw pre-sign text

    Client->>API: sign_in_normal(course, activity_id, params)
    API->>Activity: Submit sign-in (type/location)
    Activity-->>API: Sign-in result
    API-->>Client: Result -> User
Loading
sequenceDiagram
    participant Job as Video Job
    participant Tracker as Video Tracker
    participant API as Progress API
    participant Server as Chaoxing Server

    Job->>Tracker: study_video() start
    Tracker->>API: video_progress_log(_isdrag=4)
    API->>Server: Submit progress with drag flag
    Server-->>API: Acknowledge
    API-->>Tracker: Progress OK

    Tracker->>Server: _refresh_video_status() check
    Server-->>Tracker: Status update
    Tracker-->>Job: Return StudyResult
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I hopped through enums, endpoints, and cheer,

Pre-signs and sign-ins now hop near,
Videos report with a gentle drag,
Jobs queue up; no need to lag,
Code blooms bright — a carrot-shaped cheer! 🥕

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.08% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Title check ❓ Inconclusive The title '修复部分问题' (Fix some issues) is extremely vague and generic, providing no specific information about what was actually changed. Replace the generic title with a specific summary of the main change, such as 'Fix rate limiter and add sign-in APIs' or 'Refactor rate limiting and implement sign-in functionality'.
✅ Passed checks (1 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
api/base.py (2)

265-285: Minor: pre_sign does not return an error indicator on failure.

The method logs an error when status_code != 200 but still returns resp_txt without raising or returning a distinct error value. This could mask failures from callers.

💡 Consider returning None or raising on failure
         resp = s.get('https://mobilelearn.chaoxing.com/newsign/preSign', params=params)
         resp_txt = resp.text
         logger.debug("Request url" + resp.url)
         if resp.status_code != 200:
             logger.error("Failed to get sign in, return code: " + str(resp.status_code) + "message: " + resp_txt)
+            return None

         return resp_txt
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/base.py` around lines 265 - 285, pre_sign currently logs errors when
resp.status_code != 200 but still returns resp.text, which hides failures from
callers; change pre_sign (which uses SessionManager.get_session() and calls
s.get(...)) to raise a descriptive exception (including resp.status_code,
resp.text and resp.url) on non-200 responses instead of returning resp_txt, or
alternatively return None on failure and update callers to handle None — pick
one consistent approach and implement it inside pre_sign so callers can detect
and handle errors.

200-200: Consider documenting the magic number 1024 fallback.

Returning 1024 as a default when fid is missing from cookies works, but the meaning of this value is unclear. A brief comment would improve maintainability.

📝 Add documentation
     def get_fid(self):
         _session = SessionManager.get_session()
+        # 1024 is a default fid value that works for most API calls when the actual fid is unavailable
         return _session.cookies.get("fid", 1024)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/base.py` at line 200, The fallback value 1024 used in the expression
_session.cookies.get("fid", 1024) is a magic number and needs clarification—add
a short inline comment explaining why 1024 is the default (e.g., legacy ID,
sentinel, or max/default value) or replace it with a well-named constant (e.g.,
DEFAULT_FID = 1024) and reference that constant here so the intent of the
fallback for the "fid" cookie is clear and maintainable.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/base.py`:
- Around line 89-94: The code double-adds extra_wait into the delay: extra_wait
is included in call_wait and then added again when setting self.last_call,
inflating rate-limit delays; fix by setting self.last_call = now + call_wait
(i.e., remove the additional + extra_wait) so last_call and
time.sleep(call_wait) use the same computed delay (referencing variables
base_wait, extra_wait, call_wait and attribute self.last_call in the
rate-limiting logic).
- Around line 510-514: The logger.debug call in api/base.py mixes Python "%"
formatting with Loguru style placeholders; change the call that currently uses
"刷新视频状态返回码异常: {}" % resp.status_code to use Loguru's brace-style placeholder and
pass resp.status_code as an argument (e.g., logger.debug("刷新视频状态返回码异常: {}",
resp.status_code)), leaving the subsequent logger.debug(resp.text) as-is.
- Around line 314-316: The code is logging the Match object `msg` instead of the
captured distance string; update the logic around `pattern`, `resp_txt`, and
`msg` to check that `msg` is not None, extract the captured group via
`msg.group(1)` (or handle missing group safely), and log that extracted value
(e.g. `distance = msg.group(1)` then `logger.warning(f"距离签到位置 {distance}m")`);
ensure you handle the None case (skip logging or log a sensible fallback) so you
never try to call group on None.

In `@main.py`:
- Around line 87-88: The --auto-sign flag is parsed as args.auto_sign but never
used; either remove the flag or wire it into the runtime config and sign-in
workflow. Update build_config_from_args() to accept and set an auto_sign boolean
from args.auto_sign (so the returned config includes config.auto_sign), then in
the main execution path where the config is consumed, branch on config.auto_sign
to invoke the sign-in workflow: call the API methods in api/base.py
(get_activity_list, pre_sign, sign_in_normal) in the appropriate sequence and
handle/report errors; alternatively, if you intend to postpone the feature,
remove the parser.add_argument("--auto-sign", ...) line to avoid dead flags.

---

Nitpick comments:
In `@api/base.py`:
- Around line 265-285: pre_sign currently logs errors when resp.status_code !=
200 but still returns resp.text, which hides failures from callers; change
pre_sign (which uses SessionManager.get_session() and calls s.get(...)) to raise
a descriptive exception (including resp.status_code, resp.text and resp.url) on
non-200 responses instead of returning resp_txt, or alternatively return None on
failure and update callers to handle None — pick one consistent approach and
implement it inside pre_sign so callers can detect and handle errors.
- Line 200: The fallback value 1024 used in the expression
_session.cookies.get("fid", 1024) is a magic number and needs clarification—add
a short inline comment explaining why 1024 is the default (e.g., legacy ID,
sentinel, or max/default value) or replace it with a well-named constant (e.g.,
DEFAULT_FID = 1024) and reference that constant here so the intent of the
fallback for the "fid" cookie is clear and maintainable.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 27686567-285c-48c5-930b-2c021a7dd5df

📥 Commits

Reviewing files that changed from the base of the PR and between d4816a5 and 4ca982d.

📒 Files selected for processing (2)
  • api/base.py
  • main.py

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
api/base.py (2)

602-607: ⚠️ Potential issue | 🟠 Major

Apply the refreshed metadata back to the loop state.

The recovery branch fetches a new duration and playTime, but it only stores duration in a throwaway _duration variable. After a 403, the next iteration can keep using the stale duration, and play_time may become a string straight from the status payload.

🐛 Proposed fix
-                        _dtoken = refreshed_meta.get("dtoken", _dtoken)
-                        _duration = refreshed_meta.get("duration", duration)
-                        play_time = refreshed_meta.get("playTime", play_time)
-
-                        logger.debug("Refreshed token: {}, duration: {}, play time: {}", _dtoken, _duration, play_time)
+                        _dtoken = refreshed_meta.get("dtoken", _dtoken)
+                        duration = int(refreshed_meta.get("duration", duration))
+                        play_time = int(refreshed_meta.get("playTime", play_time))
+
+                        logger.debug("Refreshed token: {}, duration: {}, play time: {}", _dtoken, duration, play_time)
                         continue
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/base.py` around lines 602 - 607, The refreshed_meta handling currently
assigns to throwaway locals and leaves loop state stale; in the recovery branch
update the actual loop variables by assigning _dtoken =
refreshed_meta["dtoken"], duration = refreshed_meta["duration"], and play_time =
int(refreshed_meta["playTime"]) (or coerce/validate numeric) instead of using
.get fallbacks, and if any of those keys are missing raise/log an error rather
than silently falling back; update the values before the logger.debug call so
the next iteration uses the refreshed state.

391-417: ⚠️ Potential issue | 🟠 Major

The new fast-path still cannot send rt=4.

Line 577 now passes _isdrag=4, but params["rt"] is still derived from _job['rt'] or the fallback {0.9, 1}. This path changes isdrag, not rt, so the advertised seekable-video behavior is still missing.

🐛 One minimal fix
-        rt = _job['rt']
+        rt = 4 if _isdrag == 4 else _job['rt']

Also applies to: 577-578

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/base.py` around lines 391 - 417, The fast-path sets _isdrag but never
updates params["rt"], so when callers pass _isdrag=4 the request still uses
_job["rt"] or the old fallback; update the params construction (the params dict
creation where "isdrag" and "view" are set) to explicitly set params["rt"] based
on the _isdrag argument (e.g., override params["rt"] = _isdrag when _isdrag is
provided or when it indicates the seekable/fast-path value) instead of relying
solely on _job["rt"].
♻️ Duplicate comments (2)
api/base.py (2)

314-316: ⚠️ Potential issue | 🟡 Minor

Still logging the Match object here.

re.match() returns Match | None, so this warning never prints the numeric distance. Extract group(1) after a None check.

🐛 Proposed fix
         pattern = r"[^0-9\.]*(.+)米[^0-9\.]*"
         msg = re.match(pattern, resp_txt)
-        logger.warning(f"距离签到位置 {msg}m")
+        if msg:
+            logger.warning(f"距离签到位置 {msg.group(1)}m")
+        else:
+            logger.warning(f"无法解析签到距离: {resp_txt}")
#!/bin/bash
python - <<'PY'
import re

resp_txt = "距离签到位置 12.5米"
msg = re.match(r"[^0-9\.]*(.+)米[^0-9\.]*", resp_txt)

print(msg)
print(msg.group(1) if msg else None)
PY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/base.py` around lines 314 - 316, The warning currently logs the Match
object returned by re.match instead of the captured numeric string; in the block
where pattern = r"[^0-9\.]*(.+)米[^0-9\.]*" and msg = re.match(pattern,
resp_txt), check if msg is not None and then extract the captured group via
msg.group(1) (or set a default like None/empty if no match) and pass that value
into logger.warning (e.g., logger.warning(f"距离签到位置 {distance}m")) so the numeric
distance is printed rather than the Match object.

512-512: ⚠️ Potential issue | 🟠 Major

This non-200 log path still raises before it logs.

"刷新视频状态返回码异常: {}" % resp.status_code has no % placeholder, so Python raises TypeError and _refresh_video_status() never reaches the intended return None on this branch. Use Loguru's brace formatting with a separate argument.

🐛 Proposed fix
-            logger.debug("刷新视频状态返回码异常: {}" % resp.status_code)
+            logger.debug("刷新视频状态返回码异常: {}", resp.status_code)
#!/bin/bash
python - <<'PY'
try:
    print("刷新视频状态返回码异常: {}" % 403)
except Exception as exc:
    print(type(exc).__name__, exc)
PY
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@api/base.py` at line 512, The log call in _refresh_video_status uses Python’s
percent-formatting incorrectly and raises before returning; change the
logger.debug invocation so it uses Loguru’s brace-style formatting with the
status code as a separate argument (i.e. pass the message template "刷新视频状态返回码异常:
{}" and resp.status_code as the next parameter) so the log prints the status
without raising and the function can return None as intended.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@api/base.py`:
- Around line 256-262: The get_activity_list() function currently assumes
resp.json() returns the expected structure and will raise on invalid JSON or
missing keys; wrap the JSON decode and subsequent key access in a try/except
that catches JSON decode errors (ValueError/JSONDecodeError) and
KeyError/TypeError, log the exception with context (including resp.url or
truncated resp.text at debug level), and return [] as a safe fallback; validate
that data is a dict and contains "result" and nested "data" -> "activeList"
before returning it.

---

Outside diff comments:
In `@api/base.py`:
- Around line 602-607: The refreshed_meta handling currently assigns to
throwaway locals and leaves loop state stale; in the recovery branch update the
actual loop variables by assigning _dtoken = refreshed_meta["dtoken"], duration
= refreshed_meta["duration"], and play_time = int(refreshed_meta["playTime"])
(or coerce/validate numeric) instead of using .get fallbacks, and if any of
those keys are missing raise/log an error rather than silently falling back;
update the values before the logger.debug call so the next iteration uses the
refreshed state.
- Around line 391-417: The fast-path sets _isdrag but never updates
params["rt"], so when callers pass _isdrag=4 the request still uses _job["rt"]
or the old fallback; update the params construction (the params dict creation
where "isdrag" and "view" are set) to explicitly set params["rt"] based on the
_isdrag argument (e.g., override params["rt"] = _isdrag when _isdrag is provided
or when it indicates the seekable/fast-path value) instead of relying solely on
_job["rt"].

---

Duplicate comments:
In `@api/base.py`:
- Around line 314-316: The warning currently logs the Match object returned by
re.match instead of the captured numeric string; in the block where pattern =
r"[^0-9\.]*(.+)米[^0-9\.]*" and msg = re.match(pattern, resp_txt), check if msg
is not None and then extract the captured group via msg.group(1) (or set a
default like None/empty if no match) and pass that value into logger.warning
(e.g., logger.warning(f"距离签到位置 {distance}m")) so the numeric distance is printed
rather than the Match object.
- Line 512: The log call in _refresh_video_status uses Python’s
percent-formatting incorrectly and raises before returning; change the
logger.debug invocation so it uses Loguru’s brace-style formatting with the
status code as a separate argument (i.e. pass the message template "刷新视频状态返回码异常:
{}" and resp.status_code as the next parameter) so the log prints the status
without raising and the function can return None as intended.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: c9babdd6-e2d3-4098-8db3-f2e4854cd102

📥 Commits

Reviewing files that changed from the base of the PR and between 4ca982d and 8261951.

📒 Files selected for processing (1)
  • api/base.py

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant