Skip to content

Conversation

liblaf-bot[bot]
Copy link
Contributor

@liblaf-bot liblaf-bot bot commented Sep 2, 2025

⚠️MegaLinter analysis: Success with warnings

Descriptor Linter Files Fixed Errors Warnings Elapsed time
✅ BASH shellcheck 3 0 0 0.05s
✅ BASH shfmt 3 0 0 0 0.02s
⚠️ COPYPASTE jscpd yes 1 no 1.94s
⚠️ JSON jsonlint 10 1 0 0.17s
✅ JSON prettier 10 0 0 0 0.35s
✅ JSON v8r 10 0 0 4.48s
⚠️ MARKDOWN markdownlint 3 2 46 0 0.9s
⚠️ PYTHON ruff yes yes 100 no 1.6s
✅ SPELL cspell 48 0 0 3.72s
✅ YAML prettier 4 0 0 0 0.54s
✅ YAML v8r 4 0 0 4.71s
✅ YAML yamllint 4 0 0 0.48s

Detailed Issues

⚠️ COPYPASTE / jscpd - 1 error
Clone found (python):
 - src/thu_learn_downloader/download/downloader.py [162:10 - 173:5] (11 lines, 93 tokens)
   src/thu_learn_downloader/download/downloader.py [117:13 - 128:3]

┌────────┬────────────────┬─────────────┬──────────────┬──────────────┬──────────────────┬───────────────────┐
│ Format │ Files analyzed │ Total lines │ Total tokens │ Clones found │ Duplicated lines │ Duplicated tokens │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ bash   │ 12             │ 274         │ 1764         │ 0            │ 0 (0%)           │ 0 (0%)            │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ python │ 20             │ 1305        │ 10048        │ 1            │ 11 (0.84%)       │ 93 (0.93%)        │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ Total: │ 32             │ 1579        │ 11812        │ 1            │ 11 (0.7%)        │ 93 (0.79%)        │
└────────┴────────────────┴─────────────┴──────────────┴──────────────┴──────────────────┴───────────────────┘
Found 1 clones.
HTML report saved to megalinter-reports/copy-paste/html/
ERROR: jscpd found too many duplicates (0.7%) over threshold (0%)
Error: ERROR: jscpd found too many duplicates (0.7%) over threshold (0%)
    at ThresholdReporter.report (/node-deps/node_modules/@jscpd/finder/dist/index.js:612:13)
    at /node-deps/node_modules/@jscpd/finder/dist/index.js:110:18
    at Array.forEach (<anonymous>)
    at /node-deps/node_modules/@jscpd/finder/dist/index.js:109:22
    at async /node-deps/node_modules/jscpd/dist/jscpd.js:351:5
⚠️ JSON / jsonlint - 1 error
File: .vscode/settings.json
Parse error on line 17, column 5:
....customTags": [    // ref: <https://squ...
----------------------^
Unexpected token "/"
⚠️ MARKDOWN / markdownlint - 46 errors
CHANGELOG.md:11 MD024/no-duplicate-heading Multiple headings with the same content [Context: "💥 BREAKING CHANGES"]
CHANGELOG.md:32 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:37 MD024/no-duplicate-heading Multiple headings with the same content [Context: "⬆️ Dependencies"]
CHANGELOG.md:65 MD024/no-duplicate-heading Multiple headings with the same content [Context: "👷 Build System"]
CHANGELOG.md:84 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:91 MD024/no-duplicate-heading Multiple headings with the same content [Context: "✨ Features"]
CHANGELOG.md:99 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🔧 Continuous Integration"]
CHANGELOG.md:105 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:109 MD024/no-duplicate-heading Multiple headings with the same content [Context: "👷 Build System"]
CHANGELOG.md:115 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:121 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:127 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:133 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:137 MD024/no-duplicate-heading Multiple headings with the same content [Context: "📝 Documentation"]
CHANGELOG.md:141 MD024/no-duplicate-heading Multiple headings with the same content [Context: "👷 Build System"]
CHANGELOG.md:145 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🔧 Continuous Integration"]
CHANGELOG.md:151 MD024/no-duplicate-heading Multiple headings with the same content [Context: "📝 Documentation"]
CHANGELOG.md:156 MD024/no-duplicate-heading Multiple headings with the same content [Context: "👷 Build System"]
CHANGELOG.md:162 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:168 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:174 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:178 MD024/no-duplicate-heading Multiple headings with the same content [Context: "📝 Documentation"]
CHANGELOG.md:183 MD024/no-duplicate-heading Multiple headings with the same content [Context: "👷 Build System"]
CHANGELOG.md:189 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🔧 Continuous Integration"]
CHANGELOG.md:195 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:199 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🔧 Continuous Integration"]
CHANGELOG.md:205 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:209 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🔧 Continuous Integration"]
CHANGELOG.md:215 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:219 MD024/no-duplicate-heading Multiple headings with the same content [Context: "📝 Documentation"]
CHANGELOG.md:223 MD024/no-duplicate-heading Multiple headings with the same content [Context: "👷 Build System"]
CHANGELOG.md:230 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:236 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:244 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:250 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:257 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:261 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🔧 Continuous Integration"]
CHANGELOG.md:268 MD024/no-duplicate-heading Multiple headings with the same content [Context: "📝 Documentation"]
CHANGELOG.md:274 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:280 MD024/no-duplicate-heading Multiple headings with the same content [Context: "✨ Features"]
CHANGELOG.md:286 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:294 MD024/no-duplicate-heading Multiple headings with the same content [Context: "📝 Documentation"]
CHANGELOG.md:304 MD024/no-duplicate-heading Multiple headings with the same content [Context: "👷 Build System"]
CHANGELOG.md:308 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🔧 Continuous Integration"]
docs/README.md:1 MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "{% include "../README.md" %}"]
README.md:15 MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
⚠️ PYTHON / ruff - 100 errors
TRY301 Abstract `raise` to an inner function
  --> src/thu_learn_downloader/client/client.py:33:17
   |
31 |             token = self.cookies.get("XSRF-TOKEN")
32 |             if token is None:
33 |                 raise KeyError("XSRF-TOKEN not found in cookies")
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
34 |             return token
35 |         except KeyError as e:
   |

TRY003 Avoid specifying long messages outside the exception class
  --> src/thu_learn_downloader/client/client.py:33:23
   |
31 |             token = self.cookies.get("XSRF-TOKEN")
32 |             if token is None:
33 |                 raise KeyError("XSRF-TOKEN not found in cookies")
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
34 |             return token
35 |         except KeyError as e:
   |

EM101 Exception must not use a string literal, assign to variable first
  --> src/thu_learn_downloader/client/client.py:33:32
   |
31 |             token = self.cookies.get("XSRF-TOKEN")
32 |             if token is None:
33 |                 raise KeyError("XSRF-TOKEN not found in cookies")
   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
34 |             return token
35 |         except KeyError as e:
   |
help: Assign to variable; remove string literal

TRY300 Consider moving this statement to an `else` block
  --> src/thu_learn_downloader/client/client.py:34:13
   |
32 |             if token is None:
33 |                 raise KeyError("XSRF-TOKEN not found in cookies")
34 |             return token
   |             ^^^^^^^^^^^^
35 |         except KeyError as e:
36 |             print(f"无法获取CSRF token: {e}")
   |

