├── .config.example.ini ├── .gitignore ├── Book.py ├── Doc ├── For_Developers.md └── Usage.md ├── General.py ├── LICENSE ├── Notify.py ├── Query.py ├── README.md ├── Time.py ├── _Auto_book.py ├── _Book_seat.py ├── _Detect_network.py ├── _Pick_seat.py ├── __init__.py └── requirements.txt /.config.example.ini: -------------------------------------------------------------------------------- 1 | # 所有内容中一律不需要添加引号、空格以及其他不必要的符号 2 | [Location] 3 | University = BIT 4 | Campus = LX 5 | 6 | 7 | [Account] 8 | common_hash = 9 | # 常用账号检索名(可任意填写,以便后续使用),并用英文逗号分割,不能添加多余的空格 10 | # Eg: user1,user2,user3 11 | commonu = 12 | # 常用账号用户名,规范同common_hash,需要与common_hash对应 13 | # Eg: 123456,654321,135790 14 | commonp = 15 | # 常用账号密码,规范同commonu 16 | # Eg: 123456,password,20180101 17 | # 以上三项需一一对应且数量相同 18 | zombieu = 19 | # 僵尸账号 20 | zombiep = 21 | # 僵尸账号对应密码 22 | 23 | 24 | [Site_url] 25 | init_url = http://seat.lib.bit.edu.cn 26 | login_url = http://login.bit.edu.cn/cas/login 27 | seat_url = /CAS/docs/examples/cas_simple_login.php 28 | 29 | 30 | [Index] 31 | room_id = 311,308,211,208,ns1,stkj,zgc4 32 | area_no = 17,17,16,16,4,3,34 33 | start_no = 960,951,942,933,655,0,2393 34 | room_list = 3,4,5,6,8,9,11,12,13,14,16,17,20,22,23,24,25,27,28,29,30,32,33,34 35 | base_para = 51350 36 | delta_para = 366 37 | 38 | 39 | [Time] 40 | book_time = 6:00 41 | # 早晨开放预约时间 42 | pick_time = 8:30 43 | # 未签到席位释放时间 44 | advanced_second_to_prepare = 300 45 | # 在目标时间前多少秒开始校时 46 | interval_second_to_calibrate = 1 47 | # 校时间隔秒数 48 | advanced_second_to_book = 10 49 | # 在目标时间前多少秒开始发送预约请求(此数值不宜过大,建议为10) 50 | 51 | 52 | # --------------------------- 53 | # --- 以下为自定义功能参数 --- 54 | # --------------------------- 55 | [_Auto_book] 56 | notify_type = 57 | # 局部通知类型参数,填写方法参见Notify标签中的Type变量的介绍 58 | # 若为空,则自动继承全局通知类型;若此方法不需要通知,则填0 59 | type = 0 60 | # 自动预约次日为0,捡漏当天为1 61 | # 无需校时立即开始:当日为2,次日为3 62 | target_name = 63 | # 预约账号,填写在Account.common_hash中的账号名 64 | target_room = 65 | # 目标阅览室名称 66 | target_seat = 67 | # 目标席位序号 68 | max_try_times = 200 69 | # 最大尝试次数,不建议超过300 70 | 71 | 72 | [_Book_seat] 73 | notify_type = 0 74 | # 局部通知类型参数,填写方法参见Notify标签中的Type变量的介绍 75 | # 若为空,则自动继承全局通知类型;若此方法不需要通知,则填0 76 | target_names = 77 | # 填写在Account.common_hash中的账号名,如user1 78 | target_rooms = 79 | # 目标阅览室 80 | target_seats = 81 | # 目标座位序号(数字) 82 | date = 0 83 | # 0为当日,1为次日 84 | 85 | 86 | [_Pick_seat] 87 | notify_type = 88 | # 局部通知类型参数,填写方法参见Notify标签中的Type变量的介绍 89 | # 若为空,则自动继承全局通知类型;若此方法不需要通知,则填0 90 | target_users = 91 | # 填写在Account.common_hash中的账号名,如user1 92 | target_rooms = 311-X 93 | # 目标阅览室,若房间号为X则为刷该阅览室所有房间 94 | date = 0 95 | # 0为当日,1为次日 96 | interval = 1 97 | # 时间间隔(s) 98 | 99 | 100 | [Notify] 101 | Type = 145 102 | # 全局通知类型选项 103 | # 1-邮箱,2-短信,3-微信,4-音乐,5-弹框 104 | # 通知的数字代码直接连写,如:填145则意味着启用邮箱+音乐+弹框通知 105 | # 若不需要任何通知则填0 106 | 107 | # --- 邮箱相关 --- 108 | Sender = 109 | # 发送人邮箱,例如test@test.com 110 | Password = 111 | # 邮箱密码 112 | Reciever = 113 | # 接收人邮箱,例如test@test.com 114 | # === 邮箱结束 === 115 | 116 | # --- 短信相关 --- 117 | Tel_no = 118 | # === 短信结束 === 119 | 120 | # --- 微信通知 --- 121 | Wechat_id = 122 | # === 微信结束 === 123 | 124 | # --- 音乐开始 --- 125 | Music_path = C:\Windows\Media\Ring08.wav 126 | # 音乐文件路径 127 | # === 音乐结束 === 128 | 129 | # --- 弹窗开始 --- 130 | # === 弹窗结束 === 131 | 132 | 133 | [Others] 134 | HasAdvanced = False 135 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | __pycache__/ 3 | 4 | *.txt 5 | test.* 6 | .*/ 7 | 8 | Advanced/ 9 | _Advanced_*.py 10 | data/ 11 | .config.ini* 12 | -------------------------------------------------------------------------------- /Book.py: -------------------------------------------------------------------------------- 1 | from General import General 2 | import requests, time, datetime, json 3 | from bs4 import BeautifulSoup 4 | 5 | 6 | class Book: 7 | """ 8 | 预约类 9 | """ 10 | def __init__(self, username, password=-1): 11 | configs = General.get_config() 12 | self.cfg_main = configs[0] 13 | self.init_url = self.cfg_main.get('Site_url', 'init_url') 14 | self.login_url = self.cfg_main.get('Site_url', 'login_url') 15 | self.seat_url = self.init_url + self.cfg_main.get('Site_url', 'seat_url') 16 | # Get urls 17 | self.room_ids = self.cfg_main.get('Index', 'room_id').split(',') 18 | self.area_nos = self.cfg_main.get('Index', 'area_no').split(',') 19 | self.start_nos = self.cfg_main.get('Index', 'start_no').split(',') 20 | self.room_list = self.cfg_main.get('Index', 'room_list').split(',') 21 | self.base_para = self.cfg_main.getint('Index', 'base_para') 22 | self.delta_para = self.cfg_main.getint('Index', 'delta_para') 23 | # Get indexes 24 | self.username, self.password = General.index(username, password) 25 | self.session = requests.session() 26 | self.url = '' 27 | self.post = dict() 28 | # Preparations 29 | self.login() 30 | 31 | def login(self): 32 | """ 33 | :return: cookies 34 | """ 35 | self.session.get(self.init_url) 36 | r = self.session.get(self.login_url) 37 | soup = BeautifulSoup(r.text, 'html.parser') 38 | data = dict() 39 | data['username'] = self.username 40 | data['password'] = self.password 41 | for elem in soup.find_all('input')[4:8]: 42 | data[elem.attrs['name']] = elem.attrs['value'] 43 | self.session.post(self.login_url, params=data) 44 | r = self.session.get(self.seat_url) 45 | soup = BeautifulSoup(r.text, 'html.parser') 46 | login_url3 = self.init_url + soup.find('script').text[14:85] 47 | r = self.session.get(login_url3) 48 | current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 49 | if r.text[1] == '<': 50 | print("%s 登录成功 (%s)" % (current_time, self.username)) 51 | return self 52 | else: 53 | print("%s 登录失败 (%s)" % (current_time, self.username)) 54 | return {} 55 | 56 | def prepare(self, room, seat, date): 57 | """ 58 | :param room: 311, ns1... 59 | :param seat: A-I / 1-9 60 | :param date: 0 -> today, 1 -> tomorrow 61 | :return: -1 -> wrong input, 1 -> run properly 62 | """ 63 | """ 64 | who_in = [3, 4, 5, 6, 8, 9, 11, 12, 13, 14, 16, 17, 20, 22, 23, 24, 25, 27, 28, 29, 30, 32, 33, 34] 65 | item_no = dict() 66 | item_no[1] = {'area_no': 4, 'seat_amount': 55, 'start_no': 655} 67 | item_no['311'] = {'area_no': 17, 'seat_amount': 9, 'start_no': 960} 68 | item_no['308'] = {'area_no': 17, 'seat_amount': 9, 'start_no': 951} 69 | item_no[211] = {'area_no': 16, 'seat_amount': 9, 'start_no': 942} 70 | item_no[208] = {'area_no': 16, 'seat_amount': 9, 'start_no': 933} 71 | item_no['4'] = {'area_no': 34, 'seat_amount': 204, 'start_no': 2393} 72 | """ 73 | diff = abs(datetime.date.today() - datetime.date(2017, 11, 2)).days + int(date) 74 | index = self.room_ids.index(str(room)) 75 | area_no = self.area_nos[index] 76 | segment = self.base_para + diff + self.delta_para * self.room_list.index(area_no) 77 | if area_no == '16': 78 | segment += 75412 79 | seat = int(self.start_nos[index]) + int(seat) 80 | self.url = 'http://seat.lib.bit.edu.cn/api.php/spaces/%s/book' % seat 81 | cookies = self.session.cookies.get_dict() 82 | self.post = {'access_token': cookies['access_token'], 'userid': cookies['userid'], 'segment': segment, 'type': 1} 83 | return self 84 | 85 | def book(self): 86 | available = self.check_available() 87 | if not available: 88 | return -3 89 | r = self.session.post(self.url, params=self.post) 90 | try: 91 | d = json.loads(r.text) 92 | except json.decoder.JSONDecodeError: 93 | d = json.loads(r.text[1:]) 94 | try: 95 | ts = d['data']['_hash_']['expire'] 96 | # TypeError: 'NoneType' object is not subscriptable 97 | server_time = datetime.datetime.strptime(ts, "%Y-%m-%d %H:%M:%S") 98 | current_time = server_time - datetime.timedelta(hours = 1) 99 | msg = d['msg'] 100 | print("%s %s" % (str(current_time), msg)) 101 | if '时间' in msg: d['status'] = -1 102 | return d['status'] 103 | # 这里有两种 一种是此空间已经被预约/行程冲突 一种是时间未到 104 | # status == 1 预约成功 105 | # status == 0 已经被预约/行程冲突 106 | # status == -1 时间未到 107 | except: 108 | return -2 109 | 110 | def check_available(self): 111 | sections = self.cfg_main.sections() 112 | if 'Others' in sections and self.cfg_main.get('Others', 'HasAdvanced') == 'True': 113 | from Advanced.Available import CheckAvailable 114 | cur = CheckAvailable(self.username) 115 | a = cur.set_available() if not cur.is_available() else True 116 | return a 117 | else: 118 | return True 119 | 120 | 121 | if __name__ == '__main__': 122 | test = Book('ldh') 123 | test.prepare('211', '1', '1') 124 | test.book() 125 | -------------------------------------------------------------------------------- /Doc/For_Developers.md: -------------------------------------------------------------------------------- 1 | # 代码结构 2 | 3 | [TOC] 4 | 5 | ## 核心模块 6 | 7 | - [Book.py](../Book.py) 8 | 9 | - Book (预约相关) 10 | 11 | - *index(first, second=-1)* 12 | 13 | 定位配置文件中的账号密码 14 | 15 | - login(self) 16 | 17 | CAS登录 18 | 19 | - prepare(self, room, seat, date) 20 | 21 | 预约准备 22 | 23 | - book(self) 24 | 25 | 预约 26 | 27 | - [General.py](../General.py) 28 | 29 | - General 30 | 31 | 通用的方法。 32 | 33 | - Maintain.py 34 | 35 | - detect() 36 | 37 | 检测僵尸账号是否正常 38 | 39 | - Manage.py (不对外公开) 40 | 41 | - Manage 42 | 43 | - login(self) 44 | - query_info(self, action, student_id='', page=1) 45 | - del_renege(self, student_id) 46 | - del_book(self, sid, date_delta) 47 | - get_book_time(self, href) 48 | 49 | - Crawl 50 | 51 | 爬取数据 52 | 53 | - *crawl_user()* 54 | - *crawl_book()* 55 | 56 | - rm_space(string) 57 | 58 | ## 功能模块 59 | 60 | ------ 61 | 62 | [联系作者](mailto:code@defjia.top) -------------------------------------------------------------------------------- /Doc/Usage.md: -------------------------------------------------------------------------------- 1 | # 使用说明 2 | 3 | [TOC] 4 | 5 | ## 简介 6 | 7 | 本使用说明旨在帮助非开发者的Windows用户方便快速地使用本系统预约座位。 8 | 9 | ## 初始化 10 | 11 | ### 环境要求 12 | 13 | - [Python3](https://www.python.org/downloads/) 14 | 15 | - [pip3](https://pypi.org/project/pip/)(一般安装Python时自带) 16 | 17 | - [安装帮助 - 官方文档](https://pip-cn.readthedocs.io/en/latest/installing.html) 18 | - [安装帮助 - 菜鸟教程](http://www.runoob.com/w3cnote/python-pip-install-usage.html) 19 | 20 | - [GitHub Desktop(功能强大的图形化界面)](https://desktop.github.com/) / [Git(可选命令行或简洁的图形化界面)](https://git-scm.com/downloads) 21 | 22 | - 文本编辑器(主要用来编辑配置文件) 23 | 24 | 记事本即可,也可使用[Sublime](https://www.sublimetext.com/3)、[Notepad++](https://notepad-plus-plus.org/download/v7.5.9.html)等工具。 25 | 26 | - Python IDE(可选) 27 | 28 | - [Python IDE推荐 - 菜鸟教程](http://www.runoob.com/python/python-ide.html) 29 | 30 | - Markdown软件(可选) 31 | 32 | 主要用来在本地查看文档 33 | 34 | - [Typora](https://typora.io/#windows) 35 | 36 | ### 步骤 37 | 38 | 1. Clone项目代码到本地 39 | 40 | - 使用GitHub Desktop 41 | 42 | - 已有GitHub账号 43 | 44 | 1. 在项目主页点击右上角Fork按钮,将项目Fork到个人主页 45 | 46 | (若不做此步骤,则可直接当做没有GitHub账号操作) 47 | 48 | 2. 在GitHub Desktop上登录个人账户 49 | 3. 依次点击File - Clone repository - GitHub.com - YourUsername/Auto_Reservation_System_BE - Clone 50 | 51 | - 没有GitHub账号 52 | 53 | 1. 在GitHub Desktop中依次点击File - Clone repository - URL 54 | 55 | 2. 在Repository URL一栏中输入 56 | 57 | `https://github.com/DefJia/Auto_Reservation_System_BE.git` 58 | 59 | 3. 选择路径并点击Clone 60 | 61 | - 使用Git 62 | 63 | - 使用命令行(Git Bash) 64 | 65 | - 在文件管理器中切换到目标文件夹,在空白处点击右键,打开Git Bash 66 | - `git clone https://github.com/DefJia/Auto_Reservation_System_BE.git` 67 | 68 | - 使用图形化界面(Git GUI) 69 | 70 | - 在文件管理器的空白处点击右键,打开Git GUI 71 | 72 | - 点击Clone Existing Repository 73 | 74 | - 在Source Location中输入 75 | 76 | `https://github.com/DefJia/Auto_Reservation_System_BE.git` 77 | 78 | - 在Target Directory中选中目标文件夹 79 | 80 | - 点击Clone 81 | 82 | 2. 安装Python依赖库 83 | 84 | - 以管理员身份打开“[命令提示符](https://zh.wikihow.com/%E6%89%93%E5%BC%80Windows%E7%B3%BB%E7%BB%9F%E7%9A%84%E5%91%BD%E4%BB%A4%E6%8F%90%E7%A4%BA%E7%AC%A6)” 85 | 86 | - 切换到项目根目录下 87 | 88 | - `cd C:\xxx\path\xxx\` 89 | 90 | - 安装依赖 91 | 92 | - `pip3 install -r requirements.txt` 93 | 94 | 若提示pip3不可执行,则尝试去掉“3” 95 | 96 | 3. 复制.config.example.ini为.config.ini,并按照文件内的说明在.config.ini中修改或添加相关个性化信息。 97 | 98 | ## 使用方法 99 | 100 | 所有以**单下划线**命名的文件为可用功能,一个文件对应一个功能,需要提前在配置文件中修改对应参数,然后才可以运行,否则会报错。 101 | 102 | ### 自动预约 103 | 104 | 按照.config.example.ini的示例和注释,修改或填写.config.ini中_Auto_book和Time(不建议修改)下的参数;同时还要确保Account中至少有一个账号信息。 105 | 106 | ### 模拟预约 107 | 108 | 按照.config.example.ini的示例和注释,修改或填写.config.ini中_Book_seat下的参数;同时还要确保Account中有对应的账号信息。 109 | 110 | ### 捡漏座位 111 | 112 | ## 代码更新 113 | 114 | ### 使用GitHub Desktop 115 | 116 | ### 使用Git GUI 117 | 118 | ### 使用Git Bash 119 | 120 | 1. 在文件管理器中打开项目根目录,右键点击空白处,选中Git Bash并打开 121 | 122 | 2. 在命令框中运行 123 | 124 | `git pull` 125 | 126 | 3. 若报错,则运行 127 | 128 | `git reset --hard HEAD` 129 | 130 | 后再回到2 131 | 132 | ## 注意事项 133 | 134 | - 请勿擅自修改代码内容 135 | - 尽量每次使用前进行代码更新 136 | 137 | ------ 138 | 139 | [联系作者](mailto:code@defjia.top) -------------------------------------------------------------------------------- /General.py: -------------------------------------------------------------------------------- 1 | import os 2 | from configparser import ConfigParser 3 | from collections import OrderedDict 4 | import platform 5 | 6 | 7 | class General: 8 | @staticmethod 9 | def get_config(): 10 | config_lst = list() 11 | env = platform.system() 12 | split_sign = '\\' if env == 'Windows' else '/' 13 | cwd_raw = os.getcwd() 14 | cwd_lst = cwd_raw.split(split_sign) 15 | cwd_root = cwd_raw[:-(len(cwd_lst[-1])+1)] if cwd_lst[-1] == 'Advanced' else cwd_raw 16 | cfg = ConfigParser() 17 | cfg.read(cwd_root + split_sign + '.config.ini', encoding='utf-8') 18 | config_lst.append(cfg) # 根目录下config 19 | hasAdvanced = cfg.getboolean('Others', 'HasAdvanced') 20 | if hasAdvanced: 21 | cfg.read(cwd_root + split_sign + 'Advanced' + split_sign + '.config.ini', encoding='utf-8') 22 | config_lst.append(cfg) # Advanced文件夹下config 23 | return config_lst 24 | 25 | @staticmethod 26 | def index(first, second=-1): 27 | """ 28 | :param first: 29 | :param second: 30 | :return: 31 | 三种可能性: 32 | 1. 单个参数,人名缩写 33 | 2. 单个参数,僵尸账号序号 34 | 3. 两个参数,账号和密码(临时) 35 | 返回两个参数————用户名和密码 36 | """ 37 | cfg = General.get_config() 38 | cfg_main = cfg[0] 39 | if second == -1: 40 | if type(first) == str and not first.isdigit(): 41 | # 第一种情况 42 | hash = cfg_main.get('Account', 'common_hash').split(',').index(first) 43 | username = cfg_main.get('Account', 'commonu').split(',')[hash] 44 | password = cfg_main.get('Account', 'commonp').split(',')[hash] 45 | return username, password 46 | elif type(first) == str and len(first) == 10: 47 | return first, None 48 | else: 49 | zombieu = cfg_main.get('Account', 'zombieu').split(',') 50 | zombiep = cfg_main.get('Account', 'zombiep').split(',') 51 | first = int(first) 52 | if first < 0: 53 | first = 0 54 | elif first >= min(len(zombieu), len(zombiep)): 55 | first = min(len(zombieu), len(zombiep)) - 1 56 | username = zombieu[first] 57 | password = zombiep[first] 58 | return username, password 59 | else: 60 | return first, second 61 | 62 | 63 | if __name__ == '__main__': 64 | a = General() 65 | a.get_config() 66 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /Notify.py: -------------------------------------------------------------------------------- 1 | from General import General 2 | from configparser import ConfigParser 3 | from tkinter import Tk 4 | from tkinter.messagebox import showinfo 5 | import threading 6 | import pyaudio 7 | import wave 8 | import smtplib 9 | from email.mime.text import MIMEText 10 | from email.header import Header 11 | 12 | cfg_main = General.get_config()[0] 13 | 14 | 15 | class Output: 16 | @staticmethod 17 | def continuous_output(text): 18 | print(text) 19 | 20 | @staticmethod 21 | def final_output(text, type): 22 | type = cfg_main.get('Notify', 'Type') if type == '' else str(type) 23 | if '1' in type: 24 | mail = Mail(text, text) 25 | mail.send() 26 | elif '4' in type: 27 | music = Music() 28 | music.play_audio_async() 29 | elif '5' in type: 30 | message_box = Massage_box(text) 31 | message_box.show_messagebox_async() 32 | 33 | 34 | class Music: 35 | def __init__(self): 36 | self.filepath = cfg_main.get('Notify', 'Music_path') 37 | 38 | def play_audio(self): 39 | CHUNK = 1024 40 | wf = wave.open(self.filepath, 'rb') 41 | p = pyaudio.PyAudio() 42 | stream = p.open(format=p.get_format_from_width(wf.getsampwidth()), 43 | channels=wf.getnchannels(), 44 | rate=wf.getframerate(), 45 | output=True) 46 | data = wf.readframes(CHUNK) 47 | while data != b'': 48 | stream.write(data) 49 | data = wf.readframes(CHUNK) 50 | stream.stop_stream() 51 | stream.close() 52 | p.terminate() 53 | 54 | def play_audio_async(self): 55 | do_therad = threading.Thread(target=self.play_audio) 56 | do_therad.start() 57 | 58 | 59 | class Massage_box: 60 | def __init__(self, text=''): 61 | self.text = text 62 | 63 | def show_messagebox(self): 64 | root = Tk() 65 | root.withdraw() 66 | showinfo(message = self.text) 67 | root.destroy() 68 | 69 | def show_messagebox_async(self): 70 | do_therad = threading.Thread(target=self.show_messagebox) 71 | do_therad.start() 72 | 73 | 74 | class Mail: 75 | def __init__(self, subject, context): 76 | self.from_ = cfg_main.get('Notify', 'Sender') 77 | self.pwd = cfg_main.get('Notify', 'Password') 78 | self.to = cfg_main.get('Notify', 'Reciever').split(',') 79 | self.subject = subject 80 | self.context = context 81 | 82 | def send(self): 83 | message = MIMEText(self.context, 'plain', 'utf-8') 84 | message['From'] = self.from_ 85 | message['To'] = self.to[0] 86 | message['Subject'] = self.subject 87 | try: 88 | smtpObj = smtplib.SMTP() 89 | smtp_server = 'smtp.' + self.from_.split('@')[1] 90 | smtpObj.connect(smtp_server) 91 | smtpObj.login(self.from_, self.pwd) 92 | smtpObj.sendmail(self.from_, self.to, message.as_string()) 93 | smtpObj.quit() 94 | print('邮件发送成功!') 95 | except smtplib.SMTPException as e: 96 | print('邮件发送失败!',e) 97 | 98 | 99 | if __name__ == '__main__': 100 | a = Music() 101 | a.play_audio_async() 102 | -------------------------------------------------------------------------------- /Query.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import json 3 | import requests 4 | from configparser import ConfigParser 5 | 6 | 7 | class Query: 8 | """ 9 | To query available seats on specified date and room. 10 | """ 11 | 12 | def __init__(self, date, room_id): 13 | """ 14 | :param date: 0 -> today, 1 -> tomorrow 15 | :param room_id: 211, 308, 311, 208 16 | :return code, list -> when code > 0, which means code = len(list), query results. 17 | """ 18 | self.cfg = ConfigParser() 19 | self.cfg.read('.config.ini', encoding='utf8') 20 | self.room_id = room_id 21 | self.area_nos = self.cfg.get('Index', 'area_no').split(',') 22 | self.room_ids = self.cfg.get('Index', 'room_id').split(',') 23 | index = self.room_ids.index(str(room_id)) 24 | self.area_no = self.area_nos[index] 25 | self.date = date 26 | self.res = list() 27 | 28 | def get_list(self): 29 | url = 'http://seat.lib.bit.edu.cn/api.php/spaces_old' 30 | params = {'area': self.area_no, 'endTime': '22:40'} 31 | time_ = datetime.datetime.now() + datetime.timedelta(days=self.date) 32 | params['day'] = time_.strftime('%Y-%m-%d') 33 | params['startTime'] = '8:00' if self.date > 0 or datetime.datetime.now().hour < 8 else time_.strftime('%H:%M') 34 | # The next day or 8:00am before the current day -> 8:00 35 | try: 36 | r = requests.get(url, params=params) 37 | except: 38 | return None 39 | d = None 40 | try: 41 | d = json.loads(r.text[1:], encoding='utf8') 42 | except: 43 | d = json.loads(r.text, encoding='utf8') 44 | finally: 45 | if type(d) == dict: 46 | data = d['data']['list'] 47 | for elem in data: 48 | if elem['status'] == 1: 49 | # 可预约 50 | if len(elem['name']) == 5: 51 | # 形如311-A 52 | room_id = elem['name'][:3] 53 | seat_no = ord(elem['name'][-1]) - 64 54 | else: 55 | room_id = self.room_id 56 | seat_no = int(elem['name']) 57 | self.res.append((room_id, seat_no)) 58 | return self.res 59 | else: 60 | return None 61 | 62 | 63 | if __name__ == '__main__': 64 | cur = Query(0, 'ns1') 65 | print(cur.get_list()) 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 图书馆座位预约系统辅助工具 2 | 3 | [TOC] 4 | 5 | > Notice:由于认证系统升级,该项目内部分方法已不再适用,在进行适配之前可能无法正常运行项目代码。欢迎开发者提交PR共同维护该项目👏🏻。 6 | 7 | ## 前言 8 | 9 | ​ 目前,包括[BIT](http://seat.lib.bit.edu.cn)、[CSU](http://libzw.csu.edu.cn)、HLJU在内的诸多高校的图书馆都在使用盛卡科技公司开发的图书馆座位预约系统,这个仓库旨在辅助各位同学更方便地使用预约系统预约座位,**不可用于商业盈利或非法用途**。 10 | 11 | ​ 目前配置文件只支持BIT图书馆部分席位预约,欢迎大家共同维护或提Issues,交流反馈QQ群:[622744769](https://jq.qq.com/?_wv=1027&k=5y4hHLO)。 12 | 13 | ## 文档 14 | 15 | - [使用说明](Doc/Usage.md) 16 | - [开发者文档](Doc/For_Developers.md) 17 | 18 | ## 成员 19 | 20 | ### 已加入高校 21 | 22 | - [x] BIT 23 | - [ ] CSU 24 | 25 | ​ 高校名称以简称代替,基本功能完成后可在框内打钩。 26 | 27 | ​ 不同高校除配置文件中的URL和相关索引信息,以及登录方法不同外,别无二致,所以只需提供相关代码即可,欢迎大家Pull Requests。 28 | 29 | ## 项目进度 30 | 31 | ### 功能模块 32 | 33 | - [x] 定时预约 34 | - [x] 模拟预约 35 | - [ ] 座位捡漏 36 | 37 | ### 重要且紧急 38 | 39 | - [ ] 优化_Auto_book的输出信息 40 | - [ ] _Auto_book添加多线程 41 | - [ ] _Auto_book添加多账号 42 | - [ ] 完善各种输出信息 43 | - [x] 添加自动定时预约功能 44 | 45 | ### 重要不紧急 46 | 47 | - [x] 修改文件结构。 48 | - [x] index方法修改为Book类的静态方法,可以通过拼音码(常用用户)、序号(僵尸账号)指定学号密码。 49 | - [ ] 添加邮件/微信提醒功能 50 | - [ ] 在config.ini中标注僵尸账号的可用性,以及修改相关方法。 51 | - [ ] 错误输入反馈,定义Error类 52 | - [ ] 整理各种返回值 53 | - [ ] 整理BIT座位序号索引 54 | ### 不重要不紧急 55 | 56 | - [ ] 增加入馆时自动预约功能。 57 | - [ ] 增加关注座位入馆提醒功能 58 | - [ ] 添加自动维护未签到座位功能 59 | 60 | ------ 61 | 62 | [联系作者](mailto:code@defjia.top) -------------------------------------------------------------------------------- /Time.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import time 3 | import requests 4 | from configparser import ConfigParser 5 | import ast 6 | 7 | 8 | class Time: 9 | def __init__(self): 10 | self.cfg = ConfigParser() 11 | self.cfg.read('.config.ini', encoding='utf8') 12 | 13 | def wait_until(self, type): 14 | """ 15 | It needs to be clarified that the time is that on remote server. 16 | :return: 0 -> time is up 17 | """ 18 | pass 19 | 20 | def time_control(self, type): 21 | """ 22 | :param type: 0 -> pre_book, 1 -> pick 23 | :return: 24 | """ 25 | tmp = 'book' if type == 0 else 'pick' 26 | target_time = self.cfg.get('Time', tmp + '_time').split(':') 27 | hour = int(target_time[0]) 28 | minute = int(target_time[1]) 29 | prepare_seconds = self.cfg.getint('Time', 'advanced_second_to_prepare') 30 | interval_seconds = self.cfg.getint('Time', 'interval_second_to_calibrate') 31 | start_seconds = self.cfg.getint('Time', 'advanced_second_to_book') 32 | while True: 33 | if self.cal_seconds(0, (hour, minute), prepare_seconds): 34 | # 本地时间符合之后,开始验证服务器时间 35 | while not self.cal_seconds(1, (hour, minute), start_seconds): 36 | time.sleep(interval_seconds) 37 | return 0 38 | else: 39 | # 本地时间不符合,则继续等待 40 | time.sleep(interval_seconds) 41 | 42 | def cal_seconds(self, time_type, target_time, target_delta_seconds): 43 | """ 44 | :param time_type: 0 -> local time, 1 -> server time 45 | :param target_time: target time tuple -> (hour, minute) 46 | :param target_delta_seconds: target delta seconds 47 | :return: true or false 48 | """ 49 | target_seconds = (target_time[0] * 60 + target_time[1]) * 60 50 | current_time = datetime.datetime.now() if time_type == 0 else self.get_server_time() 51 | current_hour = current_time.hour 52 | current_minute = current_time.minute 53 | current_second = current_time.second 54 | current_seconds = current_hour * 3600 + current_minute * 60 + current_second 55 | current_delta_second = target_seconds - current_seconds 56 | # print('模式%d, 时间差%d' % (time_type, current_delta_second)) 57 | return True if 0 <= current_delta_second <= target_delta_seconds else False 58 | 59 | @staticmethod 60 | def get_server_time(): 61 | host = 'http://seat.lib.bit.edu.cn' 62 | r = requests.get(host) 63 | dic = ast.literal_eval(str(r.headers)) 64 | t = datetime.datetime.strptime(dic['Date'], "%a, %d %b %Y %H:%M:%S GMT") + datetime.timedelta(hours=8) 65 | return t 66 | 67 | 68 | if __name__ == '__main__': 69 | res = Time() 70 | # r = res.time_control(0) 71 | r = res.get_server_time() 72 | print(r) 73 | -------------------------------------------------------------------------------- /_Auto_book.py: -------------------------------------------------------------------------------- 1 | from Time import Time 2 | from Book import Book 3 | import datetime 4 | from General import General 5 | from Notify import Output 6 | 7 | 8 | if __name__ == '__main__': 9 | method_name = '_Auto_book' 10 | cfg = General.get_config()[0] 11 | notify_type = cfg.get(method_name, 'notify_type') 12 | type = cfg.getint(method_name, 'type') 13 | target_name = cfg.get(method_name, 'target_name') 14 | target_room = cfg.get(method_name, 'target_room') 15 | target_seat = cfg.getint(method_name, 'target_seat') 16 | max_try_times = cfg.getint(method_name, 'max_try_times') 17 | 18 | output = Output() 19 | t = Time() 20 | 21 | while True: 22 | date = 0 if type in (1, 2) else 1 23 | if type in (0, 1): t.time_control(type) # 时间控制 24 | b = Book(target_name).prepare(target_room, target_seat, date) 25 | flag = -1 # 是否预约成功 26 | cnt = 0 # 发送请求次数 27 | while flag != 1 and cnt < max_try_times: 28 | flag = b.book() 29 | cnt += 1 30 | # current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S") 31 | # output.continuous_output(current_time) 32 | text = format('预约成功') 33 | output.final_output(text, notify_type) 34 | -------------------------------------------------------------------------------- /_Book_seat.py: -------------------------------------------------------------------------------- 1 | from Book import Book 2 | from General import General 3 | from Notify import Output 4 | 5 | 6 | if __name__ == '__main__': 7 | method_name = '_Book_seat' 8 | cfg = General.get_config()[0] 9 | notify_type = cfg.get(method_name, 'notify_type') 10 | target_users = cfg.get(method_name, 'target_names').split(',') 11 | target_seats = cfg.get(method_name, 'target_seats').split(',') 12 | target_rooms = cfg.get(method_name, 'target_rooms').split(',') 13 | date = cfg.getint(method_name, 'date') 14 | 15 | output = Output() 16 | 17 | for i in range(min(len(target_rooms), len(target_seats), len(target_users))): 18 | book = Book(target_users[i]) 19 | book = book.prepare(target_rooms[i], target_seats[i], date) 20 | res = book.book() 21 | # while res != 1: 22 | # res = book.book() 23 | -------------------------------------------------------------------------------- /_Detect_network.py: -------------------------------------------------------------------------------- 1 | import requests 2 | from Book import Book 3 | from General import General 4 | from Notify import Output 5 | import time, datetime, requests 6 | 7 | class Test: 8 | def __init__(self): 9 | configs = General.get_config() 10 | self.cfg_main = configs[0] 11 | self.init_url = self.cfg_main.get('Site_url', 'init_url') 12 | 13 | def if_response(self): 14 | try: 15 | r = requests.get(self.init_url, timeout=5) 16 | except: 17 | print(datetime.datetime.now()) 18 | time.sleep(5) 19 | return self.if_response() 20 | else: 21 | print('网络修复!') 22 | output = Output() 23 | output.final_output('网络修复', 45) 24 | return 0 25 | 26 | def if_availiable(self): 27 | pass 28 | 29 | if __name__ == "__main__": 30 | a = Test() 31 | a.if_response() 32 | 33 | ''' 34 | while True: 35 | try: 36 | r = requests.get(url) 37 | a = r.text 38 | except: 39 | a = 315 40 | if len(a) != 315: 41 | 42 | method_name = '_Book_seat' 43 | cfg = General.get_config()[0] 44 | notify_type = cfg.get(method_name, 'notify_type') 45 | target_users = cfg.get(method_name, 'target_names').split(',') 46 | target_seats = cfg.get(method_name, 'target_seats').split(',') 47 | target_rooms = cfg.get(method_name, 'target_rooms').split(',') 48 | date = cfg.getint(method_name, 'date') 49 | 50 | 51 | output = Output() 52 | text = format('网络修复!') 53 | output.final_output(text, notify_type) 54 | 55 | for i in range(min(len(target_rooms), len(target_seats), len(target_users))): 56 | book = Book(target_users[i]) 57 | book = book.prepare(target_rooms[i], target_seats[i], date) 58 | book.book() 59 | text = format('预约成功') 60 | output.final_output(text, notify_type) 61 | break 62 | print(datetime.datetime.now()) 63 | time.sleep(5) 64 | ''' -------------------------------------------------------------------------------- /_Pick_seat.py: -------------------------------------------------------------------------------- 1 | from Book import Book 2 | from Query import Query 3 | import time 4 | from datetime import datetime 5 | from General import General 6 | from Notify import Output 7 | 8 | 9 | if __name__ == '__main__': 10 | method_name = '_Pick_seat' 11 | cfg = General.get_config()[0] 12 | notify_type = cfg.get(method_name, 'notify_type') 13 | output = Output() 14 | 15 | search = {'X':0, 'A': 1, 'B': 2, 'C': 3, 'D': 4, 'E': 5, 'F': 6, 'G': 7, 'H': 8, 'I': 9} 16 | all = range(1,10) 17 | target_users = cfg.get(method_name, 'target_users').split(',') 18 | target_rooms = cfg.get(method_name, 'target_rooms').split(',') 19 | ori_seat = [(x.split('-')[0],search[x.split('-')[1]]) for x in target_rooms] 20 | target_seat = ori_seat.copy() 21 | for elem in ori_seat: 22 | if elem[1] == 0: 23 | for i in all: 24 | target_seat.append((elem[0],i)) 25 | target_seat = list(set([x for x in target_seat if x[1]!= 0])) 26 | 27 | date = cfg.getint(method_name, 'date') 28 | sleep_second = cfg.getint(method_name, 'interval') 29 | 30 | cur = 0 31 | while len(target_users) and len(target_seat): 32 | try: 33 | # 获取可用座位信息 34 | query = [] 35 | target_floor = set(x[0] for x in target_seat) 36 | for floor in target_floor: 37 | q = Query(date, floor) 38 | query += q.get_list() 39 | query = set(query) 40 | cur += 1 41 | 42 | # 若有可用座位 43 | if len(query) != 0: 44 | # 判断是否为所指定座位 45 | del_sid = [] 46 | for sid in range(len(target_seat)): 47 | if target_seat[sid] in query: 48 | seat = target_seat[sid] 49 | room_id = seat[0] 50 | seat_id = seat[1] 51 | text = format('%s-%s次车有余票,正在尝试下单,下单用户%s' % (room_id, seat_id, target_users[0]) ) 52 | output.continuous_output(text) 53 | 54 | account = Book(target_users[0]) 55 | account.prepare(room_id, seat_id, date) 56 | account.book() 57 | 58 | text = format('%s-%s次车,下单成功,下单用户%s' % (room_id, seat_id, target_users[0])) 59 | output.final_output(text, notify_type) 60 | 61 | 62 | output.continuous_output(text) 63 | 64 | # 删除该用户&预约座位&查询 65 | del target_users[0] 66 | del_sid.append(sid) 67 | query.remove(seat) 68 | if not target_users: 69 | cur = -1 70 | text = format('提醒:已无可用用户,即将退出!') 71 | output.continuous_output(text) 72 | target_seat.clear() 73 | break 74 | # 更新剩余指定车次列表 75 | target_seat = [target_seat[x] for x in range(len(target_seat)) if x not in del_sid ] 76 | if len(target_seat) != 0: 77 | text = format('第%d次查询,仍有车次未预约成功!时间%s...' % (cur, str(datetime.now())[:-7])) 78 | if len(query) !=0: 79 | text += format('\t*提醒:在指定范围之外有空余车次可用*') 80 | output.continuous_output(text) 81 | elif len(target_seat) == 0: 82 | text = format('恭喜您所有指定车次均已预约成功!') 83 | output.continuous_output(text) 84 | else: 85 | text = format('第%d次查询,所有列车均无余票,时间%s...' % (cur, str(datetime.now())[:-7])) 86 | output.continuous_output(text) 87 | time.sleep(sleep_second) 88 | except Exception as e: 89 | output.continuous_output(repr(e)) 90 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/DefJia/Auto_Reservation_System_BE/2480339dee342adf639abdfb7fbff17dccc417cf/__init__.py -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | bs4 2 | requests 3 | configparser 4 | datetime 5 | pyaudio 6 | wave 7 | --------------------------------------------------------------------------------