-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #15 from shinkuan/dev
v1.2.0
- Loading branch information
Showing
22 changed files
with
55,754 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Binary file not shown.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
{"majsoul_account_ids":[24201683]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,159 @@ | ||
from rich.console import Console | ||
from rich.logging import RichHandler | ||
from collections import defaultdict | ||
from dataclasses import dataclass, asdict, field | ||
from os.path import exists | ||
from os import environ | ||
from json import load, dump | ||
from logging import getLogger | ||
from pathlib import Path | ||
|
||
pRoot = Path(".") | ||
|
||
pathConf = pRoot / "mhmp.json" | ||
pathResVer = pRoot / "resver.json" | ||
|
||
|
||
@dataclass | ||
class ResVer: | ||
version: str = None | ||
emotes: dict[str, list] = None | ||
|
||
@classmethod | ||
def fromdict(cls, data: dict): | ||
# purge | ||
if "max_charid" in data: | ||
data.pop("max_charid") | ||
if "emos" in data: | ||
data["emotes"] = data.pop("emos") | ||
return cls(**data) | ||
|
||
|
||
@dataclass | ||
class Conf: | ||
@dataclass | ||
class Base: | ||
log_level: str = "info" | ||
pure_python_protobuf: bool = False | ||
|
||
@dataclass | ||
class Hook: | ||
enable_skins: bool = True | ||
enable_aider: bool = False | ||
enable_chest: bool = False | ||
random_star_char: bool = False | ||
no_cheering_emotes: bool = False | ||
|
||
mhm: Base = None | ||
hook: Hook = None | ||
dump: dict = None | ||
mitmdump: dict = None | ||
proxinject: dict = None | ||
|
||
@classmethod | ||
def default(cls): | ||
return cls( | ||
mhm=cls.Base(), | ||
hook=cls.Hook(), | ||
dump={"with_dumper": False, "with_termlog": True}, | ||
mitmdump={"http2": False, "mode": ["[email protected]:7070"]}, | ||
proxinject={"name": "jantama_mahjongsoul", "set-proxy": "127.0.0.1:7070"}, | ||
) | ||
|
||
@classmethod | ||
def fromdict(cls, data: dict): | ||
# purge | ||
if "server" in data: | ||
data.pop("server") | ||
if "plugin" in data: | ||
data["hook"] = data.pop("plugin") | ||
# to dataclass | ||
for key, struct in [("mhm", cls.Base), ("hook", cls.Hook)]: | ||
if key in data: | ||
data[key] = struct(**data[key]) | ||
return cls(**data) | ||
|
||
|
||
if exists(pathConf): | ||
conf = Conf.fromdict(load(open(pathConf, "r"))) | ||
else: | ||
conf = Conf.default() | ||
|
||
if exists(pathResVer): | ||
resver = ResVer.fromdict(load(open(pathResVer, "r"))) | ||
else: | ||
resver = ResVer() | ||
|
||
|
||
def fetch_resver(): | ||
"""Fetch the latest character id and emojis""" | ||
import requests | ||
import random | ||
import re | ||
|
||
rand_a: int = random.randint(0, int(1e9)) | ||
rand_b: int = random.randint(0, int(1e9)) | ||
|
||
ver_url = f"https://game.maj-soul.com/1/version.json?randv={rand_a}{rand_b}" | ||
response = requests.get(ver_url, proxies={"https": None}) | ||
response.raise_for_status() | ||
version: str = response.json().get("version") | ||
|
||
if resver.version == version: | ||
return | ||
|
||
res_url = f"https://game.maj-soul.com/1/resversion{version}.json" | ||
response = requests.get(res_url, proxies={"https": None}) | ||
response.raise_for_status() | ||
res_data: dict = response.json() | ||
|
||
emotes: defaultdict[str, list[int]] = defaultdict(list) | ||
pattern = rf"en\/extendRes\/emo\/e(\d+)\/(\d+)\.png" | ||
|
||
for text in res_data.get("res"): | ||
matches = re.search(pattern, text) | ||
|
||
if matches: | ||
charid = matches.group(1) | ||
emo = int(matches.group(2)) | ||
|
||
if emo == 13: | ||
continue | ||
emotes[charid].append(emo) | ||
for value in emotes.values(): | ||
value.sort() | ||
|
||
resver.version = version | ||
resver.emotes = {key: value[9:] for key, value in sorted(emotes.items())} | ||
|
||
with open(pathResVer, "w") as f: | ||
dump(asdict(resver), f) | ||
|
||
|
||
def no_cheering_emotes(): | ||
exclude = set(range(13, 19)) | ||
for emo in resver.emotes.values(): | ||
emo[:] = sorted(set(emo) - exclude) | ||
|
||
|
||
def init(): | ||
with console.status("[magenta]Fetch the latest server version") as status: | ||
fetch_resver() | ||
if conf.hook.no_cheering_emotes: | ||
no_cheering_emotes() | ||
if conf.mhm.pure_python_protobuf: | ||
environ["PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION"] = "python" | ||
|
||
with open(pathConf, "w") as f: | ||
dump(asdict(conf), f, indent=2) | ||
|
||
|
||
# console | ||
console = Console() | ||
|
||
|
||
# logger | ||
logger = getLogger(__name__) | ||
logger.propagate = False | ||
logger.setLevel(conf.mhm.log_level.upper()) | ||
logger.addHandler(RichHandler(markup=True, rich_tracebacks=True)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
from .common import main | ||
|
||
if __name__ == "__main__": | ||
main() |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
from mitmproxy import http | ||
|
||
|
||
from . import logger | ||
from .hook import hooks | ||
from .proto import MsgManager | ||
|
||
|
||
def log(mger: MsgManager): | ||
msg = mger.m | ||
logger.info(f"[i][gold1]& {mger.tag} {msg.type.name} {msg.method} {msg.id}") | ||
logger.debug(f"[cyan3]# {msg.amended} {msg.data}") | ||
|
||
|
||
class WebSocketAddon: | ||
def __init__(self): | ||
self.manager = MsgManager() | ||
|
||
def websocket_start(self, flow: http.HTTPFlow): | ||
logger.info(" ".join(["[i][green]Connected", flow.id[:13]])) | ||
|
||
def websocket_end(self, flow: http.HTTPFlow): | ||
logger.info(" ".join(["[i][blue]Disconnected", flow.id[:13]])) | ||
|
||
def websocket_message(self, flow: http.HTTPFlow): | ||
# make type checker happy | ||
assert flow.websocket is not None | ||
|
||
try: | ||
self.manager.parse(flow) | ||
except: | ||
logger.warning(" ".join(["[i][red]Unsupported Message @", flow.id[:13]])) | ||
logger.debug(__import__("traceback").format_exc()) | ||
|
||
return | ||
|
||
if self.manager.member: | ||
for hook in hooks: | ||
try: | ||
hook.hook(self.manager) | ||
except: | ||
logger.warning(" ".join(["[i][red]Error", self.manager.m.method])) | ||
logger.debug(__import__("traceback").format_exc()) | ||
|
||
if self.manager.m.amended: | ||
self.manager.apply() | ||
|
||
log(self.manager) | ||
|
||
|
||
addons = [WebSocketAddon()] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,62 @@ | ||
import asyncio | ||
|
||
from . import pRoot, logger, conf, resver, init | ||
|
||
|
||
PROXINJECTOR = pRoot / "common/proxinject/proxinjector-cli" | ||
|
||
|
||
def _cmd(dict): | ||
return [obj for key, value in dict.items() for obj in (f"--{key}", value)] | ||
|
||
|
||
async def start_proxy(): | ||
from mitmproxy.tools.dump import DumpMaster | ||
from mitmproxy.options import Options | ||
from .addons import addons | ||
|
||
master = DumpMaster(Options(**conf.mitmdump), **conf.dump) | ||
master.addons.add(*addons) | ||
await master.run() | ||
return master | ||
|
||
|
||
async def start_inject(): | ||
cmd = [PROXINJECTOR, *_cmd(conf.proxinject)] | ||
|
||
while True: | ||
process = await asyncio.subprocess.create_subprocess_exec( | ||
*cmd, stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE | ||
) | ||
|
||
stdout, stderr = await process.communicate() | ||
|
||
await asyncio.sleep(0.8) | ||
|
||
|
||
def main(): | ||
async def start(): | ||
logger.info(f"[i]log level: {conf.mhm.log_level}") | ||
logger.info(f"[i]pure python protobuf: {conf.mhm.pure_python_protobuf}") | ||
|
||
logger.info(f"[i]version: {resver.version}") | ||
logger.info(f"[i]characters: {len(resver.emotes)}") | ||
|
||
tasks = set() | ||
|
||
if conf.mitmdump: | ||
tasks.add(start_proxy()) | ||
logger.info(f"[i]mitmdump launched @ {len(conf.mitmdump.get('mode'))} mode") | ||
|
||
# if conf.proxinject: | ||
# tasks.add(start_inject()) | ||
# logger.info(f"[i]proxinject launched @ {conf.proxinject.get('set-proxy')}") | ||
|
||
await asyncio.gather(*tasks) | ||
|
||
init() | ||
|
||
try: | ||
asyncio.run(start()) | ||
except KeyboardInterrupt: | ||
pass |
Oops, something went wrong.