126 Commits

Author SHA1 Message Date
Jalin
c87724d170 更换免费打码接口地址 #140 2019-03-07 09:58:06 +08:00
Jalin
cdedfa859d 增加动态获取查询地址 #121 2019-02-20 23:11:55 +08:00
Jalin
484c5f2aa3 add bark to docker env example 2019-02-10 22:32:58 +08:00
Zzde
996fc245cc 增加bark通知 2019-02-02 14:20:15 +08:00
Jalin
66ad9c9e5c 增加网络默认超时时间 2019-01-29 15:37:04 +08:00
Jalin
e81a239609 env.docker 增加时间筛选示例 2019-01-29 15:26:53 +08:00
Breeze Chen
8ed02ea625 通知消息 增加 乘车人,发车时间、到达时间、账号等信息 2019-01-27 16:05:35 +08:00
Breeze Chen
9462e0d72c 增加:发车时间段筛选 2019-01-27 15:34:06 +08:00
Jalin
696f888ad1 优化避免无座检测逻辑 2019-01-25 20:52:30 +08:00
Jalin
68445e1913 优化停留间隔,循环获取乘客问题 2019-01-25 14:10:27 +08:00
Jalin
547300b609 查询时禁止 redirect, 修改 typo #113 2019-01-24 22:22:49 +08:00
Jalin
4398e60305 增加 网络错误时,停留一段时间 #12 2019-01-22 23:12:25 +08:00
Jalin
15f6932f50 更新 docker-compose.yml 2019-01-22 22:34:52 +08:00
Jalin
35c45bbeac 优化错误提示 2019-01-22 22:16:15 +08:00
Jalin
f781c353c3 优化用户检测流程 2019-01-22 21:53:41 +08:00
Jalin
79929da88d Merge branch 'master' of github.com:pjialin/py12306 2019-01-22 13:49:26 +08:00
Jalin
25ba185c27 修改 gitpd 使用 2019-01-22 13:49:14 +08:00
Jalin
33e38da2b2 Merge branch 'pr98' 2019-01-22 12:51:16 +08:00
pjialin
91e68f68c6 Merge pull request #93 from imhy123/master
使用expire判断节点下线
2019-01-22 12:19:14 +08:00
Jalin
5c8a1327dc Merge branch 'pr77' 2019-01-22 11:58:40 +08:00
Jalin
8b90194f65 修改 docker-compose 使用说明 2019-01-22 11:58:26 +08:00
Jan Keromnes
5eb412d45a 更新 readme,配置Gitpod 2019-01-21 19:15:31 +00:00
spirithy
864c239a24 使用expire判断节点下线 2019-01-21 00:10:19 +08:00
Jalin
3a7700c2d4 增加 cdn 检测超时配置 2019-01-19 15:48:45 +08:00
Jalin
c613b54dec 移除子节点乘客检测,web 增加 cdn 显示 2019-01-19 15:11:30 +08:00
Jalin
0c8174592d 增加节点订阅消息丢失重连 2019-01-19 11:56:34 +08:00
Jalin
05ffcffe68 更新 readme,修复子节点用户初始化问题 2019-01-18 22:36:19 +08:00
Jalin
c2cf3ef79c 优化排队错误直接结束排队 2019-01-18 20:17:31 +08:00
Jalin
69f1a84c04 优化 cdn 优化下单逻辑 2019-01-18 19:31:30 +08:00
Jalin
21b092245b 优化座位提示 2019-01-18 19:07:03 +08:00
Jalin
6d47181513 增加通知详细信息 #43 #51 增加新的语音消息服务商 2019-01-18 18:58:37 +08:00
brucedone
89c7e2b0f9 fix the readme typo 2019-01-18 18:32:44 +08:00
brucedone
1ff1676f46 add docker-compose deploy 2019-01-18 18:29:56 +08:00
Jalin
493411ecbf 增加排除车次, 增加通过序号确定唯一联系人 #68 2019-01-18 18:11:50 +08:00
Jalin
b72f7ec5dc 车次支持大小写 2019-01-18 17:29:00 +08:00
Jalin
ecd809dc16 增加未支付订单检测 #75 移除语音重复通知 2019-01-18 17:13:13 +08:00
Jalin
26063f5be6 增加 cdn 查询 2019-01-18 17:08:39 +08:00
Jalin
532e165688 优化提示 2019-01-18 10:39:01 +08:00
Jalin
901ba7f15a Merge https://github.com/a2r0n/py12306 into pr 2019-01-18 10:33:03 +08:00
Jalin
2e89bd1d89 修改名称错误 2019-01-18 10:31:15 +08:00
a2r0n
8139eef48f 增加status.json加载失败时的容错处理. 2019-01-17 14:41:59 +08:00
Jalin
deab8142a1 修改用词 2019-01-17 13:19:21 +08:00
Jalin
e4831d6df9 修复编码问题 优化提示 2019-01-17 11:21:14 +08:00
pjialin
9b7f16d60a Merge pull request #61 from kbj/master
修正提示文案
2019-01-16 23:05:31 +08:00
Weey
e6b30b8427 修正提示文案 2019-01-16 22:32:29 +08:00
Jalin
d4e1ef1049 优化错误输出 2019-01-16 21:18:57 +08:00
Jalin
033c34e035 修复编码不兼容 #14 #57 2019-01-16 19:17:10 +08:00
Jalin
ab84b92665 Merge branch 'master' of github.com:pjialin/py12306 2019-01-16 17:35:19 +08:00
Jalin
5ef5df52a5 修复时间错误可能导致查询无票 2019-01-16 17:34:59 +08:00
pjialin
b93807155e Merge pull request #54 from idealhack/patch-1
docs: update docker command example in README
2019-01-16 17:07:12 +08:00
Yang Li
b3683cbbd2 docs: update docker command example in README 2019-01-16 16:58:57 +08:00
Jalin
25b6bd536a 修复 log 文件不会自动创建 2019-01-16 13:17:06 +08:00
pjialin
d4339e2e38 Merge pull request #48 from feng409/master
移除重复命令
2019-01-16 12:50:11 +08:00
chemf
e7f293edaf 移除重复命令 2019-01-16 12:38:43 +08:00
Jalin
9346546dd9 Merge branch 'master' of github.com:pjialin/py12306 2019-01-15 23:25:01 +08:00
Jalin
45ce85afde 增加邮箱 ssl 支持 #33 2019-01-15 23:24:26 +08:00
pjialin
7437564e64 Merge pull request #42 from kbj/master
新增一个Telegram推送Bot地址
2019-01-15 23:20:25 +08:00
Jalin
d98c98069a 修复提交订单成功后不停止排队 #35 2019-01-15 23:12:12 +08:00
Weey
76a2b0f98a 新增另外一个Telegram推送Bot 2019-01-15 22:34:08 +08:00
Jalin
e4f20350fe 修复不立即登录问题 2019-01-15 12:55:55 +08:00
Jalin
71f2972e3f 更新 readme 2019-01-15 12:27:54 +08:00
Jalin
957c768a43 修改 key 参数规则 2019-01-15 12:27:24 +08:00
Jalin
6685c1570e Merge https://github.com/wonderful60/py12306 into pr 2019-01-15 12:02:30 +08:00
littlefatty.wong
6a5d58605d 给PushBear添加SC_KEY参数用于填写send_key 2019-01-15 11:57:51 +08:00
Jalin
ebca190c26 Merge https://github.com/wonderful60/py12306 into pr 2019-01-15 11:46:45 +08:00
littlefatty.wong
e788bb03cc 将配置文件参数SKEY修改为S_KEY以符合参数规则 2019-01-15 11:35:23 +08:00
littlefatty.wong
02b6169b35 add ServerChan and PushBear支持 2019-01-15 11:13:51 +08:00
Jalin
1719bc718a Merge branch 'master' of github.com:pjialin/py12306 2019-01-15 10:06:33 +08:00
Jalin
21fa98c9d0 增加软座 #17 2019-01-15 10:06:22 +08:00
pjialin
08ff445604 Merge pull request #23 from feng409/master
[FIX] 移除重复代码
2019-01-15 09:39:35 +08:00
chemf
e0b10870dd [FIX] 移除重复代码 2019-01-15 00:26:10 +08:00
Weey
2b5e0cf8be 新增推送到Telegram 2019-01-14 22:34:32 +08:00
Echowxsy
8a0309dd7c 添加钉钉消息通知测试 2019-01-14 20:18:28 +08:00
Echowxsy
18df704c08 添加订单完成时钉钉通知入口 2019-01-14 20:18:28 +08:00
Echowxsy
d7c9a1cbd4 实现钉钉通知方法 2019-01-14 20:18:28 +08:00
Echowxsy
b78f1c6af7 添加钉钉配置文件 2019-01-14 20:18:28 +08:00
Echowxsy
ae418ec3dd 添加钉钉消息通知依赖 2019-01-14 20:18:28 +08:00
Jalin
9f8187a58f Merge branch 'master' of github.com:pjialin/py12306 2019-01-14 18:18:53 +08:00
Jalin
b333b09f83 修复 bug #16,增加任务更新后重新检测乘客 2019-01-14 18:18:26 +08:00
pjialin
82b1f2c3c6 修改错字 2019-01-14 16:07:23 +08:00
Jalin
c8988cd214 增加集群状态支持 2019-01-14 16:06:27 +08:00
Jalin
01cf6cca33 优化查询错误 2019-01-14 12:34:48 +08:00
Jalin
f724139d8c 修复类型转换错误 2019-01-13 12:50:37 +08:00
Jalin
454d5b2fbf 更新 Docker 时区 为 Shanghai 2019-01-13 12:12:13 +08:00
Jalin
70e2c0e736 新增免费打码 2019-01-13 11:56:08 +08:00
Jalin
25049389ff 优化程序流程 2019-01-13 11:02:02 +08:00
Jalin
7c62c1ebad 更新 requirements 2019-01-12 22:56:09 +08:00
Jalin
5838026b25 Merge branch 'master' of github.com:pjialin/py12306 2019-01-12 22:47:54 +08:00
Jalin
a0bdc4ca82 增加 Web 管理页面 2019-01-12 22:47:27 +08:00
pjialin
3b15f260ae Merge pull request #4 from SCUTJcfeng/master
修复Windows signal模块没有SIGHUP的问题
2019-01-12 19:00:31 +08:00
fengjc
fc595d4590 修复Windows signal模块没有SIGHUP的问题 2019-01-12 16:59:17 +08:00
Jalin
b1e826b27a 优化结果 2019-01-12 13:42:09 +08:00
Jalin
c49ac26f0e 增加用户,统计,应用接口 2019-01-12 13:09:59 +08:00
Jalin
5d492c8e9e Merge branch 'master' into develop 2019-01-12 12:19:21 +08:00
Jalin
ee677c56ee 增加验车防止用户重复状态失效 2019-01-12 12:18:39 +08:00
Jalin
a337f972a8 增加 web 目录 2019-01-12 12:16:11 +08:00
Jalin
d5097d2374 修复 windows 下 signal.SIGHUP 错误 2019-01-12 11:08:38 +08:00
Jalin
0c14ebd0ab 修复邮件发送者错误 2019-01-12 10:51:09 +08:00
Jalin
845f303293 增加邮件通知 2019-01-11 21:50:18 +08:00
pjialin
dba893e705 增加 加群链接 2019-01-11 14:03:49 +08:00
Jalin
8e9468406a 优化乘客检测 2019-01-11 13:44:42 +08:00
Jalin
ed04957863 update readme 2019-01-11 12:32:22 +08:00
Jalin
ba489fd9bc 优化提示 2019-01-11 12:22:57 +08:00
Jalin
a87f10c884 增加动态加载配置 2019-01-11 01:42:04 +08:00
Jalin
da469f3ea1 修改验证码为 image64 方式 2019-01-10 20:48:58 +08:00
Jalin
247c46db2a update readme 2019-01-10 18:55:08 +08:00
pjialin
ebdd2b6645 Update README.md 2019-01-10 17:44:46 +08:00
Jalin
bbba2caab6 增加 Docker 使用说明 2019-01-10 17:41:51 +08:00
Jalin
f93fdcfd88 增加 docker 支持 2019-01-10 17:23:13 +08:00
Jalin
b323e3d953 增加 redis key prefix 2019-01-10 14:50:17 +08:00
Jalin
e25dda3a03 更新 requirements.txt 2019-01-10 13:56:20 +08:00
Jalin
2ee8dc5da1 支持分布式集群 2019-01-10 13:47:31 +08:00
Jalin
c804de2e07 修复并发锁问题 2019-01-10 13:01:05 +08:00
Jalin
5de41150d9 优化事件处理 2019-01-10 11:06:07 +08:00
Jalin
6dc09572c1 增加事件通知 2019-01-10 02:13:19 +08:00
Jalin
b0888dd8c6 优化同步处理 2019-01-09 22:20:54 +08:00
Jalin
0b054997c5 完成用户保持 2019-01-09 21:01:12 +08:00
Jalin
da5f4e93c6 完成分布式查询 2019-01-09 11:14:49 +08:00
Jalin
bc29d8a8aa 优化输出信息 2019-01-08 19:12:44 +08:00
Jalin
a0f26435b7 增加单个任务添加多个出发和到达车站 2019-01-08 17:28:08 +08:00
Jalin
054c476c4e 优化错误处理 2019-01-08 16:45:54 +08:00
Jalin
bb136d17ba 增加输出日志到文件 2019-01-08 12:08:06 +08:00
Jalin
33be8c9237 添加 README 2019-01-08 11:02:33 +08:00
Jalin
98bafed66c 增加 requirements.txt 2019-01-08 02:14:16 +08:00
Jalin
bcf1f623d6 Merge branch 'master' of github.com:pjialin/py12306 2019-01-08 02:07:19 +08:00
pjialin
44d9a91da6 Initial commit 2019-01-08 02:04:12 +08:00
21 changed files with 89 additions and 627 deletions

