mirror of
https://github.com/573dev/gfdm-server.git
synced 2026-08-28 13:23:58 -05:00
Add more service classes
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
from v8_server.eamuse.services.facility import Facility
|
||||
from v8_server.eamuse.services.message import Message
|
||||
from v8_server.eamuse.services.package import Package
|
||||
from v8_server.eamuse.services.pcbevent import PCBEvent
|
||||
from v8_server.eamuse.services.pcbtracker import PCBTracker
|
||||
from v8_server.eamuse.services.services import ServiceRequest, Services, ServiceType
|
||||
|
||||
|
||||
__all__ = [
|
||||
"Facility",
|
||||
"Message",
|
||||
"PCBEvent",
|
||||
"PCBTracker",
|
||||
"Package",
|
||||
"ServiceRequest",
|
||||
"ServiceType",
|
||||
"Services",
|
||||
]
|
||||
|
||||
113
v8_server/eamuse/services/facility.py
Normal file
113
v8_server/eamuse/services/facility.py
Normal file
@@ -0,0 +1,113 @@
|
||||
from lxml.builder import E
|
||||
|
||||
from v8_server.eamuse.utils.xml import XMLBinTypes as T, e_type
|
||||
|
||||
|
||||
class Facility(object):
|
||||
"""
|
||||
Handle the Facility request.
|
||||
|
||||
The only method of note is the "get" method. This method expects to return a bunch
|
||||
of information about the arcade this cabinet is in, as well as some settings for
|
||||
URLs and the name of the cab.
|
||||
|
||||
Example:
|
||||
<call model="K32:J:B:A:2011033000" srcid="00010203040506070809">
|
||||
<facility encoding="SHIFT_JIS" method="get"/>
|
||||
</call>
|
||||
"""
|
||||
|
||||
# Methods
|
||||
GET = "get"
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
"""
|
||||
Example:
|
||||
<response>
|
||||
<facility expire="600">
|
||||
<location>
|
||||
<id>US-123</id>
|
||||
<country>US</country>
|
||||
<region>.</region>
|
||||
<name>H</name>
|
||||
<type __type="u8">0</type>
|
||||
</location>
|
||||
<line>
|
||||
<id>.</id>
|
||||
<class __type="u8">0</class>
|
||||
</line>
|
||||
<portfw>
|
||||
<globalip __type="ip4" __count="1">127.0.0.1</globalip>
|
||||
<globalport __type="u16">80</globalport>
|
||||
<privateport __type="u16">80</privateport>
|
||||
</portfw>
|
||||
<public>
|
||||
<flag __type="u8">1</flag>
|
||||
<name>.</name>
|
||||
<latitude>0</latitude>
|
||||
<longitude>0</longitude>
|
||||
</public>
|
||||
<share>
|
||||
<eacoin>
|
||||
<notchamount __type="s32">3000</notchamount>
|
||||
<notchcount __type="s32">3</notchcount>
|
||||
<supplylimit __type="s32">10000</supplylimit>
|
||||
</eacoin>
|
||||
<eapass>
|
||||
<valid __type="u16">365</valid>
|
||||
</eapass>
|
||||
<url>
|
||||
<eapass>www.ea-pass.konami.net</eapass>
|
||||
<arcadefan>www.konami.jp/am</arcadefan>
|
||||
<konaminetdx>http://am.573.jp</konaminetdx>
|
||||
<konamiid>http://id.konami.net</konamiid>
|
||||
<eagate>http://eagate.573.jp</eagate>
|
||||
</url>
|
||||
</share>
|
||||
</facility>
|
||||
</response>
|
||||
"""
|
||||
|
||||
# TODO: The facility data should be read in from a config file instead of being
|
||||
# hard coded here
|
||||
|
||||
return E.response(
|
||||
E.facility(
|
||||
E.location(
|
||||
E.id("CA-123"),
|
||||
E.country("CA"),
|
||||
E.region("MB"),
|
||||
E.name("SenPi Arcade"),
|
||||
E.type("0", e_type(T.u8)),
|
||||
),
|
||||
E.line(E.id("."), E("class", "0", e_type(T.u8))),
|
||||
E.portfw(
|
||||
E.globalip("127.0.0.1", e_type(T.ip4, count=1)),
|
||||
E.globalport("80", e_type(T.u16)),
|
||||
E.privateport("80", e_type(T.u16)),
|
||||
),
|
||||
E.public(
|
||||
E.flag("1", e_type(T.u8)),
|
||||
E.name("Gotem"),
|
||||
E.latitude("0"),
|
||||
E.longitude("0"),
|
||||
),
|
||||
E.share(
|
||||
E.eacoin(
|
||||
E.notchamount("3000", e_type(T.s32)),
|
||||
E.notchcount("3", e_type(T.s32)),
|
||||
E.supplylimit("10000", e_type(T.s32)),
|
||||
),
|
||||
E.eapass(E.valid("365", e_type(T.u16))),
|
||||
E.url(
|
||||
E.eapass("www.ea-pass.konami.net"),
|
||||
E.arcadefan("www.konami.jp/am"),
|
||||
E.konaminetdx("http://am.573.jp"),
|
||||
E.konamiid("http://id.konami.net"),
|
||||
E.eagate("http://eagate.573.jp"),
|
||||
),
|
||||
),
|
||||
{"expire": "600"},
|
||||
)
|
||||
)
|
||||
27
v8_server/eamuse/services/message.py
Normal file
27
v8_server/eamuse/services/message.py
Normal file
@@ -0,0 +1,27 @@
|
||||
from lxml.builder import E
|
||||
|
||||
|
||||
class Message(object):
|
||||
"""
|
||||
Handle the Message request.
|
||||
|
||||
It is unknown as to what this does. Possibly it's for operator messages
|
||||
|
||||
Example:
|
||||
<call model="K32:J:B:A:2011033000" srcid="00010203040506070809">
|
||||
<message method="get"/>
|
||||
</call>
|
||||
"""
|
||||
|
||||
# Methods
|
||||
GET = "get"
|
||||
|
||||
@classmethod
|
||||
def get(cls):
|
||||
"""
|
||||
Example:
|
||||
<response>
|
||||
<message expire="600"/>
|
||||
</response>
|
||||
"""
|
||||
return E.response(E.message({"expire": "600"}))
|
||||
44
v8_server/eamuse/services/package.py
Normal file
44
v8_server/eamuse/services/package.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from lxml.builder import E
|
||||
|
||||
from v8_server.eamuse.services.services import ServiceRequest
|
||||
from v8_server.eamuse.utils.xml import get_xml_attrib
|
||||
|
||||
|
||||
class Package(object):
|
||||
"""
|
||||
Handle the Package request.
|
||||
|
||||
This is for supporting downloading of updates. We do not support this.
|
||||
|
||||
Example:
|
||||
<call model="K32:J:B:A:2011033000" srcid="00010203040506070809">
|
||||
<package method="list" pkgtype="all"/>
|
||||
</call>
|
||||
"""
|
||||
|
||||
# Methods
|
||||
LIST = "list"
|
||||
|
||||
# PKGTypes
|
||||
PKGTYPE_ALL = "all"
|
||||
|
||||
@classmethod
|
||||
def list(cls, req: ServiceRequest):
|
||||
"""
|
||||
Example:
|
||||
<response>
|
||||
<package expire="600"/>
|
||||
</response>
|
||||
"""
|
||||
# Grab the pkgtype in case we ever need it
|
||||
pkgtype = get_xml_attrib(req.xml[0], "pkgtype")
|
||||
|
||||
if pkgtype == cls.PKGTYPE_ALL:
|
||||
response = E.response(E.package({"expire": "600"}))
|
||||
else:
|
||||
raise Exception(
|
||||
"Not sure how to handle this package request. "
|
||||
f'pkgtype "{pkgtype}" is unknown for request: {req}'
|
||||
)
|
||||
|
||||
return response
|
||||
96
v8_server/eamuse/services/pcbevent.py
Normal file
96
v8_server/eamuse/services/pcbevent.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from datetime import datetime
|
||||
|
||||
from lxml import etree
|
||||
from lxml.builder import E
|
||||
|
||||
from v8_server.eamuse.services.services import ServiceRequest
|
||||
|
||||
|
||||
class PCBEventItem(object):
|
||||
"""
|
||||
Contains the data for a PCBEvent Item
|
||||
|
||||
Example:
|
||||
<item>
|
||||
<name __type="str">K32.mode.std</name>
|
||||
<value __type="s32">1</value>
|
||||
<time __type="time">1602992363</time>
|
||||
</item>
|
||||
"""
|
||||
|
||||
DATE_FMT = "%b/%d/%Y-%H:%M:%S"
|
||||
|
||||
def __init__(self, xml_root: etree) -> None:
|
||||
# Build our data from the `item` xml root object
|
||||
self.name = xml_root.find("name").text
|
||||
self.value = int(xml_root.find("value").text)
|
||||
self.time = datetime.fromtimestamp(int(xml_root.find("time").text))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f'PCBEventItem<name: "{self.name}", value: {self.value}, '
|
||||
f"time: {self.time.strftime(self.DATE_FMT)}>"
|
||||
)
|
||||
|
||||
|
||||
class PCBEvent(object):
|
||||
"""
|
||||
Handle the PCBEvent request.
|
||||
|
||||
It is unknown as to what this does.
|
||||
|
||||
Example:
|
||||
<call model="K32:J:B:A:2011033000" srcid="00010203040506070809">
|
||||
<pcbevent method="put">
|
||||
<time __type="time">1602992385</time>
|
||||
<seq __type="u32">4</seq>
|
||||
<item>
|
||||
<name __type="str">K32.mode.std</name>
|
||||
<value __type="s32">1</value>
|
||||
<time __type="time">1602992363</time>
|
||||
</item>
|
||||
<item>
|
||||
<name __type="str">K32.playcnt.t</name>
|
||||
<value __type="s32">1</value>
|
||||
<time __type="time">1602992363</time>
|
||||
</item>
|
||||
<item>
|
||||
<name __type="str">game.e</name>
|
||||
<value __type="s32">1</value>
|
||||
<time __type="time">1602992371</time>
|
||||
</item>
|
||||
</pcbevent>
|
||||
</call>
|
||||
"""
|
||||
|
||||
DATE_FMT = "%b/%d/%Y-%H:%M:%S"
|
||||
|
||||
def __init__(self, req: ServiceRequest) -> None:
|
||||
# Build our item from the data
|
||||
xml_root = req.xml[0]
|
||||
|
||||
self.time = datetime.fromtimestamp(int(xml_root.find("time").text))
|
||||
self.seq = int(xml_root.find("seq").text)
|
||||
|
||||
self.items = []
|
||||
for item in xml_root.findall("item"):
|
||||
self.items.append(PCBEventItem(item))
|
||||
|
||||
# Methods
|
||||
PUT = "put"
|
||||
|
||||
@classmethod
|
||||
def put(cls):
|
||||
"""
|
||||
Example:
|
||||
<response>
|
||||
<pcbevent expire="600"/>
|
||||
</response>
|
||||
"""
|
||||
return E.response(E.pcbevent({"expire": "600"}))
|
||||
|
||||
def __repr__(self) -> str:
|
||||
return (
|
||||
f"PCBEvent<time: {self.time.strftime(self.DATE_FMT)}, "
|
||||
f"seq: {self.seq}, items: {self.items}>"
|
||||
)
|
||||
@@ -22,4 +22,17 @@ class PCBTracker(object):
|
||||
|
||||
@classmethod
|
||||
def alive(cls):
|
||||
"""
|
||||
Example (if Paseli is not active):
|
||||
<response>
|
||||
<pcbtracker ecenable="0"/>
|
||||
</response>
|
||||
|
||||
Potentially if Paseli is active, the response might look like so:
|
||||
<response>
|
||||
<pcbtracker time="" limit="" ecenable="1" eclimit=""/>
|
||||
</response>
|
||||
|
||||
I am unsure what the `time`, `limit`, and `eclimit` responses would be.
|
||||
"""
|
||||
return E.response(E.pcbtracker({"ecenable": "1" if cls.PASELI_ACTIVE else "0"}))
|
||||
|
||||
@@ -8,13 +8,13 @@ from random import randint
|
||||
from time import time
|
||||
from typing import Any, Callable, Dict, Optional, Tuple, Union
|
||||
|
||||
from flask import Flask, Request
|
||||
from flask import Request
|
||||
from kbinxml import KBinXML
|
||||
from lxml import etree
|
||||
from lxml.builder import E
|
||||
from lxml.etree import _Element as eElement
|
||||
|
||||
from v8_server import LOG_PATH
|
||||
from v8_server import LOG_PATH, app
|
||||
from v8_server.eamuse.utils.arc4 import EAmuseARC4
|
||||
from v8_server.eamuse.utils.eamuse import Model
|
||||
from v8_server.eamuse.utils.lz77 import Lz77
|
||||
@@ -101,7 +101,7 @@ class Services(object):
|
||||
return services
|
||||
|
||||
@classmethod
|
||||
def route(cls, app: Flask, _type: ServiceType) -> Callable[[Any], Any]:
|
||||
def route(cls, _type: ServiceType) -> Callable[[Any], Any]:
|
||||
def decorator(f):
|
||||
func = app.route(f"/{_type.value}", methods=["POST"])(f)
|
||||
return func
|
||||
|
||||
@@ -1,26 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, Optional
|
||||
|
||||
|
||||
class XMLBinTypes(object):
|
||||
s8 = "s8"
|
||||
u8 = "u8"
|
||||
s16 = "s16"
|
||||
u16 = "u16"
|
||||
s32 = "s32"
|
||||
u32 = "u32"
|
||||
s64 = "s64"
|
||||
u64 = "u64"
|
||||
ip4 = "ip4"
|
||||
time = "time"
|
||||
|
||||
|
||||
def e_type(_type, count: Optional[int] = None) -> Dict[str, str]:
|
||||
result = {"__type": _type}
|
||||
if count is not None:
|
||||
result["__count"] = str(count)
|
||||
return result
|
||||
from typing import Optional
|
||||
|
||||
|
||||
class Model:
|
||||
|
||||
@@ -1,9 +1,29 @@
|
||||
from datetime import datetime
|
||||
from typing import Optional
|
||||
from typing import Dict, Optional
|
||||
|
||||
from lxml import etree
|
||||
|
||||
|
||||
class XMLBinTypes(object):
|
||||
s8 = "s8"
|
||||
u8 = "u8"
|
||||
s16 = "s16"
|
||||
u16 = "u16"
|
||||
s32 = "s32"
|
||||
u32 = "u32"
|
||||
s64 = "s64"
|
||||
u64 = "u64"
|
||||
ip4 = "ip4"
|
||||
time = "time"
|
||||
|
||||
|
||||
def e_type(_type, count: Optional[int] = None) -> Dict[str, str]:
|
||||
result = {"__type": _type}
|
||||
if count is not None:
|
||||
result["__count"] = str(count)
|
||||
return result
|
||||
|
||||
|
||||
def get_xml_tag(xml: etree) -> str:
|
||||
return str(xml.tag)
|
||||
|
||||
|
||||
@@ -2,14 +2,19 @@ import random
|
||||
from typing import Dict, Tuple
|
||||
|
||||
from flask import request
|
||||
from lxml import etree as ET # noqa: N812
|
||||
from lxml.builder import E
|
||||
from sqlalchemy.orm.exc import MultipleResultsFound
|
||||
|
||||
from v8_server import app
|
||||
from v8_server.eamuse.services.pcbtracker import PCBTracker
|
||||
from v8_server.eamuse.services.services import ServiceRequest, Services, ServiceType
|
||||
from v8_server.eamuse.utils.eamuse import e_type
|
||||
from v8_server.eamuse.services import (
|
||||
Facility,
|
||||
Message,
|
||||
Package,
|
||||
PCBEvent,
|
||||
PCBTracker,
|
||||
ServiceRequest,
|
||||
Services,
|
||||
ServiceType,
|
||||
)
|
||||
from v8_server.eamuse.utils.xml import get_xml_attrib
|
||||
from v8_server.model.connection import Database
|
||||
from v8_server.model.user import Card, ExtID, Profile, RefID, User
|
||||
@@ -43,13 +48,7 @@ def catch_all(u_path: str) -> str:
|
||||
return "You want path: %s" % u_path
|
||||
|
||||
|
||||
def base_response(element: str, attributes: Dict[str, str] = None) -> ET:
|
||||
if attributes is None:
|
||||
attributes = {}
|
||||
return E.response(E(element, {**attributes, "expire": "600"}))
|
||||
|
||||
|
||||
@Services.route(app, ServiceType.PCBTRACKER)
|
||||
@Services.route(ServiceType.PCBTRACKER)
|
||||
def pcbtracker_service() -> FlaskResponse:
|
||||
req = ServiceRequest(request)
|
||||
|
||||
@@ -61,89 +60,57 @@ def pcbtracker_service() -> FlaskResponse:
|
||||
return req.response(response)
|
||||
|
||||
|
||||
@Services.route(ServiceType.MESSAGE)
|
||||
def message_service() -> FlaskResponse:
|
||||
req = ServiceRequest(request)
|
||||
|
||||
if req.method == Message.GET:
|
||||
response = Message.get()
|
||||
else:
|
||||
raise Exception(f"Not sure how to handle this Message Request: {req}")
|
||||
|
||||
return req.response(response)
|
||||
|
||||
|
||||
@Services.route(ServiceType.PCBEVENT)
|
||||
def pcbevent_service() -> FlaskResponse:
|
||||
req = ServiceRequest(request)
|
||||
event = PCBEvent(req)
|
||||
app.logger.info(event)
|
||||
|
||||
if req.method == PCBEvent.PUT:
|
||||
response = PCBEvent.put()
|
||||
else:
|
||||
raise Exception(f"Not sure how to handle this PCBEvent Request: {req}")
|
||||
|
||||
return req.response(response)
|
||||
|
||||
|
||||
@Services.route(ServiceType.PACKAGE)
|
||||
def package_service() -> FlaskResponse:
|
||||
req = ServiceRequest(request)
|
||||
|
||||
if req.method == Package.LIST:
|
||||
response = Package.list(req)
|
||||
else:
|
||||
raise Exception(f"Not sure how to handle this Package Request: {req}")
|
||||
|
||||
return req.response(response)
|
||||
|
||||
|
||||
@Services.route(ServiceType.FACILITY)
|
||||
def facility_service() -> FlaskResponse:
|
||||
req = ServiceRequest(request)
|
||||
|
||||
if req.method == Facility.GET:
|
||||
response = Facility.get()
|
||||
else:
|
||||
raise Exception(f"Not sure how to handle this Facility Request: {req}")
|
||||
|
||||
return req.response(response)
|
||||
|
||||
|
||||
'''
|
||||
@app.route("/message/service", methods=["POST"])
|
||||
def message() -> Tuple[bytes, Dict[str, str]]:
|
||||
"""
|
||||
Unknown what this does. Possibly for operator messages?
|
||||
"""
|
||||
_ = eamuse_read_xml(request)
|
||||
response = base_response("message")
|
||||
return eamuse_prepare_xml(response, request)
|
||||
|
||||
|
||||
@app.route("/pcbevent/service", methods=["POST"])
|
||||
def pcbevent() -> Tuple[bytes, Dict[str, str]]:
|
||||
"""
|
||||
Handle a PCBEvent request. We do nothing for this aside from logging the event.
|
||||
"""
|
||||
_ = eamuse_read_xml(request)
|
||||
response = base_response("pcbevent")
|
||||
return eamuse_prepare_xml(response, request)
|
||||
|
||||
|
||||
@app.route("/facility/service", methods=["POST"])
|
||||
def facility() -> Tuple[bytes, Dict[str, str]]:
|
||||
"""
|
||||
Handle a facility request. The only method of note is the "get" request,
|
||||
which expects to return a bunch of information about the arcade this cabinet is in,
|
||||
as well as some settings for URLs and the name of the cab.
|
||||
"""
|
||||
_ = eamuse_read_xml(request)
|
||||
|
||||
response = E.response(
|
||||
E.facility(
|
||||
E.location(
|
||||
E.id("US-123"),
|
||||
E.country("US"),
|
||||
E.region("."),
|
||||
E.name("H"),
|
||||
E.type("0", e_type("u8")),
|
||||
),
|
||||
E.line(E.id("."), E("class", "0", e_type("u8"))),
|
||||
E.portfw(
|
||||
E.globalip("192.168.1.139", e_type("ip4", count=1)),
|
||||
E.globalport("80", e_type("u16")),
|
||||
E.privateport("80", e_type("u16")),
|
||||
),
|
||||
E.public(
|
||||
E.flag("1", e_type("u8")),
|
||||
E.name("."),
|
||||
E.latitude("0"),
|
||||
E.longitude("0"),
|
||||
),
|
||||
E.share(
|
||||
E.eacoin(
|
||||
E.notchamount("3000", e_type("s32")),
|
||||
E.notchcount("3", e_type("s32")),
|
||||
E.supplylimit("10000", e_type("s32")),
|
||||
),
|
||||
E.eapass(E.valid("365", e_type("u16"))),
|
||||
E.url(
|
||||
E.eapass("www.ea-pass.konami.net"),
|
||||
E.arcadefan("www.konami.jp/am"),
|
||||
E.konaminetdx("http://am.573.jp"),
|
||||
E.konamiid("http://id.konami.net"),
|
||||
E.eagate("http://eagate.573.jp"),
|
||||
),
|
||||
),
|
||||
{"expire": "600"},
|
||||
)
|
||||
)
|
||||
return eamuse_prepare_xml(response, request)
|
||||
|
||||
|
||||
@app.route("/package/service", methods=["POST"])
|
||||
def package() -> Tuple[bytes, Dict[str, str]]:
|
||||
"""
|
||||
This is for supporting downloading of updates. We don't support this.
|
||||
"""
|
||||
_ = eamuse_read_xml(request)
|
||||
|
||||
response = base_response("package")
|
||||
return eamuse_prepare_xml(response, request)
|
||||
|
||||
|
||||
class CardStatus(object):
|
||||
"""
|
||||
List of statuses we return to the game for various reasons
|
||||
|
||||
Reference in New Issue
Block a user