artemis/titles/pokken/index.py

211 lines
7.5 KiB
Python
Raw Normal View History

from typing import Tuple, List, Dict
2024-01-09 08:07:04 +00:00
from starlette.requests import Request
from starlette.requests import Request
from starlette.responses import Response, JSONResponse
from starlette.routing import Route, WebSocketRoute
from starlette.websockets import WebSocket, WebSocketState, WebSocketDisconnect
import ast
2023-03-12 04:42:12 +00:00
from datetime import datetime
2023-02-17 07:09:26 +00:00
import yaml
import logging, coloredlogs
from logging.handlers import TimedRotatingFileHandler
2023-03-12 04:42:12 +00:00
import inflection
2023-02-27 21:51:17 +00:00
from os import path
2023-02-28 05:41:32 +00:00
from google.protobuf.message import DecodeError
2023-02-17 07:09:26 +00:00
from core import CoreConfig, Utils
from core.title import BaseServlet
from .config import PokkenConfig
from .base import PokkenBase
from .const import PokkenConstants
from .proto import jackal_pb2
2023-03-09 16:38:58 +00:00
class PokkenServlet(BaseServlet):
2023-02-17 07:09:26 +00:00
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
super().__init__(core_cfg, cfg_dir)
2023-02-17 07:09:26 +00:00
self.config_dir = cfg_dir
self.game_cfg = PokkenConfig()
if path.exists(f"{cfg_dir}/pokken.yaml"):
self.game_cfg.update(yaml.safe_load(open(f"{cfg_dir}/pokken.yaml")))
2023-02-17 07:09:26 +00:00
self.logger = logging.getLogger("pokken")
if not hasattr(self.logger, "inited"):
log_fmt_str = "[%(asctime)s] Pokken | %(levelname)s | %(message)s"
log_fmt = logging.Formatter(log_fmt_str)
2023-03-09 16:38:58 +00:00
fileHandler = TimedRotatingFileHandler(
"{0}/{1}.log".format(self.core_cfg.server.log_dir, "pokken"),
encoding="utf8",
when="d",
backupCount=10,
)
2023-02-17 07:09:26 +00:00
fileHandler.setFormatter(log_fmt)
2023-03-09 16:38:58 +00:00
2023-02-17 07:09:26 +00:00
consoleHandler = logging.StreamHandler()
consoleHandler.setFormatter(log_fmt)
self.logger.addHandler(fileHandler)
self.logger.addHandler(consoleHandler)
2023-03-09 16:38:58 +00:00
2023-02-17 07:09:26 +00:00
self.logger.setLevel(self.game_cfg.server.loglevel)
2023-03-09 16:38:58 +00:00
coloredlogs.install(
level=self.game_cfg.server.loglevel, logger=self.logger, fmt=log_fmt_str
)
2023-02-17 07:09:26 +00:00
self.logger.inited = True
self.base = PokkenBase(core_cfg, self.game_cfg)
@classmethod
def is_game_enabled(cls, game_code: str, core_cfg: CoreConfig, cfg_dir: str) -> bool:
game_cfg = PokkenConfig()
2023-03-09 16:38:58 +00:00
if path.exists(f"{cfg_dir}/{PokkenConstants.CONFIG_NAME}"):
2023-03-09 16:38:58 +00:00
game_cfg.update(
yaml.safe_load(open(f"{cfg_dir}/{PokkenConstants.CONFIG_NAME}"))
)
if not game_cfg.server.enable:
return False
return True
2024-01-09 08:07:04 +00:00
def get_routes(self) -> List[Route]:
return [
Route("/pokken/", self.render_POST, methods=['POST']),
Route("/pokken/matching", self.handle_matching, methods=['POST']),
WebSocketRoute("/pokken/admission", self.handle_admission)
]
def get_allnet_info(self, game_code: str, game_ver: int, keychip: str) -> Tuple[str, str]:
return (
2024-01-09 22:49:18 +00:00
f"https://{self.game_cfg.server.hostname}:{Utils.get_title_port_ssl(self.core_cfg)}/pokken/",
f"{self.game_cfg.server.hostname}:{Utils.get_title_port_ssl(self.core_cfg)}/pokken/",
2023-03-09 16:38:58 +00:00
)
2023-03-05 03:46:26 +00:00
def get_mucha_info(self, core_cfg: CoreConfig, cfg_dir: str) -> Tuple[bool, str]:
2024-01-09 08:07:04 +00:00
if not self.game_cfg.server.enable:
return (False, [], [])
2023-03-09 16:38:58 +00:00
2024-01-09 08:07:04 +00:00
return (True, PokkenConstants.GAME_CDS, PokkenConstants.NETID_PREFIX)
async def handle_admission(self, ws: WebSocket) -> None:
client_ip = Utils.get_ip_addr(ws)
await ws.accept()
while True:
try:
msg: Dict = await ws.receive_json()
except WebSocketDisconnect as e:
self.logger.debug(f"Client {client_ip} disconnected - {e}")
break
except Exception as e:
self.logger.error(f"Could not load JSON from message from {client_ip} - {e}")
if ws.client_state != WebSocketState.DISCONNECTED:
await ws.close()
break
self.logger.debug(f"Admission: Message from {client_ip}:{ws.client.port} - {msg}")
api = msg.get("api", "noop")
handler = getattr(self.base, f"handle_admission_{api.lower()}")
resp = await handler(msg, client_ip)
if resp is None:
resp = {}
if "type" not in resp:
resp['type'] = "res"
if "data" not in resp:
resp['data'] = {}
if "api" not in resp:
resp['api'] = api
if "result" not in resp:
resp['result'] = 'true'
self.logger.debug(f"Websocket response: {resp}")
try:
await ws.send_json(resp)
except WebSocketDisconnect as e:
self.logger.debug(f"Client {client_ip} disconnected - {e}")
break
except Exception as e:
self.logger.error(f"Could not send JSON message to {client_ip} - {e}")
break
if ws.client_state != WebSocketState.DISCONNECTED:
await ws.close()
2023-02-17 07:09:26 +00:00
2024-01-09 08:07:04 +00:00
async def render_POST(self, request: Request) -> bytes:
content = await request.body()
2023-02-28 05:41:32 +00:00
if content == b"":
self.logger.info("Empty request")
return b""
2023-02-17 07:09:26 +00:00
pokken_request = jackal_pb2.Request()
2023-02-28 05:41:32 +00:00
try:
pokken_request.ParseFromString(content)
except DecodeError as e:
2023-08-08 14:17:56 +00:00
self.logger.warning(f"{e} {content}")
2023-02-28 05:41:32 +00:00
return b""
endpoint = jackal_pb2.MessageType.DESCRIPTOR.values_by_number[
pokken_request.type
].name.lower()
2023-04-12 06:34:29 +00:00
self.logger.debug(pokken_request)
2023-03-09 16:38:58 +00:00
2023-02-17 07:09:26 +00:00
handler = getattr(self.base, f"handle_{endpoint}", None)
if handler is None:
2023-08-08 14:17:56 +00:00
self.logger.warning(f"No handler found for message type {endpoint}")
2023-02-17 07:09:26 +00:00
return self.base.handle_noop(pokken_request)
2023-03-12 20:30:57 +00:00
self.logger.info(f"{endpoint} request from {Utils.get_ip_addr(request)}")
2024-01-09 08:07:04 +00:00
ret = await handler(pokken_request)
return Response(ret)
2024-01-09 08:07:04 +00:00
async def handle_matching(self, request: Request) -> bytes:
if not self.game_cfg.server.enable_matching:
2024-01-09 08:07:04 +00:00
return Response()
2024-01-09 08:07:04 +00:00
content = await request.body()
client_ip = Utils.get_ip_addr(request)
2023-03-12 04:42:12 +00:00
if content is None or content == b"":
self.logger.info("Empty matching request")
2024-01-09 08:07:04 +00:00
return JSONResponse(self.base.handle_matching_noop())
2023-03-12 04:42:12 +00:00
json_content = ast.literal_eval(
content.decode()
.replace("null", "None")
.replace("true", "True")
.replace("false", "False")
)
2023-03-12 04:42:12 +00:00
self.logger.info(f"Matching {json_content['call']} request")
self.logger.debug(json_content)
handler = getattr(
self.base,
f"handle_matching_{inflection.underscore(json_content['call'])}",
None,
)
2023-03-12 04:42:12 +00:00
if handler is None:
2023-08-08 14:17:56 +00:00
self.logger.warning(
f"No handler found for message type {json_content['call']}"
)
2024-01-09 08:07:04 +00:00
return JSONResponse(self.base.handle_matching_noop())
2023-03-12 04:42:12 +00:00
ret = handler(json_content, client_ip)
2023-03-12 04:42:12 +00:00
if ret is None:
ret = {}
2023-03-12 04:42:12 +00:00
if "result" not in ret:
ret["result"] = "true"
if "data" not in ret:
ret["data"] = {}
if "timestamp" not in ret:
ret["timestamp"] = int(datetime.now().timestamp() * 1000)
2023-03-12 04:42:12 +00:00
self.logger.debug(f"Response {ret}")
2024-01-09 08:07:04 +00:00
return JSONResponse(ret)