增加邮件通知

This commit is contained in:
Jalin
2019-01-11 21:50:18 +08:00
parent dba893e705
commit 845f303293
10 changed files with 114 additions and 23 deletions

View File

@@ -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

View File

@@ -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 = [
{

View File

@@ -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 = [
{

View File

@@ -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):

View File

@@ -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

View File

@@ -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)

View File

@@ -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,
}

View File

@@ -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__()

View File

@@ -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

View File

@@ -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(