TRY002 Create your own exception
  --> src/thu_learn_downloader/client/client.py:38:19
   |
36 |             print(f"无法获取CSRF token: {e}")
37 |             print(f"当前cookies: {list(self.cookies.keys())}")
38 |             raise Exception("登录状态可能已失效,请重新登录") from e
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |

EM101 Exception must not use a string literal, assign to variable first
  --> src/thu_learn_downloader/client/client.py:38:29
   |
36 |             print(f"无法获取CSRF token: {e}")
37 |             print(f"当前cookies: {list(self.cookies.keys())}")
38 |             raise Exception("登录状态可能已失效,请重新登录") from e
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
help: Assign to variable; remove string literal

RUF001 String contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/client/client.py:38:39
   |
36 |             print(f"无法获取CSRF token: {e}")
37 |             print(f"当前cookies: {list(self.cookies.keys())}")
38 |             raise Exception("登录状态可能已失效,请重新登录") from e
   |                                                ^^
   |

ANN201 Missing return type annotation for public function `get_course_issues`
  --> src/thu_learn_downloader/client/course.py:21:5
   |
21 | def get_course_issues():
   |     ^^^^^^^^^^^^^^^^^
22 |     """获取课程问题汇总"""
23 |     return _course_issues.copy()
   |
help: Add return type annotation

D415 First line should end with a period, question mark, or exclamation point
  --> src/thu_learn_downloader/client/course.py:22:5
   |
21 | def get_course_issues():
22 |     """获取课程问题汇总"""
   |     ^^^^^^^^^^^^^^^^^^^^^^
23 |     return _course_issues.copy()
   |
help: Add closing punctuation

ANN201 Missing return type annotation for public function `clear_course_issues`
  --> src/thu_learn_downloader/client/course.py:26:5
   |
26 | def clear_course_issues():
   |     ^^^^^^^^^^^^^^^^^^^
27 |     """清空课程问题记录"""
28 |     global _course_issues
   |
help: Add return type annotation: `None`

D415 First line should end with a period, question mark, or exclamation point
  --> src/thu_learn_downloader/client/course.py:27:5
   |
26 | def clear_course_issues():
27 |     """清空课程问题记录"""
   |     ^^^^^^^^^^^^^^^^^^^^^^
