Files
SMANAGE/backup/_flask_backup/TempLateModule/API.py
T

507 lines
18 KiB
Python

from tools import *
from Utils.Config import *
from Utils.Lib import *
from Utils.Secrets import get as _secret_get
APIS=flask.Blueprint("api", __name__, subdomain="api", static_folder = JoinPath(FlaskTempLate, "API"), static_url_path="/")
TOKEN = _secret_get('wecom', 'TOKEN', fallback='')
EncodingAESKey = _secret_get('wecom', 'ENCODING_AES_KEY', fallback='')
CorpID = _secret_get('wecom', 'CORP_ID', fallback='')
class WXBizMsgCrypt:
def __init__(self, sToken, sEncodingAESKey, sCorpId):
self.token = sToken
self.corpId = sCorpId
# 提取aeskey
aes_key = base64.b64decode(sEncodingAESKey + '=')
self.key = aes_key
self.mode = AES.MODE_CBC
# 设置加解密模式为AES的CBC模式
def VerifyURL(self, sMsgSignature, sTimeStamp, sNonce, sEchoStr):
# 验证URL函数,返回解密后的明文
try:
# 计算签名
signature = self._getSHA1(self.token, sTimeStamp, sNonce, sEchoStr)
if signature != sMsgSignature:
return (40001, "签名验证错误")
# 解密
ret, sReplyEchoStr = self._decrypt(sEchoStr)
return (0, sReplyEchoStr)
except Exception as e:
return (-40003, "解密失败")
def DecryptMsg(self, sMsgSignature, sTimeStamp, sNonce, sPostData):
# 解密函数,用于解密POST请求的数据
try:
# 解析XML
xml_tree = ET.fromstring(sPostData)
encrypt = xml_tree.find("Encrypt").text
# 计算签名
signature = self._getSHA1(self.token, sTimeStamp, sNonce, encrypt)
if signature != sMsgSignature:
return (40001, "签名验证错误")
# 解密
ret, content = self._decrypt(encrypt)
return (0, content)
except Exception as e:
return (-40003, "解密失败")
def EncryptMsg(self, sReplyMsg, sNonce, timestamp=None):
# 加密函数,用于加密响应消息
if timestamp is None:
timestamp = str(int(time.time()))
# 加密
ret, encrypt = self._encrypt(sReplyMsg)
if ret != 0:
return (ret, None)
# 生成签名
signature = self._getSHA1(self.token, timestamp, sNonce, encrypt)
# 生成XML
xml = self._generate_response_xml(encrypt, signature, timestamp, sNonce)
return (0, xml)
def _getSHA1(self, token, timestamp, nonce, encrypt):
# 生成签名
try:
sortlist = [token, timestamp, nonce, encrypt]
sortlist.sort()
sha = hashlib.sha1()
sha.update(''.join(sortlist).encode())
return sha.hexdigest()
except Exception as e:
return None
def _encrypt(self, text):
# 加密消息
try:
# 生成16位随机字符串
random_str = ''.join(random.sample('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 16))
text = text.encode('utf-8')
# 计算网络字节序
msg_len = self._length(text)
# 拼接
raw = random_str + msg_len + text + self.corpId.encode('utf-8')
# 填充
padding = len(raw) % 32
padnum = 32 - padding if padding != 0 else 32
raw = raw + bytes([padnum]) * padnum
# 加密
cipher = AES.new(self.key, self.mode, self.key[:16])
encrypted = cipher.encrypt(raw)
# 返回Base64编码
return (0, base64.b64encode(encrypted).decode('utf-8'))
except Exception as e:
return (-40003, None)
def _decrypt(self, text):
# 解密消息
try:
# Base64解码
text = base64.b64decode(text)
# 解密
cipher = AES.new(self.key, self.mode, self.key[:16])
decrypted = cipher.decrypt(text)
# 去除填充
padnum = decrypted[-1]
if padnum < 1 or padnum > 32:
padnum = 0
result = decrypted[:-padnum]
# 提取内容
content = result[16:]
msglen = int.from_bytes(content[:4], 'big')
message = content[4:4+msglen].decode('utf-8')
from_corpid = content[4+msglen:].decode('utf-8')
# 验证CorpID
if from_corpid != self.corpId:
return (-40005, None)
return (0, message)
except Exception as e:
return (-40003, None)
def _length(self, text):
# 将消息长度转换为4字节的网络字节序
l = len(text)
return l.to_bytes(4, 'big')
def _generate_response_xml(self, encrypt, signature, timestamp, nonce):
# 生成响应XML
xml = f"""<xml>
<Encrypt><![CDATA[{encrypt}]]></Encrypt>
<MsgSignature><![CDATA[{signature}]]></MsgSignature>
<TimeStamp>{timestamp}</TimeStamp>
<Nonce><![CDATA[{nonce}]]></Nonce>
</xml>"""
return xml
# 初始化加密类实例
wxcpt = WXBizMsgCrypt(TOKEN, EncodingAESKey, CorpID)
# 企业微信消息接收接口
@APIS.route('/', methods=['GET', 'POST'])
def APISIndex():
msg_signature = flask.request.args.get('msg_signature', '')
timestamp = flask.request.args.get('timestamp', '')
nonce = flask.request.args.get('nonce', '')
if not msg_signature or not timestamp or not nonce:
return flask.render_template("/API/index.html")
if flask.request.method == 'GET':
# 处理URL验证请求
echostr = flask.request.args.get('echostr', '')
ret, sEchoStr = wxcpt.VerifyURL(msg_signature, timestamp, nonce, echostr)
if ret != 0:
return flask.Response("URL验证失败", status=400)
return flask.Response(sEchoStr)
else:
# 处理消息接收请求
post_data = flask.request.get_data(as_text=True)
ret, message = wxcpt.DecryptMsg(msg_signature, timestamp, nonce, post_data)
if ret != 0:
return flask.Response("消息解密失败", status=400)
print("INFO: ", message)
# 解析消息内容
try:
msg_xml = ET.fromstring(message)
msg_type = msg_xml.find("MsgType").text
# 这里可以根据消息类型进行不同的处理
# 例如:文本消息、图片消息、事件等
# 示例:简单回复文本消息
if msg_type == "text":
content = msg_xml.find("Content").text
# 构建回复消息
reply_content = f"收到消息:{content}"
reply_xml = f"""<xml>
<ToUserName><![CDATA[{msg_xml.find("FromUserName").text}]]></ToUserName>
<FromUserName><![CDATA[{msg_xml.find("ToUserName").text}]]></FromUserName>
<CreateTime>{int(time.time())}</CreateTime>
<MsgType><![CDATA[text]]></MsgType>
<Content><![CDATA[{reply_content}]]></Content>
</xml>"""
# 加密回复消息
sNonce = ''.join(random.sample('ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789', 16))
ret, encrypted_xml = wxcpt.EncryptMsg(reply_xml, sNonce)
if ret != 0:
return flask.Response("消息加密失败", status=500)
return flask.Response(encrypted_xml)
else:
# 其他类型消息默认回复空串
return flask.Response("")
except Exception as e:
# 异常处理,返回空串表示成功接收但不回复
return flask.Response("")
@APIS.route('/ipinfo.api', methods=['GET'])
def APISIPINFOAPI():
remote_addr = flask.request.remote_addr
x_forwarded_for = flask.request.headers.get('X-Forwarded-For', None)
x_real_ip = flask.request.headers.get('X-Real-IP', None)
cf_connecting_ip = flask.request.headers.get('CF-Connecting-IP', None)
headers = dict(flask.request.headers)
request_info = {
'method': flask.request.method,
'url': flask.request.url,
'base_url': flask.request.base_url,
'full_path': flask.request.full_path,
'path': flask.request.path,
'query_string': flask.request.query_string.decode('utf-8'),
'is_secure': flask.request.is_secure,
'is_json': flask.request.is_json,
'content_type': flask.request.content_type,
'content_length': flask.request.content_length
}
ip_info = {
'ip': {
'remote_addr': remote_addr,
'x_forwarded_for': x_forwarded_for,
'x_real_ip': x_real_ip,
'cloudflare_ip': cf_connecting_ip,
'determined_ip': x_forwarded_for.split(',')[0].strip() if x_forwarded_for else remote_addr
},
'network': {
'user_agent': flask.request.user_agent.string,
'referrer': flask.request.referrer,
'language': flask.request.accept_languages.to_header(),
'encoding': flask.request.accept_encodings.to_header(),
'charset': flask.request.accept_charsets.to_header(),
'mimetypes': flask.request.accept_mimetypes.to_header()
},
'request': request_info,
'headers': headers
}
return flask.jsonify(ip_info)
@APIS.route("/mcstatus.api")
def APISMCStatusAPI():
Host = flask.request.json.get('Host') if flask.request.method == 'POST' else flask.request.args.get('Host')
Port = flask.request.json.get('Port') if flask.request.method == 'POST' else flask.request.args.get('Port')
if not Host: return flask.jsonify({"Error": "Host Required"}), 400
try:
# Try Java Edition
JavaServerInstance = JavaServer.lookup(f"{Host}:{Port}") if Port else JavaServer.lookup(Host)
ServerStatus = JavaServerInstance.status()
return flask.jsonify({
"Type": "Java",
"Host": Host,
"Port": Port or 25565,
"Version": ServerStatus.version.name,
"Players": f"{ServerStatus.players.online}/{ServerStatus.players.max}",
"Ping": ServerStatus.latency,
"Motd": str(ServerStatus.description)
})
except:
try:
# Try Bedrock Edition
BedrockPort = Port or 19132
SocketTest = socket.create_connection((Host, BedrockPort), timeout=1)
SocketTest.close()
BedrockServerInstance = BedrockServer.lookup(f"{Host}:{BedrockPort}")
BedrockStatus = BedrockServerInstance.status()
return flask.jsonify({
"Type": "Bedrock",
"Host": Host,
"Port": BedrockPort,
"Version": BedrockStatus.version.version,
"Players": f"{BedrockStatus.players_online}/{BedrockStatus.players_max}",
"Ping": BedrockStatus.latency,
"Motd": BedrockStatus.motd
})
except Exception as Error:
return flask.jsonify({"Error": str(Error)}), 404
# 扩展支持的媒体类型及其对应的MIME类型
MEDIA_EXTENSIONS = {
'.jpg': 'image/jpeg',
'.jpeg': 'image/jpeg',
'.png': 'image/png',
'.gif': 'image/gif',
'.bmp': 'image/bmp',
'.mp4': 'video/mp4',
'.ogg': 'audio/ogg',
'.mp3': 'audio/mpeg',
'.wav': 'audio/wav'
}
def GRIFZ(ZIP_FILE_PATH: str, ZIP_PASSWORD: str, IN: str = ""):
try:
with zipfile.ZipFile(ZIP_FILE_PATH, 'r') as zf:
all_files = zf.namelist()
# 筛选媒体文件,并检查是否在指定文件夹下
media_files = []
for f in all_files:
# 检查文件扩展名是否为支持的媒体类型
if os.path.splitext(f.lower())[1] in MEDIA_EXTENSIONS:
# 如果IN不为空,则只保留指定文件夹下的文件
if IN:
# 使用os.path.normpath统一路径格式,确保跨平台兼容性
normalized_path = os.path.normpath(f)
normalized_in = os.path.normpath(IN)
# 检查文件是否在IN指定的文件夹下
if normalized_path.startswith(normalized_in):
media_files.append(f)
else:
media_files.append(f)
if not media_files:
return None, "NO SUPPORTED MEDIA FILES FOUND"
random_media = random.choice(media_files)
with zf.open(random_media, pwd=ZIP_PASSWORD.encode('utf-8')) as media_file:
media_data = io.BytesIO(media_file.read())
ext = os.path.splitext(random_media)[1].lower()
return media_data, ext
except Exception as e:
return None, f"ERROR: {str(e)}"
def ResizeImage(image_data, size=(25, 25)):
try:
img = Image.open(image_data)
if img.mode in ('RGBA', 'LA'):
background = Image.new(img.mode[:-1], img.size, (255, 255, 255))
background.paste(img, img.split()[-1])
img = background
resized_img = img.resize(size, Image.Resampling.LANCZOS)
output = io.BytesIO()
format = img.format or 'JPEG'
resized_img.save(output, format=format)
output.seek(0)
return output
except Exception as e:
return None, f"压缩失败: {str(e)}"
@APIS.route("/RandomFurryPornographyPicture.api")
def APISRandomFurryPornographyPictureAPI():
PATH=os.path.join(TempLate, "FTP/yellow.zip")
APIKEY = flask.request.args.get("APIKEY")
if APIKEY == "2525": PATH = os.path.join(TempLate, "FTP/YELLOW_RESIZE.zip")
img_data, ext = GRIFZ(PATH, APIKEY)
if img_data is None: return flask.jsonify({"Code": 1, "Status": 1, "Error": ext}), 500
mime_type = f'image/{ext[1:]}' if ext != '.jpg' else 'image/jpeg'
if APIKEY == "2525": img_data = ResizeImage(img_data)
return flask.send_file(
img_data,
mimetype=mime_type,
as_attachment=False
)
def _RandomMediaFromZip(zip_path, folder_prefix):
PATH = os.path.join(TempLate, zip_path)
APIKEY = flask.request.args.get("APIKEY")
in_folder = flask.request.args.get("IN", "")
data, ext = GRIFZ(PATH, APIKEY, folder_prefix + in_folder)
if data is None:
return flask.jsonify({"Code": 1, "Status": 1, "Error": ext}), 500
mime_type = MEDIA_EXTENSIONS.get(ext, 'application/octet-stream')
data.seek(0)
if ext == '.mp4':
file_size = data.getbuffer().nbytes
range_header = flask.request.headers.get('Range', None)
if range_header:
start, end = range_header.replace('bytes=', '').split('-')
start = int(start)
end = int(end) if end else file_size - 1
data.seek(start)
chunk = data.read(end - start + 1)
response = flask.Response(
chunk,
status=206,
mimetype=mime_type,
headers={
'Content-Range': f'bytes {start}-{end}/{file_size}',
'Accept-Ranges': 'bytes',
'Content-Length': str(end - start + 1),
'Content-Disposition': 'inline; filename="random.mp4"'
}
)
else:
response = flask.Response(
flask.stream_with_context(data),
mimetype=mime_type,
headers={
'Accept-Ranges': 'bytes',
'Content-Length': str(file_size),
'Content-Disposition': 'inline; filename="random.mp4"'
}
)
return response
else:
return flask.send_file(
data,
mimetype=mime_type,
as_attachment=False,
download_name=f"random{ext}"
)
@APIS.route("/RandomEMSTC.api")
def APISRandomEMSTCAPI():
return _RandomMediaFromZip("FTP/EMSTC.zip", "EMSTC-main/")
@APIS.route("/RandomFurryMEME.api")
def APISRandomFurryMEMEAPI():
return _RandomMediaFromZip("FTP/Furry.zip", "Furry/")
@APIS.route("/RandomProgrammerMEME.api")
def APISRandomProgrammerMEMEAPI():
return _RandomMediaFromZip("FTP/Programmer.zip", "Programmer/")
@APIS.route("/Asset/Download/<sha1>")
def APIAssetDownload(sha1):
asset = GERPFileAsset.query.filter(GERPFileAsset.FileSHA1 == sha1).first()
if not asset:
return {"code": 404, "msg": "文件不存在"}, 404
RESOURCE_ROOT = os.path.join(os.path.abspath(os.path.join(os.getcwd(), "resource")))
file_path = os.path.join(RESOURCE_ROOT, sha1[0:2], sha1[2:4], sha1[4:6], sha1)
if not os.path.exists(file_path):
return {"code": 404, "msg": "文件存储不存在"}, 404
download = flask.request.args.get("download", "0") == "1"
if download:
return flask.send_file(file_path, mimetype=asset.MimeType or "application/octet-stream", as_attachment=True, download_name=asset.FileName)
response = flask.send_file(file_path, mimetype=asset.MimeType or "application/octet-stream")
response.headers["Cache-Control"] = "public, max-age=31536000"
response.headers["ETag"] = sha1
return response
@APIS.errorhandler(404)
def APISNotFound(e):
return "API 404", 404
@APIS.errorhandler(500)
def APISInternalServerError(e):
return "API 500", 500
@APIS.errorhandler(503)
def APISServiceTemporarilyUnavailable(e):
return "API 503", 503
@APIS.errorhandler(flask_wtf.csrf.CSRFError)
def APISCSRError(e):
return "CSRF 503", 503