forked from Dniel97/artemis
Merge pull request 'Chunithm Fixes and Maintenance for all!' (#46) from EmmyHeart/artemis:develop into develop
Reviewed-on: Hay1tsme/artemis#46
This commit is contained in:
commit
eaa2652647
@ -147,7 +147,7 @@ class AllnetServlet:
|
||||
resp_dict = {k: v for k, v in vars(resp).items() if v is not None}
|
||||
return (urllib.parse.unquote(urllib.parse.urlencode(resp_dict)) + "\n").encode("utf-8")
|
||||
|
||||
elif not arcade["ip"] or arcade["ip"] is None and self.config.server.strict_ip_checking:
|
||||
elif (not arcade["ip"] or arcade["ip"] is None) and self.config.server.strict_ip_checking:
|
||||
msg = f"Serial {req.serial} attempted allnet auth from bad IP {req.ip}, but arcade {arcade['id']} has no IP set! (strict checking enabled)."
|
||||
self.data.base.log_event(
|
||||
"allnet", "ALLNET_AUTH_NO_SHOP_IP", logging.ERROR, msg
|
||||
|
@ -85,6 +85,18 @@ class TitleConfig:
|
||||
self.__config, "core", "title", "port", default=8080
|
||||
)
|
||||
|
||||
@property
|
||||
def reboot_start_time(self) -> str:
|
||||
return CoreConfig.get_config_field(
|
||||
self.__config, "core", "title", "reboot_start_time", default=""
|
||||
)
|
||||
|
||||
@property
|
||||
def reboot_end_time(self) -> str:
|
||||
return CoreConfig.get_config_field(
|
||||
self.__config, "core", "title", "reboot_end_time", default=""
|
||||
)
|
||||
|
||||
|
||||
class DatabaseConfig:
|
||||
def __init__(self, parent_config: "CoreConfig") -> None:
|
||||
|
@ -6,11 +6,15 @@
|
||||
- `name`: Name for the server, used by some games in their default MOTDs. Default `ARTEMiS`
|
||||
- `is_develop`: Flags that the server is a development instance without a proxy standing in front of it. Setting to `False` tells the server not to listen for SSL, because the proxy should be handling all SSL-related things, among other things. Default `True`
|
||||
- `threading`: Flags that `reactor.run` should be called via the `Thread` standard library. May provide a speed boost, but removes the ability to kill the server via `Ctrl + C`. Default: `False`
|
||||
- `check_arcade_ip`: Checks IPs against the `arcade` table in the database, if one is defined. Default `False`
|
||||
- `strict_ip_checking`: Rejects clients if there is no IP in the `arcade` table for the respective arcade
|
||||
- `log_dir`: Directory to store logs. Server MUST have read and write permissions to this directory or you will have issues. Default `logs`
|
||||
## Title
|
||||
- `loglevel`: Logging level for the title server. Default `info`
|
||||
- `hostname`: Hostname that gets sent to clients to tell them where to connect. Games must be able to connect to your server via the hostname or IP you spcify here. Note that most games will reject `localhost` or `127.0.0.1`. Default `localhost`
|
||||
- `port`: Port that the title server will listen for connections on. Set to 0 to use the Allnet handler to reduce the port footprint. Default `8080`
|
||||
- `reboot_start_time`: 24 hour JST time that clients will see as the start of maintenance period. Leave blank for no maintenance time. Default: ""
|
||||
- `reboot_end_time`: 24 hour JST time that clients will see as the end of maintenance period. Leave blank for no maintenance time. Default: ""
|
||||
## Database
|
||||
- `host`: Host of the database. Default `localhost`
|
||||
- `username`: Username of the account the server should connect to the database with. Default `aime`
|
||||
|
@ -13,6 +13,9 @@ title:
|
||||
loglevel: "info"
|
||||
hostname: "localhost"
|
||||
port: 8080
|
||||
reboot_start_time: "04:00"
|
||||
reboot_end_time: "05:00"
|
||||
|
||||
|
||||
database:
|
||||
host: "localhost"
|
||||
|
@ -200,12 +200,30 @@ class ChuniBase:
|
||||
return {"type": data["type"], "length": 0, "gameSaleList": []}
|
||||
|
||||
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
||||
reboot_start = datetime.strftime(
|
||||
datetime.now() - timedelta(hours=4), self.date_time_format
|
||||
)
|
||||
reboot_end = datetime.strftime(
|
||||
datetime.now() - timedelta(hours=3), self.date_time_format
|
||||
)
|
||||
# if reboot start/end time is not defined use the default behavior of being a few hours ago
|
||||
if self.core_cfg.title.reboot_start_time == "" or self.core_cfg.title.reboot_end_time == "":
|
||||
reboot_start = datetime.strftime(
|
||||
datetime.utcnow() + timedelta(hours=6), self.date_time_format
|
||||
)
|
||||
reboot_end = datetime.strftime(
|
||||
datetime.utcnow() + timedelta(hours=7), self.date_time_format
|
||||
)
|
||||
else:
|
||||
# get current datetime in JST
|
||||
current_jst = datetime.now(pytz.timezone('Asia/Tokyo')).date()
|
||||
|
||||
# parse config start/end times into datetime
|
||||
reboot_start_time = datetime.strptime(self.core_cfg.title.reboot_start_time, "%H:%M")
|
||||
reboot_end_time = datetime.strptime(self.core_cfg.title.reboot_end_time, "%H:%M")
|
||||
|
||||
# offset datetimes with current date/time
|
||||
reboot_start_time = reboot_start_time.replace(year=current_jst.year, month=current_jst.month, day=current_jst.day, tzinfo=pytz.timezone('Asia/Tokyo'))
|
||||
reboot_end_time = reboot_end_time.replace(year=current_jst.year, month=current_jst.month, day=current_jst.day, tzinfo=pytz.timezone('Asia/Tokyo'))
|
||||
|
||||
# create strings for use in gameSetting
|
||||
reboot_start = reboot_start_time.strftime(self.date_time_format)
|
||||
reboot_end = reboot_end_time.strftime(self.date_time_format)
|
||||
|
||||
return {
|
||||
"gameSetting": {
|
||||
"dataVersion": "1.00.00",
|
||||
@ -385,26 +403,24 @@ class ChuniBase:
|
||||
}
|
||||
|
||||
def handle_get_user_rival_music_api_request(self, data: Dict) -> Dict:
|
||||
m = self.data.score.get_rival_music(data["rivalId"], data["nextIndex"], data["maxCount"])
|
||||
if m is None:
|
||||
return {}
|
||||
|
||||
rival_id = data["rivalId"]
|
||||
next_index = int(data["nextIndex"])
|
||||
max_count = int(data["maxCount"])
|
||||
user_rival_music_list = []
|
||||
for music in m:
|
||||
actual_music_id = self.data.static.get_song(music["musicId"])
|
||||
if actual_music_id is None:
|
||||
music_id = music["musicId"]
|
||||
else:
|
||||
music_id = actual_music_id["songId"]
|
||||
|
||||
# Fetch all the rival music entries for the user
|
||||
all_entries = self.data.score.get_rival_music(rival_id)
|
||||
|
||||
# Process the entries based on max_count and nextIndex
|
||||
for music in all_entries[next_index:]:
|
||||
music_id = music["musicId"]
|
||||
level = music["level"]
|
||||
score = music["score"]
|
||||
rank = music["rank"]
|
||||
|
||||
# Find the existing entry for the current musicId in the user_rival_music_list
|
||||
# Create a music entry for the current music_id if it's unique
|
||||
music_entry = next((entry for entry in user_rival_music_list if entry["musicId"] == music_id), None)
|
||||
|
||||
if music_entry is None:
|
||||
# If the entry doesn't exist, create a new entry
|
||||
music_entry = {
|
||||
"musicId": music_id,
|
||||
"length": 0,
|
||||
@ -412,52 +428,32 @@ class ChuniBase:
|
||||
}
|
||||
user_rival_music_list.append(music_entry)
|
||||
|
||||
# Check if the current score is higher than the previous highest score for the level
|
||||
level_entry = next(
|
||||
(
|
||||
entry
|
||||
for entry in music_entry["userRivalMusicDetailList"]
|
||||
if entry["level"] == level
|
||||
),
|
||||
None,
|
||||
)
|
||||
if level_entry is None or score > level_entry["scoreMax"]:
|
||||
# If the level entry doesn't exist or the score is higher, update or add the entry
|
||||
# Create a level entry for the current level if it's unique or has a higher score
|
||||
level_entry = next((entry for entry in music_entry["userRivalMusicDetailList"] if entry["level"] == level), None)
|
||||
if level_entry is None:
|
||||
level_entry = {
|
||||
"level": level,
|
||||
"scoreMax": score,
|
||||
"scoreRank": rank
|
||||
}
|
||||
music_entry["userRivalMusicDetailList"].append(level_entry)
|
||||
elif score > level_entry["scoreMax"]:
|
||||
level_entry["scoreMax"] = score
|
||||
level_entry["scoreRank"] = rank
|
||||
|
||||
if level_entry not in music_entry["userRivalMusicDetailList"]:
|
||||
music_entry["userRivalMusicDetailList"].append(level_entry)
|
||||
|
||||
# Calculate the length for each "musicId" by counting the unique levels
|
||||
for music_entry in user_rival_music_list:
|
||||
music_entry["length"] = len(music_entry["userRivalMusicDetailList"])
|
||||
|
||||
# Prepare the result dictionary with user rival music data
|
||||
result = {
|
||||
"userId": data["userId"],
|
||||
"rivalId": data["rivalId"],
|
||||
"nextIndex": -1,
|
||||
"userRivalMusicList": user_rival_music_list
|
||||
"nextIndex": str(next_index + len(all_entries) if len(all_entries) <= len(user_rival_music_list) else -1),
|
||||
"userRivalMusicList": user_rival_music_list[:max_count]
|
||||
}
|
||||
|
||||
return result
|
||||
def handle_get_user_rival_music_api_requestded(self, data: Dict) -> Dict:
|
||||
m = self.data.score.get_rival_music(data["rivalId"], data["nextIndex"], data["maxCount"])
|
||||
if m is None:
|
||||
return {}
|
||||
|
||||
userRivalMusicList = []
|
||||
for music in m:
|
||||
self.logger.debug(music["point"])
|
||||
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"rivalId": data["rivalId"],
|
||||
"nextIndex": -1
|
||||
|
||||
}
|
||||
|
||||
def handle_get_user_favorite_item_api_request(self, data: Dict) -> Dict:
|
||||
user_fav_item_list = []
|
||||
|
||||
@ -711,11 +707,7 @@ class ChuniBase:
|
||||
if team:
|
||||
team_id = team["id"]
|
||||
team_name = team["teamName"]
|
||||
# Determine whether to use scaled ranks, or original system
|
||||
if self.game_cfg.team.rank_scale:
|
||||
team_rank = self.data.profile.get_team_rank(team["id"])
|
||||
else:
|
||||
team_rank = self.data.profile.get_team_rank_actual(team["id"])
|
||||
team_rank = self.data.profile.get_team_rank(team["id"])
|
||||
|
||||
# Don't return anything if no team name has been defined for defaults and there is no team set for the player
|
||||
if not profile["teamId"] and team_name == "":
|
||||
@ -888,4 +880,4 @@ class ChuniBase:
|
||||
return {
|
||||
"userId": data["userId"],
|
||||
"userNetBattleData": {"recentNBSelectMusicList": []},
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ from datetime import datetime, timedelta
|
||||
from random import randint
|
||||
from typing import Dict
|
||||
|
||||
import pytz
|
||||
from core.config import CoreConfig
|
||||
from titles.chuni.const import ChuniConstants
|
||||
from titles.chuni.database import ChuniData
|
||||
@ -31,12 +32,29 @@ class ChuniNew(ChuniBase):
|
||||
match_end = datetime.strftime(
|
||||
datetime.utcnow() + timedelta(hours=16), self.date_time_format
|
||||
)
|
||||
reboot_start = datetime.strftime(
|
||||
datetime.utcnow() + timedelta(hours=6), self.date_time_format
|
||||
)
|
||||
reboot_end = datetime.strftime(
|
||||
datetime.utcnow() + timedelta(hours=7), self.date_time_format
|
||||
)
|
||||
# if reboot start/end time is not defined use the default behavior of being a few hours ago
|
||||
if self.core_cfg.title.reboot_start_time == "" or self.core_cfg.title.reboot_end_time == "":
|
||||
reboot_start = datetime.strftime(
|
||||
datetime.utcnow() + timedelta(hours=6), self.date_time_format
|
||||
)
|
||||
reboot_end = datetime.strftime(
|
||||
datetime.utcnow() + timedelta(hours=7), self.date_time_format
|
||||
)
|
||||
else:
|
||||
# get current datetime in JST
|
||||
current_jst = datetime.now(pytz.timezone('Asia/Tokyo')).date()
|
||||
|
||||
# parse config start/end times into datetime
|
||||
reboot_start_time = datetime.strptime(self.core_cfg.title.reboot_start_time, "%H:%M")
|
||||
reboot_end_time = datetime.strptime(self.core_cfg.title.reboot_end_time, "%H:%M")
|
||||
|
||||
# offset datetimes with current date/time
|
||||
reboot_start_time = reboot_start_time.replace(year=current_jst.year, month=current_jst.month, day=current_jst.day, tzinfo=pytz.timezone('Asia/Tokyo'))
|
||||
reboot_end_time = reboot_end_time.replace(year=current_jst.year, month=current_jst.month, day=current_jst.day, tzinfo=pytz.timezone('Asia/Tokyo'))
|
||||
|
||||
# create strings for use in gameSetting
|
||||
reboot_start = reboot_start_time.strftime(self.date_time_format)
|
||||
reboot_end = reboot_end_time.strftime(self.date_time_format)
|
||||
return {
|
||||
"gameSetting": {
|
||||
"isMaintenance": False,
|
||||
|
@ -646,7 +646,7 @@ class ChuniProfileData(BaseData):
|
||||
return None
|
||||
return result.fetchone()
|
||||
|
||||
def get_team_rank_actual(self, team_id: int) -> int:
|
||||
def get_team_rank(self, team_id: int) -> int:
|
||||
# Normal ranking system, likely the one used in the real servers
|
||||
# Query all teams sorted by 'teamPoint'
|
||||
result = self.execute(
|
||||
@ -663,42 +663,8 @@ class ChuniProfileData(BaseData):
|
||||
# Return the rank if found, or a default rank otherwise
|
||||
return rank if rank is not None else 0
|
||||
|
||||
def get_team_rank(self, team_id: int) -> int:
|
||||
# Scaled ranking system, designed for smaller instances.
|
||||
# Query all teams sorted by 'teamPoint'
|
||||
result = self.execute(
|
||||
select(team.c.id).order_by(team.c.teamPoint.desc())
|
||||
)
|
||||
|
||||
# Count total number of teams
|
||||
total_teams = self.execute(select(func.count()).select_from(team)).scalar()
|
||||
|
||||
# Get the rank of the team with the given team_id
|
||||
rank = None
|
||||
for i, row in enumerate(result, start=1):
|
||||
if row.id == team_id:
|
||||
rank = i
|
||||
break
|
||||
|
||||
# If the team is not found, return default rank
|
||||
if rank is None:
|
||||
return 0
|
||||
|
||||
# Define rank tiers
|
||||
tiers = {
|
||||
1: range(1, int(total_teams * 0.1) + 1), # Rainbow
|
||||
2: range(int(total_teams * 0.1) + 1, int(total_teams * 0.4) + 1), # Gold
|
||||
3: range(int(total_teams * 0.4) + 1, int(total_teams * 0.7) + 1), # Silver
|
||||
4: range(int(total_teams * 0.7) + 1, total_teams + 1), # Grey
|
||||
}
|
||||
|
||||
# Assign rank based on tier
|
||||
for tier_rank, tier_range in tiers.items():
|
||||
if rank in tier_range:
|
||||
return tier_rank
|
||||
|
||||
# Return default rank if not found in any tier
|
||||
return 0
|
||||
# RIP scaled team ranking. Gone, but forgotten
|
||||
# def get_team_rank_scaled(self, team_id: int) -> int:
|
||||
|
||||
def update_team(self, team_id: int, team_data: Dict) -> bool:
|
||||
team_data["id"] = team_id
|
||||
@ -736,4 +702,4 @@ class ChuniProfileData(BaseData):
|
||||
total_play_count += row[0]
|
||||
return {
|
||||
"total_play_count": total_play_count
|
||||
}
|
||||
}
|
||||
|
@ -201,9 +201,9 @@ class ChuniScoreData(BaseData):
|
||||
return None
|
||||
return result.lastrowid
|
||||
|
||||
def get_rival_music(self, rival_id: int, index: int, max_count: int) -> Optional[List[Dict]]:
|
||||
sql = select(playlog).where(playlog.c.user == rival_id).limit(max_count).offset(index)
|
||||
def get_rival_music(self, rival_id: int) -> Optional[List[Dict]]:
|
||||
sql = select(playlog).where(playlog.c.user == rival_id)
|
||||
result = self.execute(sql)
|
||||
if result is None:
|
||||
return None
|
||||
return result.fetchall()
|
||||
return result.fetchall()
|
||||
|
@ -4,6 +4,7 @@ import json
|
||||
import logging
|
||||
from enum import Enum
|
||||
|
||||
import pytz
|
||||
from core.config import CoreConfig
|
||||
from core.data.cache import cached
|
||||
from titles.cm.const import CardMakerConstants
|
||||
@ -61,12 +62,29 @@ class CardMakerBase:
|
||||
}
|
||||
|
||||
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
||||
reboot_start = date.strftime(
|
||||
datetime.now() + timedelta(hours=3), self.date_time_format
|
||||
)
|
||||
reboot_end = date.strftime(
|
||||
datetime.now() + timedelta(hours=4), self.date_time_format
|
||||
)
|
||||
# if reboot start/end time is not defined use the default behavior of being a few hours ago
|
||||
if self.core_cfg.title.reboot_start_time == "" or self.core_cfg.title.reboot_end_time == "":
|
||||
reboot_start = datetime.strftime(
|
||||
datetime.utcnow() + timedelta(hours=6), self.date_time_format
|
||||
)
|
||||
reboot_end = datetime.strftime(
|
||||
datetime.utcnow() + timedelta(hours=7), self.date_time_format
|
||||
)
|
||||
else:
|
||||
# get current datetime in JST
|
||||
current_jst = datetime.now(pytz.timezone('Asia/Tokyo')).date()
|
||||
|
||||
# parse config start/end times into datetime
|
||||
reboot_start_time = datetime.strptime(self.core_cfg.title.reboot_start_time, "%H:%M")
|
||||
reboot_end_time = datetime.strptime(self.core_cfg.title.reboot_end_time, "%H:%M")
|
||||
|
||||
# offset datetimes with current date/time
|
||||
reboot_start_time = reboot_start_time.replace(year=current_jst.year, month=current_jst.month, day=current_jst.day, tzinfo=pytz.timezone('Asia/Tokyo'))
|
||||
reboot_end_time = reboot_end_time.replace(year=current_jst.year, month=current_jst.month, day=current_jst.day, tzinfo=pytz.timezone('Asia/Tokyo'))
|
||||
|
||||
# create strings for use in gameSetting
|
||||
reboot_start = reboot_start_time.strftime(self.date_time_format)
|
||||
reboot_end = reboot_end_time.strftime(self.date_time_format)
|
||||
|
||||
# grab the dict with all games version numbers from user config
|
||||
games_ver = self.game_cfg.version.version(self.version)
|
||||
|
@ -5,6 +5,7 @@ from base64 import b64decode
|
||||
from os import path, stat, remove
|
||||
from PIL import ImageFile
|
||||
|
||||
import pytz
|
||||
from core.config import CoreConfig
|
||||
from titles.mai2.const import Mai2Constants
|
||||
from titles.mai2.config import Mai2Config
|
||||
@ -21,22 +22,47 @@ class Mai2Base:
|
||||
self.can_deliver = False
|
||||
self.can_usbdl = False
|
||||
self.old_server = ""
|
||||
|
||||
|
||||
if self.core_config.server.is_develop and self.core_config.title.port > 0:
|
||||
self.old_server = f"http://{self.core_config.title.hostname}:{self.core_config.title.port}/SDEY/197/"
|
||||
|
||||
|
||||
else:
|
||||
self.old_server = f"http://{self.core_config.title.hostname}/SDEY/197/"
|
||||
|
||||
def handle_get_game_setting_api_request(self, data: Dict):
|
||||
return {
|
||||
# if reboot start/end time is not defined use the default behavior of being a few hours ago
|
||||
if self.core_cfg.title.reboot_start_time == "" or self.core_cfg.title.reboot_end_time == "":
|
||||
reboot_start = datetime.strftime(
|
||||
datetime.utcnow() + timedelta(hours=6), self.date_time_format
|
||||
)
|
||||
reboot_end = datetime.strftime(
|
||||
datetime.utcnow() + timedelta(hours=7), self.date_time_format
|
||||
)
|
||||
else:
|
||||
# get current datetime in JST
|
||||
current_jst = datetime.now(pytz.timezone('Asia/Tokyo')).date()
|
||||
|
||||
# parse config start/end times into datetime
|
||||
reboot_start_time = datetime.strptime(self.core_cfg.title.reboot_start_time, "%H:%M")
|
||||
reboot_end_time = datetime.strptime(self.core_cfg.title.reboot_end_time, "%H:%M")
|
||||
|
||||
# offset datetimes with current date/time
|
||||
reboot_start_time = reboot_start_time.replace(year=current_jst.year, month=current_jst.month, day=current_jst.day, tzinfo=pytz.timezone('Asia/Tokyo'))
|
||||
reboot_end_time = reboot_end_time.replace(year=current_jst.year, month=current_jst.month, day=current_jst.day, tzinfo=pytz.timezone('Asia/Tokyo'))
|
||||
|
||||
# create strings for use in gameSetting
|
||||
reboot_start = reboot_start_time.strftime(self.date_time_format)
|
||||
reboot_end = reboot_end_time.strftime(self.date_time_format)
|
||||
|
||||
|
||||
return {
|
||||
"isDevelop": False,
|
||||
"isAouAccession": False,
|
||||
"gameSetting": {
|
||||
"isMaintenance": False,
|
||||
"requestInterval": 1800,
|
||||
"rebootStartTime": "2020-01-01 07:00:00.0",
|
||||
"rebootEndTime": "2020-01-01 07:59:59.0",
|
||||
"rebootStartTime": reboot_start,
|
||||
"rebootEndTime": reboot_end,
|
||||
"movieUploadLimit": 100,
|
||||
"movieStatus": 1,
|
||||
"movieServerUri": self.old_server + "api/movie" if self.game_config.uploads.movies else "movie",
|
||||
|
@ -4,6 +4,7 @@ import json
|
||||
import logging
|
||||
from enum import Enum
|
||||
|
||||
import pytz
|
||||
from core.config import CoreConfig
|
||||
from core.data.cache import cached
|
||||
from titles.ongeki.const import OngekiConstants
|
||||
@ -103,12 +104,30 @@ class OngekiBase:
|
||||
self.version = OngekiConstants.VER_ONGEKI
|
||||
|
||||
def handle_get_game_setting_api_request(self, data: Dict) -> Dict:
|
||||
reboot_start = date.strftime(
|
||||
datetime.now() + timedelta(hours=3), self.date_time_format
|
||||
)
|
||||
reboot_end = date.strftime(
|
||||
datetime.now() + timedelta(hours=4), self.date_time_format
|
||||
)
|
||||
# if reboot start/end time is not defined use the default behavior of being a few hours ago
|
||||
if self.core_cfg.title.reboot_start_time == "" or self.core_cfg.title.reboot_end_time == "":
|
||||
reboot_start = datetime.strftime(
|
||||
datetime.utcnow() + timedelta(hours=6), self.date_time_format
|
||||
)
|
||||
reboot_end = datetime.strftime(
|
||||
datetime.utcnow() + timedelta(hours=7), self.date_time_format
|
||||
)
|
||||
else:
|
||||
# get current datetime in JST
|
||||
current_jst = datetime.now(pytz.timezone('Asia/Tokyo')).date()
|
||||
|
||||
# parse config start/end times into datetime
|
||||
reboot_start_time = datetime.strptime(self.core_cfg.title.reboot_start_time, "%H:%M")
|
||||
reboot_end_time = datetime.strptime(self.core_cfg.title.reboot_end_time, "%H:%M")
|
||||
|
||||
# offset datetimes with current date/time
|
||||
reboot_start_time = reboot_start_time.replace(year=current_jst.year, month=current_jst.month, day=current_jst.day, tzinfo=pytz.timezone('Asia/Tokyo'))
|
||||
reboot_end_time = reboot_end_time.replace(year=current_jst.year, month=current_jst.month, day=current_jst.day, tzinfo=pytz.timezone('Asia/Tokyo'))
|
||||
|
||||
# create strings for use in gameSetting
|
||||
reboot_start = reboot_start_time.strftime(self.date_time_format)
|
||||
reboot_end = reboot_end_time.strftime(self.date_time_format)
|
||||
|
||||
return {
|
||||
"gameSetting": {
|
||||
"dataVersion": "1.00.00",
|
||||
|
Loading…
Reference in New Issue
Block a user