rename old
This commit is contained in:
82
old_py12306/helpers/OCR.py
Normal file
82
old_py12306/helpers/OCR.py
Normal file
@@ -0,0 +1,82 @@
|
||||
import math
|
||||
import random
|
||||
|
||||
from py12306.config import Config
|
||||
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
|
||||
|
||||
|
||||
class OCR:
|
||||
"""
|
||||
图片识别
|
||||
"""
|
||||
session = None
|
||||
|
||||
def __init__(self):
|
||||
self.session = Request()
|
||||
|
||||
@classmethod
|
||||
def get_img_position(cls, img):
|
||||
"""
|
||||
获取图像坐标
|
||||
:param img_path:
|
||||
:return:
|
||||
"""
|
||||
self = cls()
|
||||
if Config().AUTO_CODE_PLATFORM == 'free':
|
||||
return self.get_image_by_free_site(img)
|
||||
return self.get_img_position_by_ruokuai(img)
|
||||
|
||||
def get_img_position_by_ruokuai(self, img):
|
||||
ruokuai_account = Config().AUTO_CODE_ACCOUNT
|
||||
soft_id = '119671'
|
||||
soft_key = '6839cbaca1f942f58d2760baba5ed987'
|
||||
rc = RKClient(ruokuai_account.get('user'), ruokuai_account.get('pwd'), soft_id, soft_key)
|
||||
result = rc.rk_create(img, 6113)
|
||||
if "Result" in result:
|
||||
return self.get_image_position_by_offset(list(result['Result']))
|
||||
CommonLog.print_auto_code_fail(result.get("Error", CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR))
|
||||
return None
|
||||
|
||||
def get_image_position_by_offset(self, offsets):
|
||||
positions = []
|
||||
width = 75
|
||||
height = 75
|
||||
for offset in offsets:
|
||||
random_x = random.randint(-5, 5)
|
||||
random_y = random.randint(-5, 5)
|
||||
offset = int(offset)
|
||||
x = width * ((offset - 1) % 4 + 1) - width / 2 + random_x
|
||||
y = height * math.ceil(offset / 4) - height / 2 + random_y
|
||||
positions.append(int(x))
|
||||
positions.append(int(y))
|
||||
return positions
|
||||
|
||||
def get_image_by_free_site(self, img):
|
||||
data = {
|
||||
'base64': img
|
||||
}
|
||||
response = self.session.post(API_FREE_CODE_QCR_API, json=data)
|
||||
result = response.json()
|
||||
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
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
pass
|
||||
# code_result = AuthCode.get_auth_code()
|
||||
0
old_py12306/helpers/__init__.py
Normal file
0
old_py12306/helpers/__init__.py
Normal file
53
old_py12306/helpers/api.py
Normal file
53
old_py12306/helpers/api.py
Normal file
@@ -0,0 +1,53 @@
|
||||
# coding=utf-8
|
||||
# 查询余票
|
||||
import time
|
||||
|
||||
HOST_URL_OF_12306 = 'kyfw.12306.cn'
|
||||
BASE_URL_OF_12306 = 'https://' + HOST_URL_OF_12306
|
||||
|
||||
LEFT_TICKETS = {
|
||||
"url": BASE_URL_OF_12306 + "/otn/{type}?leftTicketDTO.train_date={left_date}&leftTicketDTO.from_station={left_station}&leftTicketDTO.to_station={arrive_station}&purpose_codes=ADULT",
|
||||
}
|
||||
|
||||
API_BASE_LOGIN = {
|
||||
"url": BASE_URL_OF_12306 + '/passport/web/login',
|
||||
}
|
||||
|
||||
API_USER_LOGIN_CHECK = BASE_URL_OF_12306 + '/otn/login/conf'
|
||||
|
||||
API_AUTH_CODE_DOWNLOAD = {
|
||||
'url': BASE_URL_OF_12306 + '/passport/captcha/captcha-image?login_site=E&module=login&rand=sjrand&_={random}'
|
||||
}
|
||||
API_AUTH_CODE_BASE64_DOWNLOAD = BASE_URL_OF_12306 + '/passport/captcha/captcha-image64?login_site=E&module=login&rand=sjrand&_={random}'
|
||||
API_AUTH_CODE_CHECK = {
|
||||
'url': BASE_URL_OF_12306 + '/passport/captcha/captcha-check?answer={answer}&rand=sjrand&login_site=E&_={random}'
|
||||
}
|
||||
API_AUTH_UAMTK = {
|
||||
'url': BASE_URL_OF_12306 + '/passport/web/auth/uamtk'
|
||||
}
|
||||
API_AUTH_UAMAUTHCLIENT = {
|
||||
'url': BASE_URL_OF_12306 + '/otn/uamauthclient'
|
||||
}
|
||||
|
||||
API_USER_INFO = {
|
||||
'url': BASE_URL_OF_12306 + '/otn/modifyUser/initQueryUserInfoApi'
|
||||
}
|
||||
API_USER_PASSENGERS = BASE_URL_OF_12306 + '/otn/confirmPassenger/getPassengerDTOs'
|
||||
API_SUBMIT_ORDER_REQUEST = BASE_URL_OF_12306 + '/otn/leftTicket/submitOrderRequest'
|
||||
API_CHECK_ORDER_INFO = BASE_URL_OF_12306 + '/otn/confirmPassenger/checkOrderInfo'
|
||||
API_INITDC_URL = BASE_URL_OF_12306 + '/otn/confirmPassenger/initDc' # 生成订单时需要先请求这个页面
|
||||
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_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'
|
||||
83
old_py12306/helpers/auth_code.py
Normal file
83
old_py12306/helpers/auth_code.py
Normal file
@@ -0,0 +1,83 @@
|
||||
import random
|
||||
import time
|
||||
|
||||
from requests.exceptions import SSLError
|
||||
|
||||
from py12306.config import Config
|
||||
from py12306.helpers.OCR import OCR
|
||||
from py12306.helpers.api import *
|
||||
from py12306.helpers.request import Request
|
||||
from py12306.helpers.func import *
|
||||
from py12306.log.common_log import CommonLog
|
||||
from py12306.log.user_log import UserLog
|
||||
|
||||
|
||||
class AuthCode:
|
||||
"""
|
||||
验证码类
|
||||
"""
|
||||
session = None
|
||||
data_path = None
|
||||
retry_time = 5
|
||||
|
||||
def __init__(self, session):
|
||||
self.data_path = Config().RUNTIME_DIR
|
||||
self.session = session
|
||||
|
||||
@classmethod
|
||||
def get_auth_code(cls, session):
|
||||
self = cls(session)
|
||||
img = self.download_code()
|
||||
position = OCR.get_img_position(img)
|
||||
if not position: # 打码失败
|
||||
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
|
||||
|
||||
def retry_get_auth_code(self): # TODO 安全次数检测
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_RETRY_AUTH_CODE.format(self.retry_time)).flush()
|
||||
time.sleep(self.retry_time)
|
||||
return self.get_auth_code(self.session)
|
||||
|
||||
def download_code(self):
|
||||
url = API_AUTH_CODE_BASE64_DOWNLOAD.format(random=random.random())
|
||||
# code_path = self.data_path + 'code.png'
|
||||
try:
|
||||
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)
|
||||
result = response.json()
|
||||
if result.get('image'):
|
||||
return result.get('image')
|
||||
raise SSLError('返回数据为空')
|
||||
except SSLError as e:
|
||||
UserLog.add_quick_log(
|
||||
UserLog.MESSAGE_DOWNLAOD_AUTH_CODE_FAIL.format(e, self.retry_time)).flush()
|
||||
time.sleep(self.retry_time)
|
||||
return self.download_code()
|
||||
|
||||
def check_code(self, answer):
|
||||
"""
|
||||
校验验证码
|
||||
:return:
|
||||
"""
|
||||
url = API_AUTH_CODE_CHECK.get('url').format(answer=answer, random=time_int())
|
||||
response = self.session.get(url)
|
||||
result = response.json()
|
||||
if result.get('result_code') == '4':
|
||||
UserLog.add_quick_log(UserLog.MESSAGE_CODE_AUTH_SUCCESS).flush()
|
||||
return True
|
||||
else:
|
||||
# {'result_message': '验证码校验失败', 'result_code': '5'}
|
||||
UserLog.add_quick_log(
|
||||
UserLog.MESSAGE_CODE_AUTH_FAIL.format(result.get('result_message'))).flush()
|
||||
self.session.cookies.clear_session_cookies()
|
||||
|
||||
return False
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
code_result = AuthCode.get_auth_code()
|
||||
234
old_py12306/helpers/cdn.py
Normal file
234
old_py12306/helpers/cdn.py
Normal file
@@ -0,0 +1,234 @@
|
||||
import random
|
||||
import json
|
||||
from datetime import timedelta
|
||||
from os import path
|
||||
|
||||
from py12306.cluster.cluster import Cluster
|
||||
from py12306.config import Config
|
||||
from py12306.app import app_available_check
|
||||
from py12306.helpers.api import API_CHECK_CDN_AVAILABLE, HOST_URL_OF_12306
|
||||
from py12306.helpers.func import *
|
||||
from py12306.helpers.request import Request
|
||||
from py12306.log.common_log import CommonLog
|
||||
|
||||
|
||||
@singleton
|
||||
class Cdn:
|
||||
"""
|
||||
CDN 管理
|
||||
"""
|
||||
items = []
|
||||
available_items = []
|
||||
unavailable_items = []
|
||||
recheck_available_items = []
|
||||
recheck_unavailable_items = []
|
||||
retry_time = 3
|
||||
is_ready = False
|
||||
is_finished = False
|
||||
is_ready_num = 10 # 当可用超过 10,已准备好
|
||||
is_alive = True
|
||||
is_recheck = False
|
||||
|
||||
safe_stay_time = 0.2
|
||||
retry_num = 1
|
||||
thread_num = 5
|
||||
check_time_out = 3
|
||||
|
||||
last_check_at = 0
|
||||
save_second = 5
|
||||
check_keep_second = 60 * 60 * 24
|
||||
|
||||
def __init__(self):
|
||||
self.cluster = Cluster()
|
||||
self.init_config()
|
||||
create_thread_and_run(self, 'watch_cdn', False)
|
||||
|
||||
def init_data(self):
|
||||
self.items = []
|
||||
self.available_items = []
|
||||
self.unavailable_items = []
|
||||
self.is_finished = False
|
||||
self.is_ready = False
|
||||
self.is_recheck = False
|
||||
|
||||
def init_config(self):
|
||||
self.check_time_out = Config().CDN_CHECK_TIME_OUT
|
||||
|
||||
def update_cdn_status(self, auto=False):
|
||||
if auto:
|
||||
self.init_config()
|
||||
if Config().is_cdn_enabled():
|
||||
self.run()
|
||||
else:
|
||||
self.destroy()
|
||||
|
||||
@classmethod
|
||||
def run(cls):
|
||||
self = cls()
|
||||
app_available_check()
|
||||
self.is_alive = True
|
||||
self.start()
|
||||
pass
|
||||
|
||||
def start(self):
|
||||
if not Config.is_cdn_enabled(): return
|
||||
self.load_items()
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_CDN_START_TO_CHECK.format(len(self.items))).flush()
|
||||
self.restore_items()
|
||||
for i in range(self.thread_num): # 多线程
|
||||
create_thread_and_run(jobs=self, callback_name='check_available', wait=False)
|
||||
|
||||
def load_items(self):
|
||||
with open(Config().CDN_ITEM_FILE, encoding='utf-8') as f:
|
||||
for line, val in enumerate(f):
|
||||
self.items.append(val.rstrip('\n'))
|
||||
|
||||
def restore_items(self):
|
||||
"""
|
||||
恢复已有数据
|
||||
:return: bool
|
||||
"""
|
||||
result = False
|
||||
if path.exists(Config().CDN_ENABLED_AVAILABLE_ITEM_FILE):
|
||||
with open(Config().CDN_ENABLED_AVAILABLE_ITEM_FILE, encoding='utf-8') as f:
|
||||
result = f.read()
|
||||
try:
|
||||
result = json.loads(result)
|
||||
except json.JSONDecodeError as e:
|
||||
result = {}
|
||||
|
||||
# if Config.is_cluster_enabled(): # 集群不用同步 cdn
|
||||
# result = self.get_data_from_cluster()
|
||||
|
||||
if result:
|
||||
self.last_check_at = result.get('last_check_at', '')
|
||||
if self.last_check_at: self.last_check_at = str_to_time(self.last_check_at)
|
||||
self.available_items = result.get('items', [])
|
||||
self.unavailable_items = result.get('fail_items', [])
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_CDN_RESTORE_SUCCESS.format(self.last_check_at)).flush()
|
||||
return True
|
||||
return False
|
||||
|
||||
# def get_data_from_cluster(self):
|
||||
# available_items = self.cluster.session.smembers(Cluster.KEY_CDN_AVAILABLE_ITEMS)
|
||||
# last_time = self.cluster.session.get(Cluster.KEY_CDN_LAST_CHECK_AT, '')
|
||||
# if available_items and last_time:
|
||||
# return {'items': available_items, 'last_check_at': last_time}
|
||||
# return False
|
||||
|
||||
def is_need_to_recheck(self):
|
||||
"""
|
||||
是否需要重新检查 cdn
|
||||
:return:
|
||||
"""
|
||||
if self.last_check_at and (
|
||||
time_now() - self.last_check_at).seconds > self.check_keep_second:
|
||||
return True
|
||||
return False
|
||||
|
||||
def get_unchecked_item(self):
|
||||
if not self.is_recheck:
|
||||
items = list(set(self.items) - set(self.available_items) - set(self.unavailable_items))
|
||||
else:
|
||||
items = list(set(self.items) - set(self.recheck_available_items) - set(self.recheck_unavailable_items))
|
||||
if items: return random.choice(items)
|
||||
return None
|
||||
|
||||
def check_available(self):
|
||||
while True and self.is_alive:
|
||||
item = self.get_unchecked_item()
|
||||
if not item: return self.check_did_finished()
|
||||
self.check_item_available(item)
|
||||
|
||||
def watch_cdn(self):
|
||||
"""
|
||||
监控 cdn 状态,自动重新检测
|
||||
:return:
|
||||
"""
|
||||
while True:
|
||||
if self.is_alive and not self.is_recheck and self.is_need_to_recheck(): # 重新检测
|
||||
self.is_recheck = True
|
||||
self.is_finished = False
|
||||
CommonLog.add_quick_log(
|
||||
CommonLog.MESSAGE_CDN_START_TO_RECHECK.format(len(self.items), time_now())).flush()
|
||||
for i in range(self.thread_num): # 多线程
|
||||
create_thread_and_run(jobs=self, callback_name='check_available', wait=False)
|
||||
stay_second(self.retry_num)
|
||||
|
||||
def destroy(self):
|
||||
"""
|
||||
关闭 CDN
|
||||
:return:
|
||||
"""
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_CDN_CLOSED).flush()
|
||||
self.is_alive = False
|
||||
self.init_data()
|
||||
|
||||
def check_item_available(self, item, try_num=0):
|
||||
session = Request()
|
||||
response = session.get(API_CHECK_CDN_AVAILABLE.format(item), headers={'Host': HOST_URL_OF_12306},
|
||||
timeout=self.check_time_out,
|
||||
verify=False)
|
||||
|
||||
if response.status_code == 200:
|
||||
if not self.is_recheck:
|
||||
self.available_items.append(item)
|
||||
else:
|
||||
self.recheck_available_items.append(item)
|
||||
if not self.is_ready: self.check_is_ready()
|
||||
elif try_num < self.retry_num: # 重试
|
||||
stay_second(self.safe_stay_time)
|
||||
return self.check_item_available(item, try_num + 1)
|
||||
else:
|
||||
if not self.is_recheck:
|
||||
self.unavailable_items.append(item)
|
||||
else:
|
||||
self.recheck_unavailable_items.append(item)
|
||||
if not self.is_recheck and (
|
||||
not self.last_check_at or (time_now() - self.last_check_at).seconds > self.save_second):
|
||||
self.save_available_items()
|
||||
stay_second(self.safe_stay_time)
|
||||
|
||||
def check_did_finished(self):
|
||||
self.is_ready = True
|
||||
if not self.is_finished:
|
||||
self.is_finished = True
|
||||
if self.is_recheck:
|
||||
self.is_recheck = False
|
||||
self.available_items = self.recheck_available_items
|
||||
self.unavailable_items = self.recheck_unavailable_items
|
||||
self.recheck_available_items = []
|
||||
self.recheck_unavailable_items = []
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_CDN_CHECKED_SUCCESS.format(len(self.available_items))).flush()
|
||||
self.save_available_items()
|
||||
|
||||
def save_available_items(self):
|
||||
self.last_check_at = time_now()
|
||||
data = {'items': self.available_items, 'fail_items': self.unavailable_items,
|
||||
'last_check_at': str(self.last_check_at)}
|
||||
with open(Config().CDN_ENABLED_AVAILABLE_ITEM_FILE, 'w') as f:
|
||||
f.write(json.dumps(data))
|
||||
|
||||
# if Config.is_master():
|
||||
# self.cluster.session.sadd(Cluster.KEY_CDN_AVAILABLE_ITEMS, self.available_items)
|
||||
# self.cluster.session.set(Cluster.KEY_CDN_LAST_CHECK_AT, time_now())
|
||||
|
||||
def check_is_ready(self):
|
||||
if len(self.available_items) > self.is_ready_num:
|
||||
self.is_ready = True
|
||||
else:
|
||||
self.is_ready = False
|
||||
|
||||
@classmethod
|
||||
def get_cdn(cls):
|
||||
self = cls()
|
||||
if self.is_ready and self.available_items:
|
||||
return random.choice(self.available_items)
|
||||
return None
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
# Const.IS_TEST = True
|
||||
Cdn.run()
|
||||
while not Cdn().is_finished:
|
||||
stay_second(1)
|
||||
48
old_py12306/helpers/event.py
Normal file
48
old_py12306/helpers/event.py
Normal file
@@ -0,0 +1,48 @@
|
||||
from py12306.helpers.func import *
|
||||
from py12306.config import Config
|
||||
|
||||
|
||||
@singleton
|
||||
class Event():
|
||||
"""
|
||||
处理事件
|
||||
"""
|
||||
# 事件
|
||||
KEY_JOB_DESTROY = 'job_destroy'
|
||||
KEY_USER_JOB_DESTROY = 'user_job_destroy'
|
||||
KEY_USER_LOADED = 'user_loaded'
|
||||
cluster = None
|
||||
|
||||
def __init__(self):
|
||||
from py12306.cluster.cluster import Cluster
|
||||
self.cluster = Cluster()
|
||||
|
||||
def job_destroy(self, data={}, callback=False): # 停止查询任务
|
||||
from py12306.query.query import Query
|
||||
if Config().is_cluster_enabled() and not callback:
|
||||
return self.cluster.publish_event(self.KEY_JOB_DESTROY, data) # 通知其它节点退出
|
||||
|
||||
job = Query.job_by_name(data.get('name'))
|
||||
if job:
|
||||
job.destroy()
|
||||
|
||||
def user_loaded(self, data={}, callback=False): # 用户初始化完成
|
||||
if Config().is_cluster_enabled() and not callback:
|
||||
return self.cluster.publish_event(self.KEY_USER_LOADED, data) # 通知其它节点退出
|
||||
from py12306.query.query import Query
|
||||
|
||||
if not Config().is_cluster_enabled() or Config().is_master():
|
||||
query = Query.wait_for_ready()
|
||||
for job in query.jobs:
|
||||
if job.account_key == data.get('key'):
|
||||
create_thread_and_run(job, 'check_passengers', Const.IS_TEST) # 检查乘客信息 防止提交订单时才检查
|
||||
stay_second(1)
|
||||
|
||||
def user_job_destroy(self, data={}, callback=False):
|
||||
from py12306.user.user import User
|
||||
if Config().is_cluster_enabled() and not callback:
|
||||
return self.cluster.publish_event(self.KEY_JOB_DESTROY, data) # 通知其它节点退出
|
||||
|
||||
user = User.get_user(data.get('key'))
|
||||
if user:
|
||||
user.destroy()
|
||||
203
old_py12306/helpers/func.py
Normal file
203
old_py12306/helpers/func.py
Normal file
@@ -0,0 +1,203 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
import datetime
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
import random
|
||||
import threading
|
||||
import functools
|
||||
import time
|
||||
|
||||
from time import sleep
|
||||
from types import MethodType
|
||||
|
||||
|
||||
def singleton(cls):
|
||||
"""
|
||||
将一个类作为单例
|
||||
来自 https://wiki.python.org/moin/PythonDecoratorLibrary#Singleton
|
||||
"""
|
||||
|
||||
cls.__new_original__ = cls.__new__
|
||||
|
||||
@functools.wraps(cls.__new__)
|
||||
def singleton_new(cls, *args, **kw):
|
||||
it = cls.__dict__.get('__it__')
|
||||
if it is not None:
|
||||
return it
|
||||
|
||||
cls.__it__ = it = cls.__new_original__(cls, *args, **kw)
|
||||
it.__init_original__(*args, **kw)
|
||||
return it
|
||||
|
||||
cls.__new__ = singleton_new
|
||||
cls.__init_original__ = cls.__init__
|
||||
cls.__init__ = object.__init__
|
||||
|
||||
return cls
|
||||
|
||||
|
||||
# 座位 # TODO
|
||||
# def get_number_by_name(name):
|
||||
# return config.SEAT_TYPES[name]
|
||||
|
||||
|
||||
# def get_seat_name_by_number(number): # TODO remove config
|
||||
# return [k for k, v in config.SEAT_TYPES.items() if v == number].pop()
|
||||
|
||||
|
||||
# 初始化间隔
|
||||
def init_interval_by_number(number):
|
||||
if isinstance(number, dict):
|
||||
min = float(number.get('min'))
|
||||
max = float(number.get('max'))
|
||||
else:
|
||||
min = number / 2
|
||||
max = number
|
||||
return {
|
||||
'min': min,
|
||||
'max': max
|
||||
}
|
||||
|
||||
|
||||
def get_interval_num(interval, decimal=2):
|
||||
return round(random.uniform(interval.get('min'), interval.get('max')), decimal)
|
||||
|
||||
|
||||
def stay_second(second, call_back=None):
|
||||
sleep(second)
|
||||
if call_back:
|
||||
return call_back()
|
||||
|
||||
|
||||
def sleep_forever():
|
||||
"""
|
||||
当不是主线程时,假象停止
|
||||
:return:
|
||||
"""
|
||||
if not is_main_thread():
|
||||
while True: sleep(10000000)
|
||||
|
||||
|
||||
def is_main_thread():
|
||||
return threading.current_thread() == threading.main_thread()
|
||||
|
||||
|
||||
def current_thread_id():
|
||||
return threading.current_thread().ident
|
||||
|
||||
|
||||
def time_now():
|
||||
return datetime.datetime.now()
|
||||
|
||||
|
||||
def timestamp_to_time(timestamp):
|
||||
time_struct = time.localtime(timestamp)
|
||||
return time.strftime('%Y-%m-%d %H:%M:%S', time_struct)
|
||||
|
||||
|
||||
def get_file_modify_time(filePath):
|
||||
timestamp = os.path.getmtime(filePath)
|
||||
return timestamp_to_time(timestamp)
|
||||
|
||||
|
||||
def get_file_total_line_num(file, encoding='utf-8'):
|
||||
with open(file, 'r', encoding=encoding) as f:
|
||||
return len(f.readlines())
|
||||
|
||||
|
||||
def touch_file(path):
|
||||
with open(path, 'a'): pass
|
||||
|
||||
|
||||
def pick_file_lines(file, lines):
|
||||
return [x for i, x in enumerate(file) if i in lines]
|
||||
|
||||
|
||||
def str_to_time(str):
|
||||
return datetime.datetime.strptime(str, '%Y-%m-%d %H:%M:%S.%f')
|
||||
|
||||
|
||||
def time_int():
|
||||
return int(time.time())
|
||||
|
||||
|
||||
def is_number(val):
|
||||
if isinstance(val, int): return val
|
||||
if isinstance(val, str): return val.isdigit()
|
||||
return False
|
||||
|
||||
|
||||
def create_thread_and_run(jobs, callback_name, wait=True, daemon=True, args=(), kwargs={}):
|
||||
threads = []
|
||||
if not isinstance(jobs, list): jobs = [jobs]
|
||||
for job in jobs:
|
||||
thread = threading.Thread(target=getattr(job, callback_name), args=args, kwargs=kwargs)
|
||||
thread.setDaemon(daemon)
|
||||
thread.start()
|
||||
threads.append(thread)
|
||||
if wait:
|
||||
for thread in threads: thread.join()
|
||||
|
||||
|
||||
def jobs_do(jobs, do):
|
||||
if not isinstance(jobs, list): jobs = [jobs]
|
||||
for job in jobs:
|
||||
getattr(job, do)()
|
||||
|
||||
|
||||
def dict_find_key_by_value(data, value, default=None):
|
||||
result = [k for k, v in data.items() if v == value]
|
||||
return result.pop() if len(result) else default
|
||||
|
||||
|
||||
def objects_find_object_by_key_value(objects, key, value, default=None):
|
||||
result = [obj for obj in objects if getattr(obj, key) == value]
|
||||
return result.pop() if len(result) else default
|
||||
|
||||
|
||||
def dict_count_key_num(data: dict, key, like=False):
|
||||
count = 0
|
||||
for k in data.keys():
|
||||
if like:
|
||||
if k.find(key) >= 0: count += 1
|
||||
elif k == key:
|
||||
count += 1
|
||||
return count
|
||||
|
||||
|
||||
def array_dict_find_by_key_value(data, key, value, default=None):
|
||||
result = [v for k, v in enumerate(data) if key in v and v[key] == value]
|
||||
return result.pop() if len(result) else default
|
||||
|
||||
|
||||
def get_true_false_text(value, true='', false=''):
|
||||
if value: return true
|
||||
return false
|
||||
|
||||
|
||||
def sleep_forever_when_in_test():
|
||||
if Const.IS_TEST: sleep_forever()
|
||||
|
||||
|
||||
def expand_class(cls, key, value, keep_old=True):
|
||||
if (keep_old):
|
||||
setattr(cls, 'old_' + key, getattr(cls, key))
|
||||
setattr(cls, key, MethodType(value, cls))
|
||||
return cls
|
||||
|
||||
|
||||
def available_value(value):
|
||||
if isinstance(value, str) or isinstance(value, bytes):
|
||||
return value
|
||||
return str(value)
|
||||
|
||||
|
||||
def md5(value):
|
||||
return hashlib.md5(json.dumps(value).encode()).hexdigest()
|
||||
|
||||
|
||||
@singleton
|
||||
class Const:
|
||||
IS_TEST = False
|
||||
IS_TEST_NOTIFICATION = False
|
||||
206
old_py12306/helpers/notification.py
Normal file
206
old_py12306/helpers/notification.py
Normal file
@@ -0,0 +1,206 @@
|
||||
import urllib
|
||||
|
||||
from py12306.config import Config
|
||||
from py12306.helpers.api import *
|
||||
from py12306.helpers.request import Request
|
||||
from py12306.log.common_log import CommonLog
|
||||
|
||||
|
||||
class Notification():
|
||||
"""
|
||||
通知类
|
||||
"""
|
||||
session = None
|
||||
|
||||
def __init__(self):
|
||||
self.session = Request()
|
||||
|
||||
@classmethod
|
||||
def voice_code(cls, phone, name='', content=''):
|
||||
self = cls()
|
||||
if Config().NOTIFICATION_VOICE_CODE_TYPE == 'dingxin':
|
||||
self.send_voice_code_of_dingxin(phone, name=name, info=content)
|
||||
else:
|
||||
self.send_voice_code_of_yiyuan(phone, name=name, content=content)
|
||||
|
||||
@classmethod
|
||||
def dingtalk_webhook(cls, content=''):
|
||||
self = cls()
|
||||
self.send_dingtalk_by_webbook(content=content)
|
||||
|
||||
@classmethod
|
||||
def send_email(cls, to, title='', content=''):
|
||||
self = cls()
|
||||
self.send_email_by_smtp(to, title, content)
|
||||
|
||||
@classmethod
|
||||
def send_to_telegram(cls, content=''):
|
||||
self = cls()
|
||||
self.send_to_telegram_bot(content=content)
|
||||
|
||||
@classmethod
|
||||
def server_chan(cls, skey='', title='', content=''):
|
||||
self = cls()
|
||||
self.send_serverchan(skey=skey, title=title, content=content)
|
||||
|
||||
@classmethod
|
||||
def push_bear(cls, skey='', title='', content=''):
|
||||
self = cls()
|
||||
self.send_pushbear(skey=skey, title=title, content=content)
|
||||
|
||||
@classmethod
|
||||
def push_bark(cls, content=''):
|
||||
self = cls()
|
||||
self.push_to_bark(content)
|
||||
|
||||
def send_voice_code_of_yiyuan(self, phone, name='', content=''):
|
||||
"""
|
||||
发送语音验证码
|
||||
购买地址 https://market.aliyun.com/products/57126001/cmapi019902.html?spm=5176.2020520132.101.5.37857218O6iJ3n
|
||||
:return:
|
||||
"""
|
||||
appcode = Config().NOTIFICATION_API_APP_CODE
|
||||
if not appcode:
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_EMPTY_APP_CODE).flush()
|
||||
return False
|
||||
body = {
|
||||
'userName': name,
|
||||
'mailNo': content
|
||||
}
|
||||
params = {
|
||||
'content': body,
|
||||
'mobile': phone,
|
||||
'sex': 2,
|
||||
'tNum': 'T170701001056'
|
||||
}
|
||||
response = self.session.request(url=API_NOTIFICATION_BY_VOICE_CODE + urllib.parse.urlencode(params),
|
||||
method='GET', headers={'Authorization': 'APPCODE {}'.format(appcode)})
|
||||
result = response.json()
|
||||
response_message = result.get('showapi_res_body.remark')
|
||||
if response.status_code in [400, 401, 403]:
|
||||
return CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_FORBID).flush()
|
||||
if response.status_code == 200 and result.get('showapi_res_body.flag'):
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_SEND_SUCCESS.format(response_message)).flush()
|
||||
return True
|
||||
else:
|
||||
return CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_SEND_FAIL.format(response_message)).flush()
|
||||
|
||||
def send_voice_code_of_dingxin(self, phone, name='', info={}):
|
||||
"""
|
||||
发送语音验证码 ( 鼎信 )
|
||||
购买地址 https://market.aliyun.com/products/56928004/cmapi026600.html?spm=5176.2020520132.101.2.51547218rkAXxy
|
||||
:return:
|
||||
"""
|
||||
appcode = Config().NOTIFICATION_API_APP_CODE
|
||||
if not appcode:
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_EMPTY_APP_CODE).flush()
|
||||
return False
|
||||
data = {
|
||||
'tpl_id': 'TP1901174',
|
||||
'phone': phone,
|
||||
'param': 'name:{name},job_name:{left_station}到{arrive_station}{set_type},orderno:{orderno}'.format(
|
||||
name=name, left_station=info.get('left_station'), arrive_station=info.get('arrive_station'),
|
||||
set_type=info.get('set_type'), orderno=info.get('orderno'))
|
||||
}
|
||||
response = self.session.request(url=API_NOTIFICATION_BY_VOICE_CODE_DINGXIN, method='POST', data=data,
|
||||
headers={'Authorization': 'APPCODE {}'.format(appcode)})
|
||||
result = response.json()
|
||||
response_message = result.get('return_code')
|
||||
if response.status_code in [400, 401, 403]:
|
||||
return CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_FORBID).flush()
|
||||
if response.status_code == 200 and result.get('return_code') == '00000':
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_SEND_SUCCESS.format(response_message)).flush()
|
||||
return True
|
||||
else:
|
||||
return CommonLog.add_quick_log(CommonLog.MESSAGE_VOICE_API_SEND_FAIL.format(response_message)).flush()
|
||||
|
||||
def send_email_by_smtp(self, to, title, content):
|
||||
import smtplib
|
||||
from email.message import EmailMessage
|
||||
to = to if isinstance(to, list) else [to]
|
||||
message = EmailMessage()
|
||||
message['Subject'] = title
|
||||
message['From'] = Config().EMAIL_SENDER
|
||||
message['To'] = to
|
||||
message.set_content(content)
|
||||
try:
|
||||
server = smtplib.SMTP(Config().EMAIL_SERVER_HOST)
|
||||
server.login(Config().EMAIL_SERVER_USER, Config().EMAIL_SERVER_PASSWORD)
|
||||
server.ehlo()
|
||||
server.starttls()
|
||||
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_dingtalk_by_webbook(self, content):
|
||||
from dingtalkchatbot.chatbot import DingtalkChatbot
|
||||
webhook = Config().DINGTALK_WEBHOOK
|
||||
dingtalk = DingtalkChatbot(webhook)
|
||||
dingtalk.send_text(msg=content, is_at_all=True)
|
||||
pass
|
||||
|
||||
def send_to_telegram_bot(self, content):
|
||||
bot_api_url = Config().TELEGRAM_BOT_API_URL
|
||||
if not bot_api_url:
|
||||
return False
|
||||
data = {
|
||||
'text': content
|
||||
}
|
||||
response = self.session.request(url=bot_api_url, method='POST', data=data)
|
||||
result = response.json().get('result')
|
||||
response_status = result.get('statusCode')
|
||||
if response_status == 200:
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_TELEGRAM_SUCCESS).flush()
|
||||
else:
|
||||
response_error_message = result.get('description')
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_TELEGRAM_FAIL.format(response_error_message)).flush()
|
||||
|
||||
def push_to_bark(self, content):
|
||||
bark_url = Config().BARK_PUSH_URL
|
||||
if not bark_url:
|
||||
return False
|
||||
|
||||
response = self.session.request(url=bark_url + '/' + content, method='get')
|
||||
result = response.json()
|
||||
response_status = result.get('code')
|
||||
if response_status == 200:
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_BARK_SUCCESS).flush()
|
||||
else:
|
||||
response_error_message = result.get('message')
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_BARK_FAIL.format(response_error_message)).flush()
|
||||
|
||||
def send_serverchan(self, skey, title, content):
|
||||
from lightpush import lightpush
|
||||
lgp = lightpush()
|
||||
lgp.set_single_push(key=skey)
|
||||
try:
|
||||
lgp.single_push(title, content)
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_SERVER_CHAN_SUCCESS).flush()
|
||||
except Exception as e:
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_SERVER_CHAN_FAIL.format(e)).flush()
|
||||
|
||||
def send_pushbear(self, skey, title, content):
|
||||
from lightpush import lightpush
|
||||
lgp = lightpush()
|
||||
lgp.set_group_push(key=skey)
|
||||
try:
|
||||
lgp.group_push(title, content)
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_PUSH_BEAR_SUCCESS).flush()
|
||||
except Exception as e:
|
||||
CommonLog.add_quick_log(CommonLog.MESSAGE_SEND_PUSH_BEAR_SUCCESS.format(e)).flush()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
name = '张三4'
|
||||
content = '你的车票 广州 到 深圳 购买成功,请登录 12306 进行支付'
|
||||
# Notification.voice_code('13800138000', name, content)
|
||||
# Notification.send_email('user@email.com', name, content)
|
||||
# Notification.dingtalk_webhook(content)
|
||||
Notification.voice_code('13800138000', name, {
|
||||
'left_station': '广州',
|
||||
'arrive_station': '深圳',
|
||||
'set_type': '硬座',
|
||||
'orderno': 'E123542'
|
||||
})
|
||||
79
old_py12306/helpers/request.py
Normal file
79
old_py12306/helpers/request.py
Normal file
@@ -0,0 +1,79 @@
|
||||
import requests
|
||||
from requests.exceptions import *
|
||||
|
||||
from py12306.helpers.func import *
|
||||
from requests_html import HTMLSession, HTMLResponse
|
||||
|
||||
requests.packages.urllib3.disable_warnings()
|
||||
|
||||
|
||||
class Request(HTMLSession):
|
||||
"""
|
||||
请求处理类
|
||||
"""
|
||||
|
||||
# session = {}
|
||||
def save_to_file(self, url, path):
|
||||
response = self.get(url, stream=True)
|
||||
with open(path, 'wb') as f:
|
||||
for chunk in response.iter_content(chunk_size=1024):
|
||||
f.write(chunk)
|
||||
return response
|
||||
|
||||
@staticmethod
|
||||
def _handle_response(response, **kwargs) -> HTMLResponse:
|
||||
"""
|
||||
扩充 response
|
||||
:param response:
|
||||
:param kwargs:
|
||||
:return:
|
||||
"""
|
||||
response = HTMLSession._handle_response(response, **kwargs)
|
||||
expand_class(response, 'json', Request.json)
|
||||
return response
|
||||
|
||||
def add_response_hook(self, hook):
|
||||
hooks = self.hooks['response']
|
||||
if not isinstance(hooks, list):
|
||||
hooks = [hooks]
|
||||
hooks.append(hook)
|
||||
self.hooks['response'] = hooks
|
||||
return self
|
||||
|
||||
def json(self, default={}):
|
||||
"""
|
||||
重写 json 方法,拦截错误
|
||||
:return:
|
||||
"""
|
||||
from py12306.app import Dict
|
||||
try:
|
||||
result = self.old_json()
|
||||
return Dict(result)
|
||||
except:
|
||||
return Dict(default)
|
||||
|
||||
def request(self, *args, **kwargs): # 拦截所有错误
|
||||
try:
|
||||
if not 'timeout' in kwargs:
|
||||
from py12306.config import Config
|
||||
kwargs['timeout'] = Config().TIME_OUT_OF_REQUEST
|
||||
response = super().request(*args, **kwargs)
|
||||
return response
|
||||
except RequestException as e:
|
||||
from py12306.log.common_log import CommonLog
|
||||
if e.response:
|
||||
response = e.response
|
||||
else:
|
||||
response = HTMLResponse(HTMLSession)
|
||||
# response.status_code = 500
|
||||
expand_class(response, 'json', Request.json)
|
||||
response.reason = response.reason if response.reason else CommonLog.MESSAGE_RESPONSE_EMPTY_ERROR
|
||||
return response
|
||||
|
||||
def cdn_request(self, url: str, cdn=None, method='GET', **kwargs):
|
||||
from py12306.helpers.api import HOST_URL_OF_12306
|
||||
from py12306.helpers.cdn import Cdn
|
||||
if not cdn: cdn = Cdn.get_cdn()
|
||||
url = url.replace(HOST_URL_OF_12306, cdn)
|
||||
|
||||
return self.request(method, url, headers={'Host': HOST_URL_OF_12306}, verify=False, **kwargs)
|
||||
46
old_py12306/helpers/station.py
Normal file
46
old_py12306/helpers/station.py
Normal file
@@ -0,0 +1,46 @@
|
||||
from os import path
|
||||
|
||||
from py12306.config import Config
|
||||
from py12306.helpers.func import *
|
||||
|
||||
|
||||
@singleton
|
||||
class Station:
|
||||
stations = []
|
||||
station_kvs = {}
|
||||
|
||||
def __init__(self):
|
||||
if path.exists(Config().STATION_FILE):
|
||||
result = open(Config().STATION_FILE, encoding='utf-8').read()
|
||||
result = result.lstrip('@').split('@')
|
||||
for i in result:
|
||||
tmp_info = i.split('|')
|
||||
self.stations.append({
|
||||
'key': tmp_info[2],
|
||||
'name': tmp_info[1],
|
||||
'pinyin': tmp_info[3],
|
||||
'id': tmp_info[5]
|
||||
})
|
||||
self.station_kvs[tmp_info[1]] = tmp_info[2]
|
||||
|
||||
@classmethod
|
||||
def get_station_by_name(cls, name):
|
||||
return cls.get_station_by(name, 'name')
|
||||
|
||||
@classmethod
|
||||
def get_station_by(cls, value, field):
|
||||
self = cls()
|
||||
for station in self.stations:
|
||||
if station.get(field) == value:
|
||||
return station
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_station_key_by_name(cls, name):
|
||||
self = cls()
|
||||
return self.station_kvs[name]
|
||||
|
||||
@classmethod
|
||||
def get_station_name_by_key(cls, key):
|
||||
return cls.get_station_by(key, 'key').get('name')
|
||||
|
||||
49
old_py12306/helpers/type.py
Normal file
49
old_py12306/helpers/type.py
Normal file
@@ -0,0 +1,49 @@
|
||||
from py12306.helpers.func import *
|
||||
|
||||
|
||||
@singleton
|
||||
class UserType:
|
||||
ADULT = 1
|
||||
CHILD = 2
|
||||
STUDENT = 3
|
||||
SOLDIER = 4
|
||||
|
||||
dicts = {
|
||||
'成人': ADULT,
|
||||
'儿童': CHILD,
|
||||
'学生': STUDENT,
|
||||
'残疾军人、伤残人民警察': SOLDIER,
|
||||
}
|
||||
|
||||
|
||||
@singleton
|
||||
class OrderSeatType:
|
||||
dicts = {
|
||||
'特等座': 'P',
|
||||
'商务座': 9,
|
||||
'一等座': 'M',
|
||||
'二等座': 'O',
|
||||
'软卧': 4,
|
||||
'硬卧': 3,
|
||||
'动卧': 1,
|
||||
'软座': 2,
|
||||
'硬座': 1,
|
||||
'无座': 1,
|
||||
}
|
||||
|
||||
|
||||
@singleton
|
||||
class SeatType:
|
||||
NO_SEAT = 26
|
||||
dicts = {
|
||||
'特等座': 25,
|
||||
'商务座': 32,
|
||||
'一等座': 31,
|
||||
'二等座': 30,
|
||||
'软卧': 23,
|
||||
'硬卧': 28,
|
||||
'动卧': 33,
|
||||
'软座': 24,
|
||||
'硬座': 29,
|
||||
'无座': NO_SEAT,
|
||||
}
|
||||
Reference in New Issue
Block a user