View File

@@ -1,6 +1,10 @@
# 🚂 py12306 购票助手
分布式,多账号,多任务购票
## 前言
今年回家的票明显要难买很多,早早就答应了父母今年的票没问题,到现在一张票没买到,虽然家里已经订了汽车票,让我不用操心,但是想想他们一行还有小孩,心还是很伤的。
这段时间从 12306Bypass 到 testerSunshine 大佬写的 [12306](https://github.com/testerSunshine/12306),还是没买到票,索性就自己写了一个,希望也能帮助到更多人
## Features
- [x] 多日期查询余票
- [x] 自动打码下单
@@ -14,7 +18,7 @@
- [x] 邮件通知
- [x] Web 管理页面
- [x] 微信消息通知
- [ ] 代理池支持 ([pyproxy-async](https://github.com/pjialin/pyproxy-async))
- [ ] 代理池支持
## 使用
py12306 需要运行在 python 3.6 以上版本(其它版本暂未测试)
@@ -32,8 +36,9 @@ cp env.py.example env.py
```
自动打码
(若快已停止服务,目前只能设置**free**打码模式)
free 已对接到打码共享平台,[https://py12306-helper.pjialin.com](https://py12306-helper.pjialin.com/),欢迎参与分享
目前支持免费打码,和若快打码
注:免费打码无法保证持续可用,如失效请手动切换到若快平台,需要先到 [http://www.ruokuai.com](http://www.ruokuai.com/login) 注册一个账号后填写到配置中
语音通知
@@ -143,7 +148,7 @@ docker-compose up -d
### 关于防封
目前查询和登录操作是分开的,查询是不依赖用户是否登录,放在 A 云 T 云容易被限制 ip建议在其它网络环境下运行
QQ 交流群 [780289875](https://jq.qq.com/?_wv=1027&k=5PgzDwV)TG 群 [Py12306 交流](https://t.me/joinchat/F3sSegrF3x8KAmsd1mTu7w)
交流群 [274781597](http://shang.qq.com/wpa/qunwpa?idkey=8eab0b6402096266a62263c1cd452149926adb5cba7a2b7a98a5adc65869addf)
### Online IDE
[![在 Gitpod 中打开](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io#https://github.com/pjialin/py12306)
@@ -151,7 +156,6 @@ QQ 交流群 [780289875](https://jq.qq.com/?_wv=1027&k=5PgzDwV)TG 群 [Py1230
## Thanks
- 感谢大佬 [testerSunshine](https://github.com/testerSunshine/12306),借鉴了部分实现
- 感谢所有提供 pr 的大佬
- 感谢大佬 [zhaipro](https://github.com/zhaipro/easy12306) 的验证码本地识别模型与算法
## License

View File

@@ -2218,51 +2218,3 @@
117.27.241.218
112.65.92.116
52.114.128.43
183.66.109.254
60.28.100.248
111.161.122.240
121.31.28.101
222.218.87.252
113.16.212.251
58.18.254.253
124.225.107.254
14.204.186.174
14.204.185.254
14.204.185.123
220.165.142.253
42.123.108.8
42.123.107.43
120.241.66.115
112.90.135.229
183.56.172.113
27.155.108.102
27.155.108.93
61.132.238.115
113.194.59.199
218.64.94.181
122.191.168.109
42.49.185.169
42.49.185.170
175.154.187.252
118.123.233.254
118.123.237.245
123.138.157.122
113.142.80.223
117.23.2.252
218.26.75.236
218.26.75.206
183.131.124.249
36.25.241.251
153.99.235.91
221.230.143.254
120.221.24.14
218.58.205.182
182.34.127.253
150.138.214.124
61.54.7.192
115.54.16.245
218.12.228.246
121.22.247.254
124.236.28.230
218.60.185.251
42.101.72.9

View File

File diff suppressed because one or more lines are too long

View File

@@ -6,14 +6,12 @@ USER_ACCOUNTS = [
{
'key': 0, # 如使用多个账号 key 不能重复
'user_name': 'your user name',
'password': '忽略',
'type': 'qr' # qr 为扫码登录,填写其他为密码登录
'password': 'your password'
},
# {
# 'key': 'wangwu',
# 'user_name': 'wangwu@qq.com',
# 'password': 'wangwu',
# 'type': ''
# 'password': 'wangwu'
# }
]
@@ -30,8 +28,7 @@ QUERY_JOB_THREAD_ENABLED = 0 # 是否开启多线程查询,开启后第个任
# 打码平台账号
# 目前只支持免费打码接口 和 若快打码注册地址http://www.ruokuai.com/login
AUTO_CODE_PLATFORM = 'free' # 免费填写 free 若快 ruokuai # 免费打码无法保证持续可用,如失效请手动切换 #个人本地打码填写 user,并修改 API_USER_CODE_QCR_API
API_USER_CODE_QCR_API = ''
AUTO_CODE_PLATFORM = 'free' # 免费填写 free 若快 ruokuai # 免费打码无法保证持续可用,如失效请手动切换
AUTO_CODE_ACCOUNT = {
'user': 'your user name',
'pwd': 'your password'

View File

@@ -6,14 +6,12 @@ USER_ACCOUNTS = [
{
'key': 0, # 如使用多个账号 key 不能重复
'user_name': 'your user name',
'password': '忽略',
'type': 'qr' # qr 为扫码登录,填写其他为密码登录
'password': 'your password'
},
# {
# 'key': 'wangwu',
# 'user_name': 'wangwu@qq.com',
# 'password': 'wangwu',
# 'type': ''
# 'password': 'wangwu'
# }
]
@@ -30,8 +28,7 @@ QUERY_JOB_THREAD_ENABLED = 0 # 是否开启多线程查询,开启后第个任
# 打码平台账号
# 目前只支持免费打码接口 和 若快打码注册地址http://www.ruokuai.com/login
AUTO_CODE_PLATFORM = 'free' # 免费填写 free 若快 ruokuai # 免费打码无法保证持续可用,如失效请手动切换; 个人打码填写 user 并修改API_USER_CODE_QCR_API 为自己地址
API_USER_CODE_QCR_API = ''
AUTO_CODE_PLATFORM = 'free' # 免费填写 free 若快 ruokuai # 免费打码无法保证持续可用,如失效请手动切换
AUTO_CODE_ACCOUNT = { # 使用 free 可用省略
'user': 'your user name',
'pwd': 'your password'
@@ -112,19 +109,14 @@ WEB_PORT = 8008 # 监听端口
CDN_ENABLED = 0
CDN_CHECK_TIME_OUT = 1 # 检测单个 cdn 是否可用超时时间
# 是否使用浏览器缓存中的RAIL_EXPIRATION 和 RAIL_DEVICEID
CACHE_RAIL_ID_ENABLED = 0
RAIL_EXPIRATION = '' #浏览12306 网站中的Cache的RAIL_EXPIRATION 值
RAIL_DEVICEID = '' #浏览12306 网站中的Cache的RAIL_DEVICEID 值
# 查询任务
QUERY_JOBS = [
{
# 'job_name': 'bj -> sz', # 任务名称,不填默认会以车站名命名,不可重复
'account_key': 0, # 将会使用指定账号下单
'left_dates': [ # 出发日期 :Array
"2020-01-25",
"2020-01-26",
"2019-01-25",
"2019-01-26",
],
'stations': { # 车站 支持多个车站同时查询 :Dict or :List
'left': '北京',
@@ -140,7 +132,7 @@ QUERY_JOBS = [
# }],
'members': [ # 乘客姓名,会根据当前账号自动识别乘客类型 购买儿童票 设置两个相同的姓名即可,程序会自动识别 如 ['张三', '张三']
"张三",
#"*王五", #在姓名前加*表示学生购买成人票
"王五",
# 7, # 支持通过序号确定唯一乘客,序号查看可通过 python main.py -t 登录成功之后在 runtime/user/ 下找到对应的 用户名_passengers.json 文件,找到对应的 code 填入
],
'allow_less_member': 0, # 是否允许余票不足时提交部分乘客

View File

@@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
import os
import signal
import sys
@@ -10,19 +11,16 @@ from py12306.log.order_log import OrderLog
def app_available_check():
# return True # Debug
if Config().IS_DEBUG:
return True
now = time_now()
if now.weekday() == 1 and (now.hour > 23 and now.minute > 30 or now.hour < 5):
if now.hour >= 23 or now.hour < 6:
CommonLog.add_quick_log(CommonLog.MESSAGE_12306_IS_CLOSED.format(time_now())).flush()
open_time = datetime.datetime(now.year, now.month, now.day, 5)
open_time = datetime.datetime(now.year, now.month, now.day, 6)
if open_time < now:
open_time += datetime.timedelta(1)
sleep((open_time - now).seconds)
elif 1 < now.hour < 5:
CommonLog.add_quick_log(CommonLog.MESSAGE_12306_IS_CLOSED.format(time_now())).flush()
open_time = datetime.datetime(now.year, now.month, now.day, 5)
sleep((open_time - now).seconds)
return True
@@ -83,7 +81,7 @@ class App:
@classmethod
def check_auto_code(cls):
if Config().AUTO_CODE_PLATFORM == 'free' or Config().AUTO_CODE_PLATFORM == 'user': return True
if Config().AUTO_CODE_PLATFORM == 'free': return True
if not Config().AUTO_CODE_ACCOUNT.get('user') or not Config().AUTO_CODE_ACCOUNT.get('pwd'):
return False
return True

View File

@@ -22,8 +22,6 @@ class Config:
QUERY_JOB_THREAD_ENABLED = 0
# 打码平台账号
AUTO_CODE_PLATFORM = ''
#用户打码平台地址
API_USER_CODE_QCR_API = ''
AUTO_CODE_ACCOUNT = {'user': '', 'pwd': ''}
# 输出日志到文件
OUT_PUT_LOG_TO_FILE_ENABLED = 0
@@ -96,10 +94,6 @@ class Config:
CDN_ITEM_FILE = PROJECT_DIR + 'data/cdn.txt'
CDN_ENABLED_AVAILABLE_ITEM_FILE = QUERY_DATA_DIR + 'available.json'
CACHE_RAIL_ID_ENABLED = 0
RAIL_EXPIRATION = ''
RAIL_DEVICEID = ''
# Default time out
TIME_OUT_OF_REQUEST = 5
@@ -219,10 +213,6 @@ class Config:
def is_cdn_enabled():
return Config().CDN_ENABLED
@staticmethod
def is_cache_rail_id_enabled():
return Config().CACHE_RAIL_ID_ENABLED
class EnvLoader:
envs = []

View File

@@ -2,7 +2,7 @@ import math
import random
from py12306.config import Config
from py12306.helpers.api import API_FREE_CODE_QCR_API
from py12306.helpers.api import *
from py12306.helpers.request import Request
from py12306.log.common_log import CommonLog
from py12306.vender.ruokuai.main import RKClient
@@ -25,7 +25,7 @@ class OCR:
:return:
"""
self = cls()
if Config().AUTO_CODE_PLATFORM == 'free' or Config().AUTO_CODE_PLATFORM == 'user':
if Config().AUTO_CODE_PLATFORM == 'free':
return self.get_image_by_free_site(img)
return self.get_img_position_by_ruokuai(img)
@@ -56,16 +56,22 @@ class OCR:
def get_image_by_free_site(self, img):
data = {
'img': img
'base64': img
}
if Config().AUTO_CODE_PLATFORM == 'free':
response = self.session.post(API_FREE_CODE_QCR_API, data=data, timeout=30)
else:
response = self.session.post(Config().API_USER_CODE_QCR_API, data=data, timeout=30)
response = self.session.post(API_FREE_CODE_QCR_API, json=data)
result = response.json()
if result.get('msg') == 'success':
pos = result.get('result')
return self.get_image_position_by_offset(pos)
if result.get('success') and result.get('data.check'):
check_data = {
'check': result.get('data.check'),
'img_buf': img,
'logon': 1,
'type': 'D'
}
check_response = self.session.post(API_FREE_CODE_QCR_API_CHECK, json=check_data)
check_result = check_response.json()
if check_result.get('res'):
position = check_result.get('res')
return position.replace('(', '').replace(')', '').split(',')
CommonLog.print_auto_code_fail(CommonLog.MESSAGE_GET_RESPONSE_FROM_FREE_AUTO_CODE)
return None

View File

@@ -1,4 +1,6 @@
# coding=utf-8
# 查询余票
import time
HOST_URL_OF_12306 = 'kyfw.12306.cn'
BASE_URL_OF_12306 = 'https://' + HOST_URL_OF_12306
@@ -13,18 +15,6 @@ API_BASE_LOGIN = {
API_USER_LOGIN_CHECK = BASE_URL_OF_12306 + '/otn/login/conf'
API_AUTH_QRCODE_BASE64_DOWNLOAD = {
'url': BASE_URL_OF_12306 + '/passport/web/create-qr64'
}
API_AUTH_QRCODE_CHECK = {
'url': BASE_URL_OF_12306 + '/passport/web/checkqr'
}
API_USER_LOGIN = {
'url': BASE_URL_OF_12306 + '/otn/login/userLogin'
}
API_AUTH_CODE_DOWNLOAD = {
'url': BASE_URL_OF_12306 + '/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&_={random}'
}
@@ -50,11 +40,12 @@ API_GET_QUEUE_COUNT = BASE_URL_OF_12306 + '/otn/confirmPassenger/getQueueCount'
API_CONFIRM_SINGLE_FOR_QUEUE = BASE_URL_OF_12306 + '/otn/confirmPassenger/confirmSingleForQueue'
API_QUERY_ORDER_WAIT_TIME = BASE_URL_OF_12306 + '/otn/confirmPassenger/queryOrderWaitTime?{}' # 排队查询
API_QUERY_INIT_PAGE = BASE_URL_OF_12306 + '/otn/leftTicket/init'
# API_GET_BROWSER_DEVICE_ID = BASE_URL_OF_12306 + '/otn/HttpZF/logdevice'
API_GET_BROWSER_DEVICE_ID = 'https://12306-rail-id-v2.pjialin.com/'
API_FREE_CODE_QCR_API = 'https://12306-ocr.pjialin.com/check/'
API_NOTIFICATION_BY_VOICE_CODE = 'http://ali-voice.showapi.com/sendVoice?'
API_NOTIFICATION_BY_VOICE_CODE_DINGXIN = 'http://yuyin2.market.alicloudapi.com/dx/voice_notice'
# API_FREE_CODE_QCR_API = 'http://60.205.200.159/api' # 19-03-07 接口已失效
API_FREE_CODE_QCR_API = 'https://12306.jiedanba.cn/api/v2/getCheck'
API_FREE_CODE_QCR_API_CHECK = 'http://check.huochepiao.360.cn/img_vcode'
API_CHECK_CDN_AVAILABLE = 'https://{}/otn/dynamicJs/omseuuq'

View File

@@ -33,7 +33,6 @@ class AuthCode:
return self.retry_get_auth_code()
answer = ','.join(map(str, position))
if not self.check_code(answer):
return self.retry_get_auth_code()
return position
@@ -47,7 +46,6 @@ class AuthCode:
url = API_AUTH_CODE_BASE64_DOWNLOAD.format(random=random.random())
# code_path = self.data_path + 'code.png'
try:
self.session.cookies.clear_session_cookies()
UserLog.add_quick_log(UserLog.MESSAGE_DOWNLAODING_THE_CODE).flush()
# response = self.session.save_to_file(url, code_path) # TODO 返回错误情况
response = self.session.get(url)

View File

@@ -33,11 +33,6 @@ class Notification():
self = cls()
self.send_email_by_smtp(to, title, content)
@classmethod
def send_email_with_qrcode(cls, to, title='', qrcode_path=''):
self = cls()
self.send_email_by_smtp_with_qrcode(to, title, qrcode_path)
@classmethod
def send_to_telegram(cls, content=''):
self = cls()
@@ -130,55 +125,15 @@ class Notification():
message.set_content(content)
try:
server = smtplib.SMTP(Config().EMAIL_SERVER_HOST)
server.login(Config().EMAIL_SERVER_USER, Config().EMAIL_SERVER_PASSWORD)
server.ehlo()
server.starttls()
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()
def send_email_by_smtp_with_qrcode(self, to, title, qrcode_path):
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.image import MIMEImage
to = to if isinstance(to, list) else [to]
message = MIMEMultipart()
message['Subject'] = title
message['From'] = Config().EMAIL_SENDER
message['To'] = ", ".join(to)
htmlFile = """
<html>
<head></head>
<body>
<p>
这是你的二维码
</p>
<p>
<br /><img src="cid:0", width=200, height=200 ></p>
</body>
</html>
"""
htmlApart = MIMEText(htmlFile, 'html')
imageFile = qrcode_path
imageApart = MIMEImage(open(imageFile, 'rb').read(), imageFile.split('.')[-1])
imageApart.add_header('Content-ID', '<0>')
message.attach(imageApart)
message.attach(htmlApart)
try:
server = smtplib.SMTP(Config().EMAIL_SERVER_HOST)
server.ehlo()
server.starttls()
server.login(Config().EMAIL_SERVER_USER, Config().EMAIL_SERVER_PASSWORD)
server.send_message(message)
server.quit()
CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_EMAIL_WITH_QRCODE_SUCCESS).flush()
self.push_bark(CommonLog.MESSAGE_SEND_EMAIL_WITH_QRCODE_SUCCESS)
except Exception as e:
CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_EMAIL_FAIL.format(e)).flush()
def send_dingtalk_by_webbook(self, content):
from dingtalkchatbot.chatbot import DingtalkChatbot
webhook = Config().DINGTALK_WEBHOOK

View File

@@ -1,106 +0,0 @@
# -*- coding: utf-8 -*-
import png
def print_qrcode(path):
"""
将二维码输出到控制台
需要终端尺寸足够大才能显示
:param path: 二维码图片路径 (PNG 格式)
:return: None
"""
reader = png.Reader(path)
width, height, rows, info = reader.read()
lines = list(rows)
planes = info['planes'] # 通道数
threshold = (2 ** info['bitdepth']) / 2 # 色彩阈值
# 识别二维码尺寸
x_flag = -1 # x 边距标志
y_flag = -1 # y 边距标志
x_white = -1 # 定位图案白块 x 坐标
y_white = -1 # 定位图案白块 y 坐标
i = y_flag
while i < height:
if y_white > 0 and x_white > 0:
break
j = x_flag
while j < width:
total = 0
for k in range(planes):
px = lines[i][j * planes + k]
total += px
avg = total / planes
black = avg < threshold
if y_white > 0 and x_white > 0:
break
if x_flag > 0 > x_white and not black:
x_white = j
if x_flag == -1 and black:
x_flag = j
if y_flag > 0 > y_white and not black:
y_white = i
if y_flag == -1 and black:
y_flag = i
if x_flag > 0 and y_flag > 0:
i += 1
j += 1
i += 1
assert y_white - y_flag == x_white - x_flag
scale = y_white - y_flag
assert width - x_flag == height - y_flag
module_count = int((width - x_flag * 2) / scale)
whole_white = ''
whole_black = ' '
down_black = ''
up_black = ''
dual_flag = False
last_line = []
output = '\n'
for i in range(module_count + 2):
output += up_black
output += '\n'
i = y_flag
while i < height - y_flag:
if dual_flag:
output += whole_white
t = 0
j = x_flag
while j < width - x_flag:
total = 0
for k in range(planes):
px = lines[i][j * planes + k]
total += px
avg = total / planes
black = avg < threshold
if dual_flag:
last_black = last_line[t]
if black and last_black:
output += whole_black
elif black and not last_black:
output += down_black
elif not black and last_black:
output += up_black
elif not black and not last_black:
output += whole_white
else:
last_line[t:t+1] = [black]
t = t + 1
j += scale
if dual_flag:
output += whole_white + '\n'
dual_flag = not dual_flag
i += scale
output += whole_white
for i in range(module_count):
output += up_black if last_line[i] else whole_white
output += whole_white + '\n'
print(output, flush=True)

View File

@@ -33,9 +33,8 @@ class Request(HTMLSession):
return response
def add_response_hook(self, hook):
hooks = self.hooks['response']
if not isinstance(hooks, list):
hooks = [hooks]
exist_hooks = self.hooks['response']
if not isinstance(exist_hooks, list): hooks = [exist_hooks]
hooks.append(hook)
self.hooks['response'] = hooks
return self
@@ -77,18 +76,3 @@ class Request(HTMLSession):
url = url.replace(HOST_URL_OF_12306, cdn)
return self.request(method, url, headers={'Host': HOST_URL_OF_12306}, verify=False, **kwargs)
def dump_cookies(self):
cookies = []
for _, item in self.cookies._cookies.items():
for _, urls in item.items():
for _, cookie in urls.items():
from http.cookiejar import Cookie
assert isinstance(cookie, Cookie)
if cookie.domain:
cookies.append({
'name': cookie.name,
'value': cookie.value,
'url': 'https://' + cookie.domain + cookie.path,
})
return cookies

View File

@@ -35,8 +35,6 @@ class CommonLog(BaseLog):
MESSAGE_SEND_EMAIL_SUCCESS = '邮件发送成功,请检查收件箱'
MESSAGE_SEND_EMAIL_FAIL = '邮件发送失败,请手动检查配置,错误原因 {}'
MESSAGE_SEND_EMAIL_WITH_QRCODE_SUCCESS = '二维码邮件发送成功,请检查收件箱扫描登陆'
MESSAGE_SEND_TELEGRAM_SUCCESS = 'Telegram推送成功'
MESSAGE_SEND_TELEGRAM_FAIL = 'Telegram推送失败错误原因 {}'
@@ -96,24 +94,15 @@ class CommonLog(BaseLog):
self.add_quick_log('多线程查询: {}'.format(get_true_false_text(Config().QUERY_JOB_THREAD_ENABLED, enable, disable)))
self.add_quick_log('CDN 状态: {}'.format(get_true_false_text(Config().CDN_ENABLED, enable, disable))).flush()
self.add_quick_log('通知状态:')
if Config().NOTIFICATION_BY_VOICE_CODE:
self.add_quick_log(
'语音验证码: {}'.format(get_true_false_text(Config().NOTIFICATION_BY_VOICE_CODE, enable, disable)))
if Config().EMAIL_ENABLED:
self.add_quick_log('邮件通知: {}'.format(get_true_false_text(Config().EMAIL_ENABLED, enable, disable)))
if Config().DINGTALK_ENABLED:
self.add_quick_log('钉钉通知: {}'.format(get_true_false_text(Config().DINGTALK_ENABLED, enable, disable)))
if Config().TELEGRAM_ENABLED:
self.add_quick_log('Telegram通知: {}'.format(get_true_false_text(Config().TELEGRAM_ENABLED, enable, disable)))
if Config().SERVERCHAN_ENABLED:
self.add_quick_log(
'ServerChan通知: {}'.format(get_true_false_text(Config().SERVERCHAN_ENABLED, enable, disable)))
if Config().BARK_ENABLED:
self.add_quick_log('Bark通知: {}'.format(get_true_false_text(Config().BARK_ENABLED, enable, disable)))
if Config().PUSHBEAR_ENABLED:
self.add_quick_log(
'PushBear通知: {}'.format(get_true_false_text(Config().PUSHBEAR_ENABLED, enable, disable)))
self.add_quick_log().flush(sep='\t\t')
self.add_quick_log(
'语音验证码: {}'.format(get_true_false_text(Config().NOTIFICATION_BY_VOICE_CODE, enable, disable)))
self.add_quick_log('邮件通知: {}'.format(get_true_false_text(Config().EMAIL_ENABLED, enable, disable)))
self.add_quick_log('钉钉通知: {}'.format(get_true_false_text(Config().DINGTALK_ENABLED, enable, disable)))
self.add_quick_log('Telegram通知: {}'.format(get_true_false_text(Config().TELEGRAM_ENABLED, enable, disable)))
self.add_quick_log('ServerChan通知: {}'.format(get_true_false_text(Config().SERVERCHAN_ENABLED, enable, disable)))
self.add_quick_log('Bark通知: {}'.format(get_true_false_text(Config().BARK_ENABLED, enable, disable)))
self.add_quick_log(
'PushBear通知: {}'.format(get_true_false_text(Config().PUSHBEAR_ENABLED, enable, disable))).flush(sep='\t\t')
self.add_quick_log('查询间隔: {}'.format(Config().QUERY_INTERVAL))
self.add_quick_log('用户心跳检测间隔: {}'.format(Config().USER_HEARTBEAT_INTERVAL))
self.add_quick_log('WEB 管理页面: {}'.format(get_true_false_text(Config().WEB_ENABLE, enable, disable)))
@@ -141,10 +130,3 @@ class CommonLog(BaseLog):
self.add_quick_log('打码失败: 错误原因 {reason}'.format(reason=reason))
self.flush()
return self
@classmethod
def print_auth_code_info(cls, reason):
self = cls()
self.add_quick_log('打码信息: {reason}'.format(reason=reason))
self.flush()
return self

View File

@@ -13,9 +13,6 @@ class UserLog(BaseLog):
MESSAGE_DOWNLAODING_THE_CODE = '正在下载验证码...'
MESSAGE_CODE_AUTH_FAIL = '验证码验证失败 错误原因: {}'
MESSAGE_CODE_AUTH_SUCCESS = '验证码验证成功 开始登录...'
MESSAGE_QRCODE_DOWNLOADING = '正在下载二维码...'
MESSAGE_QRCODE_DOWNLOADED = '二维码保存在: {},请使用手机客户端扫描'
MESSAGE_QRCODE_FAIL = '二维码获取失败: {}, {} 秒后重试'
MESSAGE_LOGIN_FAIL = '登录失败 错误原因: {}'
MESSAGE_LOADED_USER = '正在尝试恢复用户: {}'
MESSAGE_LOADED_USER_SUCCESS = '用户恢复成功: {}'

View File

@@ -1,9 +1,6 @@
import asyncio
import urllib
# from py12306.config import UserType
from pyppeteer import launch
from py12306.config import Config
from py12306.helpers.api import *
from py12306.helpers.func import *
@@ -13,73 +10,6 @@ from py12306.log.common_log import CommonLog
from py12306.log.order_log import OrderLog
class DomBounding:
def __init__(self, rect: dict) -> None:
super().__init__()
self.x = rect['x']
self.y = rect['y']
self.width = rect['width']
self.height = rect['height']
@singleton
class Browser:
def __init__(self) -> None:
super().__init__()
def request_init_slide(self, session, html):
""" 处理滑块,拿到 session_id, sig """
OrderLog.add_quick_log('正在识别滑动验证码...').flush()
return asyncio.get_event_loop_policy().new_event_loop().run_until_complete(
self.__request_init_slide(session, html))
async def __request_init_slide(self, session, html):
""" 异步获取 """
browser = await launch(headless=True, autoClose=True, handleSIGINT=False, handleSIGTERM=False,
handleSIGHUP=False)
page = await browser.newPage()
await page.setViewport({'width': 1200, 'height': 1080})
await page.setRequestInterception(True)
load_js = """() => {
__old = navigator.userAgent; navigator.__defineGetter__('userAgent', () => __old.replace('Headless', ''));
__old = navigator.appVersion; navigator.__defineGetter__('appVersion', () => __old.replace('Headless', ''));
var __newProto = navigator.__proto__; delete __newProto.webdriver; navigator.__proto__ = __newProto;
}"""
source_url = 'https://kyfw.12306.cn/otn'
html = html.replace('href="/otn', f'href="{source_url}').replace('src="/otn', f'src="{source_url}')
@page.on('framenavigated')
async def on_frame_navigated(_):
await page.evaluate(load_js)
@page.on('request')
async def on_request(req):
if req.url.startswith(API_INITDC_URL):
if req.isNavigationRequest():
await page.setCookie(*session.dump_cookies())
return await req.respond({'body': html})
return await req.continue_()
await page.goto(API_INITDC_URL, timeout=30000)
slide_btn = await page.waitForSelector('#slide_passcode .nc-lang-cnt', timeout=30000)
rect = await slide_btn.boundingBox()
pos = DomBounding(rect)
pos.x += 5
pos.y += 10
await page.mouse.move(pos.x, pos.y)
await page.mouse.down()
await page.mouse.move(pos.x + pos.width, pos.y, steps=30)
await page.mouse.up()
# 等待获取 session id
await page.evaluate(
'async () => {let i = 3 * 10; while (!csessionid && i >= 0) await new Promise(resolve => setTimeout(resolve, 100), i--);}')
ret = await page.evaluate('JSON.stringify({session_id: csessionid, sig: sig})')
await page.close()
await browser.close()
return json.loads(ret)
class Order:
"""
处理下单
@@ -111,7 +41,6 @@ class Order:
assert isinstance(user, UserJob)
self.query_ins = query
self.user_ins = user
self.is_slide = False
self.make_passenger_ticket_str()
@@ -134,25 +63,10 @@ class Order:
return self.order_did_success()
elif not order_request_res:
return
init_res, self.is_slide, init_html = self.user_ins.request_init_dc_page()
if not init_res:
return
slide_info = {}
if self.is_slide:
try:
slide_info = Browser().request_init_slide(self.session, init_html)
if not slide_info.get('session_id') or not slide_info.get('sig'):
raise Exception()
except Exception:
OrderLog.add_quick_log('滑动验证码识别失败').flush()
return
OrderLog.add_quick_log('滑动验证码识别成功').flush()
if not self.check_order_info(slide_info):
return
if not self.get_queue_count():
return
if not self.confirm_single_for_queue():
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
@@ -171,8 +85,7 @@ class Order:
# 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,
self.user_ins.user_name)
normal_message = OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_OF_EMAIL_CONTENT.format(self.order_id, self.user_ins.user_name)
if Config().EMAIL_ENABLED: # 邮件通知
Notification.send_email(Config().EMAIL_RECEIVER, OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_TITLE,
normal_message + info_message)
@@ -187,7 +100,7 @@ class Order:
Notification.push_bear(Config().PUSHBEAR_KEY, OrderLog.MESSAGE_ORDER_SUCCESS_NOTIFICATION_TITLE,
normal_message + info_message)
if Config().BARK_ENABLED:
Notification.push_bark(normal_message + info_message)
Notification.push_bark(normal_message+info_message)
if Config().NOTIFICATION_BY_VOICE_CODE: # 语音通知
if Config().NOTIFICATION_VOICE_CODE_TYPE == 'dingxin':
@@ -225,7 +138,7 @@ class Order:
}
response = self.session.post(API_SUBMIT_ORDER_REQUEST, data)
result = response.json()
if result.get('data') == '0':
if result.get('data') == 'N':
OrderLog.add_quick_log(OrderLog.MESSAGE_SUBMIT_ORDER_REQUEST_SUCCESS).flush()
return True
else:
@@ -239,7 +152,7 @@ class Order:
result.get('messages', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR))).flush()
return False
def check_order_info(self, slide_info=None):
def check_order_info(self):
"""
cancel_flag=2
bed_level_order_num=000000000000000000000000000000
@@ -262,12 +175,6 @@ class Order:
'_json_att': '',
'REPEAT_SUBMIT_TOKEN': self.user_ins.global_repeat_submit_token
}
if self.is_slide:
data.update({
'sessionId': slide_info['session_id'],
'sig': slide_info['sig'],
'scene': 'nc_login',
})
response = self.session.post(API_CHECK_ORDER_INFO, data)
result = response.json()
if result.get('data.submitStatus'): # 成功
@@ -344,7 +251,7 @@ class Order:
if ticket_number != '充足' and int(ticket_number) <= 0:
if self.query_ins.current_seat == SeatType.NO_SEAT: # 允许无座
ticket_number = ticket[1]
if not int(ticket_number): # 跳过无座
if not int(ticket_number): # 跳过无座
OrderLog.add_quick_log(OrderLog.MESSAGE_GET_QUEUE_INFO_NO_SEAT).flush()
return False
@@ -467,12 +374,10 @@ class Order:
elif 'waitTime' in result_data:
# 计算等待时间
wait_time = int(result_data.get('waitTime'))
if wait_time == -1: # 成功
if wait_time == -1 or wait_time == -100: # 成功
# /otn/confirmPassenger/resultOrderForDcQueue 请求订单状态 目前不需要
# 不应该走到这
return order_id
elif wait_time == -100: # 重新获取订单号
pass
elif wait_time >= 0: # 等待
OrderLog.add_quick_log(
OrderLog.MESSAGE_QUERY_ORDER_WAIT_TIME_WAITING.format(result_data.get('waitCount', 0),
@@ -524,12 +429,11 @@ class Order:
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,{enc_str}_'.format(
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'],
enc_str=passenger['enc_str'],
passenger_mobile=passenger['mobile']
)
passenger_tickets.append(tmp_str)

View File

@@ -1,5 +1,5 @@
import sys
from datetime import timedelta
from datetime import datetime
from py12306.app import app_available_check
from py12306.cluster.cluster import Cluster
@@ -66,8 +66,6 @@ class Job:
INDEX_LEFT_TIME = 8
INDEX_ARRIVE_TIME = 9
max_buy_time = 32
def __init__(self, info, query):
self.cluster = Cluster()
self.query = query
@@ -138,29 +136,11 @@ class Job:
QueryLog.add_log('\n').flush(sep='\t\t', publish=False)
if Const.IS_TEST: return
def judge_date_legal(self, date):
date_now = datetime.datetime.now()
date_query = datetime.datetime.strptime(str(date), "%Y-%m-%d")
diff = (date_query - date_now).days
if date_now.day == date_query.day:
diff = 0
if diff < 0:
msg = '乘车日期错误,比当前时间还早!!'
QueryLog.add_quick_log(msg).flush(publish=False)
raise RuntimeError(msg)
elif diff > self.max_buy_time:
msg = '乘车日期错误,超出一个月预售期!!'
QueryLog.add_quick_log(msg).flush(publish=False)
raise RuntimeError(msg)
else:
return date_query.strftime("%Y-%m-%d")
def query_by_date(self, date):
"""
通过日期进行查询
:return:
"""
date = self.judge_date_legal(date)
from py12306.helpers.cdn import Cdn
QueryLog.add_log(('\n' if not is_main_thread() else '') + QueryLog.MESSAGE_QUERY_START_BY_DATE.format(date,
self.left_station,

View File

@@ -1,4 +1,3 @@
from base64 import b64decode
from py12306.config import Config
from py12306.cluster.cluster import Cluster
from py12306.app import app_available_check
@@ -6,7 +5,7 @@ from py12306.helpers.func import *
from py12306.helpers.request import Request
from py12306.log.query_log import QueryLog
from py12306.query.job import Job
from py12306.helpers.api import API_QUERY_INIT_PAGE, API_GET_BROWSER_DEVICE_ID
from py12306.helpers.api import API_QUERY_INIT_PAGE
@singleton
@@ -30,7 +29,6 @@ class Query:
def __init__(self):
self.session = Request()
self.request_device_id()
self.cluster = Cluster()
self.update_query_interval()
self.update_query_jobs()
@@ -119,32 +117,6 @@ class Query:
self.jobs.append(job)
return job
def request_device_id(self):
"""
获取加密后的浏览器特征 ID
:return:
"""
response = self.session.get(API_GET_BROWSER_DEVICE_ID)
if response.status_code == 200:
try:
result = json.loads(response.text)
response = self.session.get(b64decode(result['id']).decode())
if response.text.find('callbackFunction') >= 0:
result = response.text[18:-2]
result = json.loads(result)
if not Config().is_cache_rail_id_enabled():
self.session.cookies.update({
'RAIL_EXPIRATION': result.get('exp'),
'RAIL_DEVICEID': result.get('dfp'),
})
else:
self.session.cookies.update({
'RAIL_EXPIRATION': Config().RAIL_EXPIRATION,
'RAIL_DEVICEID': Config().RAIL_DEVICEID,
})
except:
return False
@classmethod
def wait_for_ready(cls):
self = cls()
@@ -177,14 +149,11 @@ class Query:
return self.api_type
response = self.session.get(API_QUERY_INIT_PAGE)
if response.status_code == 200:
res = re.search(r'var CLeftTicketUrl = \'(.*)\';', response.text)
res = re.search(r'var CLeftTicketUrl = \'(leftTicket/queryX)\';', response.text)
try:
self.api_type = res.group(1)
except IndexError:
pass
if not self.api_type:
QueryLog.add_quick_log('查询地址获取失败, 正在重新获取...').flush()
sleep(1)
return cls.get_query_api_type()
# def get_jobs_from_cluster(self):

View File

@@ -1,4 +1,4 @@
import base64
import json
import pickle
import re
from os import path
@@ -11,7 +11,6 @@ from py12306.helpers.event import Event
from py12306.helpers.func import *
from py12306.helpers.request import Request
from py12306.helpers.type import UserType
from py12306.helpers.qrcode import print_qrcode
from py12306.log.order_log import OrderLog
from py12306.log.user_log import UserLog
from py12306.log.common_log import CommonLog
@@ -24,7 +23,6 @@ class UserJob:
key = None
user_name = ''
password = ''
type = 'qr'
user = None
info = {} # 用户信息
last_heartbeat = None
@@ -32,7 +30,6 @@ class UserJob:
user_loaded = False # 用户是否已加载成功
passengers = []
retry_time = 3
retry_count = 0
login_num = 0 # 尝试登录次数
# Init page
@@ -54,7 +51,6 @@ class UserJob:
self.key = str(info.get('key'))
self.user_name = info.get('user_name')
self.password = info.get('password')
self.type = info.get('type')
def update_user(self):
from py12306.user.user import User
@@ -115,10 +111,7 @@ class UserJob:
if expire: UserLog.print_user_expired()
self.is_ready = False
UserLog.print_start_login(user=self)
if self.type == 'qr':
return self.qr_login()
else:
return self.login()
return self.login()
def login(self):
"""
@@ -132,7 +125,6 @@ class UserJob:
}
answer = AuthCode.get_auth_code(self.session)
data['answer'] = answer
self.request_device_id()
response = self.session.post(API_BASE_LOGIN.get('url'), data)
result = response.json()
if result.get('result_code') == 0: # 登录成功
@@ -157,82 +149,6 @@ class UserJob:
return False
def qr_login(self):
self.request_device_id()
image_uuid, png_path = self.download_code()
while True:
data = {
'RAIL_DEVICEID': self.session.cookies.get('RAIL_DEVICEID'),
'RAIL_EXPIRATION': self.session.cookies.get('RAIL_EXPIRATION'),
'uuid': image_uuid,
'appid': 'otn'
}
response = self.session.post(API_AUTH_QRCODE_CHECK.get('url'), data)
result = response.json()
result_code = int(result.get('result_code'))
if result_code == 0:
time.sleep(2)
elif result_code == 1:
UserLog.add_quick_log('请确认登录').flush()
time.sleep(2)
elif result_code == 2:
break
elif result_code == 3:
try:
os.remove(png_path)
except Exception as e:
UserLog.add_quick_log('无法删除文件: {}'.format(e)).flush()
image_uuid = self.download_code()
try:
os.remove(png_path)
except Exception as e:
UserLog.add_quick_log('无法删除文件: {}'.format(e)).flush()
self.session.get(API_USER_LOGIN, allow_redirects=True)
new_tk = self.auth_uamtk()
user_name = self.auth_uamauthclient(new_tk)
self.update_user_info({'user_name': user_name})
self.session.get(API_USER_LOGIN, allow_redirects=True)
self.login_did_success()
return True
def download_code(self):
try:
UserLog.add_quick_log(UserLog.MESSAGE_QRCODE_DOWNLOADING).flush()
response = self.session.post(API_AUTH_QRCODE_BASE64_DOWNLOAD.get('url'), data={'appid': 'otn'})
result = response.json()
if result.get('result_code') == '0':
img_bytes = base64.b64decode(result.get('image'))
try:
os.mkdir(Config().USER_DATA_DIR + '/qrcode')
except FileExistsError:
pass
png_path = path.normpath(Config().USER_DATA_DIR + '/qrcode/%d.png' % time.time())
with open(png_path, 'wb') as file:
file.write(img_bytes)
file.close()
if os.name == 'nt':
os.startfile(png_path)
else:
print_qrcode(png_path)
UserLog.add_log(UserLog.MESSAGE_QRCODE_DOWNLOADED.format(png_path)).flush()
Notification.send_email_with_qrcode(Config().EMAIL_RECEIVER, '你有新的登录二维码啦!', png_path)
self.retry_count = 0
return result.get('uuid'), png_path
raise KeyError('获取二维码失败: {}'.format(result.get('result_message')))
except Exception as e:
UserLog.add_quick_log(
UserLog.MESSAGE_QRCODE_FAIL.format(e, self.retry_time)).flush()
self.retry_count = self.retry_count + 1
if self.retry_count == 20:
self.retry_count = 0
try:
os.remove(self.get_cookie_path())
except:
pass
time.sleep(self.retry_time)
return self.download_code()
def check_user_is_login(self):
response = self.session.get(API_USER_LOGIN_CHECK)
is_login = response.json().get('data.is_login', False) == 'Y'
@@ -244,10 +160,7 @@ class UserJob:
return is_login
def auth_uamtk(self):
response = self.session.post(API_AUTH_UAMTK.get('url'), {'appid': 'otn'}, headers={
'Referer': 'https://kyfw.12306.cn/otn/passport?redirect=/otn/login/userLogin',
'Origin': 'https://kyfw.12306.cn'
})
response = self.session.post(API_AUTH_UAMTK.get('url'), {'appid': 'otn'})
result = response.json()
if result.get('newapptk'):
return result.get('newapptk')
@@ -262,36 +175,6 @@ class UserJob:
# TODO 处理获取失败情况
return False
def request_device_id(self):
"""
获取加密后的浏览器特征 ID
:return:
"""
response = self.session.get(API_GET_BROWSER_DEVICE_ID)
if response.status_code == 200:
try:
result = json.loads(response.text)
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/94.0.4606.61 Safari/537.36"
}
self.session.headers.update(headers)
response = self.session.get(base64.b64decode(result['id']).decode())
if response.text.find('callbackFunction') >= 0:
result = response.text[18:-2]
result = json.loads(result)
if not Config().is_cache_rail_id_enabled():
self.session.cookies.update({
'RAIL_EXPIRATION': result.get('exp'),
'RAIL_DEVICEID': result.get('dfp'),
})
else:
self.session.cookies.update({
'RAIL_EXPIRATION': Config().RAIL_EXPIRATION,
'RAIL_DEVICEID': Config().RAIL_DEVICEID,
})
except:
return False
def login_did_success(self):
"""
用户登录成功
@@ -429,7 +312,7 @@ class UserJob:
UserLog.MESSAGE_GET_USER_PASSENGERS_FAIL.format(
result.get('messages', CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR), self.retry_time)).flush()
if Config().is_slave():
self.load_user_from_remote() # 加载最新 cookie
self.load_user_from_remote() # 加载最新 cookie
stay_second(self.retry_time)
return self.get_user_passengers()
@@ -442,8 +325,7 @@ class UserJob:
name: '项羽',
type: 1,
id_card: 0000000000000000000,
type_text: '成人',
enc_str: 'aaaaaa'
type_text: '成人'
}]
"""
self.get_user_passengers()
@@ -451,11 +333,6 @@ class UserJob:
for member in members:
is_member_code = is_number(member)
if not is_member_code:
if member[0] == "*":
audlt = 1
member = member[1:]
else:
audlt = 0
child_check = array_dict_find_by_key_value(results, 'name', member)
if not is_member_code and child_check:
new_member = child_check.copy()
@@ -466,8 +343,6 @@ class UserJob:
passenger = array_dict_find_by_key_value(self.passengers, 'code', member)
else:
passenger = array_dict_find_by_key_value(self.passengers, 'passenger_name', member)
if audlt:
passenger['passenger_type'] = UserType.ADULT
if not passenger:
UserLog.add_quick_log(
UserLog.MESSAGE_USER_PASSENGERS_IS_INVALID.format(self.user_name, member)).flush()
@@ -478,8 +353,7 @@ class UserJob:
'id_card_type': passenger.get('passenger_id_type_code'),
'mobile': passenger.get('mobile_no'),
'type': passenger.get('passenger_type'),
'type_text': dict_find_key_by_value(UserType.dicts, int(passenger.get('passenger_type'))),
'enc_str': passenger.get('allEncStr')
'type_text': dict_find_key_by_value(UserType.dicts, int(passenger.get('passenger_type')))
}
results.append(new_member)
@@ -499,16 +373,12 @@ class UserJob:
# 系统忙,请稍后重试
if html.find('系统忙,请稍后重试') != -1:
OrderLog.add_quick_log(OrderLog.MESSAGE_REQUEST_INIT_DC_PAGE_FAIL).flush() # 重试无用,直接跳过
return False, False, html
return False
try:
self.global_repeat_submit_token = token.groups()[0]
self.ticket_info_for_passenger_form = json.loads(form.groups()[0].replace("'", '"'))
self.order_request_dto = json.loads(order.groups()[0].replace("'", '"'))
except:
return False, False, html # TODO Error
pass # TODO Error
slide_val = re.search(r"var if_check_slide_passcode.*='(\d?)'", html)
is_slide = False
if slide_val:
is_slide = int(slide_val[1]) == 1
return True, is_slide, html
return True

View File

@@ -32,7 +32,7 @@ class User:
@classmethod
def run(cls):
self = cls()
# app_available_check() 用户系统不休息
app_available_check()
self.start()
pass

View File

@@ -1,4 +1,4 @@
-i https://pypi.tuna.tsinghua.edu.cn/simple
-i http://mirrors.aliyun.com/pypi/simple/ --trusted-host mirrors.aliyun.com
appdirs==1.4.3
beautifulsoup4==4.7.0
bs4==0.0.1
@@ -12,12 +12,12 @@ Flask-JWT-Extended==3.15.0
idna==2.8
itsdangerous==1.1.0
Jinja2==2.10
lxml==4.6.5
lxml==4.3.0
MarkupSafe==1.1.0
parse==1.9.0
pyee==5.0.0
PyJWT==1.7.1
pyppeteer-box==0.0.27
pyppeteer==0.0.25
pyquery==1.4.0
redis==3.0.1
requests==2.21.0
@@ -25,10 +25,9 @@ requests-html==0.9.0
six==1.12.0
soupsieve==1.6.2
tqdm==4.28.1
urllib3==1.24.2
urllib3==1.24.1
w3lib==1.19.0
websockets==7.0
Werkzeug==0.15.5
Werkzeug==0.14.1
DingtalkChatbot==1.3.0
lightpush==0.1.3
pypng
lightpush==0.1.3