├── GUIEventBinder.py ├── LICENSE ├── README.md ├── Template.xlsx ├── base ├── __init__.py ├── delayer.py └── urlop.py ├── captcha ├── __init__.py ├── inference.py └── killer.py ├── core ├── __init__.py ├── course.py ├── encryptor.py └── login.py ├── gui ├── __init__.py ├── frame │ ├── __init__.py │ ├── about.py │ ├── configure.py │ └── main.py └── listctrl │ ├── __init__.py │ ├── basic_listctrl.py │ ├── logs.py │ ├── member_view.py │ ├── optional.py │ └── selected.py ├── handler ├── Error.py ├── Opa.py ├── Pool.py ├── __init__.py └── struct │ ├── __init__.py │ └── user.py ├── llogger.py ├── main.py └── model ├── model.ckpt.data-00000-of-00001 ├── model.ckpt.index └── model.ckpt.meta /GUIEventBinder.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | import wx 5 | import gui 6 | import threading 7 | 8 | _user_pool_ = None 9 | 10 | def init(Pool): 11 | global _user_pool_ 12 | _user_pool_ = Pool 13 | 14 | Main_MenuBar_File_Handler.bindEvent() 15 | Main_MenuBar_Edit_Handler.bindEvent() 16 | Main_MenuBar_Operation_Handler.bindEvent() 17 | Main_MenuBar_Help_Handler.bindEvent() 18 | Main_Member_Menu_Handler.bindEvent() 19 | Config_Button_Handler.bindEvent() 20 | Config_Choice_Handler.bindEvent() 21 | Config_TextCtrl_Handler.bindEvent() 22 | Config_ListCtrl_Selected_Handler.bindEvent() 23 | 24 | def getUserOpFromTextCtrl(): 25 | global _user_pool_ 26 | account = gui.frame_configure.textctrl_account.GetLineText(0) 27 | userop = _user_pool_.getUserOp(account) 28 | return userop 29 | 30 | def bind_main_menu_event(handler, source, attr_names): 31 | for i in attr_names: 32 | gui.frame_main.Bind(wx.EVT_MENU, getattr(handler, i), getattr(source, i)) 33 | 34 | 35 | # Frame Main MenuBar Handler 36 | 37 | class Main_MenuBar_File_Handler: 38 | @staticmethod 39 | def bindEvent(): 40 | events = ('run', 'save', 'save_as', 'import_from_xlsx', 'exit') 41 | bind_main_menu_event(Main_MenuBar_File_Handler, gui.frame_main.menu_bar.file, events) 42 | 43 | @staticmethod 44 | def run(event): 45 | _user_pool_.runTakeAll() 46 | 47 | @staticmethod 48 | def save(event): 49 | pass 50 | 51 | @staticmethod 52 | def save_as(event): 53 | pass 54 | 55 | @staticmethod 56 | def import_from_xlsx(event): 57 | dlg = wx.FileDialog(gui.frame_main, u"选择导入文件 *.xlsx", style=wx.FD_DEFAULT_STYLE) 58 | dlg.SetWildcard('*.xlsx') 59 | if dlg.ShowModal() == wx.ID_OK: 60 | path_name = dlg.GetPath() 61 | threading.Thread(target=_user_pool_.loadFromXlsx, args=(path_name,)).start() 62 | 63 | dlg.Destroy() 64 | 65 | @staticmethod 66 | def exit(event): 67 | exit() 68 | 69 | 70 | class Main_MenuBar_Operation_Handler: 71 | @staticmethod 72 | def bindEvent(): 73 | events = ('login', 'login_all', 'take', 'take_all', 'verify', 'verify_all') 74 | bind_main_menu_event(Main_MenuBar_Operation_Handler, gui.frame_main.menu_bar.operation, events) 75 | 76 | 77 | @staticmethod 78 | def login(event): 79 | Main_Member_Menu_Handler.login(event) 80 | 81 | @staticmethod 82 | def login_all(event): 83 | _user_pool_.runLoginAll() 84 | 85 | @staticmethod 86 | def take(event): 87 | Main_Member_Menu_Handler.take(event) 88 | 89 | @staticmethod 90 | def take_all(event): 91 | _user_pool_.runTakeAll() 92 | 93 | @staticmethod 94 | def verify(event): 95 | Main_Member_Menu_Handler.verify(event) 96 | 97 | @staticmethod 98 | def verify_all(event): 99 | _user_pool_.runVerifyAll() 100 | 101 | 102 | 103 | class Main_MenuBar_Edit_Handler: 104 | @staticmethod 105 | def bindEvent(): 106 | events = ('add', 'delete', 'delete_all') 107 | bind_main_menu_event(Main_MenuBar_Edit_Handler, gui.frame_main.menu_bar.edit, events) 108 | 109 | 110 | @staticmethod 111 | def add(event): 112 | gui.frame_configure.textctrl_account.Clear() 113 | gui.frame_configure.textctrl_password.Clear() 114 | gui.frame_configure.textctrl_keys.Clear() 115 | 116 | gui.frame_configure.textctrl_account.Enable(True) 117 | 118 | gui.frame_configure.Show(True) 119 | 120 | @staticmethod 121 | def delete(event): 122 | Main_Member_Menu_Handler.delete(event) 123 | 124 | @staticmethod 125 | def delete_all(event): 126 | pass 127 | 128 | 129 | 130 | 131 | class Main_MenuBar_Help_Handler: 132 | @staticmethod 133 | def bindEvent(): 134 | events = ('help', 'about') 135 | bind_main_menu_event(Main_MenuBar_Help_Handler, gui.frame_main.menu_bar.help, events) 136 | 137 | 138 | @staticmethod 139 | def help(event): 140 | pass 141 | 142 | @staticmethod 143 | def about(event): 144 | dlg = gui.About_Dialog(gui.frame_main) 145 | dlg.ShowModal() 146 | dlg.Destroy() 147 | 148 | # Frame Main Member Menu Handler 149 | 150 | class Main_Member_Menu_Handler: 151 | @staticmethod 152 | def bindEvent(): 153 | events = ('view', 'configure', 'login', 'take', 'verify', 'delete') 154 | bind_main_menu_event(Main_Member_Menu_Handler, gui.frame_main.listctrl_member.menu, events) 155 | 156 | @staticmethod 157 | def view(event): 158 | gui.frame_configure.textctrl_account.Clear() 159 | gui.frame_configure.textctrl_password.Clear() 160 | gui.frame_configure.textctrl_keys.Clear() 161 | 162 | gui.frame_configure.textctrl_account.Enable(False) 163 | 164 | index = gui.frame_main.listctrl_member.GetFirstSelected() 165 | if index == -1: 166 | return 167 | item = _user_pool_.queue[index].user 168 | 169 | gui.frame_configure.textctrl_account.write(item.account) 170 | gui.frame_configure.textctrl_keys.write(item.keys) 171 | 172 | gui.frame_configure.Show(True) 173 | 174 | @staticmethod 175 | def login(event): 176 | index = gui.frame_main.listctrl_member.GetFirstSelected() 177 | 178 | userop = _user_pool_.queue[index] 179 | if index != -1: 180 | 181 | threading.Thread(target=userop.login, name='Menu_Login').start() 182 | 183 | 184 | 185 | @staticmethod 186 | def take(event): 187 | index = gui.frame_main.listctrl_member.GetFirstSelected() 188 | 189 | userop = _user_pool_.queue[index] 190 | if index != -1: 191 | menu_text = gui.frame_main.listctrl_member.menu.take.GetText() 192 | if 'Stop' in menu_text: 193 | threading.Thread(target=userop.taker.stop, name='Menu_Stop').start() 194 | elif 'Take' in menu_text: 195 | threading.Thread(target=userop.takeCourse, name='Menu_Take').start() 196 | 197 | 198 | 199 | @staticmethod 200 | def configure(event): 201 | Main_Member_Menu_Handler.view(event) 202 | 203 | @staticmethod 204 | def verify(event): 205 | index = gui.frame_main.listctrl_member.GetFirstSelected() 206 | 207 | userop = _user_pool_.queue[index] 208 | threading.Thread(target=userop.verify).start() 209 | 210 | 211 | @staticmethod 212 | def delete(event): 213 | gui.frame_configure.textctrl_account.Clear() 214 | gui.frame_configure.textctrl_password.Clear() 215 | gui.frame_configure.textctrl_keys.Clear() 216 | 217 | gui.frame_configure.textctrl_account.Enable(False) 218 | 219 | index = gui.frame_main.listctrl_member.GetFirstSelected() 220 | if index == -1: 221 | return 222 | _user_pool_.delete(index) 223 | 224 | 225 | # Frame Configure Button Handler 226 | 227 | def bind_config_button_event(handler, source, attr_names): 228 | for i in attr_names: 229 | gui.frame_configure.Bind(wx.EVT_BUTTON, getattr(handler, i), getattr(source, 'button_%s' % i)) 230 | 231 | 232 | class Config_Button_Handler: 233 | @staticmethod 234 | def bindEvent(): 235 | events = ('login', 'load_course', 'load_usercourse', 'save_settings') 236 | bind_config_button_event(Config_Button_Handler, gui.frame_configure, events) 237 | 238 | @staticmethod 239 | def login(event): 240 | def after(): 241 | gui.frame_configure.button_login.Enable(True) 242 | gui.frame_configure.textctrl_password.Clear() 243 | 244 | def add_or_delete(userop): 245 | if not userop.user.ready: 246 | _user_pool_.remove(userop) 247 | else: 248 | gui.frame_configure.textctrl_account.Enable(False) 249 | gui.frame_configure.textctrl_password.Clear() 250 | 251 | if not gui.frame_configure.textctrl_account.IsEnabled(): 252 | gui.frame_configure.button_login.Enable(False) 253 | password = gui.frame_configure.textctrl_password.GetLineText(0) 254 | userop = getUserOpFromTextCtrl() 255 | 256 | if password: 257 | userop.user.password = password 258 | threading.Thread(target=userop.login).start() 259 | 260 | userop.join(exec_foo=after) 261 | else: 262 | account = gui.frame_configure.textctrl_account.GetLineText(0) 263 | password = gui.frame_configure.textctrl_password.GetLineText(0) 264 | keys = gui.frame_configure.textctrl_keys.GetLineText(0) 265 | 266 | userop = _user_pool_.add(account, password, keys) 267 | 268 | threading.Thread(target=userop.login).start() 269 | 270 | userop.join(exec_foo=add_or_delete, args=(userop,)) 271 | 272 | @staticmethod 273 | def load_course(event): 274 | gui.frame_configure.button_load_course.Enable(False) 275 | userop = getUserOpFromTextCtrl() 276 | 277 | threading.Thread(target=userop.taker.puller.displayCourse).start() 278 | 279 | userop.join(gui.frame_configure.button_load_course.Enable, args=(True,)) 280 | 281 | @staticmethod 282 | def load_usercourse(event): 283 | 284 | gui.frame_configure.button_load_usercourse.Enable(False) 285 | userop = getUserOpFromTextCtrl() 286 | 287 | threading.Thread(target=userop.taker.puller.displayUserCourse).start() 288 | 289 | userop.join(gui.frame_configure.button_load_usercourse.Enable, args=(True,)) 290 | 291 | # @staticmethod 292 | # def export(event): 293 | # pass 294 | 295 | @staticmethod 296 | def save_settings(event): 297 | dlg = wx.MessageDialog(gui.frame_configure, u'你确定要保存设置吗?', 298 | u'提示', wx.YES_NO | wx.ICON_QUESTION) 299 | if dlg.ShowModal() == wx.YES: 300 | userop = getUserOpFromTextCtrl() 301 | userop.user.keys = gui.frame_configure.textctrl_keys.GetLineText(0) 302 | 303 | userop.user.force_post = gui.frame_configure.checkbox_force.GetValue() 304 | 305 | timer_refresh = gui.frame_configure.spinctrl_timer.GetValue() 306 | if userop.user.timer_refresh != timer_refresh: 307 | userop.user.timer_refresh = timer_refresh 308 | userop.cancelGetNotice() 309 | userop.timingGetNotice() 310 | 311 | userop.user.delay_range = [gui.frame_configure.spinctrl_start.GetValue(), 312 | gui.frame_configure.spinctrl_end.GetValue()] 313 | 314 | 315 | # Frame Configure Choice Handler 316 | class Config_Choice_Handler: 317 | @staticmethod 318 | def bindEvent(): 319 | gui.frame_configure.Bind(wx.EVT_CHOICE, Config_Choice_Handler.season, 320 | gui.frame_configure.choice_season) 321 | 322 | @staticmethod 323 | def season(event): 324 | selected_index = gui.frame_configure.choice_season.GetCurrentSelection() 325 | 326 | userop = getUserOpFromTextCtrl() 327 | 328 | userop.taker.puller.season_code.setSeletedIndex(selected_index) 329 | 330 | 331 | # Frame Configure Choice Handler 332 | class Config_TextCtrl_Handler: 333 | @staticmethod 334 | def bindEvent(): 335 | gui.frame_configure.Bind(wx.EVT_TEXT, Config_TextCtrl_Handler.keys, 336 | gui.frame_configure.textctrl_keys) 337 | 338 | @staticmethod 339 | def keys(event): 340 | if gui.frame_configure.listctrl_optional.style == 0: # STYLE_OPTIONAL 341 | 342 | userop = getUserOpFromTextCtrl() 343 | if userop: 344 | userop.taker.puller.displayTarget() 345 | 346 | 347 | def bind_config_menu_event(handler, source, attr_names): 348 | for i in attr_names: 349 | gui.frame_configure.Bind(wx.EVT_MENU, getattr(handler, i), getattr(source, i)) 350 | 351 | 352 | # Frame Configure Selected Handler 353 | class Config_ListCtrl_Selected_Handler: 354 | @staticmethod 355 | def bindEvent(): 356 | items = ('delete',) 357 | bind_config_menu_event(Config_ListCtrl_Selected_Handler, gui.frame_configure.listctrl_selected.menu, items) 358 | 359 | @staticmethod 360 | def delete(event): 361 | index = gui.frame_configure.listctrl_selected.GetFirstSelected() 362 | if index != -1: 363 | userop = getUserOpFromTextCtrl() 364 | course = userop.taker.puller.selected.index(index) 365 | dlg = wx.MessageDialog(gui.frame_configure, u'你确定要退选课程:[%s] ?' % course.__str__(), 366 | u'提示', wx.YES_NO | wx.ICON_QUESTION) 367 | if dlg.ShowModal() == wx.YES: 368 | if userop.taker.postCancel(): 369 | wx.MessageDialog(gui.frame_configure, u'退选成功!', u'完成', wx.OK | wx.ICON_INFORMATION) 370 | else: 371 | wx.MessageDialog(gui.frame_configure, u'退选失败,(详情请看主窗口的logs的ServerMsg)。', u'错误', 372 | wx.OK | wx.ICON_ERROR) 373 | 374 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # TakeTheCourse_GDUT 2 | # GDUT教务系统选课助手 3 | 4 | # 功能 5 | 6 | * 支持自动验证码识别登录。 7 | * 支持关键字、优先级、多备用课程的选课方式。 8 | * 支持批量登录、选课、验证操作。 9 | * 支持批量导入用户。 10 | 11 | # 更新说明 12 | * **2019/03/28** 13 | * 加入退选课程功能。 14 | * 部分操作加入信息窗确认操作。 15 | * 加入显示目标课程,通过修改keys文本刷新列表。 16 | * 删除导出课程按钮。 17 | * **2019/03/25** 18 | * 重构核心代码。 19 | * 支持GUI界面交互。 20 | * **2018/12/20** 21 | * 上传杂乱代码。 22 | 23 | 24 | # 使用方法 25 | 26 | ## **文件 ``Template.xlsx`` 使用方法** 27 | 28 | * ``account`` : 教务系统账号 29 | * ``password`` : 教务系统密码 30 | * ``key`` : 所选课程名字(或教师名字)关键字。``,``(英文逗号): 顺序优先级的与操作 ; ``|`` :顺序优先级的或操作 31 | 32 | 注意:优先级从左到右 33 | 34 | 35 | 36 | # 说明 37 | ## 由于选课的测试次数限制,所以程序难免会出现选课不成功的问题。 38 | ## 建议使用快捷键F4手动选课,而不是依赖自动流程。 39 | *** 40 | ## 由于我已经脱离了选课的噩梦了,所以我应该不会再对软件进行测试了。。至于是否能选课成功就无法得知,如果选课成功记得给我个Star来告诉我。。。还有如果有什么bugs之类的可以提issues。但不一定会解决。随缘。 41 | 42 | 43 | # 该项目仅用于技术交流学习,请不要用于非法用途。 44 | 45 | # LICENSE 46 | Apache-2.0 47 | -------------------------------------------------------------------------------- /Template.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZSAIm/TakeTheCourse_GDUT/bfca8210f9b613acccba607a55ca5fa015a24dd1/Template.xlsx -------------------------------------------------------------------------------- /base/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZSAIm/TakeTheCourse_GDUT/bfca8210f9b613acccba607a55ca5fa015a24dd1/base/__init__.py -------------------------------------------------------------------------------- /base/delayer.py: -------------------------------------------------------------------------------- 1 | 2 | import time 3 | import threading 4 | from random import randint 5 | 6 | __delayer__ = None 7 | 8 | def init(): 9 | global __delayer__ 10 | __delayer__ = Delayer() 11 | 12 | 13 | def timingRun(*args, **kwargs): 14 | global __delayer__ 15 | return __delayer__.timingRun(*args, **kwargs) 16 | 17 | def cancelTiming(user): 18 | global __delayer__ 19 | return __delayer__.cancelTiming(user) 20 | 21 | 22 | class Delayer(object): 23 | def __init__(self): 24 | 25 | self.executors = {} 26 | 27 | self._thread = None 28 | self.exec_lock = threading.Lock() 29 | 30 | def downCounter(self): 31 | while True: 32 | tmp = self.executors.copy() 33 | with self.exec_lock: 34 | for i, j in tmp.items(): 35 | self.executors[i].sec -= 1 36 | if j.sec < 1 or not j.thread.isAlive(): 37 | self.executors[i].sec = 0 38 | del self.executors[i] 39 | 40 | if not self.executors: 41 | break 42 | 43 | time.sleep(1) 44 | 45 | def timingRun(self, user, foo, sec, msg='', args=None, kwargs=None): 46 | if not self._thread or not self._thread.isAlive(): 47 | self._thread = threading.Thread(target=self.downCounter) 48 | self._thread.start() 49 | 50 | if isinstance(sec, list) or isinstance(sec, tuple): 51 | sec = randint(sec[0] * 1000, sec[1] * 1000) / 1000.0 52 | 53 | timer = threading.Timer(sec, foo, args=args, kwargs=kwargs) 54 | timer.setName(user.account) 55 | 56 | info = DelayerInfo(user, foo, sec, msg, timer) 57 | 58 | timer.start() 59 | user.op._thread = timer 60 | 61 | with self.exec_lock: 62 | self.executors[user.account] = info 63 | 64 | return sec 65 | 66 | def cancelTiming(self, user): 67 | with self.exec_lock: 68 | if user.account in self.executors: 69 | self.executors[user.account].thread.cancel() 70 | del self.executors[user.account] 71 | return True 72 | else: 73 | return False 74 | 75 | 76 | class DelayerInfo(object): 77 | def __init__(self, user, foo, sec, msg, thread): 78 | self.user = user 79 | self.foo = foo 80 | self.sec = sec 81 | self.msg = msg 82 | self.thread = thread 83 | 84 | def getName(self): 85 | return self.user 86 | 87 | def getFoo(self): 88 | return self.foo 89 | 90 | def getDelayTime(self): 91 | return self.sec 92 | 93 | def getMessage(self): 94 | return self.msg 95 | 96 | def getThreadObj(self): 97 | return self.thread 98 | 99 | 100 | if not __delayer__: 101 | init() -------------------------------------------------------------------------------- /base/urlop.py: -------------------------------------------------------------------------------- 1 | 2 | import urllib.request, urllib.parse, urllib.error 3 | import random 4 | import threading 5 | import time 6 | import http.cookiejar 7 | import socket 8 | import gzip, zlib 9 | import queue 10 | from handler.Error import UrlOpTimeOutError 11 | 12 | DEFAULT_HEADERS = { 13 | 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) ' 14 | 'Chrome/68.0.3440.84 Safari/537.36', 15 | 'Host': 'jxfw.gdut.edu.cn', 16 | 'Accept-Language': 'zh-CN,zh;q=0.9', 17 | 'Accept-Encoding': 'gzip, deflate', 18 | 'Connection': 'keep-alive', 19 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 20 | 'Upgrade-Insecure-Requests': '1', 21 | 'Referer': 'jxfw.gdut.edu.cn' 22 | } 23 | 24 | 25 | 26 | def raw_decompress(data, headers): 27 | encoding = headers.get('Content-Encoding') 28 | 29 | if encoding == 'gzip': 30 | data = gzip.decompress(data) 31 | elif encoding == 'deflate': 32 | data = zlib.decompress(data) 33 | 34 | return data 35 | 36 | 37 | class UrlRequestOp(object): 38 | def __init__(self): 39 | self.opener = None 40 | 41 | self.cookiejar = None 42 | 43 | self.timeout = 10 44 | 45 | self.__branch_threads__ = [] 46 | self.__ret_queue__ = queue.LifoQueue() 47 | 48 | self.__queue_lock__ = threading.Lock() 49 | 50 | self.proxy = None 51 | 52 | self.retry_time_ms = random.randrange(1000, 3000) 53 | 54 | self.buildOpener() 55 | 56 | self.timeout_flags = {} 57 | 58 | def loadUrlop(self, _from): 59 | self.opener = _from.opener 60 | self.cookiejar = _from.cookiejar 61 | self.proxy = _from.proxy 62 | 63 | def install(self, opener=None, cookiejar=None, proxy=None): 64 | if cookiejar: 65 | self.cookiejar = cookiejar 66 | if proxy: 67 | self.proxy = proxy 68 | 69 | if not opener: 70 | if proxy or cookiejar: 71 | self.buildOpener(self.cookiejar, self.proxy) 72 | else: 73 | self.opener = opener 74 | 75 | def loadCookieJar(self, cookiejar): 76 | self.cookiejar = cookiejar 77 | 78 | 79 | def setNewCookieJar(self): 80 | self.cookiejar = http.cookiejar.MozillaCookieJar() 81 | self.buildOpener(self.cookiejar, self.proxy) 82 | 83 | def newRetryTime(self): 84 | self.retry_time_ms = random.randrange(1000, 3000) 85 | 86 | def setProxy(self, proxy): 87 | self.proxy = proxy 88 | self.buildOpener(self.cookiejar, proxy) 89 | 90 | def clearProxy(self): 91 | self.proxy = None 92 | self.buildOpener(self.cookiejar, None) 93 | 94 | def buildOpener(self, cookiejar=None, proxy=None): 95 | self.opener, self.cookiejar = self.getNewOpener(cookiejar, proxy) 96 | 97 | def getNewOpener(self, cookiejar=None, proxy=None): 98 | processor = [] 99 | if not cookiejar: 100 | cookiejar = http.cookiejar.MozillaCookieJar() 101 | 102 | processor.append(urllib.request.HTTPCookieProcessor(cookiejar)) 103 | 104 | if proxy: 105 | processor.append(urllib.request.ProxyHandler(proxy)) 106 | 107 | opener = urllib.request.build_opener(*processor) 108 | opener.addheaders = list(DEFAULT_HEADERS.items()) 109 | 110 | return opener, cookiejar 111 | 112 | def __timeout_setflag__(self, id): 113 | self.timeout_flags[id][1] = True 114 | 115 | def cancelTimeOut_flag(self, id): 116 | self.timeout_flags[id][0].cancel() 117 | del self.timeout_flags[id] 118 | 119 | def request(self, branch_num=0, isolate=False, max_retry=0, **kwargs): 120 | 121 | opener_cookiejars = [] 122 | 123 | for i in range(branch_num+1): 124 | opener = self.opener 125 | cookiejar = self.cookiejar 126 | 127 | if isolate: 128 | opener, cookiejar = self.getNewOpener() 129 | 130 | opener_cookiejars.append((opener, cookiejar)) 131 | 132 | thread = threading.Thread(target=self.__request__, args=(i, opener, kwargs,)) 133 | self.__branch_threads__.append(thread) 134 | thread.start() 135 | 136 | time.sleep(random.random() / 10.0) 137 | 138 | timeout_id = object() 139 | self.timeout_flags[timeout_id] = [None, False] 140 | 141 | timer = threading.Timer(kwargs.get('timeout', self.timeout), self.__timeout_setflag__, args=(timeout_id,)) 142 | timer.setName('TimeOutFlagSetter') 143 | self.timeout_flags[timeout_id][0] = timer 144 | timer.start() 145 | 146 | er_kw = { 147 | 'user': getattr(self, 'user', None), 148 | 'msg': kwargs 149 | } 150 | 151 | 152 | while True: 153 | if self.timeout_flags[timeout_id][1]: 154 | self.cancelTimeOut_flag(timeout_id) 155 | raise UrlOpTimeOutError(er_kw) 156 | 157 | for i in self.__branch_threads__: 158 | if i.isAlive(): 159 | break 160 | else: 161 | self.cancelTimeOut_flag(timeout_id) 162 | if not self.__ret_queue__.empty(): 163 | id, raw, res = self.__ret_queue__.get() 164 | opener, cookiejar = opener_cookiejars[id] 165 | 166 | self.install(opener=opener, cookiejar=cookiejar) 167 | 168 | self.clearBranch() 169 | return raw, res 170 | 171 | else: 172 | if max_retry == -1 or max_retry > 0: 173 | if max_retry != -1: 174 | max_retry -= 1 175 | time.sleep(self.retry_time_ms / 1000.0) 176 | self.newRetryTime() 177 | 178 | return self.request(branch_num, isolate, max_retry, **kwargs) 179 | else: 180 | raise UrlOpTimeOutError(er_kw) 181 | 182 | time.sleep(0.01) 183 | 184 | def clearBranch(self): 185 | self.__branch_threads__ = [] 186 | while not self.__ret_queue__.empty(): 187 | _ = self.__ret_queue__.get() 188 | 189 | def __request__(self, id, opener, kwargs): 190 | 191 | req_params = { 192 | 'url': kwargs.get('url'), 193 | 'data': kwargs.get('data', None), 194 | 'headers': kwargs.get('headers', {}), 195 | 'origin_req_host': kwargs.get('origin_req_host', None), 196 | 'unverifiable': kwargs.get('unverifiable', False), 197 | 'method': kwargs.get('method', None) 198 | } 199 | timeout = kwargs.get('timeout', 5) 200 | 201 | if req_params['data'] is not None: 202 | req_params['data'] = urllib.parse.urlencode(req_params['data']).encode() 203 | 204 | try: 205 | req = urllib.request.Request(**req_params) 206 | if not self.__ret_queue__.empty(): 207 | return 208 | 209 | res = opener.open(req, timeout=timeout) 210 | raw = raw_decompress(res.read(), res.info()) 211 | 212 | except Exception as e: 213 | print(e) 214 | pass 215 | 216 | else: 217 | self.__ret_queue__.put((id, raw, res)) 218 | 219 | 220 | -------------------------------------------------------------------------------- /captcha/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZSAIm/TakeTheCourse_GDUT/bfca8210f9b613acccba607a55ca5fa015a24dd1/captcha/__init__.py -------------------------------------------------------------------------------- /captcha/inference.py: -------------------------------------------------------------------------------- 1 | import tensorflow as tf 2 | 3 | # INPUT_NODE = 140 * 60 4 | BATCH_SIZE = 100 5 | 6 | NUM_CHANNELS = 1 7 | # NUM_LABELS = 24*4 8 | OUTPUT_NODE = 24*4 9 | 10 | CONV1_DEEP = 32 11 | CONV1_SIZE = 5 12 | 13 | CONV2_DEEP = 64 14 | CONV2_SIZE = 5 15 | 16 | CONV3_DEEP = 128 17 | CONV3_SIZE = 5 18 | 19 | CONV4_DEEP = 256 20 | CONV4_SIZE = 5 21 | 22 | CONV5_DEEP = 512 23 | CONV5_SIZE = 3 24 | 25 | FC_SIZE = 1024 26 | 27 | 28 | def inference(input_tensor, train): # regularizer 29 | 30 | with tf.variable_scope('layer1-conv1'): 31 | conv1_weights = tf.get_variable('weight', [CONV1_SIZE, CONV1_SIZE, NUM_CHANNELS, CONV1_DEEP], 32 | initializer=tf.truncated_normal_initializer(stddev=0.1)) 33 | conv1_biases = tf.get_variable('bias', [CONV1_DEEP], initializer=tf.constant_initializer(0.0)) 34 | 35 | conv1 = tf.nn.conv2d(input_tensor, conv1_weights, strides=[1, 1, 1, 1], padding='SAME') 36 | relu1 = tf.nn.relu(tf.nn.bias_add(conv1, conv1_biases)) 37 | 38 | with tf.name_scope('layer2-pool1'): 39 | pool1 = tf.nn.max_pool(relu1, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') 40 | 41 | with tf.variable_scope('layer3-conv2'): 42 | conv2_weights = tf.get_variable('weight', [CONV2_SIZE, CONV2_SIZE, CONV1_DEEP, CONV2_DEEP]) 43 | conv2_biases = tf.get_variable('bias', [CONV2_DEEP], initializer=tf.constant_initializer(0.0)) 44 | 45 | conv2 = tf.nn.conv2d(pool1, conv2_weights, strides=[1, 1, 1, 1], padding='SAME') 46 | relu2 = tf.nn.relu(tf.nn.bias_add(conv2, conv2_biases)) 47 | 48 | with tf.name_scope('layer4-pool2'): 49 | pool2 = tf.nn.max_pool(relu2, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') 50 | 51 | with tf.variable_scope('layer5-conv3'): 52 | conv3_weights = tf.get_variable('weight', [CONV3_SIZE, CONV3_SIZE, CONV2_DEEP, CONV3_DEEP]) 53 | conv3_biases = tf.get_variable('bias', [CONV3_DEEP], initializer=tf.constant_initializer(0.0)) 54 | 55 | # conv3 = tf.nn.conv2d(pool2, conv3_weights, strides=[1, 1, 1, 1], padding='SAME') 56 | conv3 = tf.nn.conv2d(pool2, conv3_weights, strides=[1, 1, 1, 1], padding='SAME') 57 | relu3 = tf.nn.relu(tf.nn.bias_add(conv3, conv3_biases)) 58 | 59 | with tf.name_scope('layer6-pool3'): 60 | pool3 = tf.nn.max_pool(relu3, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') 61 | 62 | with tf.variable_scope('layer7-conv4'): 63 | conv4_weights = tf.get_variable('weight', [CONV4_SIZE, CONV4_SIZE, CONV3_DEEP, CONV4_DEEP]) 64 | conv4_biases = tf.get_variable('bias', [CONV4_DEEP], initializer=tf.constant_initializer(0.0)) 65 | 66 | conv4 = tf.nn.conv2d(pool3, conv4_weights, strides=[1, 1, 1, 1], padding='SAME') 67 | relu4 = tf.nn.relu(tf.nn.bias_add(conv4, conv4_biases)) 68 | 69 | with tf.name_scope('layer8-pool4'): 70 | pool4 = tf.nn.max_pool(relu4, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') 71 | 72 | with tf.variable_scope('layer9-conv5'): 73 | conv5_weights = tf.get_variable('weight', [CONV5_SIZE, CONV5_SIZE, CONV4_DEEP, CONV5_DEEP]) 74 | conv5_biases = tf.get_variable('bias', [CONV5_DEEP], initializer=tf.constant_initializer(0.0)) 75 | 76 | conv5 = tf.nn.conv2d(pool4, conv5_weights, strides=[1, 1, 1, 1], padding='SAME') 77 | relu5 = tf.nn.relu(tf.nn.bias_add(conv5, conv5_biases)) 78 | 79 | with tf.name_scope('layer10-pool5'): 80 | pool5 = tf.nn.max_pool(relu5, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') 81 | 82 | pool_shape = pool5.get_shape().as_list() 83 | 84 | nodes = pool_shape[1] * pool_shape[2] * pool_shape[3] 85 | 86 | reshaped = tf.reshape(pool5, [-1, nodes]) 87 | 88 | 89 | with tf.variable_scope('layer7-fc1'): 90 | fc1_weights = tf.get_variable('weight', [nodes, FC_SIZE], 91 | initializer=tf.truncated_normal_initializer(stddev=0.1)) 92 | 93 | fc1_biases = tf.get_variable('bias', [FC_SIZE], initializer=tf.constant_initializer(0.1)) 94 | 95 | fc1 = tf.nn.relu(tf.matmul(reshaped, fc1_weights) + fc1_biases) 96 | 97 | if train: 98 | fc1 = tf.nn.dropout(fc1, 0.50) 99 | 100 | with tf.variable_scope('layer8-fc2'): 101 | fc2_weights = tf.get_variable('weight', [FC_SIZE, OUTPUT_NODE], initializer=tf.truncated_normal_initializer(0.1)) 102 | 103 | fc2_biases = tf.get_variable('bias', [OUTPUT_NODE], initializer=tf.constant_initializer(0.1)) 104 | logit = tf.matmul(fc1, fc2_weights) + fc2_biases 105 | 106 | return logit 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /captcha/killer.py: -------------------------------------------------------------------------------- 1 | 2 | import tensorflow as tf 3 | from captcha import inference 4 | from PIL import Image 5 | import numpy as np 6 | 7 | IMG_HEIGHT = 60 8 | IMG_WIDTH = 140 9 | 10 | INPUT_NODE = IMG_HEIGHT * IMG_WIDTH 11 | 12 | MODEL_PATH_NAME = 'model/model.ckpt' 13 | 14 | CAPTCHA_CHARS = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'k', 'm', 'n', 'p', 'q', 's', 'u', 'w', 'x', 15 | 'y', '2', '3', '4', '5', '6', '7', '8'] 16 | 17 | CAPTCHA_CHARS_NUM = len(CAPTCHA_CHARS) 18 | CAPTCHA_LEN = 4 19 | 20 | OUTPUT_NODE = CAPTCHA_LEN * CAPTCHA_CHARS_NUM 21 | 22 | 23 | INPUT_SHAPE = [-1, IMG_HEIGHT, IMG_WIDTH, 1] 24 | OUTPUT_SHAPE = [-1, CAPTCHA_LEN, CAPTCHA_CHARS_NUM] 25 | 26 | 27 | __Killer__ = None 28 | 29 | def init(): 30 | global __Killer__ 31 | if not __Killer__: 32 | __Killer__ = CaptchaKiller() 33 | __Killer__.loadModel() 34 | return __Killer__ 35 | 36 | 37 | def get(f): 38 | return __Killer__.run(f)[0] 39 | 40 | 41 | class CaptchaKiller(object): 42 | def __init__(self): 43 | tf.reset_default_graph() 44 | self.session = None 45 | self.input_holder = None 46 | self.output_node = None 47 | 48 | self.cur_imgsrc = None 49 | 50 | def run(self, f): 51 | self.__loadImg__(f) 52 | self.__imgProcess__() 53 | 54 | reshape_img = np.reshape(self.cur_imgsrc, INPUT_SHAPE) 55 | predict = self.session.run(self.output_node, feed_dict={self.input_holder: reshape_img}) 56 | 57 | res = [] 58 | for i in predict: 59 | res.append(onehot_to_chars(i)) 60 | 61 | return res 62 | 63 | 64 | def loadModel(self): 65 | with tf.device('/cpu:0'): 66 | self.input_holder = tf.placeholder(tf.float32, [None, IMG_HEIGHT, IMG_WIDTH, 1]) 67 | _output = inference.inference(self.input_holder, False) 68 | 69 | self.output_node = tf.reshape(_output, OUTPUT_SHAPE) 70 | 71 | saver = tf.train.Saver() 72 | 73 | self.session = tf.Session(config=tf.ConfigProto(allow_soft_placement=True)) 74 | 75 | saver.restore(self.session, MODEL_PATH_NAME) 76 | 77 | def __loadImg__(self, f): 78 | self.cur_imgsrc = Image.open(f) 79 | 80 | def __imgProcess__(self): 81 | imgsrc = self.cur_imgsrc.convert('L') 82 | 83 | imgpx = imgsrc.load() 84 | 85 | for i in range(0, imgsrc.height): 86 | for j in range(0, imgsrc.width): 87 | 88 | if imgpx[j, i] > 120: 89 | imgpx[j, i] = 255 90 | else: 91 | imgpx[j, i] = 0 92 | for i in range(2): 93 | for y in range(0, imgsrc.height): 94 | for x in range(0, imgsrc.width): 95 | if imgpx[x, y] == 0: 96 | if x - 1 < 0 or y - 1 < 0 or x + 1 >= imgsrc.width or y + 1 >= imgsrc.height: 97 | imgpx[x, y] = 255 98 | continue 99 | count = 0 100 | if imgpx[x + 1, y] == 255: 101 | count += 1 102 | if imgpx[x - 1, y] == 255: 103 | count += 1 104 | if imgpx[x, y + 1] == 255: 105 | count += 1 106 | if imgpx[x, y - 1] == 255: 107 | count += 1 108 | if count >= 3: 109 | imgpx[x, y] = 255 110 | 111 | self.cur_imgsrc = imgsrc 112 | 113 | 114 | 115 | def onehot_to_chars(onehot): 116 | _chars = '' 117 | for m in np.argmax(onehot, axis=1): 118 | _chars += CAPTCHA_CHARS[m] 119 | return _chars -------------------------------------------------------------------------------- /core/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZSAIm/TakeTheCourse_GDUT/bfca8210f9b613acccba607a55ca5fa015a24dd1/core/__init__.py -------------------------------------------------------------------------------- /core/course.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from base.urlop import UrlRequestOp 4 | import json 5 | import math, time 6 | from random import randint 7 | import traceback 8 | from bs4 import BeautifulSoup 9 | 10 | 11 | from base import delayer 12 | from handler import Pool 13 | from handler.Error import TakerBasicException, UrlOpTimeOutError, PullerBasicException 14 | import llogger 15 | import gui 16 | 17 | ITEM_EACH_PAGE = 150 18 | MAX_RETRY = -1 19 | 20 | OPTIONAL_URL = 'http://jxfw.gdut.edu.cn/xsxklist!getDataList.action' 21 | SELECTED_URL = 'http://jxfw.gdut.edu.cn/xsxklist!getXzkcList.action' 22 | FOO_JS_URL = 'http://jxfw.gdut.edu.cn/xsxklist!xsmhxsxk.action' 23 | 24 | USER_COURSE_URL = 'http://jxfw.gdut.edu.cn/xsgrkbcx!getDataList.action' 25 | COURSE_DATECODE_URL = 'http://jxfw.gdut.edu.cn/xsgrkbcx!getXsgrbkList.action' 26 | 27 | 28 | ADD_COURSE_URL = 'http://jxfw.gdut.edu.cn/xsxklist!getAdd.action' 29 | CANCEL_COURSE_URL = 'http://jxfw.gdut.edu.cn/xsxklist!getCancel.action' 30 | 31 | KEYWORD_TAKE_FOO = 'xsxklist!getAdd.action' 32 | 33 | 34 | class SeasonCode: 35 | def __init__(self): 36 | self.selected_index = None 37 | self._list = [] 38 | self._dict = {} 39 | 40 | def extract(self, bs4): 41 | for i in bs4.find_all('option'): 42 | if i.has_attr('selected'): 43 | self.selected_index = len(self._list) 44 | self._list.append(i.get_text()) 45 | self._dict[i.get_text()] = i.get('value') 46 | 47 | def isEmpty(self): 48 | return not self._list 49 | 50 | def getCode(self, selected_index): 51 | return self._dict[self._list[selected_index]] 52 | 53 | def getAll(self): 54 | for i in self._list: 55 | yield self._dict[i], i 56 | 57 | def getCodeFromSeason(self, season): 58 | return self._dict[season] 59 | 60 | def setSeletedIndex(self, index): 61 | self.selected_index = index 62 | 63 | 64 | class Puller(object): 65 | def __init__(self, user, urlop): 66 | self.urlop = urlop 67 | self.user = user 68 | 69 | self.season_code = SeasonCode() 70 | 71 | self.selected = CoursePool() 72 | self.optional = CoursePool() 73 | 74 | self.user_course = CoursePool() 75 | 76 | def pullCourses(self): 77 | llogger.normal(self.user.account, '开始拉取选课课程信息。') 78 | 79 | self.pullOptional() 80 | self.pullSelected() 81 | 82 | def pullOptional(self): 83 | llogger.normal(self.user.account, '开始拉取可选课程信息。') 84 | self.user.status = Pool.PULLING 85 | try: 86 | self.optional = CoursePool() 87 | cur_page = 1 88 | 89 | total_res = [] 90 | res = self.__getOptional__(cur_page) 91 | total_res.append(res) 92 | rows = res.get('rows') 93 | llogger.ok(self.user.account, '可选课程拉取成功。', res) 94 | if not rows: 95 | return 96 | self.optional.extract(rows=rows) 97 | total = int(res.get('total', -1)) 98 | self.optional.total = total 99 | 100 | if total == -1: 101 | raise Exception('RespondUnknown', res) 102 | page_num = int(math.ceil(total * 1.0 / ITEM_EACH_PAGE)) 103 | 104 | for i in range(page_num - 1): 105 | cur_page += 1 106 | res = self.__getOptional__(cur_page) 107 | total_res.append(res) 108 | rows = res.get('rows') 109 | self.optional.extract(rows=rows) 110 | 111 | llogger.ok(self.user.account, '可选课程拉取成功: 共[%d]项' % self.optional.total, total_res) 112 | self.user.status = Pool.READY 113 | 114 | except TakerBasicException as e: 115 | traceback.print_exc() 116 | self.user.op.join(exec_foo=self.pullOptional) 117 | 118 | 119 | def pullSelected(self): 120 | llogger.normal(self.user.account, '开始拉取已选课程信息。') 121 | self.user.status = Pool.PULLING 122 | try: 123 | self.selected = CoursePool() 124 | res = self.__getSelected__() 125 | 126 | self.selected.extract(rows=res) 127 | 128 | llogger.ok(self.user.account, '已选课程拉取成功。', res) 129 | self.user.status = Pool.READY 130 | except TakerBasicException as e: 131 | traceback.print_exc() 132 | # time.sleep(randint(0, 500) / 1000.0) 133 | self.user.op.join(exec_foo=self.pullSelected) 134 | 135 | def __getOptional__(self, page): 136 | data = { 137 | 'page': page, 138 | 'rows': ITEM_EACH_PAGE, 139 | 'sort': 'kcrwdm', 140 | 'order': 'asc' 141 | } 142 | 143 | raw, res = self.urlop.request(url=OPTIONAL_URL, max_retry=-1, branch_num=1, method='POST', data=data) 144 | 145 | text = bytes.decode(raw) if isinstance(raw, bytes) else raw 146 | 147 | try: 148 | course_raw = json.loads(text) 149 | except json.JSONDecodeError as e: 150 | raise TakerBasicException(self.user, text, e) 151 | 152 | return course_raw 153 | 154 | def __getSelected__(self): 155 | data = { 156 | 'sort': 'kcrwdm', 157 | 'order': 'asc' 158 | } 159 | 160 | raw, res = self.urlop.request(url=SELECTED_URL, max_retry=-1, branch_num=1, method='POST', data=data) 161 | 162 | text = bytes.decode(raw) if isinstance(raw, bytes) else raw 163 | 164 | try: 165 | course_raw = json.loads(text) 166 | except json.JSONDecodeError as e: 167 | raise TakerBasicException(self.user, text, e) 168 | 169 | return course_raw 170 | 171 | 172 | def pullDateCode(self): 173 | try: 174 | self.season_code = SeasonCode() 175 | self.__getDateCode__() 176 | except PullerBasicException as e: 177 | traceback.print_exc() 178 | # self.pullDateCode() 179 | # self.user.op.join(exec_foo=self.pullDateCode) 180 | except UrlOpTimeOutError as e: 181 | traceback.print_exc() 182 | 183 | 184 | def __getDateCode__(self): 185 | raw, res = self.urlop.request(url=COURSE_DATECODE_URL, max_retry=2, branch_num=1, method='GET') 186 | 187 | text = bytes.decode(raw) if isinstance(raw, bytes) else raw 188 | 189 | bs4 = BeautifulSoup(text, "html.parser") 190 | bs4 = bs4.find('select', id='xnxqdm') 191 | 192 | if bs4: 193 | self.season_code.extract(bs4) 194 | else: 195 | raise PullerBasicException(self.user, text) 196 | 197 | 198 | def pullUserCourse(self): 199 | llogger.normal(self.user.account, '开始拉取个人课程信息。') 200 | self.user.status = Pool.PULLING 201 | 202 | if self.season_code.isEmpty(): 203 | self.pullDateCode() 204 | self.user.op.join() 205 | if self.season_code.isEmpty(): 206 | self.pullDateCode() 207 | if self.season_code.isEmpty(): 208 | return 209 | 210 | 211 | try: 212 | self.user_course = CoursePool() 213 | cur_page = 1 214 | total_res = [] 215 | res = self.__getUserCourse__(cur_page) 216 | total_res.append(res) 217 | rows = res.get('rows') 218 | if not rows: 219 | return 220 | self.user_course.extract(rows=rows) 221 | total = int(res.get('total', -1)) 222 | self.user_course.total = total 223 | 224 | if total == -1: 225 | raise Exception('RespondUnknown', res) 226 | page_num = int(math.ceil(total * 1.0 / ITEM_EACH_PAGE)) 227 | 228 | for i in range(page_num - 1): 229 | cur_page += 1 230 | res = self.__getUserCourse__(cur_page) 231 | total_res.append(res) 232 | rows = res.get('rows') 233 | self.user_course.extract(rows=rows) 234 | 235 | llogger.ok(self.user.account, '拉取个人课程成功: 共[%d]项' % self.user_course.total, total_res) 236 | self.user.status = Pool.READY 237 | except PullerBasicException as e: 238 | traceback.print_exc() 239 | # self.user.op.join(exec_foo=self.pullUserCourse) 240 | # finally: 241 | 242 | def displayTarget(self): 243 | gui.frame_configure.listctrl_optional.DeleteAllItems() 244 | 245 | for i in self.optional[self.user.keys]: 246 | data = (i.kcmc, i.xf, i.teaxm, i.pkrs, i.jxbrs, i.kcflmc) 247 | gui.frame_configure.listctrl_optional.Append(data) 248 | 249 | def displayUserCourse(self): 250 | gui.frame_configure.listctrl_optional.initUserCourseColumn() 251 | self.pullUserCourse() 252 | 253 | gui.frame_configure.choice_season.Clear() 254 | for i, j in self.season_code.getAll(): 255 | gui.frame_configure.choice_season.Append(j) 256 | 257 | gui.frame_configure.choice_season.SetSelection(self.season_code.selected_index) 258 | 259 | for i in self.user_course.getAll(): 260 | 261 | data = (i.kcmc, i.jxbmc, i.pkrs, i.teaxms, i.zc, i.xq, i.jcdm, 262 | i.jxcdmc, i.pkrq, i.kxh, i.jxhjmc, i.sknrjj) 263 | gui.frame_configure.listctrl_optional.Append(data) 264 | # else: 265 | # pass 266 | 267 | def displayCourse(self): 268 | gui.frame_configure.listctrl_optional.initOptionColumn() 269 | gui.frame_configure.listctrl_selected.initSelectedColumn() 270 | 271 | # if not self.optional.getAll(): 272 | self.pullCourses() 273 | 274 | for i in self.optional.getAll(): 275 | data = (i.kcmc, i.xf, i.teaxm, i.pkrs, i.jxbrs, i.kcflmc) 276 | gui.frame_configure.listctrl_optional.Append(data) 277 | 278 | for i in self.selected.getAll(): 279 | data = (i.kcmc, i.xf, i.teaxm, i.pkrs, i.jxbrs, i.kcflmc) 280 | gui.frame_configure.listctrl_selected.Append(data) 281 | 282 | 283 | def __getUserCourse__(self, page): 284 | data = { 285 | 'zc': '', # 全部周 286 | 'xnxqdm': self.season_code.getCode(self.season_code.selected_index), 287 | 'page': page, 288 | 'rows': 100, 289 | 'sort': 'pkrq', # 排课日期 290 | 'order': 'asc' 291 | } 292 | 293 | raw, res = self.urlop.request(url=USER_COURSE_URL, max_retry=2, branch_num=1, method='POST', data=data) 294 | 295 | text = bytes.decode(raw) if isinstance(raw, bytes) else raw 296 | 297 | try: 298 | course_raw = json.loads(text) 299 | except json.JSONDecodeError as e: 300 | raise PullerBasicException(self.user, text, e) 301 | 302 | return course_raw 303 | 304 | 305 | 306 | class Taker(UrlRequestOp): 307 | def __init__(self, user): 308 | UrlRequestOp.__init__(self) 309 | self.user = user 310 | 311 | self.counter = 0 312 | 313 | self.stop_flag = False 314 | 315 | self.puller = Puller(self.user, self) 316 | 317 | def run(self): 318 | self.user.status = Pool.TAKING 319 | self.counter += 1 320 | 321 | try: 322 | self.__run__() 323 | except TakerBasicException as e: 324 | traceback.print_exc() 325 | except UrlOpTimeOutError as e: 326 | traceback.print_exc() 327 | 328 | def __run__(self): 329 | 330 | if not self.puller.optional.getAll(): 331 | self.puller.pullCourses() 332 | 333 | targets = self.getTargets() 334 | self.user.set_MemberView_Target(targets) 335 | if not targets: 336 | self.puller.pullCourses() 337 | 338 | targets = self.getTargets() 339 | self.user.set_MemberView_Target(targets) 340 | 341 | if not targets: 342 | self.user.op.done() 343 | return 344 | 345 | if self.user.force_post or self.isAvl(): 346 | self.take() 347 | else: 348 | if not self.isStop(): 349 | self.timingRun() 350 | else: 351 | self.getStop() 352 | 353 | def timingRun(self): 354 | self.user.status = Pool.TIMING_TAKE 355 | sec = delayer.timingRun(self.user, self.user.op.takeCourse, self.user.delay_range) 356 | self.user.set_MemberView_Timing(sec) 357 | 358 | def take(self): 359 | for i in self.getTargets(): 360 | llogger.normal(self.user.account, '开始选课: [%s]' % i.__str__()) 361 | res = self.postAdd(i) 362 | 363 | if res == '1': 364 | self.succeed(i) 365 | llogger.ok(self.user.account, '选课成功: [%s]' % i.__str__(), res) 366 | else: 367 | raise TakerBasicException(self.user, res, course=i) 368 | 369 | def stop(self): 370 | llogger.normal(self.user.account, '开始中止选课。') 371 | self.stop_flag = True 372 | if delayer.cancelTiming(self.user): 373 | self.getStop() 374 | 375 | def isStop(self): 376 | return self.stop_flag 377 | 378 | def getStop(self): 379 | self.stop_flag = False 380 | self.user.status = Pool.READY 381 | llogger.ok(self.user.account, '选课中止成功。') 382 | self.user.set_MemberView_Timing(-1) 383 | 384 | def postAdd(self, course): 385 | data = { 386 | # 'jxbdm': course.jxbdm, 387 | # 'jxbrs': course.jxbrs, 388 | # 'kcdm': course.kcdm, 389 | # 'kcflmc': course.kcflmc, 390 | 'kcmc': course.kcmc, 391 | # 'kcptdm': course.kcptdm, 392 | 'kcrwdm': course.kcrwdm, 393 | # 'kkxqdm': course.kkxqdm, 394 | # 'pkrs': course.pkrs, 395 | # 'rs1': course.rs1, 396 | # 'rs2': course.rs2, 397 | # 'rwdm': course.rwdm, 398 | # 'teaxm': course.teaxm, 399 | # 'wyfjdm': course.wyfjdm, 400 | # 'xbyqdm': course.xbyqdm, 401 | # 'xf': course.xf, 402 | # 'xmmc': course.xmmc, 403 | # 'zxs': course.zxs 404 | } 405 | raw, res = self.request(url=ADD_COURSE_URL, data=data, method='POST', branch_num=1, max_retry=-1) 406 | text = bytes.decode(raw) if isinstance(raw, bytes) else raw 407 | 408 | return text 409 | 410 | def postCancel(self, course): 411 | llogger.normal(self.user.account, '开始退选: [%s]' % course.__str__()) 412 | 413 | data = { 414 | 'jxbdm': course.jxbdm, 415 | 'kcrwdm': course.kcrwdm, 416 | 'kcmc': course.kcmc 417 | 418 | } 419 | 420 | raw, res = self.request(url=CANCEL_COURSE_URL, data=data, method='POST', branch_num=0, max_retry=3) 421 | text = bytes.decode(raw) if isinstance(raw, bytes) else raw 422 | try: 423 | if int(text) > 0: 424 | llogger.ok(self.user.account, '退选成功: [%s]' % course.__str__(), text) 425 | return True 426 | else: 427 | llogger.error(self.user.account, '退选失败: [%s]' % course.__str__(), text) 428 | return False 429 | except ValueError as e: 430 | raise TakerBasicException(self.user, text, e) 431 | 432 | def getTargets(self): 433 | targets = self.puller.optional[self.user.keys] 434 | tmp = targets[:] 435 | for i in tmp: 436 | if self.puller.selected.hasCourse(i): 437 | targets.remove(i) 438 | 439 | return targets 440 | 441 | def isAvl(self): 442 | 443 | raw, res = self.request(url=FOO_JS_URL, branch_num=1, max_retry=-1, method='GET') 444 | 445 | text = bytes.decode(raw) if isinstance(raw, bytes) else raw 446 | 447 | if KEYWORD_TAKE_FOO in text: 448 | return True 449 | else: 450 | if '请输入验证码' in text: 451 | self.user.op.login() 452 | return False 453 | elif '不是选课时间' in text: 454 | return False 455 | 456 | return 457 | 458 | def succeed(self, course): 459 | self.user.success_counter += 1 460 | if not self.puller.selected.hasCourse(course): 461 | self.puller.selected.append(course) 462 | if not self.getTargets(): 463 | self.user.op.done() 464 | 465 | 466 | def getItemsFromKey(key, iter_list): 467 | items = [] 468 | for i in iter_list: 469 | if key in i.row: 470 | items.append(i) 471 | 472 | return items 473 | 474 | 475 | class CoursePool(object): 476 | def __init__(self): 477 | self.infos = [] 478 | self.total = 0 479 | 480 | def __getitem__(self, keys): 481 | 482 | keys = keys.split('|') 483 | key_and = [] 484 | for i in keys: 485 | key_and.append(i.split(',')) 486 | 487 | ret = [] 488 | for i in key_and: 489 | matchs = self.infos 490 | for j in i: 491 | matchs = getItemsFromKey(j, matchs) 492 | 493 | ret.extend(matchs) 494 | 495 | return ret 496 | 497 | def extract(self, raw_json=None, rows=None): 498 | if raw_json: 499 | rows = json.loads(raw_json) # courses list 500 | 501 | for i in rows: 502 | self.infos.append(CourseItem(i)) 503 | self.total += 1 504 | 505 | def getAll(self): 506 | return self.infos 507 | 508 | def append(self, item): 509 | self.infos.append(item) 510 | self.total += 1 511 | 512 | def remove(self, object): 513 | self.infos.remove(object) 514 | self.total -= 1 515 | 516 | def hasCourse(self, course): 517 | for i in self.infos: 518 | if course.kcdm == i.kcdm: 519 | return True 520 | else: 521 | return False 522 | 523 | def __str__(self): 524 | info_list = [] 525 | for i in self.infos: 526 | info_list.append(i.__str__()) 527 | return '\n'.join(info_list) 528 | 529 | 530 | def pack(self): 531 | return json.dumps({'total': self.total, 'rows': self.infos}) 532 | 533 | 534 | class CourseItem(object): 535 | def __init__(self, row): 536 | # self.row = json.dumps() 537 | self.row = str(row) 538 | self.jxbdm = row.get('jxbdm') 539 | self.jxbrs = row.get('jxbrs') 540 | self.kcdm = row.get('kcdm') 541 | self.kcflmc = row.get('kcflmc') 542 | self.kcmc = row.get('kcmc') 543 | self.kcptdm = row.get('kcptdm') 544 | self.kcrwdm = row.get('kcrwdm') 545 | self.kkxqdm = row.get('kkxqdm') 546 | self.pkrs = row.get('pkrs') 547 | self.rs1 = row.get('rs1') 548 | self.rs2 = row.get('rs2') 549 | self.rwdm = row.get('rwdm') 550 | self.teaxm = row.get('teaxm') 551 | self.wyfjdm = row.get('wyfjdm') 552 | self.xbyqdm = row.get('xbyqdm') 553 | self.xf = row.get('xf') 554 | self.xmmc = row.get('xmmc') 555 | self.zxs = row.get('zxs') 556 | 557 | self.dgksdm = row.get('dgksdm') 558 | self.flfzmc = row.get('flfzmc') 559 | self.jcdm = row.get('jcdm') 560 | 561 | self.jxbmc = row.get('jxbmc') 562 | self.jxcdmc = row.get('jxcdmc') 563 | self.jxhjmc = row.get('jxhjmc') 564 | self.kxh = row.get('kxh') 565 | self.pkrq = row.get('pkrq') 566 | self.rownum_ = row.get('rownum_') 567 | 568 | self.sknrjj = row.get('sknrjj') 569 | self.teaxms = row.get('teaxms') 570 | self.xq = row.get('xq') 571 | self.zc = row.get('zc') 572 | 573 | 574 | def __str__(self): 575 | return '%s - %s' % (self.kcmc, self.teaxm if self.teaxm else self.teaxms) 576 | -------------------------------------------------------------------------------- /core/encryptor.py: -------------------------------------------------------------------------------- 1 | 2 | from Crypto.Util.Padding import pad 3 | from Crypto.Cipher import AES 4 | 5 | def aes_cipher(key, aes_str): 6 | aes = AES.new(key.encode('utf-8'), AES.MODE_ECB) 7 | 8 | pad_pkcs7 = pad(aes_str.encode('utf-8'), AES.block_size, style='pkcs7') 9 | encrypt_aes = aes.encrypt(pad_pkcs7) 10 | 11 | return encrypt_aes.hex() 12 | 13 | 14 | -------------------------------------------------------------------------------- /core/login.py: -------------------------------------------------------------------------------- 1 | 2 | from base.urlop import UrlRequestOp 3 | import time 4 | import io 5 | import captcha.killer 6 | import json 7 | import traceback 8 | 9 | from handler import Pool 10 | from handler.Error import LoginBasicException, UrlOpTimeOutError 11 | 12 | import llogger 13 | 14 | CAPTCHA_URL = 'http://jxfw.gdut.edu.cn/yzm?d=%d' 15 | POST_LOGIN = 'http://jxfw.gdut.edu.cn/new/login' 16 | 17 | 18 | 19 | MAX_RETRY = -1 20 | 21 | class Login(UrlRequestOp): 22 | def __init__(self, user): 23 | UrlRequestOp.__init__(self) 24 | self.user = user 25 | self.counter = 0 26 | 27 | def run(self): 28 | llogger.normal(self.user.account, '开始登录。') 29 | self.user.status = Pool.LOGINNING 30 | try: 31 | self.__run__() 32 | except LoginBasicException as e: 33 | traceback.print_exc() 34 | except UrlOpTimeOutError as e: 35 | traceback.print_exc() 36 | # traceback.format_exc() 37 | else: 38 | pass 39 | 40 | def __run__(self): 41 | 42 | self.setNewCookieJar() 43 | 44 | self.counter += 1 45 | 46 | self.makePostData() 47 | 48 | res = self.postLogin() 49 | self.parseRespond(res) 50 | 51 | def makePostData(self): 52 | global MAX_RETRY 53 | raw, res = self.request(url=CAPTCHA_URL % (time.time() * 1000), method='GET', 54 | branch_num=1, isolate=True, max_retry=MAX_RETRY) 55 | 56 | if 'image' in res.info()['Content-Type']: 57 | 58 | fp = io.BytesIO() 59 | fp.write(raw) 60 | 61 | verifycode = captcha.killer.get(fp) 62 | self.user.setVerifyCode(verifycode) 63 | 64 | else: 65 | raise LoginBasicException(self.user, raw) 66 | 67 | def postLogin(self): 68 | global MAX_RETRY 69 | raw, res = self.request(branch_num=0, method='POST', url=POST_LOGIN, data=self.user.getPostData(), 70 | max_retry=MAX_RETRY) 71 | 72 | return bytes.decode(raw) if isinstance(raw, bytes) else raw 73 | 74 | def parseRespond(self, text): 75 | try: 76 | res_json = json.loads(text) 77 | except json.JSONDecodeError as e: 78 | 79 | raise LoginBasicException(self.user, e.msg, e) 80 | 81 | if '登录成功' in res_json['message']: 82 | self.succeed() 83 | llogger.ok(self.user.account, '登录成功。', text) 84 | else: 85 | raise LoginBasicException(self.user, text) 86 | 87 | def succeed(self): 88 | self.user.op.getReady() 89 | 90 | 91 | -------------------------------------------------------------------------------- /gui/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import wx 4 | import gui.frame.main 5 | import gui.frame.configure 6 | from gui.frame.about import About_Dialog 7 | 8 | frame_main = None 9 | frame_configure = None 10 | 11 | app = None 12 | 13 | 14 | 15 | def init(): 16 | global frame_main, frame_configure, app 17 | app = wx.App(False) 18 | frame_main = gui.frame.main.FrameMain(None) 19 | frame_configure = gui.frame.configure.FrameConfigure(frame_main) 20 | 21 | frame_main.Show(True) 22 | 23 | 24 | def MainLoop(): 25 | global app 26 | app.MainLoop() -------------------------------------------------------------------------------- /gui/frame/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /gui/frame/about.py: -------------------------------------------------------------------------------- 1 | 2 | import wx 3 | import wx.xrc 4 | import wx.adv 5 | 6 | class About_Dialog(wx.Dialog): 7 | 8 | def __init__(self, parent): 9 | wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title=u"About", pos=wx.DefaultPosition, size=wx.Size(348, 385), 10 | style=wx.DEFAULT_DIALOG_STYLE | wx.STAY_ON_TOP) 11 | 12 | self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) 13 | self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) 14 | 15 | bSizer9 = wx.BoxSizer(wx.VERTICAL) 16 | 17 | panel3 = wx.Panel(self, wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 18 | wx.BORDER_RAISED | wx.TAB_TRAVERSAL) 19 | panel3.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) 20 | 21 | bSizer10 = wx.BoxSizer(wx.VERTICAL) 22 | 23 | staticText181 = wx.StaticText(panel3, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, 24 | wx.DefaultSize, 0) 25 | staticText181.Wrap(-1) 26 | 27 | bSizer10.Add(staticText181, 0, wx.ALL, 5) 28 | 29 | staticText17 = wx.StaticText(panel3, wx.ID_ANY, u"GDUT教务系统选课助手", wx.DefaultPosition, 30 | wx.DefaultSize, wx.ALIGN_CENTER_HORIZONTAL) 31 | staticText17.Wrap(-1) 32 | 33 | staticText17.SetFont( 34 | wx.Font(20, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, wx.EmptyString)) 35 | 36 | bSizer10.Add(staticText17, 0, wx.ALIGN_CENTER | wx.ALL, 5) 37 | 38 | staticText18 = wx.StaticText(panel3, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, 39 | wx.DefaultSize, 0) 40 | staticText18.Wrap(-1) 41 | 42 | bSizer10.Add(staticText18, 0, wx.ALL, 5) 43 | 44 | staticText10 = wx.StaticText(panel3, wx.ID_ANY, u"Developed by:", wx.DefaultPosition, 45 | wx.DefaultSize, wx.ALIGN_CENTER_HORIZONTAL) 46 | staticText10.Wrap(-1) 47 | 48 | staticText10.SetFont( 49 | wx.Font(15, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, wx.EmptyString)) 50 | 51 | bSizer10.Add(staticText10, 0, wx.ALIGN_CENTER | wx.ALL | wx.EXPAND, 5) 52 | 53 | staticText101 = wx.StaticText(panel3, wx.ID_ANY, u"ZSAIm", wx.DefaultPosition, wx.DefaultSize, 54 | wx.ALIGN_CENTER_HORIZONTAL) 55 | staticText101.Wrap(-1) 56 | 57 | staticText101.SetFont( 58 | wx.Font(10, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, False, wx.EmptyString)) 59 | 60 | bSizer10.Add(staticText101, 0, wx.ALIGN_CENTER | wx.ALL, 5) 61 | 62 | staticText19 = wx.StaticText(panel3, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, 63 | wx.DefaultSize, 0) 64 | staticText19.Wrap(-1) 65 | 66 | bSizer10.Add(staticText19, 0, wx.ALL, 5) 67 | 68 | staticText102 = wx.StaticText(panel3, wx.ID_ANY, u"Github:", wx.DefaultPosition, wx.DefaultSize, 69 | wx.ALIGN_CENTER_HORIZONTAL) 70 | staticText102.Wrap(-1) 71 | 72 | staticText102.SetFont( 73 | wx.Font(15, wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_BOLD, False, wx.EmptyString)) 74 | 75 | bSizer10.Add(staticText102, 0, wx.ALIGN_CENTER | wx.ALL, 5) 76 | 77 | hyperlink1 = wx.adv.HyperlinkCtrl(panel3, wx.ID_ANY, 78 | u"https://github.com/ZSAIm/TakeTheCourse_GDUT", 79 | u"https://github.com/ZSAIm/TakeTheCourse_GDUT", wx.DefaultPosition, 80 | wx.DefaultSize, wx.adv.HL_ALIGN_CENTRE | wx.adv.HL_DEFAULT_STYLE) 81 | hyperlink1.SetFont( 82 | wx.Font(wx.NORMAL_FONT.GetPointSize(), wx.FONTFAMILY_DEFAULT, wx.FONTSTYLE_NORMAL, wx.FONTWEIGHT_NORMAL, 83 | False, wx.EmptyString)) 84 | hyperlink1.SetForegroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_WINDOW)) 85 | 86 | bSizer10.Add(hyperlink1, 0, wx.ALIGN_CENTER | wx.ALL, 5) 87 | 88 | staticText21 = wx.StaticText(panel3, wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, 89 | wx.DefaultSize, 0) 90 | staticText21.Wrap(-1) 91 | 92 | bSizer10.Add(staticText21, 0, wx.ALL, 5) 93 | 94 | panel3.SetSizer(bSizer10) 95 | panel3.Layout() 96 | bSizer10.Fit(panel3) 97 | bSizer9.Add(panel3, 1, wx.EXPAND | wx.ALL, 10) 98 | 99 | button7 = wx.Button(self, wx.ID_ANY, u"OK", wx.DefaultPosition, wx.DefaultSize, 0) 100 | bSizer9.Add(button7, 0, wx.ALIGN_CENTER | wx.ALL, 5) 101 | 102 | self.Bind(wx.EVT_BUTTON, self.close, button7) 103 | 104 | self.SetSizer(bSizer9) 105 | self.Layout() 106 | 107 | self.Centre(wx.BOTH) 108 | 109 | def close(self, event): 110 | self.Destroy() 111 | 112 | def __del__(self): 113 | pass 114 | 115 | 116 | -------------------------------------------------------------------------------- /gui/frame/configure.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import wx 4 | from gui.listctrl.optional import Optional_ListCtrl 5 | from gui.listctrl.selected import Selected_ListCtrl 6 | 7 | class FrameConfigure(wx.Dialog): 8 | def __init__(self, parent): 9 | wx.Dialog.__init__(self, parent, id=wx.ID_ANY, title=u"Configure", pos=wx.DefaultPosition, 10 | size=wx.Size(680, 600), style=wx.DEFAULT_DIALOG_STYLE) 11 | 12 | self.SetSizeHints(wx.DefaultSize, wx.DefaultSize) 13 | 14 | self.sizer_global = wx.BoxSizer(wx.HORIZONTAL) 15 | 16 | self.sizer_left = wx.BoxSizer(wx.VERTICAL) 17 | 18 | self.listctrl_optional = None 19 | self.listctrl_selected = None 20 | 21 | self.button_load_course = None 22 | self.button_load_usercourse = None 23 | # self.button_export = None 24 | self.button_login = None 25 | self.button_save_settings = None 26 | 27 | self.choice_season = None 28 | 29 | self.init_LeftPanel() 30 | 31 | self.sizer_right = wx.BoxSizer(wx.VERTICAL) 32 | self.textctrl_account = None 33 | self.textctrl_password = None 34 | self.textctrl_keys = None 35 | 36 | self.checkbox_force = None 37 | self.spinctrl_start = None 38 | self.spinctrl_end = None 39 | 40 | self.spinctrl_timer = None 41 | 42 | self.init_RightPanel() 43 | 44 | self.SetSizer(self.sizer_global) 45 | self.Layout() 46 | self.Center(wx.BOTH) 47 | 48 | 49 | 50 | def init_Optional_ListCtrl(self): 51 | sizer_optional = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, u"可选课程/目标课程/个人课表"), wx.HORIZONTAL) 52 | sizer_optional.SetMinSize(wx.Size(500, -1)) 53 | 54 | self.listctrl_optional = Optional_ListCtrl(sizer_optional.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, 55 | wx.Size(-1, -1), wx.LC_REPORT) 56 | 57 | sizer_optional.Add(self.listctrl_optional, 1, wx.ALL, 5) 58 | 59 | self.sizer_left.Add(sizer_optional, 1, wx.EXPAND, 5) 60 | 61 | def init_Seleted_ListCtrl(self): 62 | sizer_selected = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, u"已选课程"), wx.HORIZONTAL) 63 | sizer_selected.SetMinSize(wx.Size(500, 100)) 64 | 65 | self.listctrl_selected = Selected_ListCtrl(sizer_selected.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, 66 | wx.Size(-1, 100), wx.LC_REPORT) 67 | 68 | sizer_selected.Add(self.listctrl_selected, 1, wx.ALL, 5) 69 | 70 | self.sizer_left.Add(sizer_selected, 0, wx.EXPAND, 5) 71 | 72 | 73 | def init_ControlPanel(self): 74 | sizer_control_pannel = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, u'操作面板'), wx.HORIZONTAL) 75 | 76 | self.button_load_course = wx.Button(sizer_control_pannel.GetStaticBox(), wx.ID_ANY, u"加载选课课程", 77 | wx.DefaultPosition, wx.DefaultSize, 0) 78 | 79 | self.button_load_usercourse = wx.Button(sizer_control_pannel.GetStaticBox(), wx.ID_ANY, u"加载个人课表", 80 | wx.DefaultPosition, wx.DefaultSize, 0) 81 | 82 | # self.button_export = wx.Button(sizer_control_pannel.GetStaticBox(), wx.ID_ANY, u"导出课表", 83 | # wx.DefaultPosition, wx.DefaultSize, 0) 84 | # self.button_export.Enable(False) 85 | self.choice_season = wx.Choice(sizer_control_pannel.GetStaticBox(), wx.ID_ANY, 86 | wx.DefaultPosition, wx.DefaultSize, [], 0) 87 | self.choice_season.SetSelection(0) 88 | 89 | sizer_control_pannel.Add(self.button_load_course, 0, wx.ALIGN_CENTER | wx.ALL, 5) 90 | sizer_control_pannel.Add(self.button_load_usercourse, 0, wx.ALIGN_CENTER | wx.ALL, 5) 91 | # sizer_control_pannel.Add(self.button_export, 0, wx.ALIGN_CENTER | wx.ALL, 5) 92 | sizer_control_pannel.Add(self.choice_season, 1, wx.ALIGN_CENTER | wx.ALL, 5) 93 | 94 | self.sizer_left.Add(sizer_control_pannel, 0, wx.EXPAND, 5) 95 | 96 | def init_LeftPanel(self): 97 | self.init_Optional_ListCtrl() 98 | self.init_Seleted_ListCtrl() 99 | self.init_ControlPanel() 100 | 101 | self.sizer_global.Add(self.sizer_left, 1, wx.EXPAND, 5) 102 | 103 | def init_RightPanel(self): 104 | 105 | sizer_settings = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, u"设置"), wx.VERTICAL) 106 | sizer_userinfo = wx.StaticBoxSizer(wx.StaticBox(sizer_settings.GetStaticBox(), wx.ID_ANY, u"用户信息"), wx.VERTICAL) 107 | 108 | sizer_fg = wx.FlexGridSizer(0, 2, 0, 0) 109 | sizer_fg.SetFlexibleDirection(wx.BOTH) 110 | sizer_fg.SetNonFlexibleGrowMode(wx.FLEX_GROWMODE_SPECIFIED) 111 | 112 | text_account = wx.StaticText(sizer_userinfo.GetStaticBox(), wx.ID_ANY, u'账号', wx.DefaultPosition, wx.DefaultSize, 0) 113 | text_password = wx.StaticText(sizer_userinfo.GetStaticBox(), wx.ID_ANY, u'密码', wx.DefaultPosition, wx.DefaultSize, 0) 114 | text_ep = wx.StaticText(sizer_userinfo.GetStaticBox(), wx.ID_ANY, u'', wx.DefaultPosition, wx.DefaultSize, 0) 115 | 116 | text_account.Wrap(-1) 117 | text_password.Wrap(-1) 118 | 119 | self.textctrl_account = wx.TextCtrl(sizer_userinfo.GetStaticBox(), wx.ID_ANY, u'', wx.DefaultPosition, 120 | wx.DefaultSize, 0) 121 | self.textctrl_account.Enable(False) 122 | 123 | self.textctrl_password = wx.TextCtrl(sizer_userinfo.GetStaticBox(), wx.ID_ANY, u'', wx.DefaultPosition, 124 | wx.DefaultSize, wx.TE_PASSWORD) 125 | 126 | self.button_login = wx.Button(sizer_userinfo.GetStaticBox(), wx.ID_ANY, u"登录", wx.DefaultPosition, 127 | wx.Size(100, -1), 0) 128 | 129 | # ===================================================== 130 | sizer_fg.Add(text_account, 0, wx.ALIGN_CENTER | wx.ALL, 5) 131 | sizer_fg.Add(self.textctrl_account, 0, wx.ALIGN_CENTER | wx.ALL, 5) 132 | 133 | sizer_fg.Add(text_password, 0, wx.ALIGN_CENTER | wx.ALL, 5) 134 | sizer_fg.Add(self.textctrl_password, 0, wx.ALIGN_CENTER | wx.ALL, 5) 135 | 136 | sizer_fg.Add(text_ep, 0, wx.ALIGN_CENTER | wx.ALL, 5) 137 | sizer_fg.Add(self.button_login, 0, wx.ALIGN_RIGHT | wx.ALL, 5) 138 | # ===================================================== 139 | sizer_userinfo.Add(sizer_fg, 0, wx.EXPAND, 5) 140 | 141 | sizer_settings.Add(sizer_userinfo, 0, wx.EXPAND, 5) 142 | 143 | # ===================================================== 144 | sizer_course = wx.StaticBoxSizer(wx.StaticBox(sizer_settings.GetStaticBox(), wx.ID_ANY, u"选课设置"), wx.VERTICAL) 145 | 146 | sizer_course_filter = wx.StaticBoxSizer(wx.StaticBox(sizer_course.GetStaticBox(), wx.ID_ANY, u"课程过滤器"), wx.HORIZONTAL) 147 | 148 | text_keys = wx.StaticText(sizer_course_filter.GetStaticBox(), wx.ID_ANY, u'Keys', wx.DefaultPosition, wx.DefaultSize, 0) 149 | text_keys.Wrap(-1) 150 | self.textctrl_keys = wx.TextCtrl(sizer_course_filter.GetStaticBox(), wx.ID_ANY, u'', wx.DefaultPosition, 151 | wx.DefaultSize, 0) 152 | self.textctrl_keys.SetToolTip(u"过滤器规则:(优先级从左到右)\n(1) , :与操作\n(2) | :或操作\n例如: 羽毛球,李三|乒乓球,张四") 153 | 154 | # ===================================================== 155 | sizer_course_filter.Add(text_keys, 0, wx.ALIGN_CENTER | wx.ALL, 5) 156 | sizer_course_filter.Add(self.textctrl_keys, 0, wx.ALIGN_CENTER | wx.ALL, 5) 157 | 158 | # ===================================================== 159 | 160 | self.checkbox_force = wx.CheckBox(sizer_course.GetStaticBox(), wx.ID_ANY, u"强制提交", 161 | wx.DefaultPosition, wx.DefaultSize, 0) 162 | 163 | # ===================================================== 164 | 165 | sizer_post_range = wx.StaticBoxSizer(wx.StaticBox(sizer_course.GetStaticBox(), wx.ID_ANY, u"提交间隔"), wx.HORIZONTAL) 166 | self.spinctrl_start = wx.SpinCtrl(sizer_post_range.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, 167 | wx.Size(60, -1), wx.SP_ARROW_KEYS, 0, 10, 1) 168 | self.spinctrl_end = wx.SpinCtrl(sizer_post_range.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, 169 | wx.Size(60, -1), wx.SP_ARROW_KEYS, 0, 10, 3) 170 | 171 | text_line = wx.StaticText(sizer_post_range.GetStaticBox(), wx.ID_ANY, u'-', wx.DefaultPosition, wx.DefaultSize, 0) 172 | text_sec = wx.StaticText(sizer_post_range.GetStaticBox(), wx.ID_ANY, u'sec', wx.DefaultPosition, wx.DefaultSize, 0) 173 | 174 | # ===================================================== 175 | sizer_post_range.Add(self.spinctrl_start, 0, wx.ALIGN_CENTER | wx.ALL, 5) 176 | sizer_post_range.Add(text_line, 0, wx.ALIGN_CENTER | wx.ALL, 0) 177 | sizer_post_range.Add(self.spinctrl_end, 0, wx.ALIGN_CENTER | wx.ALL, 5) 178 | sizer_post_range.Add(text_sec, 0, wx.ALIGN_CENTER | wx.ALL, 0) 179 | # ===================================================== 180 | sizer_timer = wx.BoxSizer( wx.HORIZONTAL ) 181 | text_timer = wx.StaticText(sizer_course.GetStaticBox(), wx.ID_ANY, u"定时刷新", wx.DefaultPosition, 182 | wx.DefaultSize, 0) 183 | text_timer.Wrap(-1) 184 | 185 | self.spinctrl_timer = wx.SpinCtrl(sizer_course.GetStaticBox(), wx.ID_ANY, wx.EmptyString, wx.DefaultPosition, 186 | wx.Size(60, -1), wx.SP_ARROW_KEYS, 0, 300, 200) 187 | 188 | text_sec1 = wx.StaticText(sizer_course.GetStaticBox(), wx.ID_ANY, u"sec", wx.DefaultPosition, 189 | wx.DefaultSize, 0) 190 | text_sec1.Wrap(-1) 191 | 192 | 193 | # ===================================================== 194 | sizer_timer.Add(text_timer, 0, wx.ALIGN_CENTER | wx.ALL, 5) 195 | sizer_timer.Add(self.spinctrl_timer, 0, wx.ALIGN_CENTER | wx.ALL, 5) 196 | sizer_timer.Add(text_sec1, 0, wx.ALIGN_CENTER | wx.ALL, 5) 197 | 198 | # ===================================================== 199 | 200 | self.button_save_settings = wx.Button(sizer_course.GetStaticBox(), wx.ID_ANY, u"保存设置", wx.DefaultPosition, wx.DefaultSize, 0) 201 | 202 | # ===================================================== 203 | sizer_course.Add(self.checkbox_force, 0, wx.ALIGN_CENTER | wx.ALL, 5) 204 | sizer_course.Add(sizer_course_filter, 1, wx.EXPAND, 5) 205 | sizer_course.Add(sizer_post_range, 1, wx.EXPAND, 5) 206 | 207 | sizer_course.Add(sizer_timer, 0, wx.EXPAND, 5) 208 | sizer_course.Add(self.button_save_settings, 0, wx.ALIGN_RIGHT | wx.ALL, 5) 209 | 210 | sizer_settings.Add(sizer_course, 0, wx.EXPAND, 5) 211 | 212 | self.sizer_right.Add(sizer_settings, 0, wx.EXPAND, 5) 213 | 214 | self.sizer_global.Add(self.sizer_right, 0, wx.EXPAND, 5) 215 | 216 | -------------------------------------------------------------------------------- /gui/frame/main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | import wx 4 | from gui.listctrl.member_view import MemberView_ListCtrl 5 | from gui.listctrl.logs import Logs_ListCtrl 6 | import gui 7 | 8 | class FrameMain(wx.Frame): 9 | def __init__(self, parent): 10 | wx.Frame.__init__(self, parent, id=wx.ID_ANY, title=u'GDUT教务系统助手', pos=wx.DefaultPosition, size=wx.Size(-1, -1), style=wx.DEFAULT_FRAME_STYLE|wx.FULL_REPAINT_ON_RESIZE|wx.TAB_TRAVERSAL) 11 | 12 | self.SetSizeHints(wx.Size(550, -1), wx.DefaultSize) 13 | self.SetBackgroundColour(wx.SystemSettings.GetColour(wx.SYS_COLOUR_BTNFACE)) 14 | 15 | self.sizer_global = wx.BoxSizer(wx.VERTICAL) 16 | self.sizer_global.SetMinSize(wx.Size(550, -1)) 17 | 18 | self.listctrl_member = None 19 | self.init_MemberView_ListCtrl() 20 | 21 | self.listctrl_logs = None 22 | self.init_Logs_ListCtrl() 23 | 24 | self.SetSizer(self.sizer_global) 25 | self.Layout() 26 | 27 | self.status_bar = None 28 | self.init_StatusBar() 29 | 30 | self.sizer_global.Fit(self) 31 | 32 | self.menu_bar = FrameMain_MenuBar(0) 33 | self.SetMenuBar(self.menu_bar) 34 | 35 | self.Center(wx.BOTH) 36 | 37 | def init_StatusBar(self): 38 | self.status_bar = self.CreateStatusBar(10, wx.STB_SIZEGRIP, wx.ID_ANY) 39 | self.status_bar.SetStatusWidths([30, -1, 40, -1, 40, -1, 35, -1, 30, -6]) 40 | self.status_bar.SetStatusText('Total', 0) 41 | self.status_bar.SetStatusText('Ready', 2) 42 | self.status_bar.SetStatusText('Taking', 4) 43 | self.status_bar.SetStatusText('Done', 6) 44 | self.status_bar.SetStatusText('', 8) 45 | 46 | def init_MemberView_ListCtrl(self): 47 | sizer_member = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, u'MemberView'), wx.VERTICAL) 48 | self.listctrl_member = MemberView_ListCtrl(sizer_member.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, 49 | wx.Size(-1, 300), 50 | wx.LC_SINGLE_SEL | wx.LC_REPORT | wx.HSCROLL | wx.VSCROLL) 51 | 52 | sizer_member.Add(self.listctrl_member, 0, wx.ALL | wx.EXPAND | wx.FIXED_MINSIZE, 5) 53 | 54 | self.sizer_global.Add(sizer_member, 0, wx.EXPAND, 5) 55 | 56 | def init_Logs_ListCtrl(self): 57 | sizer_logs = wx.StaticBoxSizer(wx.StaticBox(self, wx.ID_ANY, u"Logs"), wx.VERTICAL) 58 | sizer_logs.SetMinSize(wx.Size(-1, 250)) 59 | 60 | self.listctrl_logs = Logs_ListCtrl(sizer_logs.GetStaticBox(), wx.ID_ANY, wx.DefaultPosition, wx.DefaultSize, 61 | wx.LC_REPORT | wx.FULL_REPAINT_ON_RESIZE) 62 | sizer_logs.Add(self.listctrl_logs, 1, wx.ALL | wx.EXPAND | wx.FIXED_MINSIZE, 5) 63 | 64 | self.sizer_global.Add(sizer_logs, 0, wx.EXPAND, 5) 65 | 66 | 67 | 68 | class FrameMain_MenuBar(wx.MenuBar): 69 | def __init__(self, *args): 70 | wx.MenuBar.__init__(self, *args) 71 | 72 | self.file = FrameMember_Menu_File() 73 | self.Append(self.file, u"File") 74 | 75 | self.edit = FrameMember_Menu_Edit() 76 | self.Append(self.edit, u'Edit') 77 | 78 | self.operation = FrameMember_Menu_Operation() 79 | self.Append(self.operation, u'Operation') 80 | 81 | self.help = FrameMember_Menu_Help() 82 | self.Append(self.help, u'Help') 83 | 84 | # self.Bind(wx.EVT_MENU_OPEN, self.OnMenuOpen) 85 | # 86 | # def OnMenuOpen(self, event): 87 | # listctrl_member_object = gui.frame_main.listctrl_member 88 | # cur_selected = listctrl_member_object.GetFirstSelected() 89 | # if cur_selected != -1: 90 | # cur_item_status = listctrl_member_object.GetItem(cur_selected, 2).Text 91 | # if cur_item_status == '选课中' or cur_item_status == '延时中': 92 | # self.operation.login.Enable(False) 93 | # self.operation.take.SetText('Stop') 94 | # else: 95 | # self.operation.login.Enable(True) 96 | # self.operation.take.SetText('Take') 97 | 98 | 99 | class FrameMember_Menu_File(wx.Menu): 100 | def __init__(self, *args): 101 | wx.Menu.__init__(self, *args) 102 | 103 | self.run = None 104 | self.save = None 105 | self.save_as = None 106 | 107 | self.import_ = None 108 | self.import_from_xlsx = None 109 | 110 | self.exit = None 111 | 112 | self.initItems() 113 | 114 | def initItems(self): 115 | self.run = wx.MenuItem(self, wx.ID_ANY, u"Run", wx.EmptyString, wx.ITEM_NORMAL) 116 | self.Append(self.run) 117 | 118 | self.AppendSeparator() 119 | 120 | self.save = wx.MenuItem(self, wx.ID_ANY, u"Save", wx.EmptyString, wx.ITEM_NORMAL) 121 | self.Append(self.save) 122 | self.save.Enable(False) 123 | 124 | self.save_as = wx.MenuItem(self, wx.ID_ANY, u"Save as", wx.EmptyString, wx.ITEM_NORMAL) 125 | self.Append(self.save_as) 126 | self.save_as.Enable(False) 127 | 128 | self.import_ = wx.Menu() 129 | self.import_from_xlsx = wx.MenuItem(self.import_, wx.ID_ANY, u"From xlsx", wx.EmptyString, 130 | wx.ITEM_NORMAL) 131 | self.import_.Append(self.import_from_xlsx) 132 | self.AppendSubMenu(self.import_, u"Import") 133 | 134 | self.AppendSeparator() 135 | 136 | self.exit = wx.MenuItem(self, wx.ID_ANY, u"Exit", wx.EmptyString, wx.ITEM_NORMAL) 137 | self.Append(self.exit) 138 | 139 | 140 | 141 | class FrameMember_Menu_Edit(wx.Menu): 142 | def __init__(self, *args): 143 | wx.Menu.__init__(self, *args) 144 | 145 | self.add = None 146 | self.delete = None 147 | self.delete_all = None 148 | 149 | self.initItems() 150 | 151 | def initItems(self): 152 | self.add = wx.MenuItem(self, wx.ID_ANY, u"Add", wx.EmptyString, wx.ITEM_NORMAL) 153 | self.Append(self.add) 154 | 155 | self.AppendSeparator() 156 | 157 | self.delete = wx.MenuItem(self, wx.ID_ANY, u"Delete", wx.EmptyString, wx.ITEM_NORMAL) 158 | self.Append(self.delete) 159 | 160 | self.delete_all = wx.MenuItem(self, wx.ID_ANY, u"Delete all", wx.EmptyString, wx.ITEM_NORMAL) 161 | self.Append(self.delete_all) 162 | self.delete_all.Enable(False) 163 | 164 | 165 | class FrameMember_Menu_Operation(wx.Menu): 166 | def __init__(self, *args): 167 | wx.Menu.__init__(self, *args) 168 | 169 | self.login = None 170 | self.login_all = None 171 | 172 | self.take = None 173 | self.take_all = None 174 | 175 | self.verify = None 176 | self.verify_all = None 177 | 178 | self.initItems() 179 | 180 | 181 | 182 | def initItems(self): 183 | self.login = wx.MenuItem(self, wx.ID_ANY, u"&Login\tF3", wx.EmptyString, wx.ITEM_NORMAL) 184 | self.Append(self.login) 185 | 186 | self.login_all = wx.MenuItem(self, wx.ID_ANY, u"Login All", wx.EmptyString, wx.ITEM_NORMAL) 187 | self.Append(self.login_all) 188 | 189 | self.AppendSeparator() 190 | 191 | self.take = wx.MenuItem(self, wx.ID_ANY, u"&Take\tF4", wx.EmptyString, wx.ITEM_NORMAL) 192 | self.Append(self.take) 193 | 194 | self.take_all = wx.MenuItem(self, wx.ID_ANY, u"Take All", wx.EmptyString, wx.ITEM_NORMAL) 195 | self.Append(self.take_all) 196 | 197 | self.AppendSeparator() 198 | 199 | self.verify = wx.MenuItem(self, wx.ID_ANY, u"&Verify\tF5", wx.EmptyString, wx.ITEM_NORMAL) 200 | self.Append(self.verify) 201 | 202 | self.verify_all = wx.MenuItem(self, wx.ID_ANY, u"Verify All", wx.EmptyString, wx.ITEM_NORMAL) 203 | self.Append(self.verify_all) 204 | 205 | 206 | class FrameMember_Menu_Help(wx.Menu): 207 | def __init__(self, *args): 208 | wx.Menu.__init__(self, *args) 209 | 210 | self.help = None 211 | self.about = None 212 | 213 | self.initItems() 214 | 215 | def initItems(self): 216 | self.help = wx.MenuItem(self, wx.ID_ANY, u"&Help", wx.EmptyString, wx.ITEM_NORMAL) 217 | self.Append(self.help) 218 | self.help.Enable(False) 219 | self.AppendSeparator() 220 | 221 | self.about = wx.MenuItem(self, wx.ID_ANY, u"&About", wx.EmptyString, wx.ITEM_NORMAL) 222 | self.Append(self.about) 223 | 224 | -------------------------------------------------------------------------------- /gui/listctrl/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZSAIm/TakeTheCourse_GDUT/bfca8210f9b613acccba607a55ca5fa015a24dd1/gui/listctrl/__init__.py -------------------------------------------------------------------------------- /gui/listctrl/basic_listctrl.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import wx 4 | 5 | ODD_BGCOLOR = wx.Colour(240, 240, 240, 255) 6 | EVEN_BGCOLOR = wx.Colour(255, 255, 255, 255) 7 | 8 | class BasicListCtrl(wx.ListCtrl): 9 | def Append(self, entry, fgcolor=None): 10 | wx.ListCtrl.Append(self, entry) 11 | item_count = self.GetItemCount() 12 | if not item_count % 2: 13 | self.SetItemBackgroundColour(item_count - 1, ODD_BGCOLOR) 14 | 15 | if fgcolor: 16 | self.SetItemTextColour(item_count-1, wx.Colour(fgcolor)) 17 | 18 | # self.ScrollList(0, 10) 19 | # self.ScrollLines(1) 20 | 21 | def DeleteItem(self, item): 22 | wx.ListCtrl.DeleteItem(self, item) 23 | item_count = self.GetItemCount() 24 | odd = True if item % 2 else False 25 | 26 | for i in range(item_count - item): 27 | 28 | self.SetItemBackgroundColour(i + item, ODD_BGCOLOR if odd else EVEN_BGCOLOR) 29 | 30 | odd = not odd 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /gui/listctrl/logs.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import wx 4 | from gui.listctrl.basic_listctrl import BasicListCtrl 5 | 6 | class Logs_ListCtrl(BasicListCtrl): 7 | def __init__(self, *args): 8 | wx.ListCtrl.__init__(self, *args) 9 | 10 | self.initColumn() 11 | 12 | def initColumn(self): 13 | 14 | self.AppendColumn('Time', format=wx.LIST_FORMAT_CENTER, width=80) 15 | self.AppendColumn('Account', format=wx.LIST_FORMAT_CENTER, width=85) 16 | self.AppendColumn('SystemMsg', width=240, format=wx.LIST_FORMAT_LEFT) 17 | self.AppendColumn('ServerMsg', width=100, format=wx.LIST_FORMAT_LEFT) 18 | 19 | 20 | def Append(self, entry, fgcolor=None): 21 | BasicListCtrl.Append(self, entry, fgcolor) 22 | self.ScrollLines(5) 23 | 24 | 25 | -------------------------------------------------------------------------------- /gui/listctrl/member_view.py: -------------------------------------------------------------------------------- 1 | 2 | import wx 3 | from gui.listctrl.basic_listctrl import BasicListCtrl 4 | import gui 5 | 6 | # MENU_STYLE_ALL = 0 7 | MENU_STYLE_DIS_LOGIN = 1 8 | MENU_STYLE_TAKE_TO_STOP = 2 9 | 10 | 11 | 12 | 13 | class MemberView_ListCtrl(BasicListCtrl): 14 | def __init__(self, *args): 15 | wx.ListCtrl.__init__(self, *args) 16 | self.SetMinSize(wx.Size(-1, 300)) 17 | 18 | self.menu = None 19 | self.initMenu() 20 | self.initColumn() 21 | 22 | self.Bind(wx.EVT_LIST_ITEM_SELECTED, self.updateMenuText, self) 23 | 24 | def initColumn(self): 25 | 26 | self.AppendColumn('', format=wx.LIST_FORMAT_CENTER, width=20) 27 | self.AppendColumn('Account', format=wx.LIST_FORMAT_CENTER, width=85) 28 | self.AppendColumn('Status', width=70, format=wx.LIST_FORMAT_CENTER) 29 | 30 | self.AppendColumn('Online', width=50, format=wx.LIST_FORMAT_CENTER) 31 | self.AppendColumn('Target', width=50, format=wx.LIST_FORMAT_CENTER) 32 | self.AppendColumn('Delay', width=58, format=wx.LIST_FORMAT_CENTER) 33 | 34 | self.AppendColumn('LastUpdateTime', width=166, format=wx.LIST_FORMAT_CENTER) 35 | 36 | # self.AppendColumn('Now', width=100, format=wx.LIST_FORMAT_CENTER) 37 | 38 | def Append(self, entry, fgcolor=None): 39 | BasicListCtrl.Append(self, (self.GetItemCount()+1, *entry), fgcolor) 40 | 41 | def initMenu(self): 42 | self.menu = MemberView_Menu() 43 | self.Bind(wx.EVT_RIGHT_DOWN, self.OnContextMenu) 44 | 45 | def updateMenuText(self, event=None): 46 | cur_selected = self.GetFirstSelected() 47 | if cur_selected != -1: 48 | cur_item_status = self.GetItem(cur_selected, 2).Text 49 | if cur_item_status == '选课中' or cur_item_status == '延时中': 50 | self.menu.login.Enable(False) 51 | self.menu.take.SetText('Stop') 52 | self.menu.take.Enable(True) 53 | 54 | gui.frame_main.menu_bar.operation.login.Enable(False) 55 | gui.frame_main.menu_bar.operation.take.SetText('&Stop\tF4') 56 | gui.frame_main.menu_bar.operation.take.Enable(True) 57 | elif cur_item_status == '完成': 58 | self.menu.login.Enable(True) 59 | self.menu.take.SetText('Take') 60 | self.menu.take.Enable(False) 61 | 62 | gui.frame_main.menu_bar.operation.login.Enable(True) 63 | gui.frame_main.menu_bar.operation.take.SetText('&Take\tF4') 64 | gui.frame_main.menu_bar.operation.take.Enable(False) 65 | else: 66 | self.menu.login.Enable(True) 67 | self.menu.take.SetText('Take') 68 | self.menu.take.Enable(True) 69 | 70 | gui.frame_main.menu_bar.operation.login.Enable(True) 71 | gui.frame_main.menu_bar.operation.take.SetText('&Take\tF4') 72 | gui.frame_main.menu_bar.operation.take.Enable(True) 73 | 74 | 75 | def OnContextMenu(self, event): 76 | self.updateMenuText(event) 77 | 78 | self.PopupMenu(self.menu, event.GetPosition()) 79 | 80 | 81 | 82 | class MemberView_Menu(wx.Menu): 83 | def __init__(self, *args): 84 | wx.Menu.__init__(self, *args) 85 | 86 | self.view = None 87 | self.configure = None 88 | self.login = None 89 | self.take = None 90 | # self.add = None 91 | self.verify = None 92 | self.delete = None 93 | 94 | self.initItems() 95 | 96 | 97 | def initItems(self): 98 | self.view = wx.MenuItem(self, wx.ID_ANY, u'View', wx.EmptyString, wx.ITEM_NORMAL) 99 | self.Append(self.view) 100 | 101 | self.configure = wx.MenuItem(self, wx.ID_ANY, u'Configure', wx.EmptyString, wx.ITEM_NORMAL) 102 | self.Append(self.configure) 103 | 104 | self.AppendSeparator() 105 | 106 | self.login = wx.MenuItem(self, wx.ID_ANY, u'Login', wx.EmptyString, wx.ITEM_NORMAL) 107 | self.Append(self.login) 108 | 109 | self.take = wx.MenuItem(self, wx.ID_ANY, u'Take', wx.EmptyString, wx.ITEM_NORMAL) 110 | self.Append(self.take) 111 | 112 | self.AppendSeparator() 113 | 114 | self.verify = wx.MenuItem(self, wx.ID_ANY, u'Verify', wx.EmptyString, wx.ITEM_NORMAL) 115 | self.Append(self.verify) 116 | 117 | self.AppendSeparator() 118 | 119 | self.delete = wx.MenuItem(self, wx.ID_ANY, u'Delete', wx.EmptyString, wx.ITEM_NORMAL) 120 | self.Append(self.delete) 121 | 122 | 123 | -------------------------------------------------------------------------------- /gui/listctrl/optional.py: -------------------------------------------------------------------------------- 1 | 2 | import wx 3 | from gui.listctrl.basic_listctrl import BasicListCtrl 4 | 5 | STYLE_OPTIONAL = 0 6 | STYLE_USER_COURSE = 1 7 | 8 | class Optional_ListCtrl(BasicListCtrl): 9 | def __init__(self, *args): 10 | wx.ListCtrl.__init__(self, *args) 11 | self.SetMinSize(wx.Size(500, 400)) 12 | 13 | # self.menu = None 14 | # self.initMenu() 15 | # self.initUserCourseColumn() 16 | self.initOptionColumn() 17 | 18 | self.style = STYLE_OPTIONAL 19 | 20 | def initOptionColumn(self): 21 | self.style = STYLE_OPTIONAL 22 | self.ClearAll() 23 | self.AppendColumn('课程名称', format=wx.LIST_FORMAT_CENTER, width=130) 24 | self.AppendColumn('学分', format=wx.LIST_FORMAT_CENTER, width=45) 25 | self.AppendColumn('教师', format=wx.LIST_FORMAT_CENTER, width=55) 26 | self.AppendColumn('限选', format=wx.LIST_FORMAT_CENTER, width=45) 27 | self.AppendColumn('已选', format=wx.LIST_FORMAT_CENTER, width=45) 28 | self.AppendColumn('课程分类', format=wx.LIST_FORMAT_CENTER, width=100) 29 | 30 | 31 | def initUserCourseColumn(self): 32 | self.style = STYLE_USER_COURSE 33 | self.ClearAll() 34 | self.AppendColumn('课程名称', format=wx.LIST_FORMAT_CENTER, width=100) 35 | self.AppendColumn('班级名称', format=wx.LIST_FORMAT_CENTER, width=60) 36 | self.AppendColumn('人数', format=wx.LIST_FORMAT_CENTER, width=40) 37 | self.AppendColumn('教师', format=wx.LIST_FORMAT_CENTER, width=60) 38 | self.AppendColumn('周次', format=wx.LIST_FORMAT_CENTER, width=38) 39 | self.AppendColumn('星期', format=wx.LIST_FORMAT_CENTER, width=38) 40 | self.AppendColumn('节次', format=wx.LIST_FORMAT_CENTER, width=60) 41 | self.AppendColumn('上课地点', format=wx.LIST_FORMAT_CENTER, width=70) 42 | self.AppendColumn('排课日期', format=wx.LIST_FORMAT_CENTER, width=80) 43 | self.AppendColumn('课序', format=wx.LIST_FORMAT_CENTER, width=38) 44 | self.AppendColumn('类型', format=wx.LIST_FORMAT_CENTER, width=60) 45 | self.AppendColumn('授课内容简介', format=wx.LIST_FORMAT_LEFT, width=200) 46 | 47 | 48 | # def initMenu(self): 49 | # self.menu = Optional_Menu() 50 | # 51 | # self.Bind(wx.EVT_RIGHT_DOWN, self.OnContextMenu) 52 | 53 | # def OnContextMenu(self, event): 54 | # self.PopupMenu(self.menu, event.GetPosition()) 55 | # 56 | # 57 | # class Optional_Menu(wx.Menu): 58 | # def __init__(self, *args): 59 | # wx.Menu.__init__(self, *args) 60 | # 61 | # self.view = None 62 | # self.add = None 63 | # self.delete = None 64 | # 65 | # self.initItems() 66 | # 67 | # def initItems(self): 68 | # self.view = wx.MenuItem(self, wx.ID_ANY, u'View', wx.EmptyString, wx.ITEM_NORMAL) 69 | # self.Append(self.view) 70 | # 71 | # self.AppendSeparator() 72 | # 73 | # self.add = wx.MenuItem(self, wx.ID_ANY, u'Add', wx.EmptyString, wx.ITEM_NORMAL) 74 | # self.Append(self.add) 75 | # 76 | # # self.AppendSeparator() 77 | # 78 | # self.delete = wx.MenuItem(self, wx.ID_ANY, u'Delete', wx.EmptyString, wx.ITEM_NORMAL) 79 | # self.Append(self.delete) 80 | # 81 | -------------------------------------------------------------------------------- /gui/listctrl/selected.py: -------------------------------------------------------------------------------- 1 | 2 | import wx 3 | from gui.listctrl.basic_listctrl import BasicListCtrl 4 | 5 | class Selected_ListCtrl(BasicListCtrl): 6 | def __init__(self, *args): 7 | wx.ListCtrl.__init__(self, *args) 8 | self.SetMinSize(wx.Size(500, 100)) 9 | 10 | self.menu = None 11 | self.initMenu() 12 | self.initSelectedColumn() 13 | 14 | def initMenu(self): 15 | self.menu = Selected_Menu() 16 | 17 | self.Bind(wx.EVT_RIGHT_DOWN, self.OnContextMenu) 18 | 19 | def OnContextMenu(self, event): 20 | self.PopupMenu(self.menu, event.GetPosition()) 21 | 22 | def initSelectedColumn(self): 23 | self.ClearAll() 24 | self.AppendColumn('课程名称', format=wx.LIST_FORMAT_CENTER, width=130) 25 | self.AppendColumn('学分', format=wx.LIST_FORMAT_CENTER, width=45) 26 | self.AppendColumn('教师', format=wx.LIST_FORMAT_CENTER, width=55) 27 | self.AppendColumn('限选', format=wx.LIST_FORMAT_CENTER, width=45) 28 | self.AppendColumn('已选', format=wx.LIST_FORMAT_CENTER, width=45) 29 | self.AppendColumn('课程分类', format=wx.LIST_FORMAT_CENTER, width=100) 30 | 31 | 32 | 33 | class Selected_Menu(wx.Menu): 34 | def __init__(self, *args): 35 | wx.Menu.__init__(self, *args) 36 | 37 | # self.view = None 38 | # self.configure = None 39 | # self.add = None 40 | self.delete = None 41 | 42 | self.initItems() 43 | 44 | def initItems(self): 45 | # self.view = wx.MenuItem(self, wx.ID_ANY, u'View', wx.EmptyString, wx.ITEM_NORMAL) 46 | # self.Append(self.view) 47 | # 48 | # self.configure = wx.MenuItem(self, wx.ID_ANY, u'Configure', wx.EmptyString, wx.ITEM_NORMAL) 49 | # self.Append(self.configure) 50 | # 51 | # self.AppendSeparator() 52 | # 53 | # self.add = wx.MenuItem(self, wx.ID_ANY, u'Add', wx.EmptyString, wx.ITEM_NORMAL) 54 | # self.Append(self.add) 55 | # 56 | # self.AppendSeparator() 57 | 58 | self.delete = wx.MenuItem(self, wx.ID_ANY, u'Delete', wx.EmptyString, wx.ITEM_NORMAL) 59 | self.Append(self.delete) 60 | -------------------------------------------------------------------------------- /handler/Error.py: -------------------------------------------------------------------------------- 1 | 2 | from base import delayer 3 | import llogger 4 | 5 | class UrlOpTimeOutError(Exception): 6 | def __init__(self, req_kwargs): 7 | self.req_kwargs = req_kwargs 8 | if req_kwargs.get('user'): 9 | llogger.error(req_kwargs.get('user').account, 'Urllib请求超时。', req_kwargs.get('msg', '')) 10 | else: 11 | llogger.error('NULL', 'Urllib请求超时。', req_kwargs.get('msg', '')) 12 | 13 | 14 | 15 | class LoginBasicException(Exception): 16 | def __init__(self, user, msg, from_err=None): 17 | self.user = user 18 | 19 | self.msg = bytes.decode(msg) if isinstance(msg, bytes) else msg 20 | 21 | self.from_err = from_err 22 | self.handleError() 23 | 24 | def handleError(self): 25 | for i, j in self.getHandlers().items(): 26 | if i in self.msg: 27 | return j() 28 | else: 29 | return self.UnknownErrorHandler() 30 | 31 | def getHandlers(self): 32 | return { 33 | '您的帐号或密码不正确': self.UserValueErrorHandler, 34 | '验证码不正确': self.VerifyCodeErrorHandler, 35 | '连接已过期': self.LoginExpireErrorHandler, 36 | '请输入验证码': self.UnloginErrorHandler, 37 | } 38 | 39 | def UserValueErrorHandler(self): 40 | self.user.op.reInit() 41 | self.user.op.fail() 42 | llogger.error(self.user.account, '帐号或密码不正确,登录失败中止。', self.msg) 43 | 44 | def VerifyCodeErrorHandler(self): 45 | self.user.op.reInit() 46 | delayer.timingRun(self.user, self.user.op.login, (0, 0.5), self.msg) 47 | llogger.warning(self.user.account, '验证码识别错误,登录失败。', self.msg) 48 | 49 | def LoginExpireErrorHandler(self): 50 | self.user.op.reInit() 51 | delayer.timingRun(self.user, self.user.op.login, (0, 0.5), self.msg) 52 | llogger.warning(self.user.account, '连接已过期,登录失败。', self.msg) 53 | 54 | def UnloginErrorHandler(self): 55 | llogger.warning(self.user.account, '操作失败: 账号已离线,重新登录。', self.msg) 56 | self.user.op.reInit() 57 | delayer.timingRun(self.user, self.user.op.login, (0, 0.5), self.msg) 58 | 59 | def UnknownErrorHandler(self): 60 | 61 | print('UnknownError in LoginBasicException: ', self.msg) 62 | llogger.error(self.user.account, '登录发生未知错误。', self.msg) 63 | 64 | 65 | # class IsAvlBasicException(Exception): 66 | 67 | 68 | class TakerBasicException(Exception): 69 | def __init__(self, user, msg, from_err=None, course=None): 70 | self.user = user 71 | self.msg = msg 72 | self.from_err = from_err 73 | 74 | self.course = course 75 | 76 | self.handleError() 77 | 78 | 79 | def handleError(self): 80 | for i, j in self.getHandlers().items(): 81 | if i in self.msg: 82 | return j() 83 | else: 84 | return self.UnknownErrorHandler() 85 | 86 | def getHandlers(self): 87 | return { 88 | '请输入验证码': self.UnloginErrorHandler, 89 | '您已被迫退出': self.UnloginErrorHandler, 90 | '选了': self.RepeatErrorHandler, 91 | '超出选课要求门数': self.ExceedErrorHandler, 92 | '不是选课时间': self.TakingTimeErrorHandler, 93 | '没有开设该课程': self.CourseNotFoundErrorHandler 94 | 95 | } 96 | 97 | def UnloginErrorHandler(self): 98 | llogger.warning(self.user.account, '操作失败:[%s]-"账号已离线,重新登录"。' % self.course, self.msg) 99 | self.user.op.reInit() 100 | delayer.timingRun(self.user, self.user.op.login, (0, 0.5), self.msg) 101 | 102 | def RepeatErrorHandler(self): 103 | llogger.warning(self.user.account, '操作失败:[%s]-"已经选了该课程"。' % self.course, self.msg) 104 | self.user.op.taker.selected.append(self.course) 105 | delayer.timingRun(self.user, self.user.op.takeCourse, (0, 0.5), self.msg) 106 | 107 | def ExceedErrorHandler(self): 108 | llogger.warning(self.user.account, '操作失败:[%s]-"超出选课要求门数"。' % self.course, self.msg) 109 | self.user.op.done() 110 | 111 | def TakingTimeErrorHandler(self): 112 | llogger.warning(self.user.account, '操作等待:[%s]-"当前不是选课时间"。' % self.course, self.msg) 113 | self.user.op.onTimingtake() 114 | delayer.timingRun(self.user, self.user.op.takeCourse, self.user.delay_range, self.msg) 115 | 116 | def CourseNotFoundErrorHandler(self): 117 | llogger.warning(self.user.account, '操作失败:[%s]-"没有开设该课程"。' % self.course, self.msg) 118 | self.user.op.taker.pullCourses() 119 | delayer.timingRun(self.user, self.user.op.takeCourse, self.user.delay_range, self.msg) 120 | 121 | def UnknownErrorHandler(self): 122 | # print('UnknownError in TakerBasicException: ', self.msg) 123 | llogger.error(self.user.account, '操作发生未知信息。', self.msg) 124 | 125 | 126 | 127 | class PullerBasicException(Exception): 128 | def __init__(self, user, msg, from_err=None): 129 | self.user = user 130 | self.msg = msg 131 | self.from_err = from_err 132 | 133 | self.handleError() 134 | 135 | def handleError(self): 136 | for i, j in self.getHandlers().items(): 137 | if i in self.msg: 138 | return j() 139 | else: 140 | return self.UnknownErrorHandler() 141 | 142 | def getHandlers(self): 143 | return { 144 | '请输入验证码': self.UnloginErrorHandler, 145 | '您已被迫退出': self.UnloginErrorHandler 146 | } 147 | 148 | def UnloginErrorHandler(self): 149 | llogger.warning(self.user.account, '拉取失败: 账号已离线,重新登录。', self.msg) 150 | self.user.op.reInit() 151 | delayer.timingRun(self.user, self.user.op.login, (0, 0.5), self.msg) 152 | 153 | def UnknownErrorHandler(self): 154 | print('UnknownError in TakerBasicException: ', self.msg) 155 | llogger.error(self.user.account, '操作发生未知信息。', self.msg) 156 | 157 | 158 | class VerifyBasicException(Exception): 159 | def __init__(self, user, msg, from_err=None): 160 | self.user = user 161 | self.msg = msg 162 | self.from_err = from_err 163 | 164 | self.handleError() 165 | 166 | def handleError(self): 167 | for i, j in self.getHandlers().items(): 168 | if i in self.msg: 169 | return j() 170 | else: 171 | return self.UnknownErrorHandler() 172 | 173 | def getHandlers(self): 174 | return { 175 | '请输入验证码': self.UnloginErrorHandler, 176 | '您已被迫退出': self.UnloginErrorHandler 177 | } 178 | 179 | def UnloginErrorHandler(self): 180 | llogger.warning(self.user.account, '操作失败: 账号已离线,重新登录。', self.msg) 181 | self.user.op.reInit() 182 | delayer.timingRun(self.user, self.user.op.login, (0, 0.5), self.msg) 183 | 184 | def UnknownErrorHandler(self): 185 | print('UnknownError in TakerBasicException: ', self.msg) 186 | llogger.error(self.user.account, '操作发生未知信息。', self.msg) -------------------------------------------------------------------------------- /handler/Opa.py: -------------------------------------------------------------------------------- 1 | 2 | import threading 3 | import os, time 4 | import json 5 | import traceback 6 | 7 | from base.urlop import UrlRequestOp 8 | from core import login, course 9 | from handler.struct.user import UserData 10 | from handler import Pool 11 | 12 | from handler.Error import LoginBasicException, UrlOpTimeOutError, PullerBasicException 13 | import gui 14 | 15 | import llogger 16 | 17 | 18 | NOTICE_URL = 'http://jxfw.gdut.edu.cn/notice!getNotice.action?_=%d' 19 | 20 | 21 | def get_cdatetime(): 22 | return time.asctime(time.localtime()) 23 | 24 | 25 | class UserOp(UrlRequestOp, object): 26 | def __init__(self, parent, account, password, keys): 27 | UrlRequestOp.__init__(self) 28 | 29 | self.parent = parent 30 | self.user = UserData(account, password, keys, self) 31 | 32 | self.loginer = login.Login(self.user) 33 | 34 | self.taker = course.Taker(self.user) 35 | self._thread = None 36 | 37 | self._get_notice_thread = None 38 | self.assign_thread_lock = threading.Lock() 39 | 40 | def login(self): 41 | with self.assign_thread_lock: 42 | if self._thread and self._thread.isAlive() and self._thread != threading.current_thread(): 43 | return 44 | if self.user.status == Pool.TAKING or self.user.status == Pool.TIMING_TAKE: 45 | return 46 | self._thread = threading.current_thread() 47 | self.reInit() 48 | # print(self._thread) 49 | self.loginer.run() 50 | 51 | def takeCourse(self): 52 | with self.assign_thread_lock: 53 | if self._thread and self._thread.isAlive() and self._thread != threading.current_thread(): 54 | return 55 | if self.user.status == Pool.DONE: 56 | return 57 | self._thread = threading.current_thread() 58 | # print(self._thread) 59 | if self.user.ready: 60 | self.taker.run() 61 | else: 62 | if self.getStatus() == Pool.DONE: 63 | return 64 | self.reInit() 65 | self.login() 66 | self.join() 67 | if self.getStatus() != Pool.FAILURE: 68 | self.takeCourse() 69 | 70 | 71 | def saveCookie(self): 72 | self.loginer.cookiejar.save('cookies/%s.txt' % self.user.account, ignore_discard=True, ignore_expires=True) 73 | 74 | def loadCookie(self): 75 | if os.path.exists('cookies/%s.txt' % self.user.account): 76 | self.setNewCookieJar() 77 | self.cookiejar.load('cookies/%s.txt' % self.user.account, ignore_discard=True, ignore_expires=True) 78 | self.buildOpener(self.cookiejar, self.proxy) 79 | self.loginer.loadUrlop(self) 80 | self.getReady() 81 | llogger.ok(self.user.account, '加载Cookie成功。[未验证]') 82 | return True 83 | else: 84 | return False 85 | 86 | def verify(self): 87 | self.user.status = Pool.VERIFYING 88 | self.taker.puller.pullSelected() 89 | targets = self.taker.getTargets() 90 | if targets: 91 | tar_str = '' 92 | for i in targets: 93 | tar_str += '[%s]' % i.__str__() 94 | llogger.error(self.user.account, '以下目标未成功: {%s}' % tar_str) 95 | self.fail() 96 | else: 97 | self.done() 98 | 99 | def getStatus(self): 100 | return self.user.status 101 | 102 | def onTimingtake(self): 103 | self.user.status = Pool.TIMING_TAKE 104 | 105 | def fail(self): 106 | self.user.status = Pool.FAILURE 107 | self.cancelGetNotice() 108 | llogger.error(self.user.account, '任务失败,停止工作。成功选取[%d]项' % self.user.success_counter) 109 | 110 | def done(self): 111 | self.user.status = Pool.DONE 112 | self.cancelGetNotice() 113 | llogger.ok(self.user.account, '完成任务,停止工作。成功选取[%d]项' % self.user.success_counter) 114 | 115 | 116 | def getReady(self): 117 | self.user.status = Pool.READY 118 | self.user.ready = True 119 | self.saveCookie() 120 | self.loadUrlop(self.loginer) 121 | self.taker.loadUrlop(self.loginer) 122 | 123 | self.timingGetNotice() 124 | 125 | def reInit(self): 126 | self.user.status = Pool.UNREADY 127 | self.user.ready = False 128 | self.cancelGetNotice() 129 | 130 | def join(self, exec_foo=None, args=()): 131 | if exec_foo: 132 | threading.Thread(target=self.__join__, args=(exec_foo, args)).start() 133 | else: 134 | self.__join__(exec_foo, args) 135 | 136 | def __join__(self, exec_foo=None, args=()): 137 | while True: 138 | with self.assign_thread_lock: 139 | if not self._thread or not self._thread.isAlive() or self._thread == threading.current_thread(): 140 | break 141 | time.sleep(0.01) 142 | 143 | if exec_foo: 144 | exec_foo(*args) 145 | 146 | def timingGetNotice(self): 147 | if not self._get_notice_thread or not self._get_notice_thread.isAlive(): 148 | llogger.normal(self.user.account, '[%ds]后拉取通知。' % self.user.timer_refresh) 149 | self._get_notice_thread = threading.Timer(self.user.timer_refresh, self.getNotice, args=(False,)) 150 | self._get_notice_thread.start() 151 | 152 | def cancelGetNotice(self): 153 | 154 | while True: 155 | if self._get_notice_thread: 156 | self._get_notice_thread.cancel() 157 | if not self._get_notice_thread or not self._get_notice_thread.isAlive() \ 158 | or self._get_notice_thread == threading.current_thread(): 159 | break 160 | else: 161 | break 162 | time.sleep(0.01) 163 | 164 | def getNotice(self, once=True): 165 | 166 | try: 167 | res = self.__getNotice__() 168 | llogger.ok(self.user.account, '拉取通知成功。', res) 169 | self.user.status = Pool.READY 170 | 171 | except LoginBasicException as e: 172 | traceback.print_exc() 173 | except PullerBasicException as e: 174 | traceback.print_exc() 175 | except UrlOpTimeOutError as e: 176 | traceback.print_exc() 177 | if not once: 178 | # self.cancelGetNotice() 179 | self._get_notice_thread = None 180 | self.timingGetNotice() 181 | else: 182 | if not once: 183 | # self.cancelGetNotice() 184 | self._get_notice_thread = None 185 | self.timingGetNotice() 186 | 187 | 188 | def __getNotice__(self): 189 | raw, res = self.request(branch_num=0, method='GET', 190 | url=NOTICE_URL % (time.time() * 1000), max_retry=3) 191 | 192 | text = bytes.decode(raw) if isinstance(raw, bytes) else raw 193 | 194 | try: 195 | res_json = json.loads(text) 196 | except json.JSONDecodeError as e: 197 | llogger.warning(self.user.account, '拉取通知失败。', text) 198 | raise PullerBasicException(self.user, text, e) 199 | 200 | return res_json 201 | 202 | def set_MemberView_Item(self): 203 | cur_status = self.getStatus() 204 | index = self.parent.getUserIndex(self.user) 205 | gui.frame_main.listctrl_member.SetItem(index, 2, Pool.status2str(cur_status)) 206 | 207 | gui.frame_main.listctrl_member.SetItem(index, 3, '√' if cur_status == Pool.READY else '×') 208 | 209 | gui.frame_main.listctrl_member.SetItem(index, 6, get_cdatetime()) -------------------------------------------------------------------------------- /handler/Pool.py: -------------------------------------------------------------------------------- 1 | 2 | import time 3 | import threading 4 | 5 | from base import delayer 6 | from handler.Opa import UserOp 7 | 8 | import gui 9 | import llogger 10 | 11 | import xlrd 12 | 13 | 14 | CAPTCHA_URL = 'http://jxfw.gdut.edu.cn/yzm?d=%d' 15 | POST_LOGIN = 'http://jxfw.gdut.edu.cn/new/login' 16 | 17 | Delayer = delayer.Delayer() 18 | 19 | 20 | UNKNOWN = object() 21 | READY = object() 22 | UNREADY = object() 23 | 24 | LOGINNING = object() 25 | TAKING = object() 26 | VERIFYING = object() 27 | 28 | TIMING_TAKE = object() 29 | 30 | IDLE = object() 31 | RUNNING = object() 32 | SUCCESS = object() 33 | 34 | DONE = object() 35 | 36 | FAILURE = object() 37 | 38 | PULLING = object() 39 | 40 | def status2str(status): 41 | return { 42 | READY: '准备就绪', 43 | UNREADY: '未就绪', 44 | UNKNOWN: '未知', 45 | LOGINNING: '登录中', 46 | TAKING: '选课中', 47 | VERIFYING: '校验中', 48 | TIMING_TAKE: '延时中', 49 | PULLING: '拉取中', 50 | IDLE: '空闲中', 51 | DONE: '完成', 52 | FAILURE: '失败' 53 | }.get(status, '未知') 54 | 55 | # √×○ 56 | 57 | class UserPool(object): 58 | def __init__(self): 59 | self.queue = [] 60 | self.members = {} 61 | self.__inspector__ = None 62 | 63 | self.statusbar_thread = threading.Thread(target=self.statusBarTiming) 64 | self.statusbar_thread.start() 65 | # UserOp.pool = self 66 | def statusBarTiming(self): 67 | while True: 68 | total = str(len(self.queue)) 69 | gui.frame_main.status_bar.SetStatusText(total, 1) 70 | 71 | ready = 0 72 | for i in self.queue: 73 | if i.getStatus() == READY: 74 | ready += 1 75 | gui.frame_main.status_bar.SetStatusText(str(ready), 3) 76 | 77 | taking = 0 78 | for i in self.queue: 79 | if i.getStatus() == TAKING or i.getStatus() == TIMING_TAKE: 80 | taking += 1 81 | gui.frame_main.status_bar.SetStatusText(str(taking), 5) 82 | 83 | done = 0 84 | for i in self.queue: 85 | if i.getStatus() == DONE: 86 | done += 1 87 | 88 | gui.frame_main.status_bar.SetStatusText(str(done), 7) 89 | 90 | cur_time = time.asctime(time.localtime()) 91 | gui.frame_main.status_bar.SetStatusText(cur_time, 9) 92 | 93 | time.sleep(0.5) 94 | 95 | 96 | def inspector(self): 97 | while True: 98 | pass 99 | time.sleep(0.1) 100 | 101 | def add(self, account, password, keys): 102 | 103 | usrop = UserOp(self, account, password, keys) 104 | 105 | if account not in self.members: 106 | self.members[account] = usrop 107 | self.queue.append(usrop) 108 | data = (account, status2str(usrop.getStatus()), u'×', u'○', u'○', u'○') 109 | gui.frame_main.listctrl_member.Append(data) 110 | llogger.ok(account, '成员添加成功。') 111 | else: 112 | llogger.warning(account, '成员已存在与列表中,请不要重复添加。') 113 | 114 | return usrop 115 | 116 | def loadFromXlsx(self, filename): 117 | book = xlrd.open_workbook(filename) 118 | sheet0 = book.sheet_by_index(0) 119 | table_head = sheet0.row_values(0) 120 | for i in range(1, sheet0.nrows): 121 | words = sheet0.row_values(i) 122 | self.add(str(int(words[0])), words[1], words[2]) 123 | 124 | def runLoginAll(self): 125 | for i in self.queue: 126 | if not i.loadCookie(): 127 | threading.Thread(target=i.login).start() 128 | # i.login() 129 | 130 | def runTakeAll(self): 131 | for i in self.queue: 132 | threading.Thread(target=i.takeCourse).start() 133 | 134 | def runVerifyAll(self): 135 | for i in self.queue: 136 | threading.Thread(target=i.verify).start() 137 | 138 | def getUserOp(self, account): 139 | return self.members.get(account, None) 140 | 141 | def getUserIndex(self, user): 142 | return self.queue.index(user.op) 143 | 144 | def remove(self, userop): 145 | index = self.queue.index(userop) 146 | self.delete(index) 147 | 148 | def delete(self, index): 149 | item = self.queue.pop(index) 150 | del self.members[item.user.account] 151 | gui.frame_main.listctrl_member.DeleteItem(index) 152 | llogger.ok(item.user.account, '成员删除成功。') 153 | 154 | def exit(self): 155 | for i in self.queue: 156 | i.cancelGetNotice() -------------------------------------------------------------------------------- /handler/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZSAIm/TakeTheCourse_GDUT/bfca8210f9b613acccba607a55ca5fa015a24dd1/handler/__init__.py -------------------------------------------------------------------------------- /handler/struct/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZSAIm/TakeTheCourse_GDUT/bfca8210f9b613acccba607a55ca5fa015a24dd1/handler/struct/__init__.py -------------------------------------------------------------------------------- /handler/struct/user.py: -------------------------------------------------------------------------------- 1 | 2 | from core import encryptor 3 | from handler import Pool 4 | import gui 5 | import time 6 | from random import randint 7 | import threading 8 | import llogger 9 | 10 | def get_cdatetime(): 11 | return time.asctime(time.localtime()) 12 | 13 | class UserData(object): 14 | def __init__(self, account, password, keys, op): 15 | self.op = op 16 | self.account = account 17 | self.password = password 18 | 19 | self.post_data = {'account': self.account, 20 | 'pwd': None, 21 | 'verifycode': None} 22 | 23 | self.verifycode = None 24 | 25 | self.keys = keys 26 | 27 | self.status = None 28 | 29 | self.ready = False 30 | 31 | self.target_num = 0 32 | 33 | self.success_counter = 0 34 | 35 | self.delay_range = [1, 3] # sec 36 | 37 | self.timer_refresh = 200 38 | 39 | self.force_post = False 40 | 41 | self.timing_thread = None 42 | 43 | def __setattr__(self, key, value): 44 | object.__setattr__(self, key, value) 45 | if key == 'verifycode' and self.verifycode is not None: 46 | pwd = encryptor.aes_cipher(self.verifycode*4, self.password) 47 | self.post_data.update({'pwd': pwd, 'verifycode': self.verifycode}) 48 | elif key == 'status': 49 | if self.status: 50 | self.set_MemberView_Item() 51 | 52 | def setVerifyCode(self, verifycode): 53 | self.verifycode = verifycode 54 | 55 | def getPostData(self): 56 | return self.post_data 57 | 58 | def isReady(self): 59 | return self.ready 60 | 61 | def set_MemberView_Timing(self, sec): 62 | 63 | index = self.op.parent.getUserIndex(self) 64 | if sec == -1: 65 | gui.frame_main.listctrl_member.SetItem(index, 5, u'○') 66 | 67 | else: 68 | gui.frame_main.listctrl_member.SetItem(index, 5, str(int(sec*1000.0))) 69 | 70 | llogger.normal(self.account, '[%d ms]后尝试下一次选课。') 71 | 72 | 73 | 74 | def set_MemberView_Target(self, target): 75 | index = self.op.parent.getUserIndex(self) 76 | gui.frame_main.listctrl_member.SetItem(index, 4, str(len(target))) 77 | 78 | 79 | def set_MemberView_Item(self): 80 | cur_status = self.op.getStatus() 81 | index = self.op.parent.getUserIndex(self) 82 | gui.frame_main.listctrl_member.SetItem(index, 2, Pool.status2str(cur_status)) 83 | if cur_status == Pool.UNREADY or cur_status == Pool.FAILURE: 84 | gui.frame_main.listctrl_member.SetItem(index, 3, u'×') 85 | elif cur_status == Pool.LOGINNING: 86 | gui.frame_main.listctrl_member.SetItem(index, 3, u'○') 87 | else: 88 | gui.frame_main.listctrl_member.SetItem(index, 3, u'√') 89 | 90 | if cur_status == Pool.READY: 91 | gui.frame_main.listctrl_member.SetItem(index, 6, get_cdatetime()) 92 | 93 | if cur_status == Pool.UNREADY or cur_status == Pool.READY: 94 | gui.frame_main.listctrl_member.SetItem(index, 4, u'○') 95 | 96 | gui.frame_main.listctrl_member.updateMenuText() 97 | 98 | if cur_status == Pool.DONE: 99 | gui.frame_main.listctrl_member.SetItem(index, 5, u'○') -------------------------------------------------------------------------------- /llogger.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | import time, threading 4 | import gui 5 | import json 6 | 7 | 8 | OK = 0 9 | ERROR = 1 10 | WARNING = 2 11 | NORMAL = 3 12 | 13 | append_lock = threading.Lock() 14 | 15 | logs_msg = [] 16 | 17 | def get_ctime(): 18 | return '%s.%03d' % (time.strftime('%H:%M:%S', time.localtime()), int(time.time() % 1 * 1000)) 19 | 20 | 21 | def ok(*entry): 22 | global append_lock, OK 23 | with append_lock: 24 | msg = (get_ctime(), *entry) 25 | logs_msg.append((OK, *msg)) 26 | gui.frame_main.listctrl_logs.Append(msg, (0, 0, 255, 255)) 27 | 28 | 29 | def error(*entry): 30 | global append_lock, ERROR 31 | with append_lock: 32 | msg = (get_ctime(), *entry) 33 | logs_msg.append((ERROR, *msg)) 34 | gui.frame_main.listctrl_logs.Append(msg, (255, 0, 0, 255)) 35 | 36 | 37 | def normal(*entry): 38 | global append_lock, NORMAL 39 | with append_lock: 40 | msg = (get_ctime(), *entry) 41 | logs_msg.append((NORMAL, *msg)) 42 | gui.frame_main.listctrl_logs.Append(msg) 43 | 44 | 45 | def warning(*entry): 46 | global append_lock, WARNING 47 | with append_lock: 48 | msg = (get_ctime(), *entry) 49 | logs_msg.append((WARNING, *msg)) 50 | gui.frame_main.listctrl_logs.Append(msg, (0, 128, 0, 255)) 51 | 52 | 53 | def export(file): 54 | global logs_msg 55 | 56 | with open(file, 'w') as f: 57 | f.write(json.dumps(logs_msg)) 58 | 59 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | 3 | # author: ZSAIm 4 | # github: https://github.com/ZSAIm/TakeTheCourse_GDUT 5 | # programming by python3.5 6 | 7 | 8 | import threading 9 | import llogger 10 | import time 11 | import sys 12 | import os 13 | import gui 14 | from handler.Pool import UserPool 15 | import captcha.killer 16 | import GUIEventBinder 17 | 18 | pool = None 19 | 20 | 21 | def init_model(): 22 | llogger.normal('NULL', '开始加载验证码识别模型。') 23 | try: 24 | captcha.killer.init() 25 | except: 26 | llogger.error('NULL', '识别模型加载失败。') 27 | else: 28 | llogger.ok('NULL', '识别模型加载成功。') 29 | 30 | def init_dir(): 31 | if not os.path.exists('logs') or os.path.isfile('logs'): 32 | os.mkdir('logs') 33 | if not os.path.exists('cookies') or os.path.isfile('cookies'): 34 | os.mkdir('cookies') 35 | 36 | def __main__(): 37 | global pool 38 | init_dir() 39 | init_model() 40 | pool = UserPool() 41 | GUIEventBinder.init(pool) 42 | 43 | 44 | def main(): 45 | threading.Thread(target=__main__).start() 46 | 47 | 48 | if __name__ == '__main__': 49 | gui.init() 50 | main() 51 | gui.MainLoop() 52 | 53 | llogger.export('logs/%s.log' % time.asctime(time.localtime()).replace(':', '-')) 54 | pool.exit() 55 | sys.exit() 56 | 57 | -------------------------------------------------------------------------------- /model/model.ckpt.data-00000-of-00001: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZSAIm/TakeTheCourse_GDUT/bfca8210f9b613acccba607a55ca5fa015a24dd1/model/model.ckpt.data-00000-of-00001 -------------------------------------------------------------------------------- /model/model.ckpt.index: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZSAIm/TakeTheCourse_GDUT/bfca8210f9b613acccba607a55ca5fa015a24dd1/model/model.ckpt.index -------------------------------------------------------------------------------- /model/model.ckpt.meta: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ZSAIm/TakeTheCourse_GDUT/bfca8210f9b613acccba607a55ca5fa015a24dd1/model/model.ckpt.meta --------------------------------------------------------------------------------