445 lines
19 KiB
Python
445 lines
19 KiB
Python
import urllib
|
||
|
||
# from py12306.config import UserType
|
||
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, SeatType
|
||
from py12306.log.common_log import CommonLog
|
||
from py12306.log.order_log import OrderLog
|
||
|
||
|
||
class Order:
|
||
"""
|
||
处理下单
|
||
"""
|
||
session = None
|
||
query_ins = None
|
||
user_ins = None
|
||
|
||
passenger_ticket_str = ''
|
||
old_passenger_str = ''
|
||
|
||
is_need_auth_code = False
|
||
|
||
max_queue_wait = 60 * 5 # 最大排队时长
|
||
current_queue_wait = 0
|
||
retry_time = 3
|
||
wait_queue_interval = 3
|
||
|
||
order_id = 0
|
||
|
||
notification_sustain_time = 60 * 30 # 通知持续时间 30 分钟
|
||
notification_interval = 5 * 60 # 通知间隔
|
||
|
||
def __init__(self, query, user):
|
||
self.session = user.session
|
||
from py12306.query.job import Job
|
||
from py12306.user.job import UserJob
|
||
assert isinstance(query, Job)
|
||
assert isinstance(user, UserJob)
|
||
self.query_ins = query
|
||
self.user_ins = user
|
||
|
||
self.make_passenger_ticket_str()
|
||
|
||
def order(self):
|
||
"""
|
||
开始下单
|
||
下单模式 暂时不清楚,使用正常步骤下单
|
||
:return:
|
||
"""
|
||
# Debug
|
||
if Config().IS_DEBUG:
|
||
self.order_id = 'test'
|
||
self.order_did_success()
|
||
return random.randint(0, 10) > 7
|
||
return self.normal_order()
|
||
|
||
def normal_order(self):
|
||
order_request_res = self.submit_order_request()
|
||
if order_request_res == -1:
|
||
return self.order_did_success()
|
||
elif not order_request_res:
|
||
return
|
||
if not self.user_ins.request_init_dc_page(): return
|
||
if not self.check_order_info(): return
|
||
if not self.get_queue_count(): return
|
||
if not self.confirm_single_for_queue(): return
|
||
order_id = self.query_order_wait_time()
|
||
if order_id: # 发送通知
|
||
self.order_id = order_id
|
||
self.order_did_success()
|
||
return True
|
||
return False
|
||
|
||
def order_did_success(self):
|
||
OrderLog.print_ticket_did_ordered(self.order_id)
|
||
OrderLog.notification(OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_TITLE,
|
||
OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_CONTENT)
|
||
self.send_notification()
|
||
return True
|
||
|
||
def send_notification(self):
|
||
# num = 0 # 通知次数
|
||
# sustain_time = self.notification_sustain_time
|
||
info_message = OrderLog.get_order_success_notification_info(self.query_ins)
|
||
normal_message = OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_OF_EMAIL_CONTENT.format(self.order_id)
|
||
if Config().EMAIL_ENABLED: # 邮件通知
|
||
Notification.send_email(Config().EMAIL_RECEIVER, OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_TITLE,
|
||
normal_message + info_message)
|
||
if Config().DINGTALK_ENABLED: # 钉钉通知
|
||
Notification.dingtalk_webhook(normal_message + info_message)
|
||
if Config().TELEGRAM_ENABLED: # Telegram推送
|
||
Notification.send_to_telegram(normal_message + info_message)
|
||
if Config().SERVERCHAN_ENABLED: # ServerChan通知
|
||
Notification.server_chan(Config().SERVERCHAN_KEY, OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_TITLE,
|
||
normal_message + info_message)
|
||
if Config().PUSHBEAR_ENABLED: # PushBear通知
|
||
Notification.push_bear(Config().PUSHBEAR_KEY, OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_TITLE,
|
||
normal_message + info_message)
|
||
|
||
if Config().NOTIFICATION_BY_VOICE_CODE: # 语音通知
|
||
if Config().NOTIFICATION_VOICE_CODE_TYPE == 'dingxin':
|
||
voice_info = {
|
||
'left_station': self.query_ins.left_station,
|
||
'arrive_station': self.query_ins.arrive_station,
|
||
'set_type': self.query_ins.current_seat_name,
|
||
'orderno': self.order_id
|
||
}
|
||
else:
|
||
voice_info = OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_OF_VOICE_CODE_CONTENT.format(
|
||
self.query_ins.left_station, self.query_ins.arrive_station)
|
||
OrderLog.add_quick_log(OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_OF_VOICE_CODE_START_SEND)
|
||
Notification.voice_code(Config().NOTIFICATION_VOICE_CODE_PHONE, self.user_ins.get_name(), voice_info)
|
||
# 取消循环发送通知
|
||
# while sustain_time: # TODO 后面直接查询有没有待支付的订单就可以
|
||
# num += 1
|
||
# else:
|
||
# break
|
||
# sustain_time -= self.notification_interval
|
||
# sleep(self.notification_interval)
|
||
|
||
OrderLog.add_quick_log(OrderLog.MESSAGE_JOB_CLOSED).flush()
|
||
return True
|
||
|
||
def submit_order_request(self):
|
||
data = {
|
||
'secretStr': urllib.parse.unquote(self.query_ins.get_info_of_secret_str()), # 解密
|
||
'train_date': self.query_ins.left_date, # 出发时间
|
||
'back_train_date': self.query_ins.left_date, # 返程时间
|
||
'tour_flag': 'dc', # 旅途类型
|
||
'purpose_codes': 'ADULT', # 成人 | 学生
|
||
'query_from_station_name': self.query_ins.left_station,
|
||
'query_to_station_name': self.query_ins.arrive_station,
|
||
}
|
||
response = self.session.post(API_SUBMIT_ORDER_REQUEST, data)
|
||
result = response.json()
|
||
if result.get('data') == 'N':
|
||
OrderLog.add_quick_log(OrderLog.MESSAGE_SUBMIT_ORDER_REQUEST_SUCCESS).flush()
|
||
return True
|
||
else:
|
||
if (str(result.get('messages', '')).find('未处理') >= 0): # 未处理订单
|
||
# 0125 增加排队时长到 5 分钟之后,更多的是 排队失败,得通过拿到订单列表才能确认,再打个 TODO
|
||
# self.order_id = 0 # 需要拿到订单号 TODO
|
||
# return -1
|
||
pass
|
||
OrderLog.add_quick_log(
|
||
OrderLog.MESSAGE_SUBMIT_ORDER_REQUEST_FAIL.format(
|
||
result.get('messages', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR))).flush()
|
||
return False
|
||
|
||
def check_order_info(self):
|
||
"""
|
||
cancel_flag=2
|
||
bed_level_order_num=000000000000000000000000000000
|
||
passengerTicketStr=
|
||
tour_flag=dc
|
||
randCode=
|
||
whatsSelect=1
|
||
_json_att=
|
||
REPEAT_SUBMIT_TOKEN=458bf1b0a69431f34f9d2e9d3a11cfe9
|
||
:return:
|
||
"""
|
||
data = { #
|
||
'cancel_flag': 2,
|
||
'bed_level_order_num': '000000000000000000000000000000',
|
||
'passengerTicketStr': self.passenger_ticket_str,
|
||
'oldPassengerStr': self.old_passenger_str,
|
||
'tour_flag': 'dc',
|
||
'randCode': '',
|
||
'whatsSelect': '1',
|
||
'_json_att': '',
|
||
'REPEAT_SUBMIT_TOKEN': self.user_ins.global_repeat_submit_token
|
||
}
|
||
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:
|
||
error = CommonLog.MESSAGE_API_RESPONSE_CAN_NOT_BE_HANDLE
|
||
if not result.get('data.isNoActive'):
|
||
error = result.get('data.errMsg', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR)
|
||
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):
|
||
"""
|
||
获取队列人数
|
||
train_date Mon Jan 01 2019 00:00:00 GMT+0800 (China Standard Time)
|
||
train_no 630000Z12208
|
||
stationTrainCode Z122
|
||
seatType 4
|
||
fromStationTelecode GZQ
|
||
toStationTelecode RXW
|
||
leftTicket CmDJZYrwUoJ1jFNonIgPzPFdMBvSSE8xfdUwvb2lq8CCWn%2Bzk1vM3roJaHk%3D
|
||
purpose_codes 00
|
||
train_location QY
|
||
_json_att
|
||
REPEAT_SUBMIT_TOKEN 0977caf26f25d1da43e3213eb35ff87c
|
||
:return:
|
||
"""
|
||
data = { #
|
||
'train_date': '{} 00:00:00 GMT+0800 (China Standard Time)'.format(
|
||
datetime.datetime.strptime(self.query_ins.left_date, '%Y-%m-%d').strftime("%a %h %d %Y")),
|
||
'train_no': self.user_ins.ticket_info_for_passenger_form['queryLeftTicketRequestDTO']['train_no'],
|
||
'stationTrainCode': self.user_ins.ticket_info_for_passenger_form['queryLeftTicketRequestDTO'][
|
||
'station_train_code'],
|
||
'seatType': self.query_ins.current_order_seat,
|
||
'fromStationTelecode': self.user_ins.ticket_info_for_passenger_form['queryLeftTicketRequestDTO'][
|
||
'from_station'],
|
||
'toStationTelecode': self.user_ins.ticket_info_for_passenger_form['queryLeftTicketRequestDTO'][
|
||
'to_station'],
|
||
'leftTicket': self.user_ins.ticket_info_for_passenger_form['leftTicketStr'],
|
||
'purpose_codes': self.user_ins.ticket_info_for_passenger_form['purpose_codes'],
|
||
'train_location': self.user_ins.ticket_info_for_passenger_form['train_location'],
|
||
'_json_att': '',
|
||
'REPEAT_SUBMIT_TOKEN': self.user_ins.global_repeat_submit_token,
|
||
}
|
||
response = self.session.post(API_GET_QUEUE_COUNT, data)
|
||
result = response.json()
|
||
if result.get('status', False): # 成功
|
||
"""
|
||
"data": {
|
||
"count": "66",
|
||
"ticket": "0,73",
|
||
"op_2": "false",
|
||
"countT": "0",
|
||
"op_1": "true"
|
||
}
|
||
|
||
"""
|
||
# 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_INFO_SUCCESS.format(current_position, ticket_number)).flush()
|
||
return True
|
||
else:
|
||
# 加入小黑屋
|
||
OrderLog.add_quick_log(OrderLog.MESSAGE_GET_QUEUE_COUNT_FAIL.format(
|
||
result.get('messages', result.get('validateMessages', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR)))).flush()
|
||
return False
|
||
|
||
def confirm_single_for_queue(self):
|
||
"""
|
||
确认排队
|
||
passengerTicketStr
|
||
oldPassengerStr
|
||
randCode
|
||
purpose_codes 00
|
||
key_check_isChange FEE6C6634A3EAA93E1E6CFC39A99E555A92E438436F18AFF78837CDB
|
||
leftTicketStr CmDJZYrwUoJ1jFNonIgPzPFdMBvSSE8xfdUwvb2lq8CCWn%2Bzk1vM3roJaHk%3D
|
||
train_location QY
|
||
choose_seats
|
||
seatDetailType 000
|
||
whatsSelect 1
|
||
roomType 00
|
||
dwAll N
|
||
_json_att
|
||
REPEAT_SUBMIT_TOKEN 0977caf26f25d1da43e3213eb35ff87c
|
||
:return:
|
||
"""
|
||
data = { #
|
||
'passengerTicketStr': self.passenger_ticket_str,
|
||
'oldPassengerStr': self.old_passenger_str,
|
||
'randCode': '',
|
||
'purpose_codes': self.user_ins.ticket_info_for_passenger_form['purpose_codes'],
|
||
'key_check_isChange': self.user_ins.ticket_info_for_passenger_form['key_check_isChange'],
|
||
'leftTicketStr': self.user_ins.ticket_info_for_passenger_form['leftTicketStr'],
|
||
'train_location': self.user_ins.ticket_info_for_passenger_form['train_location'],
|
||
'choose_seats': '',
|
||
'seatDetailType': '000',
|
||
'whatsSelect': '1',
|
||
'roomType': '00',
|
||
'dwAll': 'N',
|
||
'_json_att': '',
|
||
'REPEAT_SUBMIT_TOKEN': self.user_ins.global_repeat_submit_token,
|
||
}
|
||
|
||
if self.is_need_auth_code: # 目前好像是都不需要了,有问题再处理
|
||
pass
|
||
|
||
response = self.session.post(API_CONFIRM_SINGLE_FOR_QUEUE, data)
|
||
result = response.json()
|
||
|
||
if 'data' in result:
|
||
"""
|
||
"data": {
|
||
"submitStatus": true
|
||
}
|
||
"""
|
||
if result.get('data.submitStatus'): # 成功
|
||
OrderLog.add_quick_log(OrderLog.MESSAGE_CONFIRM_SINGLE_FOR_QUEUE_SUCCESS).flush()
|
||
return True
|
||
else:
|
||
# 加入小黑屋 TODO
|
||
OrderLog.add_quick_log(
|
||
OrderLog.MESSAGE_CONFIRM_SINGLE_FOR_QUEUE_ERROR.format(
|
||
result.get('data.errMsg', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR))).flush()
|
||
else:
|
||
OrderLog.add_quick_log(OrderLog.MESSAGE_CONFIRM_SINGLE_FOR_QUEUE_FAIL.format(
|
||
result.get('messages', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR))).flush()
|
||
return False
|
||
|
||
def query_order_wait_time(self):
|
||
"""
|
||
排队查询
|
||
random 1546849953542
|
||
tourFlag dc
|
||
_json_att
|
||
REPEAT_SUBMIT_TOKEN 0977caf26f25d1da43e3213eb35ff87c
|
||
:return:
|
||
"""
|
||
self.current_queue_wait = self.max_queue_wait
|
||
self.queue_num = 0
|
||
while self.current_queue_wait:
|
||
self.current_queue_wait -= self.wait_queue_interval
|
||
self.queue_num += 1
|
||
# TODO 取消超时订单,待优化
|
||
data = { #
|
||
'random': str(random.random())[2:],
|
||
'tourFlag': 'dc',
|
||
'_json_att': '',
|
||
'REPEAT_SUBMIT_TOKEN': self.user_ins.global_repeat_submit_token,
|
||
}
|
||
|
||
response = self.session.get(API_QUERY_ORDER_WAIT_TIME.format(urllib.parse.urlencode(data)))
|
||
result = response.json()
|
||
|
||
if result.get('status') and 'data' in result:
|
||
"""
|
||
"data": {
|
||
"queryOrderWaitTimeStatus": true,
|
||
"count": 0,
|
||
"waitTime": -1,
|
||
"requestId": 6487958947291482523,
|
||
"waitCount": 0,
|
||
"tourFlag": "dc",
|
||
"orderId": "E222646122"
|
||
}
|
||
"""
|
||
result_data = result['data']
|
||
order_id = result_data.get('orderId')
|
||
if order_id: # 成功
|
||
return order_id
|
||
elif 'waitTime' in result_data:
|
||
# 计算等待时间
|
||
wait_time = int(result_data.get('waitTime'))
|
||
if wait_time == -1 or wait_time == -100: # 成功
|
||
# /otn/confirmPassenger/resultOrderForDcQueue 请求订单状态 目前不需要
|
||
# 不应该走到这
|
||
return order_id
|
||
elif wait_time >= 0: # 等待
|
||
OrderLog.add_quick_log(
|
||
OrderLog.MESSAGE_QUERY_ORDER_WAIT_TIME_WAITING.format(result_data.get('waitCount', 0),
|
||
wait_time)).flush()
|
||
else:
|
||
if wait_time == -2 or wait_time == -3: # -2 失败 -3 订单已撤销
|
||
OrderLog.add_quick_log(
|
||
OrderLog.MESSAGE_QUERY_ORDER_WAIT_TIME_FAIL.format(result_data.get('msg'))).flush()
|
||
return False
|
||
else: # 未知原因
|
||
OrderLog.add_quick_log(
|
||
OrderLog.MESSAGE_QUERY_ORDER_WAIT_TIME_FAIL.format(
|
||
result_data.get('msg', wait_time))).flush()
|
||
return False
|
||
|
||
elif result_data.get('msg'): # 失败 对不起,由于您取消次数过多,今日将不能继续受理您的订票请求。1月8日您可继续使用订票功能。
|
||
# TODO 需要增加判断 直接结束
|
||
OrderLog.add_quick_log(
|
||
OrderLog.MESSAGE_QUERY_ORDER_WAIT_TIME_FAIL.format(
|
||
result_data.get('msg', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR))).flush()
|
||
stay_second(self.retry_time)
|
||
return False
|
||
elif result.get('messages') or result.get('validateMessages'):
|
||
OrderLog.add_quick_log(OrderLog.MESSAGE_QUERY_ORDER_WAIT_TIME_FAIL.format(
|
||
result.get('messages', result.get('validateMessages')))).flush()
|
||
return False
|
||
else:
|
||
pass
|
||
OrderLog.add_quick_log(OrderLog.MESSAGE_QUERY_ORDER_WAIT_TIME_INFO.format(self.queue_num)).flush()
|
||
stay_second(self.wait_queue_interval)
|
||
|
||
return False
|
||
|
||
def make_passenger_ticket_str(self):
|
||
"""
|
||
生成提交车次的内容
|
||
格式:
|
||
1(seatType),0,1(车票类型:ticket_type_codes),张三(passenger_name),1(证件类型:passenger_id_type_code),xxxxxx(passenger_id_no),xxxx(mobile_no),N
|
||
passengerTicketStr:
|
||
张三(passenger_name),1(证件类型:passenger_id_type_code),xxxxxx(passenger_id_no),1_
|
||
oldPassengerStr
|
||
:return:
|
||
"""
|
||
passenger_tickets = []
|
||
old_passengers = []
|
||
available_passengers = self.query_ins.passengers
|
||
if len(available_passengers) > self.query_ins.member_num_take: # 删除人数
|
||
available_passengers = available_passengers[0:self.query_ins.member_num_take]
|
||
OrderLog.print_passenger_did_deleted(available_passengers)
|
||
|
||
for passenger in available_passengers:
|
||
tmp_str = '{seat_type},0,{passenger_type},{passenger_name},{passenger_id_card_type},{passenger_id_card},{passenger_mobile},N_'.format(
|
||
seat_type=self.query_ins.current_order_seat, passenger_type=passenger['type'],
|
||
passenger_name=passenger['name'],
|
||
passenger_id_card_type=passenger['id_card_type'], passenger_id_card=passenger['id_card'],
|
||
passenger_mobile=passenger['mobile']
|
||
)
|
||
passenger_tickets.append(tmp_str)
|
||
|
||
if int(passenger['type']) != UserType.CHILD:
|
||
tmp_old_str = '{passenger_name},{passenger_id_card_type},{passenger_id_card},{passenger_type}_'.format(
|
||
passenger_name=passenger['name'],
|
||
passenger_id_card_type=passenger['id_card_type'], passenger_id_card=passenger['id_card'],
|
||
passenger_type=passenger['type'],
|
||
)
|
||
old_passengers.append(tmp_old_str)
|
||
|
||
self.passenger_ticket_str = ''.join(passenger_tickets).rstrip('_')
|
||
self.old_passenger_str = ''.join(old_passengers).rstrip('_') + '__ _ _' # 不加后面请求会出错
|