implement sdo api

master
wlt233 7 months ago
parent bf5a4bd38e
commit fc9de9ea37

1
.gitignore vendored

@ -1,4 +1,5 @@
db/
log/ log/

@ -0,0 +1,79 @@
# from https://blog.csdn.net/u011843342/article/details/131925837
import logging
import sys
from typing import Dict, Any
from loguru import logger
LOG_FORMAT = '{time:YYYY-MM-DD HH:mm:ss} [{level}] {module}:{name}:{line} - {message}'
LOG_PATH = "./log"
logger.add(f"{LOG_PATH}/info.log", filter=lambda record: "INFO" in record['level'].name, rotation="10 MB",
retention="3 days", level="INFO", format=LOG_FORMAT)
logger.add(f"{LOG_PATH}/debug.log", filter=lambda record: "DEBUG" in record['level'].name, rotation="10 MB",
retention="3 days", level="DEBUG", format=LOG_FORMAT)
logger.add(f"{LOG_PATH}/error.log", filter=lambda record: "ERROR" in record['level'].name, rotation="10 MB",
retention="1 days", level="ERROR", format=LOG_FORMAT)
S_LOGGING_CONFIG_DEFAULTS: Dict[str, Any] = dict( # no cov
version=1,
disable_existing_loggers=False,
loggers={
"sanic.root": {"level": "INFO", "handlers": ["console"], "propagate": False},
"sanic.error": {
"level": "INFO",
"handlers": ["error_console"],
"propagate": False,
"qualname": "sanic.error",
},
"sanic.access": {
"level": "INFO",
"handlers": ["access_console"],
"propagate": False,
"qualname": "sanic.access",
},
"sanic.server": {
"level": "INFO",
"handlers": ["console"],
"propagate": False,
"qualname": "sanic.server",
},
},
handlers={
"console": {
"class": "logger.InterceptHandler",
},
"error_console": {
"class": "logger.InterceptHandler",
},
"access_console": {
"class": "logger.InterceptHandler",
},
}
)
class InterceptHandler(logging.Handler):
def emit(self, record: logging.LogRecord):
try:
level = logger.level(record.levelname).name
except ValueError:
level = record.levelno
# Find caller from where originated the logged message
frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__: # type: ignore
frame = frame.f_back # type: ignore
depth += 1
logger.opt(depth=depth, exception=record.exc_info).log(level, record.getMessage())
def setup_log():
logging.root.handlers = [InterceptHandler()]
logging.root.setLevel("DEBUG")
for name in logging.root.manager.loggerDict.keys():
logging.getLogger(name).handlers = []
logging.getLogger(name).propagate = True
logger.configure(handlers=[{"sink": sys.stdout, "serialize": False}])

@ -0,0 +1,31 @@
from sanic import Sanic, Request
from sanic.response import json
from sanic.log import logger
from logger import setup_log, S_LOGGING_CONFIG_DEFAULTS
from sdo import bp_sdo
setup_log()
app = Sanic("kotori", log_config=S_LOGGING_CONFIG_DEFAULTS)
app.blueprint(bp_sdo)
app.config["PUBKEY_PATH"] = "./publickey.pem"
app.config["PRIVKEY_PATH"] = "./privatekey.pem"
@app.middleware("request")
async def callback_request(request: Request):
logger.info(f"{request.method} - {request.path}")
@app.route('/')
async def test(request):
return json({'hello': 'world'})
if __name__ == '__main__':
app.run(port=8080)

