forked from Dniel97/artemis
Merge branch 'develop'
This commit is contained in:
commit
90024ddbd9
@ -191,7 +191,7 @@ class AllnetServlet:
|
||||
|
||||
self.logger.debug(f"request {req_dict}")
|
||||
|
||||
rsa = RSA.import_key(open(self.config.billing.sign_key, 'rb').read())
|
||||
rsa = RSA.import_key(open(self.config.billing.signing_key, 'rb').read())
|
||||
signer = PKCS1_v1_5.new(rsa)
|
||||
digest = SHA.new()
|
||||
|
||||
|
@ -38,6 +38,7 @@ class AllnetCountryCode(Enum):
|
||||
HONG_KONG = "HKG"
|
||||
SINGAPORE = "SGP"
|
||||
SOUTH_KOREA = "KOR"
|
||||
TAIWAN = "TWN"
|
||||
CHINA = "CHN"
|
||||
|
||||
class AllnetJapanRegionId(Enum):
|
||||
|
7
core/data/schema/versions/SDDT_2_rollback.sql
Normal file
7
core/data/schema/versions/SDDT_2_rollback.sql
Normal file
@ -0,0 +1,7 @@
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
ALTER TABLE ongeki_profile_data DROP COLUMN isDialogWatchedSuggestMemory;
|
||||
ALTER TABLE ongeki_score_best DROP COLUMN platinumScoreMax;
|
||||
ALTER TABLE ongeki_score_playlog DROP COLUMN platinumScore;
|
||||
ALTER TABLE ongeki_score_playlog DROP COLUMN platinumScoreMax;
|
||||
DROP TABLE IF EXISTS `ongeki_user_memorychapter`;
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
27
core/data/schema/versions/SDDT_3_upgrade.sql
Normal file
27
core/data/schema/versions/SDDT_3_upgrade.sql
Normal file
@ -0,0 +1,27 @@
|
||||
SET FOREIGN_KEY_CHECKS=0;
|
||||
|
||||
ALTER TABLE ongeki_profile_data ADD COLUMN isDialogWatchedSuggestMemory BOOLEAN;
|
||||
ALTER TABLE ongeki_score_best ADD COLUMN platinumScoreMax INTEGER;
|
||||
ALTER TABLE ongeki_score_playlog ADD COLUMN platinumScore INTEGER;
|
||||
ALTER TABLE ongeki_score_playlog ADD COLUMN platinumScoreMax INTEGER;
|
||||
|
||||
CREATE TABLE ongeki_user_memorychapter (
|
||||
id INT PRIMARY KEY NOT NULL AUTO_INCREMENT,
|
||||
user INT NOT NULL,
|
||||
chapterId INT NOT NULL,
|
||||
gaugeId INT NOT NULL,
|
||||
gaugeNum INT NOT NULL,
|
||||
jewelCount INT NOT NULL,
|
||||
isStoryWatched BOOLEAN NOT NULL,
|
||||
isBossWatched BOOLEAN NOT NULL,
|
||||
isDialogWatched BOOLEAN NOT NULL,
|
||||
isEndingWatched BOOLEAN NOT NULL,
|
||||
isClear BOOLEAN NOT NULL,
|
||||
lastPlayMusicId INT NOT NULL,
|
||||
lastPlayMusicLevel INT NOT NULL,
|
||||
lastPlayMusicCategory INT NOT NULL,
|
||||
UNIQUE KEY ongeki_user_memorychapter_uk (user, chapterId),
|
||||
CONSTRAINT ongeki_user_memorychapter_ibfk_1 FOREIGN KEY (user) REFERENCES aime_user (id) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
);
|
||||
|
||||
SET FOREIGN_KEY_CHECKS=1;
|
@ -53,10 +53,13 @@ class TitleServlet():
|
||||
code = endpoints["game"]
|
||||
if code not in self.title_registry:
|
||||
self.logger.warn(f"Unknown game code {code}")
|
||||
request.setResponseCode(404)
|
||||
return b""
|
||||
|
||||
index = self.title_registry[code]
|
||||
if not hasattr(index, "render_GET"):
|
||||
self.logger.warn(f"{code} does not dispatch GET")
|
||||
request.setResponseCode(405)
|
||||
return b""
|
||||
|
||||
return index.render_GET(request, endpoints["version"], endpoints["endpoint"])
|
||||
@ -65,10 +68,13 @@ class TitleServlet():
|
||||
code = endpoints["game"]
|
||||
if code not in self.title_registry:
|
||||
self.logger.warn(f"Unknown game code {code}")
|
||||
request.setResponseCode(404)
|
||||
return b""
|
||||
|
||||
index = self.title_registry[code]
|
||||
if not hasattr(index, "render_POST"):
|
||||
self.logger.warn(f"{code} does not dispatch POST")
|
||||
request.setResponseCode(405)
|
||||
return b""
|
||||
|
||||
return index.render_POST(request, int(endpoints["version"]), endpoints["endpoint"])
|
||||
|
41
docs/prod.md
Normal file
41
docs/prod.md
Normal file
@ -0,0 +1,41 @@
|
||||
# ARTEMiS Production mode
|
||||
Production mode is a configuration option that changes how the server listens to be more friendly to a production environment. This mode assumes that a proxy (for this guide, nginx) is standing in front of the server to handle port mapping and TLS. In order to activate production mode, simply change `is_develop` to `False` in `core.yaml`. Next time you start the server, you should see "Starting server in production mode".
|
||||
|
||||
## Nginx Configuration
|
||||
### Port forwarding
|
||||
Artemis requires that the following ports be forwarded to allow internet traffic to access the server. This will not change regardless of what you set in the config, as many of these ports are hard-coded in the games.
|
||||
`tcp:80` all.net, non-ssl titles
|
||||
`tcp:8443` billing
|
||||
`tcp:22345` aimedb
|
||||
`tcp:443` frontend, SSL titles
|
||||
|
||||
### A note about external proxy services (cloudflare, etc)
|
||||
Due to the way that artemis functions, it is currently not possible to put the server behind something like Cloudflare. Cloudflare only proxies web traffic on the standard ports (80, 443) and, as shown above, this does not work with artemis. Server administrators should seek other means to protect their network (VPS hosting, VPN, etc)
|
||||
|
||||
### SSL Certificates
|
||||
You will need to generate SSL certificates for some games. The certificates vary in security and validity requirements. Please see the general guide below
|
||||
- General Title: The certificate for the general title server should be valid, not self-signed and match the CN that the game will be reaching out to (e.i if your games are reaching out to titles.hostname.here, your ssl certificate should be valid for titles.hostname.here, or *.hostname.here)
|
||||
- CXB: Same requires as the title server. It must not be self-signed, and CN must match. Recomended to get a wildcard cert if possible, and use it for both Title and CXB
|
||||
- Pokken: Pokken can be self-signed, and the CN doesn't have to match, but it MUST use 2048-bit RSA. Due to the games age, andthing stronger then that will be rejected.
|
||||
|
||||
### Port mappings
|
||||
An example config is provided in the `config` folder called `nginx_example.conf`. It is set up for the following:
|
||||
`naominet.jp:tcp:80` -> `localhost:tcp:8000` for allnet
|
||||
`ib.naominet.jp:ssl:8443` -> `localhost:tcp:8444` for the billing server
|
||||
`your.hostname.here:ssl:443` -> `localhost:tcp:8080` for the SSL title server
|
||||
`your.hostname.here:tcp:80` -> `localhost:tcp:8080` for the non-SSL title server
|
||||
`cxb.hostname.here:ssl:443` -> `localhost:tcp:8080` for crossbeats (appends /SDCA/104/ to the request)
|
||||
`pokken.hostname.here:ssl:443` -> `localhost:tcp:8080` for pokken
|
||||
`frontend.hostname.here:ssl:443` -> `localhost:tcp:8090` for the frontend, includes https redirection
|
||||
|
||||
If you're using this as a guide, be sure to replace your.hostname.here with the hostname you specified in core.yaml under `titles->hostname`. Do *not* change naominet.jp, or allnet/billing will fail. Also remember to specifiy certificate paths correctly, as in the example they are simply placeholders.
|
||||
|
||||
### Multi-service ports
|
||||
It is possible to use nginx to redirect billing and title server requests to the same port that all.net uses. By setting `port` to 0 under billing and title server, you can change the nginx config to serve the following (entries not shown here should be the same)
|
||||
`ib.naominet.jp:ssl:8443` -> `localhost:tcp:8000` for the billing server
|
||||
`your.hostname.here:ssl:443` -> `localhost:tcp:8000` for the SSL title server
|
||||
`your.hostname.here:tcp:80` -> `localhost:tcp:8000` for the non-SSL title server
|
||||
`cxb.hostname.here:ssl:443` -> `localhost:tcp:8000` for crossbeats (appends /SDCA/104/ to the request)
|
||||
`pokken.hostname.here:ssl:443` -> `localhost:tcp:8000` for pokken
|
||||
|
||||
This will allow you to only use 3 ports locally, but you will still need to forward the same internet-facing ports as before.
|
@ -99,26 +99,6 @@ server {
|
||||
}
|
||||
}
|
||||
|
||||
# CXB, comment this out if you don't plan on serving crossbeats.
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name cxb.hostname.here;
|
||||
|
||||
ssl_certificate /path/to/cert/cxb.pem;
|
||||
ssl_certificate_key /path/to/cert/cxb.key;
|
||||
ssl_session_timeout 1d;
|
||||
ssl_session_cache shared:MozSSL:10m;
|
||||
ssl_session_tickets off;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2 TLSv1.3;
|
||||
ssl_ciphers "ALL:@SECLEVEL=1";
|
||||
ssl_prefer_server_ciphers off;
|
||||
|
||||
location / {
|
||||
proxy_pass http://localhost:8080/SDBT/104/;
|
||||
}
|
||||
}
|
||||
|
||||
# Frontend, set to redirect to HTTPS. Comment out if you don't intend to use the frontend
|
||||
server {
|
||||
listen 80;
|
||||
@ -134,6 +114,7 @@ server {
|
||||
# Frontend HTTPS. Comment out if you on't intend to use the frontend
|
||||
server {
|
||||
listen 443 ssl;
|
||||
server_name frontend.hostname.here;
|
||||
|
||||
ssl_certificate /path/to/cert/frontend.pem;
|
||||
ssl_certificate_key /path/to/cert/frontend.key;
|
||||
|
1
index.py
1
index.py
@ -29,6 +29,7 @@ class HttpDispatcher(resource.Resource):
|
||||
self.map_post.connect('allnet_poweron', '/sys/servlet/PowerOn', controller="allnet", action='handle_poweron', conditions=dict(method=['POST']))
|
||||
self.map_post.connect('allnet_downloadorder', '/sys/servlet/DownloadOrder', controller="allnet", action='handle_dlorder', conditions=dict(method=['POST']))
|
||||
self.map_post.connect('allnet_billing', '/request', controller="allnet", action='handle_billing_request', conditions=dict(method=['POST']))
|
||||
self.map_post.connect('allnet_billing', '/request/', controller="allnet", action='handle_billing_request', conditions=dict(method=['POST']))
|
||||
|
||||
self.map_post.connect('mucha_boardauth', '/mucha/boardauth.do', controller="mucha", action='handle_boardauth', conditions=dict(method=['POST']))
|
||||
self.map_post.connect('mucha_updatacheck', '/mucha/updatacheck.do', controller="mucha", action='handle_updatacheck', conditions=dict(method=['POST']))
|
||||
|
14
readme.md
14
readme.md
@ -16,7 +16,7 @@ Games listed below have been tested and confirmed working. Only game versions ol
|
||||
+ All versions
|
||||
|
||||
+ Ongeki
|
||||
+ All versions up to Bright
|
||||
+ All versions up to Bright Memory
|
||||
|
||||
+ Wacca
|
||||
+ Lily R
|
||||
@ -29,10 +29,8 @@ Games listed below have been tested and confirmed working. Only game versions ol
|
||||
- memcached (for non-windows platforms)
|
||||
- mysql/mariadb server
|
||||
|
||||
## Quick start guide
|
||||
1) Clone this repository
|
||||
2) Install requirements (see the platform-specific guides for instructions)
|
||||
3) Install python libraries via `pip`
|
||||
4) Copy the example configuration files into another folder (by default the server looks for the `config` directory)
|
||||
5) Edit the newly copied configuration files to your liking, using [this](docs/config.md) doc as a guide.
|
||||
6) Run the server by invoking `index.py` ex. `python3 index.py`
|
||||
## Setup guides
|
||||
Follow the platform-specific guides for [windows](docs/INSTALL_WINDOWS.md) and [ubuntu](docs/INSTALL_UBUNTU.md) to setup and run the server.
|
||||
|
||||
## Production guide
|
||||
See the [production guide](docs/prod.md) for running a production server.
|
||||
|
@ -135,7 +135,8 @@ class ChuniServlet():
|
||||
|
||||
req_data = json.loads(unzip)
|
||||
|
||||
self.logger.info(f"v{version} {endpoint} request - {req_data}")
|
||||
self.logger.info(f"v{version} {endpoint} request from {request.getClientAddress().host}")
|
||||
self.logger.debug(req_data)
|
||||
|
||||
func_to_find = "handle_" + inflection.underscore(endpoint) + "_request"
|
||||
|
||||
@ -154,7 +155,7 @@ class ChuniServlet():
|
||||
if resp == None:
|
||||
resp = {'returnCode': 1}
|
||||
|
||||
self.logger.info(f"Response {resp}")
|
||||
self.logger.debug(f"Response {resp}")
|
||||
|
||||
zipped = zlib.compress(json.dumps(resp, ensure_ascii=False).encode("utf-8"))
|
||||
|
||||
|
@ -788,6 +788,10 @@ class OngekiBase():
|
||||
for x in upsert["userChapterList"]:
|
||||
self.data.item.put_chapter(user_id, x)
|
||||
|
||||
if "userMemoryChapterList" in upsert:
|
||||
for x in upsert["userMemoryChapterList"]:
|
||||
self.data.item.put_memorychapter(user_id, x)
|
||||
|
||||
if "userItemList" in upsert:
|
||||
for x in upsert["userItemList"]:
|
||||
self.data.item.put_item(user_id, x)
|
||||
|
58
titles/ongeki/brightmemory.py
Normal file
58
titles/ongeki/brightmemory.py
Normal file
@ -0,0 +1,58 @@
|
||||
from datetime import date, datetime, timedelta
|
||||
from typing import Any, Dict
|
||||
import pytz
|
||||
import json
|
||||
|
||||
from core.config import CoreConfig
|
||||
from titles.ongeki.base import OngekiBase
|
||||
from titles.ongeki.const import OngekiConstants
|
||||
from titles.ongeki.config import OngekiConfig
|
||||
|
||||
class OngekiBrightMemory(OngekiBase):
|
||||
|
||||
def __init__(self, core_cfg: CoreConfig, game_cfg: OngekiConfig) -> None:
|
||||
super().__init__(core_cfg, game_cfg)
|
||||
self.version = OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY
|
||||
|
||||
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
||||
ret = super().handle_get_game_setting_api_request(data)
|
||||
ret["gameSetting"]["dataVersion"] = "1.35.00"
|
||||
ret["gameSetting"]["onlineDataVersion"] = "1.35.00"
|
||||
ret["gameSetting"]["maxCountCharacter"] = 50
|
||||
ret["gameSetting"]["maxCountCard"] = 300
|
||||
ret["gameSetting"]["maxCountItem"] = 300
|
||||
ret["gameSetting"]["maxCountMusic"] = 50
|
||||
ret["gameSetting"]["maxCountMusicItem"] = 300
|
||||
ret["gameSetting"]["maxCountRivalMusic"] = 300
|
||||
return ret
|
||||
|
||||
def handle_get_user_memory_chapter_api_request(self, data: Dict) -> Dict:
|
||||
memories = self.data.item.get_memorychapters(data["userId"])
|
||||
if not memories:
|
||||
return {"userId": data["userId"], "length":6, "userMemoryChapterList":[
|
||||
{"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70001, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0},
|
||||
{"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70002, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0},
|
||||
{"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70003, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0},
|
||||
{"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70004, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0},
|
||||
{"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70005, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0},
|
||||
{"gaugeId":0, "isClear": False, "gaugeNum": 0, "chapterId": 70099, "jewelCount": 0, "isBossWatched": False, "isStoryWatched": False, "isDialogWatched": False, "isEndingWatched": False, "lastPlayMusicId": 0, "lastPlayMusicLevel": 0, "lastPlayMusicCategory": 0}
|
||||
]}
|
||||
|
||||
memory_chp = []
|
||||
for chp in memories:
|
||||
tmp = chp._asdict()
|
||||
tmp.pop("id")
|
||||
tmp.pop("user")
|
||||
memory_chp.append(tmp)
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"length": len(memory_chp),
|
||||
"userMemoryChapterList": memory_chp
|
||||
}
|
||||
|
||||
def handle_get_game_music_release_state_api_request(self, data: Dict) -> Dict:
|
||||
return {
|
||||
"techScore": 0,
|
||||
"cardNum": 0
|
||||
}
|
@ -10,6 +10,7 @@ class OngekiConstants():
|
||||
VER_ONGEKI_RED = 4
|
||||
VER_ONGEKI_RED_PLUS = 5
|
||||
VER_ONGEKI_BRIGHT = 6
|
||||
VER_ONGEKI_BRIGHT_MEMORY = 7
|
||||
|
||||
EVT_TYPES: Enum = Enum('EVT_TYPES', [
|
||||
'None',
|
||||
@ -43,7 +44,7 @@ class OngekiConstants():
|
||||
Lunatic = 10
|
||||
|
||||
VERSION_NAMES = ("ONGEKI", "ONGEKI+", "ONGEKI Summer", "ONGEKI Summer+", "ONGEKI Red", "ONGEKI Red+",
|
||||
"ONGEKI Bright")
|
||||
"ONGEKI Bright", "ONGEKI Bright Memory")
|
||||
|
||||
@classmethod
|
||||
def game_ver_to_string(cls, ver: int):
|
||||
|
@ -17,6 +17,7 @@ from titles.ongeki.summerplus import OngekiSummerPlus
|
||||
from titles.ongeki.red import OngekiRed
|
||||
from titles.ongeki.redplus import OngekiRedPlus
|
||||
from titles.ongeki.bright import OngekiBright
|
||||
from titles.ongeki.brightmemory import OngekiBrightMemory
|
||||
|
||||
class OngekiServlet():
|
||||
def __init__(self, core_cfg: CoreConfig, cfg_dir: str) -> None:
|
||||
@ -32,6 +33,7 @@ class OngekiServlet():
|
||||
OngekiRed(core_cfg, self.game_cfg),
|
||||
OngekiRedPlus(core_cfg, self.game_cfg),
|
||||
OngekiBright(core_cfg, self.game_cfg),
|
||||
OngekiBrightMemory(core_cfg, self.game_cfg),
|
||||
]
|
||||
|
||||
self.logger = logging.getLogger("ongeki")
|
||||
@ -69,8 +71,10 @@ class OngekiServlet():
|
||||
internal_ver = OngekiConstants.VER_ONGEKI_RED
|
||||
elif version >= 125 and version < 130: # Red Plus
|
||||
internal_ver = OngekiConstants.VER_ONGEKI_RED_PLUS
|
||||
elif version >= 130 and version < 135: # Red Plus
|
||||
elif version >= 130 and version < 135: # Bright
|
||||
internal_ver = OngekiConstants.VER_ONGEKI_BRIGHT
|
||||
elif version >= 135 and version < 140: # Bright Memory
|
||||
internal_ver = OngekiConstants.VER_ONGEKI_BRIGHT_MEMORY
|
||||
|
||||
if all(c in string.hexdigits for c in endpoint) and len(endpoint) == 32:
|
||||
# If we get a 32 character long hex string, it's a hash and we're
|
||||
|
@ -107,6 +107,27 @@ chapter = Table(
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
memorychapter = Table(
|
||||
"ongeki_user_memorychapter",
|
||||
metadata,
|
||||
Column("id", Integer, primary_key=True, nullable=False),
|
||||
Column("user", ForeignKey("aime_user.id", ondelete="cascade", onupdate="cascade")),
|
||||
Column("chapterId", Integer),
|
||||
Column("gaugeId", Integer),
|
||||
Column("gaugeNum", Integer),
|
||||
Column("jewelCount", Integer),
|
||||
Column("isStoryWatched", Boolean),
|
||||
Column("isBossWatched", Boolean),
|
||||
Column("isDialogWatched", Boolean),
|
||||
Column("isEndingWatched", Boolean),
|
||||
Column("isClear", Boolean),
|
||||
Column("lastPlayMusicId", Integer),
|
||||
Column("lastPlayMusicLevel", Integer),
|
||||
Column("lastPlayMusicCategory", Integer),
|
||||
UniqueConstraint("user", "chapterId", name="ongeki_user_memorychapter_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
item = Table(
|
||||
"ongeki_user_item",
|
||||
metadata,
|
||||
@ -524,3 +545,22 @@ class OngekiItemData(BaseData):
|
||||
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
||||
|
||||
def put_memorychapter(self, aime_id: int, memorychapter_data: Dict) -> Optional[int]:
|
||||
memorychapter_data["user"] = aime_id
|
||||
|
||||
sql = insert(memorychapter).values(**memorychapter_data)
|
||||
conflict = sql.on_duplicate_key_update(**memorychapter_data)
|
||||
result = self.execute(conflict)
|
||||
|
||||
if result is None:
|
||||
self.logger.warn(f"put_memorychapter: Failed to update! aime_id: {aime_id}")
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_memorychapters(self, aime_id: int) -> Optional[List[Dict]]:
|
||||
sql = select(memorychapter).where(memorychapter.c.user == aime_id)
|
||||
|
||||
result = self.execute(sql)
|
||||
if result is None: return None
|
||||
return result.fetchall()
|
@ -78,6 +78,7 @@ profile = Table(
|
||||
Column("overDamageBattlePoint", Integer, server_default="0"),
|
||||
Column("bestBattlePoint", Integer, server_default="0"),
|
||||
Column("lastEmoneyBrand", Integer, server_default="0"),
|
||||
Column("isDialogWatchedSuggestMemory", Boolean),
|
||||
UniqueConstraint("user", "version", name="ongeki_profile_profile_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
@ -28,6 +28,7 @@ score_best = Table(
|
||||
Column("isLock", Boolean, nullable=False),
|
||||
Column("clearStatus", Boolean, nullable=False),
|
||||
Column("isStoryWatched", Boolean, nullable=False),
|
||||
Column("platinumScoreMax", Integer),
|
||||
UniqueConstraint("user", "musicId", "level", name="ongeki_best_score_uk"),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
@ -96,6 +97,8 @@ playlog = Table(
|
||||
Column("isAllBreak", Boolean),
|
||||
Column("playerRating", Integer),
|
||||
Column("battlePoint", Integer),
|
||||
Column("platinumScore", Integer),
|
||||
Column("platinumScoreMax", Integer),
|
||||
mysql_charset='utf8mb4'
|
||||
)
|
||||
|
||||
|
@ -746,7 +746,7 @@ class WaccaBase():
|
||||
user_id = self.data.profile.profile_to_aime_user(req.profileId)
|
||||
|
||||
for opt in req.optsUpdated:
|
||||
self.data.profile.update_option(user_id, opt.id, opt.val)
|
||||
self.data.profile.update_option(user_id, opt.opt_id, opt.opt_val)
|
||||
|
||||
for update in req.datesUpdated:
|
||||
pass
|
||||
|
@ -840,3 +840,11 @@ class GateTutorialFlag:
|
||||
self.tutorialId,
|
||||
int(self.flagWatched)
|
||||
]
|
||||
|
||||
class DateUpdate:
|
||||
def __init__(self, date_id: int = 0, timestamp: int = 0) -> None:
|
||||
self.id = date_id
|
||||
self.timestamp = timestamp
|
||||
|
||||
def make(self) -> List:
|
||||
return [self.id, self.timestamp]
|
||||
|
@ -1,7 +1,7 @@
|
||||
from typing import List, Dict
|
||||
|
||||
from titles.wacca.handlers.base import BaseRequest, BaseResponse
|
||||
from titles.wacca.handlers.helpers import UserOption
|
||||
from titles.wacca.handlers.helpers import UserOption, DateUpdate
|
||||
|
||||
# ---user/info/update---
|
||||
class UserInfoUpdateRequest(BaseRequest):
|
||||
@ -9,13 +9,17 @@ class UserInfoUpdateRequest(BaseRequest):
|
||||
super().__init__(data)
|
||||
self.profileId = int(self.params[0])
|
||||
self.optsUpdated: List[UserOption] = []
|
||||
self.datesUpdated: List = self.params[3]
|
||||
self.unknown2: List = self.params[2]
|
||||
self.datesUpdated: List[DateUpdate] = []
|
||||
self.favoritesAdded: List[int] = self.params[4]
|
||||
self.favoritesRemoved: List[int] = self.params[5]
|
||||
|
||||
for x in self.params[2]:
|
||||
for x in self.params[1]:
|
||||
self.optsUpdated.append(UserOption(x[0], x[1]))
|
||||
|
||||
for x in self.params[3]:
|
||||
self.datesUpdated.append(DateUpdate(x[0], x[1]))
|
||||
|
||||
# ---user/info/getMyroom--- TODO: Understand this better
|
||||
class UserInfogetMyroomRequest(BaseRequest):
|
||||
game_id = 0
|
||||
|
@ -275,11 +275,6 @@ class UserStatusUpdateRequestV1(BaseRequest):
|
||||
self.itemsRecieved.append(GenericItemRecv(itm[0], itm[1], itm[2]))
|
||||
|
||||
class UserStatusUpdateRequestV2(UserStatusUpdateRequestV1):
|
||||
isContinue = False
|
||||
isFirstPlayFree = False
|
||||
itemsUsed = []
|
||||
lastSongInfo: LastSongDetail
|
||||
|
||||
def __init__(self, data: Dict) -> None:
|
||||
super().__init__(data)
|
||||
self.isContinue = bool(data["params"][3])
|
||||
|
@ -211,7 +211,7 @@ class WaccaProfileData(BaseData):
|
||||
)
|
||||
|
||||
conflict = sql.on_duplicate_key_update(
|
||||
value = sql.inserted.value
|
||||
value = value
|
||||
)
|
||||
|
||||
result = self.execute(conflict)
|
||||
|
Loading…
Reference in New Issue
Block a user