diff --git a/README.md b/README.md index 144e699..8f37984 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ - [x] 分布式运行 - [x] Docker 支持 - [x] 动态修改配置文件 -- [ ] 邮件通知 +- [x] 邮件通知 - [ ] Web 管理页面 ## 使用 @@ -49,7 +49,7 @@ cp env.py.example env.py python main.py -t ``` -测试语音通知 -t -n +测试通知消息 (语音, 邮件) -t -n ```bash # 默认不会进行通知测试,要对通知进行测试需要加上 -n 参数 python main.py -t -n diff --git a/env.docker.py.example b/env.docker.py.example index e714248..48ebf9b 100644 --- a/env.docker.py.example +++ b/env.docker.py.example @@ -37,7 +37,7 @@ AUTO_CODE_ACCOUNT = { # 没找到比较好用的,现在用的这个是阿里云 API 市场上的,基本满足要求,价格也便宜 # 购买成功后到控制台找到 APPCODE 放在下面就可以了 # 地址:https://market.aliyun.com/products/57126001/cmapi019902.html -NOTIFICATION_BY_VOICE_CODE = 1 # 开启语音验证码 +NOTIFICATION_BY_VOICE_CODE = 1 # 开启语音通知 NOTIFICATION_API_APP_CODE = 'your app code' NOTIFICATION_VOICE_CODE_PHONE = 'your phone' # 接受通知的手机号 @@ -57,6 +57,14 @@ REDIS_HOST = 'localhost' # Redis host REDIS_PORT = '6379' # Redis post REDIS_PASSWORD = '' # # Redis 密码 没有可以留空 +# 邮箱配置 +EMAIL_ENABLED = 0 # 是否开启邮件通知 +EMAIL_SENDER = 'sender@example.com' # 邮件发送者 +EMAIL_RECEIVER = 'receiver@example.com' # 邮件接受者 # 可以多个 [email1@gmail.com, email2@gmail.com] +EMAIL_SERVER_HOST = 'localhost' # 邮件服务 host +EMAIL_SERVER_USER = '' +EMAIL_SERVER_PASSWORD = '' + # 查询任务 QUERY_JOBS = [ { diff --git a/env.py.example b/env.py.example index 918031e..14ba5f3 100644 --- a/env.py.example +++ b/env.py.example @@ -37,7 +37,7 @@ AUTO_CODE_ACCOUNT = { # 没找到比较好用的,现在用的这个是阿里云 API 市场上的,基本满足要求,价格也便宜 # 购买成功后到控制台找到 APPCODE 放在下面就可以了 # 地址:https://market.aliyun.com/products/57126001/cmapi019902.html -NOTIFICATION_BY_VOICE_CODE = 1 # 开启语音验证码 +NOTIFICATION_BY_VOICE_CODE = 1 # 开启语音通知 NOTIFICATION_API_APP_CODE = 'your app code' NOTIFICATION_VOICE_CODE_PHONE = 'your phone' # 接受通知的手机号 @@ -54,6 +54,15 @@ REDIS_HOST = 'localhost' # Redis host REDIS_PORT = '6379' # Redis post REDIS_PASSWORD = '' # # Redis 密码 没有可以留空 + +# 邮箱配置 +EMAIL_ENABLED = 0 # 是否开启邮件通知 +EMAIL_SENDER = 'sender@example.com' # 邮件发送者 +EMAIL_RECEIVER = 'receiver@example.com' # 邮件接受者 # 可以多个 [email1@gmail.com, email2@gmail.com] +EMAIL_SERVER_HOST = 'localhost' # 邮件服务 host +EMAIL_SERVER_USER = '' +EMAIL_SERVER_PASSWORD = '' + # 查询任务 QUERY_JOBS = [ { diff --git a/py12306/app.py b/py12306/app.py index 89986f1..00fc906 100644 --- a/py12306/app.py +++ b/py12306/app.py @@ -93,6 +93,9 @@ class App: Notification.voice_code(Config().NOTIFICATION_VOICE_CODE_PHONE, '张三', OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_OF_VOICE_CODE_CONTENT.format('北京', '深圳')) + if Config().EMAIL_ENABLED: # 语音通知 + CommonLog.add_quick_log(CommonLog.MESSAGE_TEST_SEND_EMAIL).flush() + Notification.send_email(Config().EMAIL_RECEIVER, '测试发送邮件', 'By py12306') @classmethod def run_check(cls): diff --git a/py12306/config.py b/py12306/config.py index 8789592..098b25c 100644 --- a/py12306/config.py +++ b/py12306/config.py @@ -53,6 +53,14 @@ class Config: REDIS_PORT = '6379' REDIS_PASSWORD = '' + # 邮箱配置 + EMAIL_ENABLED = 0 + EMAIL_SENDER = '' + EMAIL_RECEIVER = '' + EMAIL_SERVER_HOST = '' + EMAIL_SERVER_USER = '' + EMAIL_SERVER_PASSWORD = '' + envs = [] retry_time = 5 last_modify_time = 0 diff --git a/py12306/helpers/notification.py b/py12306/helpers/notification.py index 1013a84..06c2f62 100644 --- a/py12306/helpers/notification.py +++ b/py12306/helpers/notification.py @@ -20,6 +20,11 @@ class Notification(): self = cls() self.send_voice_code_of_yiyuan(phone, name=name, content=content) + @classmethod + def send_email(cls, to, title='', content=''): + self = cls() + self.send_email_by_smtp(to, title, content) + def send_voice_code_of_yiyuan(self, phone, name='', content=''): """ 发送语音验证码 @@ -54,6 +59,27 @@ class Notification(): else: return CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_SEND_FAIL.format(response_message)).flush() + def send_email_by_smtp(self, to, title, content): + import smtplib + from email.message import EmailMessage + to = to if isinstance(to, list) else [to] + message = EmailMessage() + message['Subject'] = title + message['From'] = 'service@pjialin.com' + message['To'] = to + message.set_content(content) + try: + server = smtplib.SMTP(Config().EMAIL_SERVER_HOST) + server.login(Config().EMAIL_SERVER_USER, Config().EMAIL_SERVER_PASSWORD) + server.send_message(message) + server.quit() + CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_EMAIL_SUCCESS).flush() + except Exception as e: + CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_EMAIL_FAIL.format(e)).flush() + if __name__ == '__main__': - Notification.voice_code('13065667742', '张三', '你的车票 广州 到 深圳 购买成功,请登录 12306 进行支付') + name = '张三3' + content = '你的车票 广州 到 深圳 购买成功,请登录 12306 进行支付' + # Notification.voice_code('13800138000', name, content) + Notification.send_email('admin@pjialin.com', name, content) diff --git a/py12306/helpers/type.py b/py12306/helpers/type.py index c480939..7e4dc17 100644 --- a/py12306/helpers/type.py +++ b/py12306/helpers/type.py @@ -33,6 +33,7 @@ class OrderSeatType: @singleton class SeatType: + NO_SEAT = 26 dicts = { '特等座': 25, '商务座': 32, @@ -42,7 +43,5 @@ class SeatType: '硬卧': 28, '动卧': 33, '硬座': 29, - '无座': 26, + '无座': NO_SEAT, } - - diff --git a/py12306/log/common_log.py b/py12306/log/common_log.py index 2d5e5a8..3957b60 100644 --- a/py12306/log/common_log.py +++ b/py12306/log/common_log.py @@ -22,8 +22,13 @@ class CommonLog(BaseLog): MESSAGE_CHECK_EMPTY_USER_ACCOUNT = '请配置 12306 账号密码' MESSAGE_TEST_SEND_VOICE_CODE = '正在测试发送语音验证码...' + MESSAGE_TEST_SEND_EMAIL = '正在测试发送邮件...' MESSAGE_CONFIG_FILE_DID_CHANGED = '配置文件已修改,正在重新加载中\n' + MESSAGE_API_RESPONSE_CAN_NOT_BE_HANDLE = '接口返回错误' + + MESSAGE_SEND_EMAIL_SUCCESS = '邮件发送成功,请检查收件箱' + MESSAGE_SEND_EMAIL_FAIL = '邮件发送失败,请手动检查配置,错误原因 {}' def __init__(self): super().__init__() diff --git a/py12306/log/order_log.py b/py12306/log/order_log.py index 77f0f33..fc23bed 100644 --- a/py12306/log/order_log.py +++ b/py12306/log/order_log.py @@ -18,13 +18,14 @@ class OrderLog(BaseLog): MESSAGE_CHECK_ORDER_INFO_SUCCESS = '检查订单成功' MESSAGE_GET_QUEUE_COUNT_SUCCESS = '排队成功,你当前排在第 {} 位, 余票还剩余 {} 张' + MESSAGE_GET_QUEUE_LESS_TICKET = '排队失败,目前排队人数已经超过余票张数' MESSAGE_GET_QUEUE_COUNT_FAIL = '排队失败,错误原因 {}' MESSAGE_CONFIRM_SINGLE_FOR_QUEUE_SUCCESS = '# 提交订单成功!#' - MESSAGE_CONFIRM_SINGLE_FOR_QUEUE_ERROR = '提交订单出错,错误原因 {}' + MESSAGE_CONFIRM_SINGLE_FOR_QUEUE_ERROR = '出票失败,错误原因 {}' MESSAGE_CONFIRM_SINGLE_FOR_QUEUE_FAIL = '提交订单失败,错误原因 {}' - MESSAGE_QUERY_ORDER_WAIT_TIME_WAITING = '排队等待中,预计还需要 {} 秒' + MESSAGE_QUERY_ORDER_WAIT_TIME_WAITING = '排队等待中,排队人数 {},预计还需要 {} 秒' MESSAGE_QUERY_ORDER_WAIT_TIME_FAIL = '排队失败,错误原因 {}' MESSAGE_QUERY_ORDER_WAIT_TIME_INFO = '第 {} 次排队,请耐心等待' @@ -34,6 +35,9 @@ class OrderLog(BaseLog): MESSAGE_ORDER_SUCCESS_NOTIFICATION_OF_VOICE_CODE_START_SEND = '正在发送语音通知, 第 {} 次' MESSAGE_ORDER_SUCCESS_NOTIFICATION_OF_VOICE_CODE_CONTENT = '你的车票 {} 到 {} 购买成功,请登录 12306 进行支付' + MESSAGE_ORDER_SUCCESS_NOTIFICATION_OF_EMAIL_CONTENT = '订单号 {},请及时器登录12306,打开 \'未完成订单\',在30分钟内完成支付!' + + MESSAGE_JOB_CLOSED = '当前任务已结束' @classmethod diff --git a/py12306/order/order.py b/py12306/order/order.py index 3bd06b1..e1e405e 100644 --- a/py12306/order/order.py +++ b/py12306/order/order.py @@ -5,7 +5,8 @@ from py12306.config import Config from py12306.helpers.api import * from py12306.helpers.func import * from py12306.helpers.notification import Notification -from py12306.helpers.type import UserType +from py12306.helpers.type import UserType, SeatType +from py12306.log.common_log import CommonLog from py12306.log.order_log import OrderLog @@ -51,6 +52,8 @@ class Order: """ # Debug if Config().IS_DEBUG: + self.order_id = 'test' + self.order_did_success() return random.randint(0, 10) > 7 return self.normal_order() @@ -76,6 +79,9 @@ class Order: def send_notification(self): num = 0 # 通知次数 sustain_time = self.notification_sustain_time + if Config().EMAIL_ENABLED: # 邮件通知 + Notification.send_email(Config().EMAIL_RECEIVER, OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_TITLE, + OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_OF_EMAIL_CONTENT.format(self.order_id)) while sustain_time: # TODO 后面直接查询有没有待支付的订单就可以 num += 1 if Config().NOTIFICATION_BY_VOICE_CODE: # 语音通知 @@ -83,10 +89,12 @@ class Order: Notification.voice_code(Config().NOTIFICATION_VOICE_CODE_PHONE, self.user_ins.get_name(), OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_OF_VOICE_CODE_CONTENT.format( self.query_ins.left_station, self.query_ins.arrive_station)) + else: + break sustain_time -= self.notification_interval sleep(self.notification_interval) - OrderLog.add_quick_log(OrderLog.MESSAGE_JOB_CLOSED) + OrderLog.add_quick_log(OrderLog.MESSAGE_JOB_CLOSED).flush() def submit_order_request(self): data = { @@ -136,15 +144,23 @@ class Order: response = self.session.post(API_CHECK_ORDER_INFO, data) result = response.json() if result.get('data.submitStatus'): # 成功 + # ifShowPassCode 需要验证码 OrderLog.add_quick_log(OrderLog.MESSAGE_CHECK_ORDER_INFO_SUCCESS).flush() if result.get('data.ifShowPassCode') != 'N': self.is_need_auth_code = True + + # if ( ticketInfoForPassengerForm.isAsync == ticket_submit_order.request_flag.isAsync & & ticketInfoForPassengerForm.queryLeftTicketRequestDTO.ypInfoDetail != "") { 不需要排队检测 js TODO return True else: - result_data = result.get('data', {}) - OrderLog.add_quick_log(OrderLog.MESSAGE_CHECK_ORDER_INFO_FAIL.format( - result_data.get('errMsg', result.get('messages', '-')) - )).flush() + error = CommonLog.MESSAGE_API_RESPONSE_CAN_NOT_BE_HANDLE + if not result.get('data.isNoActive'): + error = result.get('data.errMsg') + else: + if result.get('data.checkSeatNum'): + error = '无法提交您的订单! ' + result.get('data.errMsg') + else: + error = '出票失败! ' + result.get('data.errMsg') + OrderLog.add_quick_log(OrderLog.MESSAGE_CHECK_ORDER_INFO_FAIL.format(error)).flush() return False def get_queue_count(self): @@ -182,7 +198,7 @@ class Order: } response = self.session.post(API_GET_QUEUE_COUNT, data) result = response.json() - if result.get('data.countT') or result.get('data.ticket'): # 成功 + if result.get('status', False): # 成功 """ "data": { "count": "66", @@ -191,10 +207,22 @@ class Order: "countT": "0", "op_1": "true" } + """ - ticket = result.get('data.ticket').split(',') # 暂不清楚具体作用 - ticket_number = sum(map(int, ticket)) - current_position = int(data.get('countT', 0)) + # if result.get('isRelogin') == 'Y': # 重新登录 TODO + + ticket = result.get('data.ticket').split(',') # 余票列表 + # 这里可以判断 是真实是 硬座还是无座,避免自动分配到无座 + ticket_number = ticket[0] # 余票 + if ticket_number != '充足' or int(ticket_number) <= 0: + if self.query_ins.current_seat == SeatType.NO_SEAT: # 允许无座 + ticket_number = ticket[1] + + if result.get('data.op_2') == 'true': + OrderLog.add_quick_log(OrderLog.MESSAGE_GET_QUEUE_LESS_TICKET).flush() + return False + + current_position = int(result.get('data.countT', 0)) OrderLog.add_quick_log( OrderLog.MESSAGE_GET_QUEUE_COUNT_SUCCESS.format(current_position, ticket_number)).flush() return True @@ -275,7 +303,7 @@ class Order: """ self.current_queue_wait = self.max_queue_wait while self.current_queue_wait: - self.current_queue_wait -= 1 + self.current_queue_wait -= self.wait_queue_interval # TODO 取消超时订单,待优化 data = { # 'random': str(random.random())[2:], @@ -303,9 +331,10 @@ class Order: order_id = result_data.get('orderId') if order_id: # 成功 return order_id - elif result_data.get('waitTime') and result_data.get('waitTime') >= 0: + elif result_data.get('waitTime') != -100: OrderLog.add_quick_log( - OrderLog.MESSAGE_QUERY_ORDER_WAIT_TIME_WAITING.format(result_data.get('waitTime'))).flush() + OrderLog.MESSAGE_QUERY_ORDER_WAIT_TIME_WAITING.format(result_data.get('waitCount', 0), + result_data.get('waitTime'))).flush() elif result_data.get('msg'): # 失败 对不起,由于您取消次数过多,今日将不能继续受理您的订票请求。1月8日您可继续使用订票功能。 # TODO 需要增加判断 直接结束 OrderLog.add_quick_log(