@ -0,0 +1,16 @@
-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBANa+kI5Aa9YSBq4K
veWRBA516gmGvvNYb4Xo7BqFHHwmf5JJ4AblG6vP66kX6BVRGTpg0RvAyq4bXxaF
kMn7asnAq0I/BC0HhgkCRbXV4PnpY29xwz/Gc84fvWFyRzSLMAmHzBWF5QRz/dEJ
4hX2anxEVUqivVdbiVPBMsRzIrJlAgMBAAECgYEAkByxDBXliHLucqwYxk/shfvR
xPTdfpdxuE8Y668674IyiKz1IAtu532QbBj0EiIrqQbCLjrSiccqqAWYpkQIZAO1
r6YnhXgH7pooxPHFAopdV4oTcwvPQC1S12oUAxkEb1VsYm2Fj6NA7JFrHRVmoKSb
NuBnh5AV910cgDWzdp0CQQDeK8JswR505cDK1aYYHyKpF2h5sPsF1ToKzD5YAA3j
+57p1+KgkPNAKyyJQ7ToHpGFg4yS5Uuuttr9briibMYLAkEA93FRaHVKrguJTxFO
SA6jJGGxQcFvlF/mufzWlw88gnqYNJzTQar/lBULzl4Xql/Vy5uzRcUyyV0fuCGK
x+rfTwJBAJgXuuz6s9/w3S++XQtMXU0GolYUi3QtyaNUuSVDPD8jpWGOki27rVrz
c3Swrirtqk+Ng/GYGVyM/5PZdXp0HosCQQCZ1XFvJ7yOB84NwgyQ78itTa8N2lys
OhMPfglLUMWluOH3k6gjI1RRk+QLIKRF397i/qGttrOkTKjzqKbHM1YxAkBijDjU
mHW6YI5Gl/1cLlMfktKCAcS7y4MlV1pt9y91mw4j3km5+Ol7jGRKWODU+dmOGDWy
pIyYbpYxUwUxIe13
-----END PRIVATE KEY-----

@ -0,0 +1,6 @@
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDWvpCOQGvWEgauCr3lkQQOdeoJ
hr7zWG+F6OwahRx8Jn+SSeAG5Rurz+upF+gVURk6YNEbwMquG18WhZDJ+2rJwKtC
PwQtB4YJAkW11eD56WNvccM/xnPOH71hckc0izAJh8wVheUEc/3RCeIV9mp8RFVK
or1XW4lTwTLEcyKyZQIDAQAB
-----END PUBLIC KEY-----

@ -0,0 +1,7 @@
from sanic import Blueprint
from .misc import bp_sdo_misc
from .v1 import bp_sdo_v1
bp_sdo = Blueprint.group(bp_sdo_misc, bp_sdo_v1)

@ -0,0 +1,16 @@
from sanic import Blueprint, Request, json
from sanic.log import logger
bp_sdo_misc = Blueprint("sdo_misc")
@bp_sdo_misc.route("/report/ge/app", methods=["GET", "POST"])
async def report(request: Request):
# logger.debug(request.body.decode())
return json({ "code": 0, "msg": "", "data": { "needReport": 0 } })
@bp_sdo_misc.route("/agreement/all", methods=["GET", "POST"])
async def agreement(request: Request):
logger.debug(request.body.decode())
return json({ "return_code": 0, "error_type": 0, "return_message": "", "data": { } })

@ -0,0 +1,20 @@
from peewee import SqliteDatabase, Model, IntegerField, TextField
db = SqliteDatabase('./db/sdo.db', pragmas={'journal_mode': 'wal'})
class BaseModel(Model):
class Meta:
database = db
class SdoUser(BaseModel):
id = IntegerField(primary_key=True)
user_id = IntegerField(null=True)
phone = TextField()
password = TextField(null=True)
autokey = TextField(null=True)
ticket = TextField(null=True)
last_login_time = IntegerField(null=True)
sifkey = TextField(null=True)
db.create_tables([SdoUser])