28 |     global _course_issues
29 |     _course_issues = {
   |
help: Add closing punctuation

PLW0603 Using the global statement to update `_course_issues` is discouraged
  --> src/thu_learn_downloader/client/course.py:28:12
   |
26 | def clear_course_issues():
27 |     """清空课程问题记录"""
28 |     global _course_issues
   |            ^^^^^^^^^^^^^^
29 |     _course_issues = {
30 |         "missing_documents": [],
   |

RUF003 Comment contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/client/course.py:48:15
   |
46 |     @property
47 |     def document_classes(self) -> Sequence[DocumentClass]:
48 |         # 异常处理,记录问题
   |                   ^^
49 |         try:
50 |             response = self.client.get_with_token(
   |

F841 Local variable `json_data` is assigned to but never used
  --> src/thu_learn_downloader/client/course.py:54:13
   |
52 |                 params={"wlkcid": self.id},
53 |             )
54 |             json_data = response.json()["object"]["rows"]
   |             ^^^^^^^^^
55 |
56 |             return [
   |
help: Remove assignment to unused variable `json_data`

BLE001 Do not catch blind exception: `Exception`
  --> src/thu_learn_downloader/client/course.py:61:16
   |
59 |             ]
60 |
61 |         except Exception as e:
   |                ^^^^^^^^^
62 |             _course_issues["missing_document_classes"].append(
63 |                 {"course": self.name, "course_id": self.id, "reason": f"异常: {e!s}"}
   |

TRY300 Consider moving this statement to an `else` block
  --> src/thu_learn_downloader/client/course.py:82:13
   |
80 |             documents.sort(key=lambda document: document.title)
81 |             documents.sort(key=lambda document: document.upload_time)
82 |             return documents
   |             ^^^^^^^^^^^^^^^^
83 |
84 |         except Exception as e:
   |

BLE001 Do not catch blind exception: `Exception`
  --> src/thu_learn_downloader/client/course.py:84:16
   |
82 |             return documents
83 |
84 |         except Exception as e:
   |                ^^^^^^^^^
85 |             _course_issues["missing_documents"].append(
86 |                 {"course": self.name, "course_id": self.id, "reason": f"异常: {e!s}"}
   |

RUF003 Comment contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
   --> src/thu_learn_downloader/client/course.py:113:27
    |
111 |         ]
112 |
113 |         # 如果所有作业API都没有返回数据,记录该课程
    |                                        ^^
114 |         if not all_homeworks:
115 |             _course_issues["missing_homeworks"].append(
    |

RUF003 Comment contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
   --> src/thu_learn_downloader/client/course.py:127:27
    |
125 |             # 检查响应状态
126 |             if resp.status_code != 200:
127 |                 # 作业API失败时,只在所有作业API都失败时才记录
    |                                ^^
128 |                 return []
    |

BLE001 Do not catch blind exception: `Exception`
   --> src/thu_learn_downloader/client/course.py:148:16
    |
146 |             ]
147 |
148 |         except Exception:
    |                ^^^^^^^^^
149 |             return []
    |

TID252 Prefer absolute imports over relative imports from parent modules
  --> src/thu_learn_downloader/client/learn.py:18:9
   |
17 |     def login(self) -> None:
18 |         from ..login.browser import login_with_browser
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
19 |
20 |         try:
   |
help: Replace relative imports from parent modules with absolute imports

RUF003 Comment contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/client/learn.py:27:23
   |
25 |                 self.client.cookies[name] = value
26 |
27 |             # 验证登录是否成功,尝试访问课程页面
   |                               ^^
28 |             response = self.client.get(
29 |                 url=url.make_url(path="/f/wlxt/index/course/student/")
   |

RUF001 String contains ambiguous `!` (FULLWIDTH EXCLAMATION MARK). Did you mean `!` (EXCLAMATION MARK)?
  --> src/thu_learn_downloader/client/learn.py:32:31
   |
30 |             )
31 |             if response.status_code == 200:
32 |                 print("浏览器登录成功!")
   |                                      ^^
33 |             else:
34 |                 raise Exception("登录验证失败,请重试")
   |

TRY301 Abstract `raise` to an inner function
  --> src/thu_learn_downloader/client/learn.py:34:17
   |
32 |                 print("浏览器登录成功!")
33 |             else:
34 |                 raise Exception("登录验证失败,请重试")
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35 |
36 |         except Exception as e:
   |

TRY002 Create your own exception
  --> src/thu_learn_downloader/client/learn.py:34:23
   |
32 |                 print("浏览器登录成功!")
33 |             else:
34 |                 raise Exception("登录验证失败,请重试")
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35 |
36 |         except Exception as e:
   |

EM101 Exception must not use a string literal, assign to variable first
  --> src/thu_learn_downloader/client/learn.py:34:33
   |
32 |                 print("浏览器登录成功!")
33 |             else:
34 |                 raise Exception("登录验证失败,请重试")
   |                                 ^^^^^^^^^^^^^^^^^^^^^^
35 |
36 |         except Exception as e:
   |
help: Assign to variable; remove string literal

RUF001 String contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/client/learn.py:34:40
   |
32 |                 print("浏览器登录成功!")
33 |             else:
34 |                 raise Exception("登录验证失败,请重试")
   |                                              ^^
35 |
36 |         except Exception as e:
   |

BLE001 Do not catch blind exception: `Exception`
  --> src/thu_learn_downloader/client/learn.py:36:16
   |
34 |                 raise Exception("登录验证失败,请重试")
35 |
36 |         except Exception as e:
   |                ^^^^^^^^^
37 |             print(f"浏览器登录失败: {e}")
   |

RUF003 Comment contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/client/learn.py:42:22
   |
40 |     #     """
41 |     #     登录方法
42 |     #     如果提供了用户名和密码,使用传统登录方式(保留兼容性)
   |                                 ^^
43 |     #     否则使用浏览器登录方式
44 |     #     """
   |

RUF003 Comment contains ambiguous `(` (FULLWIDTH LEFT PARENTHESIS). Did you mean `(` (LEFT PARENTHESIS)?
  --> src/thu_learn_downloader/client/learn.py:42:31
   |
40 |     #     """
41 |     #     登录方法
42 |     #     如果提供了用户名和密码,使用传统登录方式(保留兼容性)
   |                                                   ^^
43 |     #     否则使用浏览器登录方式
44 |     #     """
   |

RUF003 Comment contains ambiguous `)` (FULLWIDTH RIGHT PARENTHESIS). Did you mean `)` (RIGHT PARENTHESIS)?
  --> src/thu_learn_downloader/client/learn.py:42:37
   |
40 |     #     """
41 |     #     登录方法
42 |     #     如果提供了用户名和密码,使用传统登录方式(保留兼容性)
   |                                                               ^^
43 |     #     否则使用浏览器登录方式
44 |     #     """
   |

RUF003 Comment contains ambiguous `(` (FULLWIDTH LEFT PARENTHESIS). Did you mean `(` (LEFT PARENTHESIS)?
  --> src/thu_learn_downloader/client/learn.py:53:26
   |
52 |     # def _login_with_credentials(self, username: str, password: str) -> None:
53 |     #     """传统的用户名密码登录方式(保留作为备用)"""
   |                                      ^^
54 |     #     response: Response = self.client.get(url=url.make_url())
55 |     #     soup: BeautifulSoup = BeautifulSoup(
   |

RUF003 Comment contains ambiguous `)` (FULLWIDTH RIGHT PARENTHESIS). Did you mean `)` (RIGHT PARENTHESIS)?
  --> src/thu_learn_downloader/client/learn.py:53:33
   |
52 |     # def _login_with_credentials(self, username: str, password: str) -> None:
53 |     #     """传统的用户名密码登录方式(保留作为备用)"""
   |                                                    ^^
54 |     #     response: Response = self.client.get(url=url.make_url())
55 |     #     soup: BeautifulSoup = BeautifulSoup(
   |

ANN001 Missing type annotation for function argument `fragment`
  --> src/thu_learn_downloader/client/url.py:14:5
   |
12 |     path: str = "",
13 |     query: Mapping | None = None,
14 |     fragment="",
   |     ^^^^^^^^
15 | ) -> str:
16 |     query = query or {}
   |

ANN201 Missing return type annotation for public function `cast`
 --> src/thu_learn_downloader/common/typing.py:1:5
  |
1 | def cast(typ, val):
  |     ^^^^
2 |     assert isinstance(val, typ)
3 |     return val
  |
help: Add return type annotation

ANN001 Missing type annotation for function argument `typ`
 --> src/thu_learn_downloader/common/typing.py:1:10
  |
1 | def cast(typ, val):
  |          ^^^
2 |     assert isinstance(val, typ)
3 |     return val
  |

ANN001 Missing type annotation for function argument `val`
 --> src/thu_learn_downloader/common/typing.py:1:15
  |
1 | def cast(typ, val):
  |               ^^^
2 |     assert isinstance(val, typ)
3 |     return val
  |

ARG001 Unused function argument: `semester`
  --> src/thu_learn_downloader/download/description.py:8:5
   |
 7 | def document(
 8 |     semester: Semester,
   |     ^^^^^^^^
 9 |     course: Course,
10 |     document_class: DocumentClass,
   |

ARG001 Unused function argument: `document_class`
  --> src/thu_learn_downloader/download/description.py:10:5
   |
 8 |     semester: Semester,
 9 |     course: Course,
10 |     document_class: DocumentClass,
   |     ^^^^^^^^^^^^^^
11 |     document: Document,
12 |     index: int,
   |

ARG001 Unused function argument: `semester`
  --> src/thu_learn_downloader/download/description.py:21:5
   |
20 | def attachment(
21 |     semester: Semester, course: Course, homework: Homework, attachment: Attachment
   |     ^^^^^^^^
22 | ) -> str:
23 |     return (
   |

ANN001 Missing type annotation for function argument `exc_type`
  --> src/thu_learn_downloader/download/downloader.py:92:24
   |
90 |         return self
91 |
92 |     def __exit__(self, exc_type, exc_val, exc_tb) -> None:
   |                        ^^^^^^^^
93 |         self.executor.__exit__(exc_type, exc_val, exc_tb)
94 |         self.live.__exit__(exc_type, exc_val, exc_tb)
   |

ANN001 Missing type annotation for function argument `exc_val`
  --> src/thu_learn_downloader/download/downloader.py:92:34
   |
90 |         return self
91 |
92 |     def __exit__(self, exc_type, exc_val, exc_tb) -> None:
   |                                  ^^^^^^^
93 |         self.executor.__exit__(exc_type, exc_val, exc_tb)
94 |         self.live.__exit__(exc_type, exc_val, exc_tb)
   |

ANN001 Missing type annotation for function argument `exc_tb`
  --> src/thu_learn_downloader/download/downloader.py:92:43
   |
90 |         return self
91 |
92 |     def __exit__(self, exc_type, exc_val, exc_tb) -> None:
   |                                           ^^^^^^
93 |         self.executor.__exit__(exc_type, exc_val, exc_tb)
94 |         self.live.__exit__(exc_type, exc_val, exc_tb)
   |

PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
   --> src/thu_learn_downloader/download/downloader.py:110:9
    |
108 |         self.progress_download.update(task_id=task_id, visible=True)
109 |         response: Response = client.get(url=url, stream=True)
110 |         os.makedirs(name=output.parent, exist_ok=True)
    |         ^^^^^^^^^^^
111 |         with output.open(mode="wb") as file:
112 |             for chunk in response.iter_content(chunk_size=8192):
    |
help: Replace with `Path(...).mkdir(parents=True)`

BLE001 Do not catch blind exception: `Exception`
   --> src/thu_learn_downloader/download/downloader.py:133:24
    |
131 |                 try:
132 |                     remote_size = int(response.headers["Content-Length"])
133 |                 except Exception:
    |                        ^^^^^^^^^
134 |                     remote_size = None
135 |             if remote_time is None:
    |

BLE001 Do not catch blind exception: `Exception`
   --> src/thu_learn_downloader/download/downloader.py:138:24
    |
136 |                 try:
137 |                     remote_time = dateutil.parser.parse(response.headers["Date"])
138 |                 except Exception:
    |                        ^^^^^^^^^
139 |                     remote_time = None
140 |         if (
    |

PTH103 `os.makedirs()` should be replaced by `Path.mkdir(parents=True)`
   --> src/thu_learn_downloader/download/downloader.py:298:9
    |
296 |             prefix=self.prefix, semester=semester, course=course, homework=homework
297 |         )
298 |         os.makedirs(readme_path.parent, exist_ok=True)
    |         ^^^^^^^^^^^
299 |         readme_path.write_text(homework.markdown)
300 |         for attachment in homework.attachments:
    |
help: Replace with `Path(...).mkdir(parents=True)`

D205 1 blank line required between summary line and description
  --> src/thu_learn_downloader/download/filename.py:11:5
   |
10 |   def sanitize_filename(filename: str) -> str:
11 | /     """输入: 文件名
12 | |     输出: 清理无效字符后的文件名,避免程序出错
13 | |     """
   | |_______^
14 |       # 定义Windows不允许的字符
15 |       invalid_chars = r'[<>:"|?*\\]'
   |
help: Insert single blank line

D415 First line should end with a period, question mark, or exclamation point
  --> src/thu_learn_downloader/download/filename.py:11:5
   |
10 |   def sanitize_filename(filename: str) -> str:
11 | /     """输入: 文件名
12 | |     输出: 清理无效字符后的文件名,避免程序出错
13 | |     """
   | |_______^
14 |       # 定义Windows不允许的字符
15 |       invalid_chars = r'[<>:"|?*\\]'
   |
help: Add closing punctuation

RUF002 Docstring contains ambiguous `:` (FULLWIDTH COLON). Did you mean `:` (COLON)?
  --> src/thu_learn_downloader/download/filename.py:11:10
   |
10 | def sanitize_filename(filename: str) -> str:
11 |     """输入: 文件名
   |            ^^
12 |     输出: 清理无效字符后的文件名,避免程序出错
13 |     """
   |

RUF002 Docstring contains ambiguous `:` (FULLWIDTH COLON). Did you mean `:` (COLON)?
  --> src/thu_learn_downloader/download/filename.py:12:7
   |
10 | def sanitize_filename(filename: str) -> str:
11 |     """输入: 文件名
12 |     输出: 清理无效字符后的文件名,避免程序出错
   |         ^^
13 |     """
14 |     # 定义Windows不允许的字符
   |

RUF002 Docstring contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/download/filename.py:12:20
   |
10 | def sanitize_filename(filename: str) -> str:
11 |     """输入: 文件名
12 |     输出: 清理无效字符后的文件名,避免程序出错
   |                                  ^^
13 |     """
14 |     # 定义Windows不允许的字符
   |

RUF003 Comment contains ambiguous `(` (FULLWIDTH LEFT PARENTHESIS). Did you mean `(` (LEFT PARENTHESIS)?
  --> src/thu_learn_downloader/download/filename.py:20:13
   |
18 |     filename = re.sub(invalid_chars, "_", filename)
19 |
20 |     # 移除控制字符(ASCII 0-31)
   |                   ^^
21 |     filename = re.sub(r"[\x00-\x1f]", "", filename)
   |

RUF003 Comment contains ambiguous `)` (FULLWIDTH RIGHT PARENTHESIS). Did you mean `)` (RIGHT PARENTHESIS)?
  --> src/thu_learn_downloader/download/filename.py:20:24
   |
18 |     filename = re.sub(invalid_chars, "_", filename)
19 |
20 |     # 移除控制字符(ASCII 0-31)
   |                               ^^
21 |     filename = re.sub(r"[\x00-\x1f]", "", filename)
   |

RUF003 Comment contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/download/filename.py:26:22
   |
24 |     filename = filename.strip(" .")
25 |
26 |     # 如果文件名为空或只包含无效字符,使用默认名称
   |                                     ^^
27 |     if not filename:
28 |         filename = "untitled"
   |

S110 `try`-`except`-`pass` detected, consider logging the exception
  --> src/thu_learn_downloader/login/auto.py:9:5
   |
 7 |           if username:
 8 |               return username
 9 | /     except Exception:
10 | |         pass
   | |____________^
11 |       return ""
   |

BLE001 Do not catch blind exception: `Exception`
  --> src/thu_learn_downloader/login/auto.py:9:12
   |
 7 |         if username:
 8 |             return username
 9 |     except Exception:
   |            ^^^^^^^^^
10 |         pass
11 |     return ""
   |

S110 `try`-`except`-`pass` detected, consider logging the exception
  --> src/thu_learn_downloader/login/auto.py:19:5
   |
17 |           if password:
18 |               return password
19 | /     except Exception:
20 | |         pass
   | |____________^
21 |       return ""
   |

BLE001 Do not catch blind exception: `Exception`
  --> src/thu_learn_downloader/login/auto.py:19:12
   |
17 |         if password:
18 |             return password
19 |     except Exception:
   |            ^^^^^^^^^
20 |         pass
21 |     return ""
   |

D415 First line should end with a period, question mark, or exclamation point
  --> src/thu_learn_downloader/login/browser.py:14:5
   |
13 |   def login_with_browser() -> dict[str, Any]:
14 | /     """通过浏览器进行统一用户登录,返回登录后的cookies
15 | |
16 | |     Returns:
17 | |         Dict[str, Any]: 包含登录后cookies的字典
18 | |
19 | |     Raises:
20 | |         Exception: 当登录失败或浏览器操作失败时抛出异常
21 | |     """
   | |_______^
22 |       # 配置Chrome选项
23 |       chrome_options = Options()
   |
help: Add closing punctuation

RUF002 Docstring contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/login/browser.py:14:21
   |
13 | def login_with_browser() -> dict[str, Any]:
14 |     """通过浏览器进行统一用户登录,返回登录后的cookies
   |                                  ^^
15 |
16 |     Returns:
   |

F841 Local variable `wait` is assigned to but never used
  --> src/thu_learn_downloader/login/browser.py:39:9
   |
38 |         # 等待页面加载
39 |         wait = WebDriverWait(driver, 30)
   |         ^^^^
40 |
41 |         # 检查是否已经重定向到统一身份认证页面
   |
help: Remove assignment to unused variable `wait`

RUF003 Comment contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/login/browser.py:45:19
   |
43 |         print("请在浏览器中输入您的用户名和密码完成登录")
44 |
45 |         # 等待用户登录完成,监控URL变化
   |                           ^^
46 |         # 通常登录成功后会重定向到课程页面或其他页面
47 |         success_indicators = [
   |

RUF003 Comment contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/login/browser.py:53:19
   |
51 |         ]
52 |
53 |         # 轮询检查登录状态,最多等待5分钟
   |                           ^^
54 |         timeout = 300  # 5分钟
55 |         start_time = time.time()
   |

RUF001 String contains ambiguous `!` (FULLWIDTH EXCLAMATION MARK). Did you mean `!` (EXCLAMATION MARK)?
  --> src/thu_learn_downloader/login/browser.py:62:31
   |
60 |             # 检查是否登录成功
61 |             if any(indicator in current_url for indicator in success_indicators):
62 |                 print("检测到登录成功!")
   |                                      ^^
63 |                 break
   |

RUF001 String contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/login/browser.py:69:31
   |
67 |                 # 尝试查找登录成功后的特征元素
68 |                 driver.find_element(By.PARTIAL_LINK_TEXT, "课程")
69 |                 print("检测到课程页面,登录成功!")
   |                                      ^^
70 |                 break
71 |             except:
   |

RUF001 String contains ambiguous `!` (FULLWIDTH EXCLAMATION MARK). Did you mean `!` (EXCLAMATION MARK)?
  --> src/thu_learn_downloader/login/browser.py:69:36
   |
67 |                 # 尝试查找登录成功后的特征元素
68 |                 driver.find_element(By.PARTIAL_LINK_TEXT, "课程")
69 |                 print("检测到课程页面,登录成功!")
   |                                                ^^
70 |                 break
71 |             except:
   |

E722 Do not use bare `except`
  --> src/thu_learn_downloader/login/browser.py:71:13
   |
69 |                 print("检测到课程页面,登录成功!")
70 |                 break
71 |             except:
   |             ^^^^^^
72 |                 pass
   |

S110 `try`-`except`-`pass` detected, consider logging the exception
  --> src/thu_learn_downloader/login/browser.py:71:13
   |
69 |                   print("检测到课程页面,登录成功!")
70 |                   break
71 | /             except:
72 | |                 pass
   | |____________________^
73 |
74 |               time.sleep(2)  # 等待2秒后再次检查
   |

TRY301 Abstract `raise` to an inner function
  --> src/thu_learn_downloader/login/browser.py:76:13
   |
74 |             time.sleep(2)  # 等待2秒后再次检查
75 |         else:
76 |             raise TimeoutException("登录超时,请重试")
   |             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
77 |
78 |         # 获取所有cookies
   |

EM101 Exception must not use a string literal, assign to variable first
  --> src/thu_learn_downloader/login/browser.py:76:36
   |
74 |             time.sleep(2)  # 等待2秒后再次检查
75 |         else:
76 |             raise TimeoutException("登录超时,请重试")
   |                                    ^^^^^^^^^^^^^^^^^^
77 |
78 |         # 获取所有cookies
   |
help: Assign to variable; remove string literal

RUF001 String contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/login/browser.py:76:41
   |
74 |             time.sleep(2)  # 等待2秒后再次检查
75 |         else:
76 |             raise TimeoutException("登录超时,请重试")
   |                                             ^^
77 |
78 |         # 获取所有cookies
   |

TRY300 Consider moving this statement to an `else` block
  --> src/thu_learn_downloader/login/browser.py:87:9
   |
85 |             cookie_dict[cookie["name"]] = cookie["value"]
86 |
87 |         return cookie_dict
   |         ^^^^^^^^^^^^^^^^^^
88 |
89 |     except WebDriverException as e:
   |

B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
  --> src/thu_learn_downloader/login/browser.py:90:9
   |
89 |     except WebDriverException as e:
90 |         raise Exception(f"浏览器操作失败: {e!s}")
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
91 |     except Exception as e:
92 |         raise Exception(f"登录过程中出现错误: {e!s}")
   |

TRY002 Create your own exception
  --> src/thu_learn_downloader/login/browser.py:90:15
   |
89 |     except WebDriverException as e:
90 |         raise Exception(f"浏览器操作失败: {e!s}")
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
91 |     except Exception as e:
92 |         raise Exception(f"登录过程中出现错误: {e!s}")
   |

TRY003 Avoid specifying long messages outside the exception class
  --> src/thu_learn_downloader/login/browser.py:90:15
   |
89 |     except WebDriverException as e:
90 |         raise Exception(f"浏览器操作失败: {e!s}")
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
91 |     except Exception as e:
92 |         raise Exception(f"登录过程中出现错误: {e!s}")
   |

EM102 Exception must not use an f-string literal, assign to variable first
  --> src/thu_learn_downloader/login/browser.py:90:25
   |
89 |     except WebDriverException as e:
90 |         raise Exception(f"浏览器操作失败: {e!s}")
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^
91 |     except Exception as e:
92 |         raise Exception(f"登录过程中出现错误: {e!s}")
   |
help: Assign to variable; remove f-string literal

BLE001 Do not catch blind exception: `Exception`
  --> src/thu_learn_downloader/login/browser.py:91:12
   |
89 |     except WebDriverException as e:
90 |         raise Exception(f"浏览器操作失败: {e!s}")
91 |     except Exception as e:
   |            ^^^^^^^^^
92 |         raise Exception(f"登录过程中出现错误: {e!s}")
93 |     finally:
   |

B904 Within an `except` clause, raise exceptions with `raise ... from err` or `raise ... from None` to distinguish them from errors in exception handling
  --> src/thu_learn_downloader/login/browser.py:92:9
   |
90 |         raise Exception(f"浏览器操作失败: {e!s}")
91 |     except Exception as e:
92 |         raise Exception(f"登录过程中出现错误: {e!s}")
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
93 |     finally:
94 |         if driver:
   |

TRY002 Create your own exception
  --> src/thu_learn_downloader/login/browser.py:92:15
   |
90 |         raise Exception(f"浏览器操作失败: {e!s}")
91 |     except Exception as e:
92 |         raise Exception(f"登录过程中出现错误: {e!s}")
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
93 |     finally:
94 |         if driver:
   |

TRY003 Avoid specifying long messages outside the exception class
  --> src/thu_learn_downloader/login/browser.py:92:15
   |
90 |         raise Exception(f"浏览器操作失败: {e!s}")
91 |     except Exception as e:
92 |         raise Exception(f"登录过程中出现错误: {e!s}")
   |               ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
93 |     finally:
94 |         if driver:
   |

EM102 Exception must not use an f-string literal, assign to variable first
  --> src/thu_learn_downloader/login/browser.py:92:25
   |
90 |         raise Exception(f"浏览器操作失败: {e!s}")
91 |     except Exception as e:
92 |         raise Exception(f"登录过程中出现错误: {e!s}")
   |                         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
93 |     finally:
94 |         if driver:
   |
help: Assign to variable; remove f-string literal

C901 `main` is too complex (20 > 10)
  --> src/thu_learn_downloader/main.py:17:5
   |
16 | @app.command()
17 | def main(
   |     ^^^^
18 |     username: Annotated[str, Option("-u", "--username")] = "",
19 |     password: Annotated[str, Option("-p", "--password")] = "",
   |

PLR0913 Too many arguments in function definition (12 > 10)
  --> src/thu_learn_downloader/main.py:17:5
   |
16 | @app.command()
17 | def main(
   |     ^^^^
18 |     username: Annotated[str, Option("-u", "--username")] = "",
19 |     password: Annotated[str, Option("-p", "--password")] = "",
   |

PLR0912 Too many branches (21 > 12)
  --> src/thu_learn_downloader/main.py:17:5
   |
16 | @app.command()
17 | def main(
   |     ^^^^
18 |     username: Annotated[str, Option("-u", "--username")] = "",
19 |     password: Annotated[str, Option("-p", "--password")] = "",
   |

PLR0915 Too many statements (62 > 50)
  --> src/thu_learn_downloader/main.py:17:5
   |
16 | @app.command()
17 | def main(
   |     ^^^^
18 |     username: Annotated[str, Option("-u", "--username")] = "",
19 |     password: Annotated[str, Option("-p", "--password")] = "",
   |

ARG001 Unused function argument: `username`
  --> src/thu_learn_downloader/main.py:18:5
   |
16 | @app.command()
17 | def main(
18 |     username: Annotated[str, Option("-u", "--username")] = "",
   |     ^^^^^^^^
19 |     password: Annotated[str, Option("-p", "--password")] = "",
20 |     save_cookie: Annotated[
   |

ARG001 Unused function argument: `password`
  --> src/thu_learn_downloader/main.py:19:5
   |
17 | def main(
18 |     username: Annotated[str, Option("-u", "--username")] = "",
19 |     password: Annotated[str, Option("-p", "--password")] = "",
   |     ^^^^^^^^
20 |     save_cookie: Annotated[
21 |         bool, Option("-save", "-save-cookie", help="保存浏览器Cookie到本地")
   |

FBT002 Boolean default positional argument in function definition
  --> src/thu_learn_downloader/main.py:20:5
   |
18 |     username: Annotated[str, Option("-u", "--username")] = "",
19 |     password: Annotated[str, Option("-p", "--password")] = "",
20 |     save_cookie: Annotated[
   |     ^^^^^^^^^^^
21 |         bool, Option("-save", "-save-cookie", help="保存浏览器Cookie到本地")
22 |     ] = True,
   |

RUF001 String contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/main.py:28:33
   |
26 |             "-all",
27 |             "--all-years",
28 |             help="下载指定年份范围内的所有课程,格式: 入学年-毕业年 (如: 2021-2025)",
   |                                               ^^
29 |         ),
30 |     ] = "",
   |

TRY301 Abstract `raise` to an inner function
  --> src/thu_learn_downloader/main.py:51:17
   |
49 |             start_year, end_year = map(int, all_years.split("-"))
50 |             if start_year >= end_year:
51 |                 raise ValueError("入学年份必须小于毕业年份")
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
52 |
53 |             print(f"📅 生成学期范围: {start_year} 年入学 - {end_year} 年毕业")
   |

EM101 Exception must not use a string literal, assign to variable first
  --> src/thu_learn_downloader/main.py:51:34
   |
49 |             start_year, end_year = map(int, all_years.split("-"))
50 |             if start_year >= end_year:
51 |                 raise ValueError("入学年份必须小于毕业年份")
   |                                  ^^^^^^^^^^^^^^^^^^^^^^^^^^
52 |
53 |             print(f"📅 生成学期范围: {start_year} 年入学 - {end_year} 年毕业")
   |
help: Assign to variable; remove string literal

RUF003 Comment contains ambiguous `:` (FULLWIDTH COLON). Did you mean `:` (COLON)?
  --> src/thu_learn_downloader/main.py:58:27
   |
56 |             generated_semesters = []
57 |             for year in range(start_year, end_year):
58 |                 # 每学年有两个学期:秋季学期 (1) 和春季学期 (2)
   |                                   ^^
59 |                 generated_semesters.append(f"{year}-{year + 1}-1")  # 秋季学期
60 |                 generated_semesters.append(f"{year}-{year + 1}-2")  # 春季学期
   |

BLE001 Do not catch blind exception: `Exception`
  --> src/thu_learn_downloader/main.py:76:16
   |
74 |                 print(f"❌ 年份范围参数错误: {e}")
75 |             return
76 |         except Exception as e:
   |                ^^^^^^^^^
77 |             print(f"❌ 解析年份范围时发生错误: {e}")
78 |             return
   |

PTH123 `open()` should be replaced by `Path.open()`
  --> src/thu_learn_downloader/main.py:82:14
   |
80 |     # 尝试加载cookie文件
81 |     try:
82 |         with open("cookies.txt") as f:
   |              ^^^^
83 |             cookies = {}
84 |             for line in f:
   |

RUF003 Comment contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/main.py:88:27
   |
86 |                 cookies[name] = value
87 |             learn.client.cookies.update(cookies)
88 |     # 如果 cookies.txt 文件不存在,则使用浏览器登录
   |                                  ^^
89 |     except FileNotFoundError:
90 |         learn.login()
   |

PTH123 `open()` should be replaced by `Path.open()`
  --> src/thu_learn_downloader/main.py:93:22
   |
91 |         if save_cookie:
92 |             try:
93 |                 with open("cookies.txt", "w") as f:
   |                      ^^^^
94 |                     for name, value in learn.client.cookies.items():
95 |                         f.write(f"{name}={value}\n")
   |

BLE001 Do not catch blind exception: `Exception`
  --> src/thu_learn_downloader/main.py:97:20
   |
95 |                         f.write(f"{name}={value}\n")
96 |                 print("✅ Cookie已保存到 cookies.txt")
97 |             except Exception as e:
   |                    ^^^^^^^^^
98 |                 print(f"⚠️ 保存cookie失败: {e}")
99 |     # else:
   |

RUF001 String contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
   --> src/thu_learn_downloader/main.py:150:35
    |
149 |         print(f"\n💡 提示: 共有 {total_issues} 门课程存在内容缺失情况")
150 |         print("   这可能是因为老师还未上传相关内容,或者该课程确实没有相应内容。")
    |                                                   ^^
151 |         print("=" * 60)
152 |     else:
    |

RUF001 String contains ambiguous `!` (FULLWIDTH EXCLAMATION MARK). Did you mean `!` (EXCLAMATION MARK)?
   --> src/thu_learn_downloader/main.py:153:30
    |
151 |         print("=" * 60)
152 |     else:
153 |         print("\n✅ 所有课程内容获取正常!")
    |                                         ^^
    |

Found 100 errors.
No fixes available (15 hidden fixes can be enabled with the `--unsafe-fixes` option).

See detailed reports in MegaLinter artifacts

MegaLinter is graciously provided by OX Security

@liblaf-bot liblaf-bot bot added autofix Apply linters automatic fixes. automerge Merge the pull request once unit tests and other checks pass. labels Sep 2, 2025
@liblaf-bot liblaf-bot bot requested a review from liblaf September 2, 2025 11:21
@liblaf-bot
Copy link
Contributor Author

liblaf-bot bot commented Sep 2, 2025

⚠️MegaLinter analysis: Success with warnings

Descriptor Linter Files Fixed Errors Warnings Elapsed time
✅ BASH shellcheck 3 0 0 0.05s
✅ BASH shfmt 3 0 0 0 0.01s
⚠️ COPYPASTE jscpd yes 1 no 1.94s
⚠️ JSON jsonlint 10 1 0 0.17s
✅ JSON prettier 10 0 0 0 0.36s
✅ JSON v8r 10 0 0 4.27s
⚠️ MARKDOWN markdownlint 3 0 46 0 0.84s
⚠️ PYTHON ruff yes yes 100 no 1.71s
✅ SPELL cspell 47 0 0 3.75s
✅ YAML prettier 3 0 0 0 0.47s
✅ YAML v8r 3 0 0 4.55s
✅ YAML yamllint 3 0 0 0.46s

Detailed Issues

⚠️ COPYPASTE / jscpd - 1 error
Clone found (python):
 - src/thu_learn_downloader/download/downloader.py [162:10 - 173:5] (11 lines, 93 tokens)
   src/thu_learn_downloader/download/downloader.py [117:13 - 128:3]

┌────────┬────────────────┬─────────────┬──────────────┬──────────────┬──────────────────┬───────────────────┐
│ Format │ Files analyzed │ Total lines │ Total tokens │ Clones found │ Duplicated lines │ Duplicated tokens │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ bash   │ 11             │ 246         │ 1586         │ 0            │ 0 (0%)           │ 0 (0%)            │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ python │ 20             │ 1305        │ 10048        │ 1            │ 11 (0.84%)       │ 93 (0.93%)        │
├────────┼────────────────┼─────────────┼──────────────┼──────────────┼──────────────────┼───────────────────┤
│ Total: │ 31             │ 1551        │ 11634        │ 1            │ 11 (0.71%)       │ 93 (0.8%)         │
└────────┴────────────────┴─────────────┴──────────────┴──────────────┴──────────────────┴───────────────────┘
Found 1 clones.
HTML report saved to megalinter-reports/copy-paste/html/
ERROR: jscpd found too many duplicates (0.71%) over threshold (0%)
Error: ERROR: jscpd found too many duplicates (0.71%) over threshold (0%)
    at ThresholdReporter.report (/node-deps/node_modules/@jscpd/finder/dist/index.js:612:13)
    at /node-deps/node_modules/@jscpd/finder/dist/index.js:110:18
    at Array.forEach (<anonymous>)
    at /node-deps/node_modules/@jscpd/finder/dist/index.js:109:22
    at async /node-deps/node_modules/jscpd/dist/jscpd.js:351:5
⚠️ JSON / jsonlint - 1 error
File: .vscode/settings.json
Parse error on line 17, column 5:
....customTags": [    // ref: <https://squ...
----------------------^
Unexpected token "/"
⚠️ MARKDOWN / markdownlint - 46 errors
CHANGELOG.md:11 MD024/no-duplicate-heading Multiple headings with the same content [Context: "💥 BREAKING CHANGES"]
CHANGELOG.md:32 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:37 MD024/no-duplicate-heading Multiple headings with the same content [Context: "⬆️ Dependencies"]
CHANGELOG.md:65 MD024/no-duplicate-heading Multiple headings with the same content [Context: "👷 Build System"]
CHANGELOG.md:84 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:91 MD024/no-duplicate-heading Multiple headings with the same content [Context: "✨ Features"]
CHANGELOG.md:99 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🔧 Continuous Integration"]
CHANGELOG.md:105 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:109 MD024/no-duplicate-heading Multiple headings with the same content [Context: "👷 Build System"]
CHANGELOG.md:115 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:121 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:127 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:133 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:137 MD024/no-duplicate-heading Multiple headings with the same content [Context: "📝 Documentation"]
CHANGELOG.md:141 MD024/no-duplicate-heading Multiple headings with the same content [Context: "👷 Build System"]
CHANGELOG.md:145 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🔧 Continuous Integration"]
CHANGELOG.md:151 MD024/no-duplicate-heading Multiple headings with the same content [Context: "📝 Documentation"]
CHANGELOG.md:156 MD024/no-duplicate-heading Multiple headings with the same content [Context: "👷 Build System"]
CHANGELOG.md:162 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:168 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:174 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:178 MD024/no-duplicate-heading Multiple headings with the same content [Context: "📝 Documentation"]
CHANGELOG.md:183 MD024/no-duplicate-heading Multiple headings with the same content [Context: "👷 Build System"]
CHANGELOG.md:189 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🔧 Continuous Integration"]
CHANGELOG.md:195 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:199 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🔧 Continuous Integration"]
CHANGELOG.md:205 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:209 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🔧 Continuous Integration"]
CHANGELOG.md:215 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:219 MD024/no-duplicate-heading Multiple headings with the same content [Context: "📝 Documentation"]
CHANGELOG.md:223 MD024/no-duplicate-heading Multiple headings with the same content [Context: "👷 Build System"]
CHANGELOG.md:230 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:236 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:244 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:250 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:257 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:261 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🔧 Continuous Integration"]
CHANGELOG.md:268 MD024/no-duplicate-heading Multiple headings with the same content [Context: "📝 Documentation"]
CHANGELOG.md:274 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:280 MD024/no-duplicate-heading Multiple headings with the same content [Context: "✨ Features"]
CHANGELOG.md:286 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🐛 Bug Fixes"]
CHANGELOG.md:294 MD024/no-duplicate-heading Multiple headings with the same content [Context: "📝 Documentation"]
CHANGELOG.md:304 MD024/no-duplicate-heading Multiple headings with the same content [Context: "👷 Build System"]
CHANGELOG.md:308 MD024/no-duplicate-heading Multiple headings with the same content [Context: "🔧 Continuous Integration"]
docs/README.md:1 MD041/first-line-heading/first-line-h1 First line in a file should be a top-level heading [Context: "{% include "../README.md" %}"]
README.md:15 MD040/fenced-code-language Fenced code blocks should have a language specified [Context: "```"]
⚠️ PYTHON / ruff - 100 errors
TRY301 Abstract `raise` to an inner function
  --> src/thu_learn_downloader/client/client.py:33:17
   |
31 |             token = self.cookies.get("XSRF-TOKEN")
32 |             if token is None:
33 |                 raise KeyError("XSRF-TOKEN not found in cookies")
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
34 |             return token
35 |         except KeyError as e:
   |

TRY003 Avoid specifying long messages outside the exception class
  --> src/thu_learn_downloader/client/client.py:33:23
   |
31 |             token = self.cookies.get("XSRF-TOKEN")
32 |             if token is None:
33 |                 raise KeyError("XSRF-TOKEN not found in cookies")
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
34 |             return token
35 |         except KeyError as e:
   |

EM101 Exception must not use a string literal, assign to variable first
  --> src/thu_learn_downloader/client/client.py:33:32
   |
31 |             token = self.cookies.get("XSRF-TOKEN")
32 |             if token is None:
33 |                 raise KeyError("XSRF-TOKEN not found in cookies")
   |                                ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
34 |             return token
35 |         except KeyError as e:
   |
help: Assign to variable; remove string literal

TRY300 Consider moving this statement to an `else` block
  --> src/thu_learn_downloader/client/client.py:34:13
   |
32 |             if token is None:
33 |                 raise KeyError("XSRF-TOKEN not found in cookies")
34 |             return token
   |             ^^^^^^^^^^^^
35 |         except KeyError as e:
36 |             print(f"无法获取CSRF token: {e}")
   |

TRY002 Create your own exception
  --> src/thu_learn_downloader/client/client.py:38:19
   |
36 |             print(f"无法获取CSRF token: {e}")
37 |             print(f"当前cookies: {list(self.cookies.keys())}")
38 |             raise Exception("登录状态可能已失效,请重新登录") from e
   |                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |

EM101 Exception must not use a string literal, assign to variable first
  --> src/thu_learn_downloader/client/client.py:38:29
   |
36 |             print(f"无法获取CSRF token: {e}")
37 |             print(f"当前cookies: {list(self.cookies.keys())}")
38 |             raise Exception("登录状态可能已失效,请重新登录") from e
   |                             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   |
help: Assign to variable; remove string literal

RUF001 String contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/client/client.py:38:39
   |
36 |             print(f"无法获取CSRF token: {e}")
37 |             print(f"当前cookies: {list(self.cookies.keys())}")
38 |             raise Exception("登录状态可能已失效,请重新登录") from e
   |                                                ^^
   |

ANN201 Missing return type annotation for public function `get_course_issues`
  --> src/thu_learn_downloader/client/course.py:21:5
   |
21 | def get_course_issues():
   |     ^^^^^^^^^^^^^^^^^
22 |     """获取课程问题汇总"""
23 |     return _course_issues.copy()
   |
help: Add return type annotation

D415 First line should end with a period, question mark, or exclamation point
  --> src/thu_learn_downloader/client/course.py:22:5
   |
21 | def get_course_issues():
22 |     """获取课程问题汇总"""
   |     ^^^^^^^^^^^^^^^^^^^^^^
23 |     return _course_issues.copy()
   |
help: Add closing punctuation

ANN201 Missing return type annotation for public function `clear_course_issues`
  --> src/thu_learn_downloader/client/course.py:26:5
   |
26 | def clear_course_issues():
   |     ^^^^^^^^^^^^^^^^^^^
27 |     """清空课程问题记录"""
28 |     global _course_issues
   |
help: Add return type annotation: `None`

D415 First line should end with a period, question mark, or exclamation point
  --> src/thu_learn_downloader/client/course.py:27:5
   |
26 | def clear_course_issues():
27 |     """清空课程问题记录"""
   |     ^^^^^^^^^^^^^^^^^^^^^^
28 |     global _course_issues
29 |     _course_issues = {
   |
help: Add closing punctuation

PLW0603 Using the global statement to update `_course_issues` is discouraged
  --> src/thu_learn_downloader/client/course.py:28:12
   |
26 | def clear_course_issues():
27 |     """清空课程问题记录"""
28 |     global _course_issues
   |            ^^^^^^^^^^^^^^
29 |     _course_issues = {
30 |         "missing_documents": [],
   |

RUF003 Comment contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/client/course.py:48:15
   |
46 |     @property
47 |     def document_classes(self) -> Sequence[DocumentClass]:
48 |         # 异常处理,记录问题
   |                   ^^
49 |         try:
50 |             response = self.client.get_with_token(
   |

F841 Local variable `json_data` is assigned to but never used
  --> src/thu_learn_downloader/client/course.py:54:13
   |
52 |                 params={"wlkcid": self.id},
53 |             )
54 |             json_data = response.json()["object"]["rows"]
   |             ^^^^^^^^^
55 |
56 |             return [
   |
help: Remove assignment to unused variable `json_data`

BLE001 Do not catch blind exception: `Exception`
  --> src/thu_learn_downloader/client/course.py:61:16
   |
59 |             ]
60 |
61 |         except Exception as e:
   |                ^^^^^^^^^
62 |             _course_issues["missing_document_classes"].append(
63 |                 {"course": self.name, "course_id": self.id, "reason": f"异常: {e!s}"}
   |

TRY300 Consider moving this statement to an `else` block
  --> src/thu_learn_downloader/client/course.py:82:13
   |
80 |             documents.sort(key=lambda document: document.title)
81 |             documents.sort(key=lambda document: document.upload_time)
82 |             return documents
   |             ^^^^^^^^^^^^^^^^
83 |
84 |         except Exception as e:
   |

BLE001 Do not catch blind exception: `Exception`
  --> src/thu_learn_downloader/client/course.py:84:16
   |
82 |             return documents
83 |
84 |         except Exception as e:
   |                ^^^^^^^^^
85 |             _course_issues["missing_documents"].append(
86 |                 {"course": self.name, "course_id": self.id, "reason": f"异常: {e!s}"}
   |

RUF003 Comment contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
   --> src/thu_learn_downloader/client/course.py:113:27
    |
111 |         ]
112 |
113 |         # 如果所有作业API都没有返回数据,记录该课程
    |                                        ^^
114 |         if not all_homeworks:
115 |             _course_issues["missing_homeworks"].append(
    |

RUF003 Comment contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
   --> src/thu_learn_downloader/client/course.py:127:27
    |
125 |             # 检查响应状态
126 |             if resp.status_code != 200:
127 |                 # 作业API失败时,只在所有作业API都失败时才记录
    |                                ^^
128 |                 return []
    |

BLE001 Do not catch blind exception: `Exception`
   --> src/thu_learn_downloader/client/course.py:148:16
    |
146 |             ]
147 |
148 |         except Exception:
    |                ^^^^^^^^^
149 |             return []
    |

TID252 Prefer absolute imports over relative imports from parent modules
  --> src/thu_learn_downloader/client/learn.py:18:9
   |
17 |     def login(self) -> None:
18 |         from ..login.browser import login_with_browser
   |         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
19 |
20 |         try:
   |
help: Replace relative imports from parent modules with absolute imports

RUF003 Comment contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/client/learn.py:27:23
   |
25 |                 self.client.cookies[name] = value
26 |
27 |             # 验证登录是否成功,尝试访问课程页面
   |                               ^^
28 |             response = self.client.get(
29 |                 url=url.make_url(path="/f/wlxt/index/course/student/")
   |

RUF001 String contains ambiguous `!` (FULLWIDTH EXCLAMATION MARK). Did you mean `!` (EXCLAMATION MARK)?
  --> src/thu_learn_downloader/client/learn.py:32:31
   |
30 |             )
31 |             if response.status_code == 200:
32 |                 print("浏览器登录成功!")
   |                                      ^^
33 |             else:
34 |                 raise Exception("登录验证失败,请重试")
   |

TRY301 Abstract `raise` to an inner function
  --> src/thu_learn_downloader/client/learn.py:34:17
   |
32 |                 print("浏览器登录成功!")
33 |             else:
34 |                 raise Exception("登录验证失败,请重试")
   |                 ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35 |
36 |         except Exception as e:
   |

TRY002 Create your own exception
  --> src/thu_learn_downloader/client/learn.py:34:23
   |
32 |                 print("浏览器登录成功!")
33 |             else:
34 |                 raise Exception("登录验证失败,请重试")
   |                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
35 |
36 |         except Exception as e:
   |

EM101 Exception must not use a string literal, assign to variable first
  --> src/thu_learn_downloader/client/learn.py:34:33
   |
32 |                 print("浏览器登录成功!")
33 |             else:
34 |                 raise Exception("登录验证失败,请重试")
   |                                 ^^^^^^^^^^^^^^^^^^^^^^
35 |
36 |         except Exception as e:
   |
help: Assign to variable; remove string literal

RUF001 String contains ambiguous `,` (FULLWIDTH COMMA). Did you mean `,` (COMMA)?
  --> src/thu_learn_downloader/client/learn.py:34:40
   |
32 |                 print("浏览器登录成功!")
33 |             else:
34 |                 raise Exception("登录验证失败,请重试")
   |                                              ^^
35 |
36 |         except Exception as e:
   |

BLE001 Do not catch blind exception: `Exception`
  --> src/thu_learn_downloader/client/learn.py:36:16
   |
34 |                 raise Exception("登录验证失败,请重试")
35 |
36 |         except Exception as e:
   |                ^^^^^^^^^
37 |             print(f"浏览器登录失败: {e}")
   |

RUF003 Comment contains

(Truncated to 10000 characters out of 35335)

See detailed reports in MegaLinter artifacts

MegaLinter is graciously provided by OX Security

@github-actions github-actions bot force-pushed the mega-linter-fix/main branch from 1531b9e to 296a6ab Compare September 3, 2025 09:33
@github-actions github-actions bot requested a review from liblaf September 3, 2025 09:33
@github-actions github-actions bot force-pushed the mega-linter-fix/main branch from 296a6ab to bd278b0 Compare September 7, 2025 01:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autofix Apply linters automatic fixes. automerge Merge the pull request once unit tests and other checks pass.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant