parent
501fd388bd
commit
aa6d628c82
|
@ -164,4 +164,10 @@
|
||||||
- 优化提交订单有很大记录无限排队的情况,感谢群里的小伙伴提供的思路
|
- 优化提交订单有很大记录无限排队的情况,感谢群里的小伙伴提供的思路
|
||||||
- 修改休眠时间为早上6点
|
- 修改休眠时间为早上6点
|
||||||
|
|
||||||
|
- 2018.1.20更新,好久没跟新了,群里的小伙伴说登录不行了,今晚抽空改了一版登录,妥妥的
|
||||||
|
- 更新新版登录功能,经测试,更稳定有高效
|
||||||
|
- 优化手动打码功能
|
||||||
|
- 更新请求第三方库
|
||||||
|
- 优化若干代码,小伙伴尽情的放肆起来
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,89 @@
|
||||||
|
import random
|
||||||
|
|
||||||
|
urls = {
|
||||||
|
"auth": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/passport/web/auth/uamtk",
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"login": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/passport/web/login",
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"getCodeImg": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&{0}".format(random.random()),
|
||||||
|
"req_type": "get"
|
||||||
|
},
|
||||||
|
"codeCheck": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/passport/captcha/captcha-check",
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"loginInit": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/login/init",
|
||||||
|
"req_type": "get"
|
||||||
|
},
|
||||||
|
"getUserInfo": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/index/initMy12306",
|
||||||
|
"req_type": "get"
|
||||||
|
},
|
||||||
|
"userLogin": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/login/userLogin",
|
||||||
|
"req_type": "get"
|
||||||
|
},
|
||||||
|
"uamauthclient": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/uamauthclient",
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"initdc_url": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/confirmPassenger/initDc",
|
||||||
|
"req_type": "get"
|
||||||
|
},
|
||||||
|
"get_passengerDTOs": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs",
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"select_url": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date={0}&leftTicketDTO.from_station={1}&leftTicketDTO.to_station={2}&purpose_codes=ADULT",
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"check_user_url": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/login/checkUser",
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"submit_station_url": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest",
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"checkOrderInfoUrl": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo",
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"getQueueCountUrl": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount",
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"checkQueueOrderUrl": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue",
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"checkRandCodeAnsyn": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/passcodeNew/checkRandCodeAnsyn",
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"codeImgByOrder": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module=passenger&rand=randp&%s" % random.random(),
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"queryOrderWaitTimeUrl": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime",
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"queryMyOrderNoCompleteUrl": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/queryOrder/queryMyOrderNoComplete",
|
||||||
|
"req_type": "post"
|
||||||
|
},
|
||||||
|
"initNoCompleteUrl": {
|
||||||
|
"req_url": "https://kyfw.12306.cn/otn/queryOrder/initNoComplete",
|
||||||
|
"req_type": "post"
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
228
init/login.py
228
init/login.py
|
@ -9,20 +9,26 @@ from time import sleep
|
||||||
from config.ticketConf import _get_yaml
|
from config.ticketConf import _get_yaml
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from damatuCode.damatuWeb import DamatuApi
|
from damatuCode.damatuWeb import DamatuApi
|
||||||
|
from myException.UserPasswordException import UserPasswordException
|
||||||
from myUrllib import myurllib2
|
from myUrllib import myurllib2
|
||||||
|
|
||||||
codeimg = 'https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module=login&rand=sjrand&%s' % random.random()
|
|
||||||
|
|
||||||
|
class GoLogin:
|
||||||
|
def __init__(self, httpClint, urlConf):
|
||||||
|
self.httpClint = httpClint
|
||||||
|
self.randCode = ""
|
||||||
|
self.urlConf = urlConf
|
||||||
|
|
||||||
def cookietp():
|
def cookietp(self):
|
||||||
stoidinput("获取Cookie")
|
print("正在获取cookie")
|
||||||
Url = "https://kyfw.12306.cn/otn/login/init"
|
url = self.urlConf["loginInit"]["req_url"]
|
||||||
myurllib2.get(Url)
|
self.httpClint.send(url)
|
||||||
|
# Url = "https://kyfw.12306.cn/otn/login/init"
|
||||||
|
# myurllib2.get(Url)
|
||||||
# for index, c in enumerate(myurllib2.cookiejar):
|
# for index, c in enumerate(myurllib2.cookiejar):
|
||||||
# stoidinput(c)
|
# stoidinput(c)
|
||||||
|
|
||||||
|
def readImg(self):
|
||||||
def readImg():
|
|
||||||
"""
|
"""
|
||||||
增加手动打码,只是登录接口,完全不用担心提交订单效率
|
增加手动打码,只是登录接口,完全不用担心提交订单效率
|
||||||
思路
|
思路
|
||||||
|
@ -31,52 +37,30 @@ def readImg():
|
||||||
3.控制台输入对应下标,按照英文逗号分开,即可手动完成打码,
|
3.控制台输入对应下标,按照英文逗号分开,即可手动完成打码,
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
print ("下载验证码...")
|
||||||
global randCode
|
codeimgUrl = self.urlConf["getCodeImg"]["req_url"]
|
||||||
stoidinput("下载验证码...")
|
|
||||||
img_path = './tkcode'
|
img_path = './tkcode'
|
||||||
result = myurllib2.get(codeimg)
|
result = self.httpClint.send(codeimgUrl)
|
||||||
try:
|
try:
|
||||||
open(img_path, 'wb').write(result)
|
open(img_path, 'wb').write(result)
|
||||||
if _get_yaml()["is_aotu_code"]:
|
if _get_yaml()["is_aotu_code"]:
|
||||||
randCode = DamatuApi(_get_yaml()["damatu"]["uesr"], _get_yaml()["damatu"]["pwd"], img_path).main()
|
self.randCode = DamatuApi(_get_yaml()["damatu"]["uesr"], _get_yaml()["damatu"]["pwd"], img_path).main()
|
||||||
else:
|
else:
|
||||||
img = Image.open('./tkcode')
|
img = Image.open('./tkcode')
|
||||||
img.show()
|
img.show()
|
||||||
codexy()
|
self.codexy()
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
print (e)
|
print (e)
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def codexy(self):
|
||||||
def stoidinput(text):
|
|
||||||
"""
|
|
||||||
正常信息输出
|
|
||||||
:param text:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
print "\033[34m[*]\033[0m %s " % text
|
|
||||||
|
|
||||||
|
|
||||||
def errorinput(text):
|
|
||||||
"""
|
|
||||||
错误信息输出
|
|
||||||
:param text:
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
print "\033[32m[!]\033[0m %s " % text
|
|
||||||
return False
|
|
||||||
|
|
||||||
|
|
||||||
def codexy():
|
|
||||||
"""
|
"""
|
||||||
获取验证码
|
获取验证码
|
||||||
:return: str
|
:return: str
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Ofset = raw_input("[*] 请输入验证码: ")
|
Ofset = raw_input("请输入验证码: ")
|
||||||
select = Ofset.split(',')
|
select = Ofset.split(',')
|
||||||
global randCode
|
|
||||||
post = []
|
post = []
|
||||||
offsetsX = 0 # 选择的答案的left值,通过浏览器点击8个小图的中点得到的,这样基本没问题
|
offsetsX = 0 # 选择的答案的left值,通过浏览器点击8个小图的中点得到的,这样基本没问题
|
||||||
offsetsY = 0 # 选择的答案的top值
|
offsetsY = 0 # 选择的答案的top值
|
||||||
|
@ -109,10 +93,86 @@ def codexy():
|
||||||
pass
|
pass
|
||||||
post.append(offsetsX)
|
post.append(offsetsX)
|
||||||
post.append(offsetsY)
|
post.append(offsetsY)
|
||||||
randCode = str(post).replace(']', '').replace('[', '').replace("'", '').replace(' ', '')
|
self.randCode = str(post).replace(']', '').replace('[', '').replace("'", '').replace(' ', '')
|
||||||
|
|
||||||
|
def auth(self):
|
||||||
|
"""认证"""
|
||||||
|
authUrl = self.urlConf["auth"]["req_url"]
|
||||||
|
authData = {"appid": "otn"}
|
||||||
|
tk = self.httpClint.send(authUrl, authData)
|
||||||
|
return tk
|
||||||
|
|
||||||
def go_login():
|
def codeCheck(self):
|
||||||
|
"""
|
||||||
|
验证码校验
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
codeCheck = self.urlConf["codeCheck"]["req_url"]
|
||||||
|
codeCheckData = {
|
||||||
|
"answer": self.randCode,
|
||||||
|
"rand": "sjrand",
|
||||||
|
"login_site": "E"
|
||||||
|
}
|
||||||
|
fresult = self.httpClint.send(codeCheck, codeCheckData)
|
||||||
|
if "result_code" in fresult and fresult["result_code"] == "4":
|
||||||
|
print ("验证码通过,开始登录..")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
if "result_message" in fresult:
|
||||||
|
print(fresult["result_message"])
|
||||||
|
sleep(1)
|
||||||
|
self.httpClint.del_cookies()
|
||||||
|
|
||||||
|
def baseLogin(self, user, passwd):
|
||||||
|
"""
|
||||||
|
登录过程
|
||||||
|
:param user:
|
||||||
|
:param passwd:
|
||||||
|
:return: 权限校验码
|
||||||
|
"""
|
||||||
|
logurl = self.urlConf["login"]["req_url"]
|
||||||
|
logData = {
|
||||||
|
"username": user,
|
||||||
|
"password": passwd,
|
||||||
|
"appid": "otn"
|
||||||
|
}
|
||||||
|
tresult = self.httpClint.send(logurl, logData)
|
||||||
|
if 'result_code' in tresult and tresult["result_code"] == 0:
|
||||||
|
print ("登录成功")
|
||||||
|
tk = self.auth()
|
||||||
|
if "newapptk" in tk and tk["newapptk"]:
|
||||||
|
return tk["newapptk"]
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
elif 'result_message' in tresult and tresult['result_message']:
|
||||||
|
messages = tresult['result_message']
|
||||||
|
if messages.find("密码输入错误") is not -1:
|
||||||
|
raise UserPasswordException("{0}".format(messages))
|
||||||
|
else:
|
||||||
|
print ("登录失败: {0}".format("".join(tresult)))
|
||||||
|
print ("尝试重新登陆")
|
||||||
|
return False
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def getUserName(self, uamtk):
|
||||||
|
"""
|
||||||
|
登录成功后,显示用户名
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
if not uamtk:
|
||||||
|
return "权限校验码不能为空"
|
||||||
|
else:
|
||||||
|
uamauthclientUrl = self.urlConf["uamauthclient"]["req_url"]
|
||||||
|
data = {"tk": uamtk}
|
||||||
|
uamauthclientResult = self.httpClint.send(uamauthclientUrl, data)
|
||||||
|
if "result_code" in uamauthclientResult and uamauthclientResult["result_code"] == 0:
|
||||||
|
print("欢迎 {} 登录".format(uamauthclientResult["username"]))
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def go_login(self):
|
||||||
"""
|
"""
|
||||||
登陆
|
登陆
|
||||||
:param user: 账户名
|
:param user: 账户名
|
||||||
|
@ -122,90 +182,26 @@ def go_login():
|
||||||
user, passwd = _get_yaml()["set"]["12306count"][0]["uesr"], _get_yaml()["set"]["12306count"][1]["pwd"]
|
user, passwd = _get_yaml()["set"]["12306count"][0]["uesr"], _get_yaml()["set"]["12306count"][1]["pwd"]
|
||||||
login_num = 0
|
login_num = 0
|
||||||
while True:
|
while True:
|
||||||
cookietp()
|
self.cookietp()
|
||||||
readImg()
|
self.httpClint.set_cookies(_jc_save_wfdc_flag="dc", _jc_save_fromStation="%u4E0A%u6D77%u8679%u6865%2CAOH", _jc_save_toStation="%u5170%u5DDE%u897F%2CLAJ", _jc_save_fromDate="2018-02-14", _jc_save_toDate="2018-01-16", RAIL_DEVICEID="EN_3_EGSe2GWGHXJeCkFQ52kHvNCrNlkz9n1GOqqQ1wR0i98WsD8Gj-a3YHZ-XYKeESWgCiJyyucgSwkFOzVHhHqfpidLPcm2vK9n83uzOPuShO3Pl4lCydAtQu4BdFqz-RVmiduNFixrcrN_Ny43135JiEtqLaI")
|
||||||
|
self.readImg()
|
||||||
login_num += 1
|
login_num += 1
|
||||||
randurl = 'https://kyfw.12306.cn/otn/passcodeNew/checkRandCodeAnsyn'
|
self.auth()
|
||||||
logurl = 'https://kyfw.12306.cn/otn/login/loginAysnSuggest'
|
if self.codeCheck():
|
||||||
surl = 'https://kyfw.12306.cn/otn/login/userLogin'
|
uamtk = self.baseLogin(user, passwd)
|
||||||
randdata = {
|
if uamtk:
|
||||||
"randCode": randCode,
|
if self.getUserName(uamtk):
|
||||||
"rand": "sjrand"
|
|
||||||
}
|
|
||||||
logdata = {
|
|
||||||
"loginUserDTO.user_name": user,
|
|
||||||
"userDTO.password": passwd,
|
|
||||||
"randCode": randCode
|
|
||||||
}
|
|
||||||
ldata = {
|
|
||||||
"_json_att": None
|
|
||||||
}
|
|
||||||
fresult = json.loads(myurllib2.Post(randurl, randdata), encoding='utf8')
|
|
||||||
checkcode = fresult['data']['msg']
|
|
||||||
if checkcode == 'FALSE':
|
|
||||||
errorinput("验证码有误,第{}次尝试重试".format(login_num))
|
|
||||||
else:
|
|
||||||
stoidinput("验证码通过,开始登录..")
|
|
||||||
sleep(1)
|
|
||||||
try:
|
|
||||||
tresult = json.loads(myurllib2.Post(logurl, logdata), encoding='utf8')
|
|
||||||
if 'data' not in tresult:
|
|
||||||
errorinput("登录失败: %s" % tresult['messages'][0])
|
|
||||||
# elif "messages" in tresult and tresult["messages"][0].find("密码输入错误") is not -1:
|
|
||||||
# errorinput("登陆失败:{}".format(tresult["messages"][0]))
|
|
||||||
# break
|
|
||||||
elif 'messages' in tresult and tresult['messages']:
|
|
||||||
messages = tresult['messages'][0]
|
|
||||||
if messages.find("密码输入错误") is not -1:
|
|
||||||
errorinput("登陆失败:{}".format(tresult["messages"][0]))
|
|
||||||
break
|
break
|
||||||
else:
|
|
||||||
errorinput("登录失败: %s" % tresult['messages'][0])
|
|
||||||
stoidinput("尝试重新登陆")
|
|
||||||
else:
|
|
||||||
stoidinput("登录成功")
|
|
||||||
myurllib2.Post(surl, ldata)
|
|
||||||
getUserinfo()
|
|
||||||
break
|
|
||||||
except ValueError as e:
|
|
||||||
if e.message == "No JSON object could be decoded":
|
|
||||||
print("12306接口无响应,正在重试")
|
|
||||||
else:
|
|
||||||
print(e.message)
|
|
||||||
except KeyError as e:
|
|
||||||
print(e.message)
|
|
||||||
except TypeError as e:
|
|
||||||
print(e.message)
|
|
||||||
except socket.error as e:
|
|
||||||
print(e.message)
|
|
||||||
sleep(1)
|
|
||||||
|
|
||||||
|
def logout(self):
|
||||||
def getUserinfo():
|
|
||||||
"""
|
|
||||||
登录成功后,显示用户名
|
|
||||||
:return:
|
|
||||||
"""
|
|
||||||
url = 'https://kyfw.12306.cn/otn/modifyUser/initQueryUserInfo'
|
|
||||||
data = dict(_json_att=None)
|
|
||||||
result = myurllib2.Post(url, data)
|
|
||||||
userinfo = result
|
|
||||||
name = r'<input name="userDTO.loginUserDTO.user_name" style="display:none;" type="text" value="(\S+)" />'
|
|
||||||
try:
|
|
||||||
stoidinput("欢迎 %s 登录" % re.search(name, result).group(1))
|
|
||||||
except AttributeError:
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def logout():
|
|
||||||
url = 'https://kyfw.12306.cn/otn/login/loginOut'
|
url = 'https://kyfw.12306.cn/otn/login/loginOut'
|
||||||
result = myurllib2.get(url)
|
result = myurllib2.get(url)
|
||||||
if result:
|
if result:
|
||||||
stoidinput("已退出")
|
print ("已退出")
|
||||||
else:
|
else:
|
||||||
errorinput("退出失败")
|
print ("退出失败")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
# if __name__ == "__main__":
|
||||||
main()
|
# # main()
|
||||||
# logout()
|
# # logout()
|
|
@ -4,22 +4,24 @@ import datetime
|
||||||
import random
|
import random
|
||||||
import re
|
import re
|
||||||
import socket
|
import socket
|
||||||
import threading
|
|
||||||
import urllib
|
import urllib
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
|
from config import urlConf
|
||||||
from init import login
|
from init import login
|
||||||
|
|
||||||
from config.emailConf import sendEmail
|
from config.emailConf import sendEmail
|
||||||
from config.ticketConf import _get_yaml
|
from config.ticketConf import _get_yaml
|
||||||
from damatuCode.damatuWeb import DamatuApi
|
from damatuCode.damatuWeb import DamatuApi
|
||||||
from init.login import go_login
|
from init.login import GoLogin
|
||||||
from myException.PassengerUserException import PassengerUserException
|
from myException.PassengerUserException import PassengerUserException
|
||||||
|
from myException.UserPasswordException import UserPasswordException
|
||||||
from myException.ticketConfigException import ticketConfigException
|
from myException.ticketConfigException import ticketConfigException
|
||||||
from myException.ticketIsExitsException import ticketIsExitsException
|
from myException.ticketIsExitsException import ticketIsExitsException
|
||||||
from myException.ticketNumOutException import ticketNumOutException
|
from myException.ticketNumOutException import ticketNumOutException
|
||||||
from myUrllib import myurllib2
|
from myUrllib.httpUtils import HTTPClient
|
||||||
|
|
||||||
reload(sys)
|
reload(sys)
|
||||||
sys.setdefaultencoding('utf-8')
|
sys.setdefaultencoding('utf-8')
|
||||||
|
@ -37,6 +39,8 @@ class select:
|
||||||
self.secretStr = ""
|
self.secretStr = ""
|
||||||
self.ticket_black_list = dict()
|
self.ticket_black_list = dict()
|
||||||
self.is_check_user = dict()
|
self.is_check_user = dict()
|
||||||
|
self.httpClint = HTTPClient()
|
||||||
|
self.confUrl = urlConf.urls
|
||||||
|
|
||||||
def get_ticket_info(self):
|
def get_ticket_info(self):
|
||||||
"""
|
"""
|
||||||
|
@ -137,12 +141,11 @@ class select:
|
||||||
获取提交车票请求token
|
获取提交车票请求token
|
||||||
:return: token
|
:return: token
|
||||||
"""
|
"""
|
||||||
initdc_url = 'https://kyfw.12306.cn/otn/confirmPassenger/initDc'
|
initdc_url = self.confUrl["initdc_url"]["req+url"]
|
||||||
initdc_result = myurllib2.get(initdc_url)
|
initdc_result = self.httpClint.send(initdc_url)
|
||||||
token_name = re.compile(r"var globalRepeatSubmitToken = '(\S+)'")
|
token_name = re.compile(r"var globalRepeatSubmitToken = '(\S+)'")
|
||||||
ticketInfoForPassengerForm_name = re.compile(r'var ticketInfoForPassengerForm=(\{.+\})?')
|
ticketInfoForPassengerForm_name = re.compile(r'var ticketInfoForPassengerForm=(\{.+\})?')
|
||||||
order_request_params_name = re.compile(r'var orderRequestDTO=(\{.+\})?')
|
order_request_params_name = re.compile(r'var orderRequestDTO=(\{.+\})?')
|
||||||
# if token_name and ticketInfoForPassengerForm_name and order_request_params_name:
|
|
||||||
self.token = re.search(token_name, initdc_result).group(1)
|
self.token = re.search(token_name, initdc_result).group(1)
|
||||||
re_tfpf = re.findall(ticketInfoForPassengerForm_name, initdc_result)
|
re_tfpf = re.findall(ticketInfoForPassengerForm_name, initdc_result)
|
||||||
re_orp = re.findall(order_request_params_name, initdc_result)
|
re_orp = re.findall(order_request_params_name, initdc_result)
|
||||||
|
@ -160,15 +163,14 @@ class select:
|
||||||
获取乘客信息
|
获取乘客信息
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
get_passengerDTOs = 'https://kyfw.12306.cn/otn/confirmPassenger/getPassengerDTOs'
|
get_passengerDTOs = self.confUrl["get_passengerDTOs"]["req_url"]
|
||||||
get_data = {
|
get_data = {
|
||||||
'_json_att': None,
|
'_json_att': None,
|
||||||
'REPEAT_SUBMIT_TOKEN': self.token
|
'REPEAT_SUBMIT_TOKEN': self.token
|
||||||
}
|
}
|
||||||
jsonData = json.loads(myurllib2.Post(get_passengerDTOs, get_data))
|
jsonData = self.httpClint.send(get_passengerDTOs, get_data)
|
||||||
if 'data' in jsonData and jsonData['data'] and 'normal_passengers' in jsonData['data'] and jsonData['data'][
|
if 'data' in jsonData and jsonData['data'] and 'normal_passengers' in jsonData['data'] and jsonData['data'][
|
||||||
'normal_passengers']:
|
'normal_passengers']:
|
||||||
# return jsonData['data']['normal_passengers']
|
|
||||||
normal_passengers = jsonData['data']['normal_passengers']
|
normal_passengers = jsonData['data']['normal_passengers']
|
||||||
_normal_passenger = [normal_passengers[i] for i in range(len(normal_passengers))if normal_passengers[i]["passenger_name"] in self.ticke_peoples]
|
_normal_passenger = [normal_passengers[i] for i in range(len(normal_passengers))if normal_passengers[i]["passenger_name"] in self.ticke_peoples]
|
||||||
return _normal_passenger if _normal_passenger else normal_passengers[0] # 如果配置乘车人没有在账号,则默认返回第一个用户
|
return _normal_passenger if _normal_passenger else normal_passengers[0] # 如果配置乘车人没有在账号,则默认返回第一个用户
|
||||||
|
@ -182,9 +184,9 @@ class select:
|
||||||
raise PassengerUserException("未查找到常用联系人,请先添加联系人在试试")
|
raise PassengerUserException("未查找到常用联系人,请先添加联系人在试试")
|
||||||
|
|
||||||
def submitOrderRequestFunc(self, from_station, to_station, station_date=None):
|
def submitOrderRequestFunc(self, from_station, to_station, station_date=None):
|
||||||
select_url = 'https://kyfw.12306.cn/otn/leftTicket/queryZ?leftTicketDTO.train_date={0}&leftTicketDTO.from_station={1}&leftTicketDTO.to_station={2}&purpose_codes=ADULT'.format(
|
select_url = self.confUrl["select_url"]["req_url"].format(
|
||||||
self.station_date if station_date is None else station_date, from_station, to_station)
|
self.station_date if station_date is None else station_date, from_station, to_station)
|
||||||
station_ticket = json.loads(myurllib2.get(select_url), encoding='utf-8')
|
station_ticket = self.httpClint.send(select_url)
|
||||||
return station_ticket
|
return station_ticket
|
||||||
|
|
||||||
def submitOrderRequestImplement(self, from_station, to_station,):
|
def submitOrderRequestImplement(self, from_station, to_station,):
|
||||||
|
@ -246,9 +248,9 @@ class select:
|
||||||
检查用户是否达到订票条件
|
检查用户是否达到订票条件
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
check_user_url = 'https://kyfw.12306.cn/otn/login/checkUser'
|
check_user_url = self.confUrl["check_user_url"]["req_url"]
|
||||||
data = dict(_json_att=None)
|
data = dict(_json_att=None)
|
||||||
check_user = json.loads(myurllib2.Post(check_user_url, data), encoding='utf-8')
|
check_user = self.httpClint.send(check_user_url, data)
|
||||||
check_user_flag = check_user['data']['flag']
|
check_user_flag = check_user['data']['flag']
|
||||||
if check_user_flag is True:
|
if check_user_flag is True:
|
||||||
return True
|
return True
|
||||||
|
@ -275,7 +277,7 @@ class select:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
submit_station_url = 'https://kyfw.12306.cn/otn/leftTicket/submitOrderRequest'
|
submit_station_url = self.confUrl["submit_station_url"]["req_url"]
|
||||||
data = [('secretStr', urllib.unquote(self.secretStr)), # 字符串加密
|
data = [('secretStr', urllib.unquote(self.secretStr)), # 字符串加密
|
||||||
('train_date', self.time()), # 出发时间
|
('train_date', self.time()), # 出发时间
|
||||||
('back_train_date', self.time()), # 返程时间
|
('back_train_date', self.time()), # 返程时间
|
||||||
|
@ -284,7 +286,7 @@ class select:
|
||||||
('query_from_station_name', self.from_station), # 起始车站
|
('query_from_station_name', self.from_station), # 起始车站
|
||||||
('query_to_station_name', self.to_station), # 终点车站
|
('query_to_station_name', self.to_station), # 终点车站
|
||||||
]
|
]
|
||||||
submitResult = json.loads(myurllib2.Post(submit_station_url, data), encoding='utf-8')
|
submitResult = self.httpClint.send(submit_station_url, data)
|
||||||
if 'data' in submitResult and submitResult['data']:
|
if 'data' in submitResult and submitResult['data']:
|
||||||
if submitResult['data'] == 'N':
|
if submitResult['data'] == 'N':
|
||||||
print ('出票成功')
|
print ('出票成功')
|
||||||
|
@ -355,7 +357,7 @@ class select:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
passengerTicketStrList, oldPassengerStr = self.getPassengerTicketStrListAndOldPassengerStr()
|
passengerTicketStrList, oldPassengerStr = self.getPassengerTicketStrListAndOldPassengerStr()
|
||||||
checkOrderInfoUrl = 'https://kyfw.12306.cn/otn/confirmPassenger/checkOrderInfo'
|
checkOrderInfoUrl = self.confUrl["checkOrderInfoUrl"]["req_url"]
|
||||||
data = OrderedDict()
|
data = OrderedDict()
|
||||||
data['cancel_flag'] = 2
|
data['cancel_flag'] = 2
|
||||||
data['bed_level_order_num'] = "000000000000000000000000000000"
|
data['bed_level_order_num'] = "000000000000000000000000000000"
|
||||||
|
@ -364,7 +366,7 @@ class select:
|
||||||
data['tour_flag'] = 'dc'
|
data['tour_flag'] = 'dc'
|
||||||
data['whatsSelect'] = 1
|
data['whatsSelect'] = 1
|
||||||
data['REPEAT_SUBMIT_TOKEN'] = self.token
|
data['REPEAT_SUBMIT_TOKEN'] = self.token
|
||||||
checkOrderInfo = json.loads(myurllib2.Post(checkOrderInfoUrl, data, ))
|
checkOrderInfo = self.httpClint.send(checkOrderInfoUrl, data)
|
||||||
if 'data' in checkOrderInfo:
|
if 'data' in checkOrderInfo:
|
||||||
if "ifShowPassCode" in checkOrderInfo["data"] and checkOrderInfo["data"]["ifShowPassCode"] == "Y":
|
if "ifShowPassCode" in checkOrderInfo["data"] and checkOrderInfo["data"]["ifShowPassCode"] == "Y":
|
||||||
is_need_code = True
|
is_need_code = True
|
||||||
|
@ -393,7 +395,7 @@ class select:
|
||||||
"""
|
"""
|
||||||
l_time = time.localtime(time.time())
|
l_time = time.localtime(time.time())
|
||||||
new_train_date = time.strftime("%a %b %d %Y", l_time)
|
new_train_date = time.strftime("%a %b %d %Y", l_time)
|
||||||
getQueueCountUrl = 'https://kyfw.12306.cn/otn/confirmPassenger/getQueueCount'
|
getQueueCountUrl = self.confUrl["getQueueCountUrl"]["req_url"]
|
||||||
data = {
|
data = {
|
||||||
'train_date': str(new_train_date) + " 00:00:00 GMT+0800 (中国标准时间)",
|
'train_date': str(new_train_date) + " 00:00:00 GMT+0800 (中国标准时间)",
|
||||||
'train_no': self.get_ticketInfoForPassengerForm()['queryLeftTicketRequestDTO']['train_no'],
|
'train_no': self.get_ticketInfoForPassengerForm()['queryLeftTicketRequestDTO']['train_no'],
|
||||||
|
@ -406,7 +408,7 @@ class select:
|
||||||
'train_location': self.get_ticketInfoForPassengerForm()['train_location'],
|
'train_location': self.get_ticketInfoForPassengerForm()['train_location'],
|
||||||
'REPEAT_SUBMIT_TOKEN': self.get_token(),
|
'REPEAT_SUBMIT_TOKEN': self.get_token(),
|
||||||
}
|
}
|
||||||
getQueueCountResult = json.loads(myurllib2.Post(getQueueCountUrl, data))
|
getQueueCountResult = self.httpClint.send(getQueueCountUrl, data)
|
||||||
if "status" in getQueueCountResult and getQueueCountResult["status"] is True:
|
if "status" in getQueueCountResult and getQueueCountResult["status"] is True:
|
||||||
if "countT" in getQueueCountResult["data"]:
|
if "countT" in getQueueCountResult["data"]:
|
||||||
ticket = getQueueCountResult["data"]["ticket"]
|
ticket = getQueueCountResult["data"]["ticket"]
|
||||||
|
@ -441,7 +443,7 @@ class select:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
passengerTicketStrList, oldPassengerStr = self.getPassengerTicketStrListAndOldPassengerStr()
|
passengerTicketStrList, oldPassengerStr = self.getPassengerTicketStrListAndOldPassengerStr()
|
||||||
checkQueueOrderUrl = "https://kyfw.12306.cn/otn/confirmPassenger/confirmSingleForQueue"
|
checkQueueOrderUrl = self.confUrl["checkQueueOrderUrl"]["req_url"]
|
||||||
data = {
|
data = {
|
||||||
"passengerTicketStr": self.set_type + "," + ",".join(passengerTicketStrList).rstrip("_{0}".format(self.set_type)),
|
"passengerTicketStr": self.set_type + "," + ",".join(passengerTicketStrList).rstrip("_{0}".format(self.set_type)),
|
||||||
"oldPassengerStr": "".join(oldPassengerStr),
|
"oldPassengerStr": "".join(oldPassengerStr),
|
||||||
|
@ -460,9 +462,9 @@ class select:
|
||||||
for i in range(3):
|
for i in range(3):
|
||||||
if is_node_code:
|
if is_node_code:
|
||||||
print("正在使用自动识别验证码功能")
|
print("正在使用自动识别验证码功能")
|
||||||
randurl = 'https://kyfw.12306.cn/otn/passcodeNew/checkRandCodeAnsyn'
|
checkRandCodeAnsyn = self.confUrl["checkRandCodeAnsyn"]["req_url"]
|
||||||
codeimg = 'https://kyfw.12306.cn/otn/passcodeNew/getPassCodeNew?module=passenger&rand=randp&%s' % random.random()
|
codeImgByOrder = self.confUrl["codeImgByOrder"]["req_url"]
|
||||||
result = myurllib2.get(codeimg)
|
result = self.httpClint.send(codeImgByOrder)
|
||||||
img_path = './tkcode'
|
img_path = './tkcode'
|
||||||
open(img_path, 'wb').write(result)
|
open(img_path, 'wb').write(result)
|
||||||
randCode = DamatuApi(_get_yaml()["damatu"]["uesr"], _get_yaml()["damatu"]["pwd"],
|
randCode = DamatuApi(_get_yaml()["damatu"]["uesr"], _get_yaml()["damatu"]["pwd"],
|
||||||
|
@ -473,7 +475,7 @@ class select:
|
||||||
"_json_att": None,
|
"_json_att": None,
|
||||||
"REPEAT_SUBMIT_TOKEN": self.get_token()
|
"REPEAT_SUBMIT_TOKEN": self.get_token()
|
||||||
}
|
}
|
||||||
fresult = json.loads(myurllib2.Post(randurl, randData), encoding='utf8') # 校验验证码是否正确
|
fresult = self.httpClint.send(checkRandCodeAnsyn, randData) # 校验验证码是否正确
|
||||||
checkcode = fresult['data']['msg']
|
checkcode = fresult['data']['msg']
|
||||||
if checkcode == 'TRUE':
|
if checkcode == 'TRUE':
|
||||||
print("验证码通过,正在提交订单")
|
print("验证码通过,正在提交订单")
|
||||||
|
@ -484,8 +486,7 @@ class select:
|
||||||
else:
|
else:
|
||||||
print("不需要验证码")
|
print("不需要验证码")
|
||||||
break
|
break
|
||||||
# print("".join(data))
|
checkQueueOrderResult = self.httpClint.send(checkQueueOrderUrl, data)
|
||||||
checkQueueOrderResult = json.loads(myurllib2.Post(checkQueueOrderUrl, data))
|
|
||||||
if "status" in checkQueueOrderResult and checkQueueOrderResult["status"]:
|
if "status" in checkQueueOrderResult and checkQueueOrderResult["status"]:
|
||||||
c_data = checkQueueOrderResult["data"] if "data" in checkQueueOrderResult else {}
|
c_data = checkQueueOrderResult["data"] if "data" in checkQueueOrderResult else {}
|
||||||
if 'submitStatus' in c_data and c_data['submitStatus'] is True:
|
if 'submitStatus' in c_data and c_data['submitStatus'] is True:
|
||||||
|
@ -509,12 +510,6 @@ class select:
|
||||||
排队获取订单等待信息,每隔3秒请求一次,最高请求次数为20次!
|
排队获取订单等待信息,每隔3秒请求一次,最高请求次数为20次!
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
# queryOrderWaitTimeUrl = "https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime"
|
|
||||||
# data = {
|
|
||||||
# "random": "{0}{1}".format(int(time.time()), random.randint(1, 9)),
|
|
||||||
# "tourFlag": "dc",
|
|
||||||
# "REPEAT_SUBMIT_TOKEN": self.get_token(),
|
|
||||||
# }
|
|
||||||
num = 1
|
num = 1
|
||||||
while True:
|
while True:
|
||||||
_random = int(round(time.time() * 1000))
|
_random = int(round(time.time() * 1000))
|
||||||
|
@ -523,10 +518,9 @@ class select:
|
||||||
print("超出排队时间,自动放弃,正在重新刷票")
|
print("超出排队时间,自动放弃,正在重新刷票")
|
||||||
break
|
break
|
||||||
try:
|
try:
|
||||||
# queryOrderWaitTimeUrl = "https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime?random={0}&tourFlag=dc&_json_att=&REPEAT_SUBMIT_TOKEN={1}".format(_random, self.get_token())
|
|
||||||
data = {"random": _random, "tourFlag": "dc"}
|
data = {"random": _random, "tourFlag": "dc"}
|
||||||
queryOrderWaitTimeUrl = "https://kyfw.12306.cn/otn/confirmPassenger/queryOrderWaitTime"
|
queryOrderWaitTimeUrl = self.confUrl["queryOrderWaitTimeUrl"]["req_url"]
|
||||||
queryOrderWaitTimeResult = json.loads(myurllib2.Post(queryOrderWaitTimeUrl, data))
|
queryOrderWaitTimeResult = self.httpClint.send(queryOrderWaitTimeUrl, data)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
queryOrderWaitTimeResult = {}
|
queryOrderWaitTimeResult = {}
|
||||||
if queryOrderWaitTimeResult:
|
if queryOrderWaitTimeResult:
|
||||||
|
@ -562,10 +556,10 @@ class select:
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
self.initNoComplete()
|
self.initNoComplete()
|
||||||
queryMyOrderNoCompleteUrl = "https://kyfw.12306.cn/otn/queryOrder/queryMyOrderNoComplete"
|
queryMyOrderNoCompleteUrl = self.confUrl["queryMyOrderNoCompleteUrl"]["req_url"]
|
||||||
data = {"_json_att": None}
|
data = {"_json_att": None}
|
||||||
try:
|
try:
|
||||||
queryMyOrderNoCompleteResult = json.loads(myurllib2.Post(queryMyOrderNoCompleteUrl, data))
|
queryMyOrderNoCompleteResult = self.httpClint.send(queryMyOrderNoCompleteUrl, data)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
queryMyOrderNoCompleteResult = {}
|
queryMyOrderNoCompleteResult = {}
|
||||||
if queryMyOrderNoCompleteResult:
|
if queryMyOrderNoCompleteResult:
|
||||||
|
@ -590,9 +584,9 @@ class select:
|
||||||
获取订单前需要进入订单列表页,获取订单列表页session
|
获取订单前需要进入订单列表页,获取订单列表页session
|
||||||
:return:
|
:return:
|
||||||
"""
|
"""
|
||||||
initNoCompleteUrl = "https://kyfw.12306.cn/otn/queryOrder/initNoComplete"
|
initNoCompleteUrl = self.confUrl["initNoCompleteUrl"]["req_url"]
|
||||||
data = {"_json_att": None}
|
data = {"_json_att": None}
|
||||||
myurllib2.Post(initNoCompleteUrl, data)
|
self.httpClint.send(initNoCompleteUrl, data)
|
||||||
|
|
||||||
# def call_submit_ticket(self, function_name=None):
|
# def call_submit_ticket(self, function_name=None):
|
||||||
# """
|
# """
|
||||||
|
@ -608,7 +602,8 @@ class select:
|
||||||
|
|
||||||
def call_login(self):
|
def call_login(self):
|
||||||
"""登录回调方法"""
|
"""登录回调方法"""
|
||||||
go_login()
|
login = GoLogin(self.httpClint, self.confUrl)
|
||||||
|
login.go_login()
|
||||||
|
|
||||||
def main(self):
|
def main(self):
|
||||||
self.call_login()
|
self.call_login()
|
||||||
|
@ -641,6 +636,9 @@ class select:
|
||||||
except ticketNumOutException as e:
|
except ticketNumOutException as e:
|
||||||
print e.message
|
print e.message
|
||||||
break
|
break
|
||||||
|
except UserPasswordException as e:
|
||||||
|
print e.message
|
||||||
|
break
|
||||||
except ValueError as e:
|
except ValueError as e:
|
||||||
if e.message == "No JSON object could be decoded":
|
if e.message == "No JSON object could be decoded":
|
||||||
print("12306接口无响应,正在重试")
|
print("12306接口无响应,正在重试")
|
||||||
|
|
|
@ -0,0 +1,2 @@
|
||||||
|
class UserPasswordException(Exception):
|
||||||
|
pass
|
|
@ -1,5 +1,8 @@
|
||||||
# -*- coding: utf8 -*-
|
# -*- coding: utf8 -*-
|
||||||
import datetime
|
import datetime
|
||||||
|
import json
|
||||||
|
import socket
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
|
||||||
|
@ -10,45 +13,84 @@ class HTTPClient(object):
|
||||||
:param method:
|
:param method:
|
||||||
:param headers: Must be a dict. Such as headers={'Content_Type':'text/html'}
|
:param headers: Must be a dict. Such as headers={'Content_Type':'text/html'}
|
||||||
"""
|
"""
|
||||||
self.session = requests.session()
|
self.initS()
|
||||||
self._set_header()
|
|
||||||
|
def initS(self):
|
||||||
|
self._s = requests.Session()
|
||||||
|
self._s.headers.update(self._set_header())
|
||||||
|
return self
|
||||||
|
|
||||||
|
def set_cookies(self, **kwargs):
|
||||||
|
"""
|
||||||
|
设置cookies
|
||||||
|
:param kwargs:
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
for k, v in kwargs.items():
|
||||||
|
self._s.cookies.set(k, v)
|
||||||
|
|
||||||
|
def del_cookies(self):
|
||||||
|
"""
|
||||||
|
删除所有的key
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
self._s.cookies.clear()
|
||||||
|
|
||||||
|
def del_cookies_by_key(self, key):
|
||||||
|
"""
|
||||||
|
删除指定key的session
|
||||||
|
:return:
|
||||||
|
"""
|
||||||
|
self._s.cookies.set(key, None)
|
||||||
|
|
||||||
def _set_header(self):
|
def _set_header(self):
|
||||||
"""设置header"""
|
"""设置header"""
|
||||||
add_header = {
|
return {
|
||||||
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
"Content-Type": "application/x-www-form-urlencoded; charset=utf-8",
|
||||||
"X-Requested-With": "xmlHttpRequest",
|
"X-Requested-With": "xmlHttpRequest",
|
||||||
"User-Agent": "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/33.0.1750.154 Safari/537.36",
|
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36",
|
||||||
"Referer": "https://kyfw.12306.cn/otn/login/init",
|
"Referer": "https://kyfw.12306.cn/otn/login/init",
|
||||||
"Accept": "*/*",
|
"Accept": "*/*",
|
||||||
}
|
}
|
||||||
self.session.headers.update(add_header)
|
|
||||||
|
|
||||||
def get(self, url, proxy=None, **kwargs):
|
def setHeaders(self, headers):
|
||||||
if proxy:
|
self._s.headers.update(headers)
|
||||||
proxies = {"http": proxy}
|
return self
|
||||||
else:
|
|
||||||
proxies = ""
|
|
||||||
response = self.session.request(method="GET",
|
|
||||||
url=url,
|
|
||||||
proxies=proxies,
|
|
||||||
**kwargs)
|
|
||||||
if response.status_code == 200:
|
|
||||||
return response.content
|
|
||||||
else:
|
|
||||||
print("请求失败。{0}".format(response))
|
|
||||||
|
|
||||||
def post(self, url, data=None, proxy=None, **kwargs):
|
def getHeadersHost(self):
|
||||||
if proxy:
|
return self._s.headers["Host"]
|
||||||
proxies = {"http": proxy}
|
|
||||||
else:
|
def setHeadersHost(self, host):
|
||||||
proxies = ""
|
self._s.headers.update({"Host": host})
|
||||||
response = self.session.request(method="POST",
|
return self
|
||||||
|
|
||||||
|
def getHeadersReferer(self):
|
||||||
|
return self._s.headers["Referer"]
|
||||||
|
|
||||||
|
def setHeadersReferer(self, referer):
|
||||||
|
self._s.headers.update({"Referer": referer})
|
||||||
|
return self
|
||||||
|
|
||||||
|
def send(self, url, data=None, **kwargs):
|
||||||
|
"""send request to url.If response 200,return response, else return None."""
|
||||||
|
method = "post"if data else "get"
|
||||||
|
response = self._s.request(method=method,
|
||||||
url=url,
|
url=url,
|
||||||
data=data,
|
data=data,
|
||||||
proxies=proxies,
|
|
||||||
**kwargs)
|
**kwargs)
|
||||||
if response.status_code == 200:
|
try:
|
||||||
return response.content
|
if response.content:
|
||||||
|
return json.loads(response.content) if method == "post" else response.content
|
||||||
else:
|
else:
|
||||||
print("请求失败。{0}".format(response))
|
return ""
|
||||||
|
except ValueError as e:
|
||||||
|
if e.message == "No JSON object could be decoded":
|
||||||
|
print("12306接口无响应,正在重试")
|
||||||
|
else:
|
||||||
|
print(e.message)
|
||||||
|
except KeyError as e:
|
||||||
|
print(e.message)
|
||||||
|
except TypeError as e:
|
||||||
|
print(e.message)
|
||||||
|
except socket.error as e:
|
||||||
|
print(e.message)
|
Loading…
Reference in New Issue