@ -0,0 +1,244 @@
from sanic import Blueprint, Request, json, Sanic
from sanic.log import logger
from sanic.exceptions import SanicException
import urllib.parse
import uuid
import json as jsonlib
import time
from utils.crypto import rsa_decrypt
from utils.crypto import des3_encrypt, des3_decrypt
from utils.crypto import base64_decode, base64_encode, md5
from .model import SdoUser
bp_sdo_v1 = Blueprint("sdo_v1", url_prefix="v1")
@bp_sdo_v1.route("/account/active", methods=["GET", "POST"])
async def account_active(request: Request):
logger.debug(request.body.decode())
return json({ "code": 0, "msg": "ok", "data": { "message": "ok", "result": 0 } })
@bp_sdo_v1.route("/basic/publickey", methods=["GET", "POST"])
async def basic_publickey(request: Request):
with open(Sanic.get_app().config["PUBKEY_PATH"], "r") as f:
public_key = f.read()
public_key = public_key.replace("\n", "").replace("/", "\\/")
public_key = public_key.replace("-----BEGIN PUBLIC KEY-----", "")
public_key = public_key.replace("-----END PUBLIC KEY-----", "")
return json({ "code": 0, "msg": "", "data": { "result": 0, "message": "ok", "key": public_key, "method": "rsa" } })
def encrypt_resp(resp, key):
return base64_encode(des3_encrypt(resp.encode(), key.encode()[:24]))
@bp_sdo_v1.route("/basic/handshake", methods=["GET", "POST"])
async def basic_handshake(request: Request):
with open(Sanic.get_app().config["PRIVKEY_PATH"], "r") as f:
private_key = f.read()
data = rsa_decrypt(base64_decode(request.body.decode()), private_key)
logger.debug(data)
param = dict(urllib.parse.parse_qsl(data))
rand_key = param["randkey"]
device_id = request.headers["X-DEVICEID"]
ctx = Sanic.get_app().ctx
if not hasattr(ctx, "rand_keys"): ctx.rand_keys = {}
ctx.rand_keys[device_id] = rand_key
logger.debug(f"{rand_key=} {device_id=}")
token = uuid.uuid4().hex
resp = '{"message":"ok","result":0,"token":"' + token +'"}'
encrypted_resp = encrypt_resp(resp, rand_key)
logger.debug(encrypted_resp)
return json({ "code": 0, "msg": "ok", "data": encrypted_resp })
@bp_sdo_v1.route("/account/initialize", methods=["GET", "POST"])
async def account_initialize(request: Request):
device_id = request.headers["X-DEVICEID"]
rand_key = Sanic.get_app().ctx.rand_keys[device_id]
logger.debug(f"{rand_key=} {device_id=}")
data = des3_decrypt(base64_decode(request.body.decode()), rand_key.encode()[:24]).decode()
logger.debug(data)
initialize_resp = {
"brand_logo": "",
"brand_name": "",
"daoyu_clientid": "",
"daoyu_download_url": "",
"device_feature": "",
"display_thirdaccout": 0,
"force_show_agreement": 0,
"greport_log_level": "",
"guest_enable": 0,
"is_match": 0,
"log_level": "",
"login_button": [],
"login_icon": [],
"login_limit_enable": 0,
"need_float_window_permission": 0,
"new_device_id_server": "",
"qq_appId": "",
"qq_key": "",
"show_guest_confirm": 0,
"voicetip_button": 0,
"voicetip_one": "",
"voicetip_two": "",
"wegame_appid": "",
"wegame_appkey": "",
"wegame_clientid": "",
"wegame_companyId": "",
"wegame_loginUrl": "",
"weibo_appKey": "",
"weibo_redirectUrl": "",
"weixin_appId": "",
"weixin_key": "",
}
initialize_resp.update({
"brand_logo": "http://gskd.sdo.com/ghome/ztc/logo/og/logo_xhdpi.png",
"brand_name": "盛趣游戏",
"force_show_agreement": 1,
"greport_log_level": "off",
"log_level": "off",
"login_button": ["official"],
"login_icon": [],
"need_float_window_permission": 0, # 1,
"new_device_id_server": md5(device_id.encode()).hex(),
"show_guest_confirm": 1,
"voicetip_button": 1,
})
logger.debug(initialize_resp)
resp = jsonlib.dumps(initialize_resp)
encrypted_resp = encrypt_resp(resp, rand_key)
logger.debug(encrypted_resp)
return json({ "code": 0, "msg": "ok", "data": encrypted_resp })
@bp_sdo_v1.route("/account/login", methods=["GET", "POST"])
async def account_login(request: Request):
device_id = request.headers["X-DEVICEID"]
rand_key = Sanic.get_app().ctx.rand_keys[device_id]
logger.debug(f"{rand_key=} {device_id=}")
data = des3_decrypt(base64_decode(request.body.decode()), rand_key.encode()[:24]).decode()
logger.debug(data)
param = dict(urllib.parse.parse_qsl(data))
phone, password = param.get("phone"), param.get("password")
if not phone or not password:
raise SanicException("Empty phone or password", status_code=403)
hashed_password = md5((password + "shiosalt").encode()).hex()
logger.debug(f"{phone=} {hashed_password=}")
login_resp = {
"activation": 0,
"autokey": "",
"captchaParams": "",
"checkCodeGuid": "",
"checkCodeUrl": "",
"hasExtendAccs": 0,
"has_realInfo": 1,
"imagecodeType": 0,
"isNewUser": 0,
"message": "ok",
"nextAction": 0,
"prompt_msg": "",
"realInfoNotification": "",
"realInfo_force": 1,
"realInfo_force_pay": 0,
"realInfo_status": 0,
"realInfo_status_pay": 0,
"result": 0,
"sdg_height": 0,
"sdg_width": 0,
"ticket": "",
"userAttribute": "0",
"userid": "",
}
user, created = SdoUser.get_or_create(phone=phone)
if not created:
if user.password != hashed_password:
return json({ "code": 31, "msg": "密码有误,请联系维护者!", "data": {} })
user_id = user.user_id
else:
user_id = str(int(time.time()))
query = SdoUser.update(user_id=user_id, password=hashed_password) \
.where(SdoUser.phone == phone)
query.execute()
sifkey = "SIF_" + uuid.uuid4().hex
autokey = "AUTO_" + uuid.uuid4().hex
ticket = "TICKET_" + uuid.uuid4().hex
login_time = int(time.time())
query = SdoUser.update(sifkey=sifkey,
autokey=autokey,
ticket=ticket,
last_login_time=login_time) \
.where(SdoUser.phone == phone)
query.execute()
login_resp.update({
"autokey": autokey,
"ticket": ticket,
"userid": user_id
})
logger.debug(login_resp)
resp = jsonlib.dumps(login_resp)
encrypted_resp = encrypt_resp(resp, rand_key)
logger.debug(encrypted_resp)
return json({ "code": 0, "msg": "ok", "data": encrypted_resp })
@bp_sdo_v1.route("/account/loginauto", methods=["GET", "POST"])
async def account_loginauto(request: Request):
device_id = request.headers["X-DEVICEID"]
rand_key = Sanic.get_app().ctx.rand_keys[device_id]
logger.debug(f"{rand_key=} {device_id=}")
data = des3_decrypt(base64_decode(request.body.decode()), rand_key.encode()[:24]).decode()
logger.debug(data)
param = dict(urllib.parse.parse_qsl(data))
autokey = param.get("autokey")
if not autokey:
raise SanicException("Empty autokey", status_code=403)
user = SdoUser.get_or_none(autokey=autokey)
if not user:
return json({ "code": 31, "msg": "账号不存在或者登陆状态已过期!", "data": {} })
user_id, ticket = user.user_id, user.ticket
login_auto_resp = {
"result": 0,
"message": "ok",
"autokey": autokey,
"userid": user_id,
"ticket": ticket
}
logger.debug(login_auto_resp)
resp = jsonlib.dumps(login_auto_resp)
encrypted_resp = encrypt_resp(resp, rand_key)
logger.debug(encrypted_resp)
return json({ "code": 0, "msg": "ok", "data": encrypted_resp })
@bp_sdo_v1.route("/basic/loginarea", methods=["GET", "POST"])
async def basic_loginarea(request: Request):
user_id = ""
if request.form: user_id = request.form["userid"][0]
return json({ "code": 0, "msg": "ok", "data": { "userid": user_id } })

@ -0,0 +1,32 @@
import base64
import hashlib
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5
from Crypto.Cipher import DES3
from Crypto.Util.Padding import pad, unpad
def md5(orig: bytes) -> bytes:
m = hashlib.md5()
m.update(orig)
return bytes.fromhex(m.hexdigest())
def base64_encode(orig: bytes) -> str:
return base64.b64encode(orig).decode()
def base64_decode(b64: str) -> bytes:
return base64.b64decode(b64)
def rsa_decrypt(cipher_text: bytes, private_key: str) -> str:
cipher = PKCS1_v1_5.new(RSA.importKey(private_key))
decrypt_text = cipher.decrypt(cipher_text, b"rsa")
return decrypt_text.decode("utf-8")
def des3_decrypt(cipher_text: bytes, key: bytes) -> bytes:
cipher = DES3.new(key, DES3.MODE_ECB)
return unpad(cipher.decrypt(cipher_text), 16)
def des3_encrypt(origin_text: bytes, key: bytes) -> bytes:
cipher = DES3.new(key, DES3.MODE_ECB)
return cipher.encrypt(pad(origin_text, 16))
Loading…
Cancel
Save