diff --git a/changelog.md b/changelog.md
index 8f60130..196f3fd 100644
--- a/changelog.md
+++ b/changelog.md
@@ -1,6 +1,10 @@
# Changelog
Documenting updates to ARTEMiS, to be updated every time the master branch is pushed to.
+## 20240630
+### DIVA
++ Added configurable festa options'
+
## 20240629
### CHUNITHM
+ Add team points
diff --git a/core/const.py b/core/const.py
index 98effb6..535a1bb 100644
--- a/core/const.py
+++ b/core/const.py
@@ -1,16 +1,18 @@
from enum import Enum
-class MainboardPlatformCodes:
- RINGEDGE = "AALE"
- RINGWIDE = "AAML"
- NU = "AAVE"
- NUSX = "AAWE"
- ALLS_UX = "ACAE"
- ALLS_HX = "ACAX"
+class MainboardPlatformCodes(Enum):
+ RINGEDGE = "AAL"
+ RINGEDGE2 = "AAS"
+ RINGWIDE = "AAM"
+ NU = "AAV"
+ NUSX = "AAW"
+ ALLS = "ACA"
+ #ALLS_UX = "ACAE"
+ #ALLS_HX = "ACAX"
-class MainboardRevisions:
+class MainboardRevisions(Enum):
RINGEDGE = 1
RINGEDGE2 = 2
@@ -29,11 +31,10 @@ class MainboardRevisions:
ALLS_HX2 = 12
-class KeychipPlatformsCodes:
- RING = "A72E"
- NU = ("A60E", "A60E", "A60E")
- NUSX = ("A61X", "A69X")
- ALLS = "A63E"
+class KeychipPlatformsCodes(Enum):
+ RING = "72"
+ NU = ("60", "61", "69")
+ ALLS = "63"
class AllnetCountryCode(Enum):
diff --git a/core/data/alembic/versions/5ea73f89d982_card_add_memo.py b/core/data/alembic/versions/5ea73f89d982_card_add_memo.py
new file mode 100644
index 0000000..84c8a18
--- /dev/null
+++ b/core/data/alembic/versions/5ea73f89d982_card_add_memo.py
@@ -0,0 +1,28 @@
+"""card_add_memo
+
+Revision ID: 5ea73f89d982
+Revises: 745448d83696
+Create Date: 2024-07-06 22:46:56.992152
+
+"""
+from alembic import op
+import sqlalchemy as sa
+from sqlalchemy.dialects import mysql
+
+# revision identifiers, used by Alembic.
+revision = '5ea73f89d982'
+down_revision = '745448d83696'
+branch_labels = None
+depends_on = None
+
+
+def upgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.add_column('aime_card', sa.Column('memo', sa.VARCHAR(length=16), nullable=True))
+ # ### end Alembic commands ###
+
+
+def downgrade():
+ # ### commands auto generated by Alembic - please adjust! ###
+ op.drop_column('aime_card', 'memo')
+ # ### end Alembic commands ###
diff --git a/core/data/schema/arcade.py b/core/data/schema/arcade.py
index 680f827..3e83bc5 100644
--- a/core/data/schema/arcade.py
+++ b/core/data/schema/arcade.py
@@ -206,17 +206,6 @@ class ArcadeData(BaseData):
return None
return result.lastrowid
- def format_serial( # TODO: Actual serial stuff
- self, platform_code: str, platform_rev: int, serial_num: int, append: int = 8888
- ) -> str:
- return f"{platform_code}{platform_rev:02d}A{serial_num:04d}{append:04d}" # 0x41 = A, 0x52 = R
-
- def validate_keychip_format(self, serial: str) -> bool:
- if re.fullmatch(r"^A[0-9]{2}[E|X][-]?[0-9]{2}[A-HJ-NP-Z][0-9]{4}([0-9]{4})?$", serial) is None:
- return False
-
- return True
-
async def get_arcade_by_name(self, name: str) -> Optional[List[Row]]:
sql = arcade.select(or_(arcade.c.name.like(f"%{name}%"), arcade.c.nickname.like(f"%{name}%")))
result = await self.execute(sql)
@@ -230,3 +219,53 @@ class ArcadeData(BaseData):
if result is None:
return None
return result.fetchall()
+
+ async def get_num_generated_keychips(self) -> Optional[int]:
+ result = await self.execute(select(func.count("serial LIKE 'A69A%'")).select_from(machine))
+ if result:
+ return result.fetchone()['count_1']
+ self.logger.error("Failed to count machine serials that start with A69A!")
+
+ def format_serial(
+ self, platform_code: str, platform_rev: int, serial_letter: str, serial_num: int, append: int, dash: bool = False
+ ) -> str:
+ return f"{platform_code}{'-' if dash else ''}{platform_rev:02d}{serial_letter}{serial_num:04d}{append:04d}"
+
+ def validate_keychip_format(self, serial: str) -> bool:
+ # For the 2nd letter, E and X are the only "real" values that have been observed
+ if re.fullmatch(r"^A[0-9]{2}[A-Z][-]?[0-9]{2}[A-HJ-NP-Z][0-9]{4}([0-9]{4})?$", serial) is None:
+ return False
+
+ return True
+
+ # Thanks bottersnike!
+ def get_keychip_suffix(self, year: int, month: int) -> str:
+ assert year > 1957
+ assert 1 <= month <= 12
+
+ year -= 1957
+ # Jan/Feb/Mar are from the previous tax year
+ if month < 4:
+ year -= 1
+ assert year >= 1 and year <= 99
+
+ month = ((month - 1) + 9) % 12 # Offset so April=0
+ return f"{year:02}{month // 6:01}{month % 6 + 1:01}"
+
+
+ def parse_keychip_suffix(self, suffix: str) -> tuple[int, int]:
+ year = int(suffix[0:2])
+ half = int(suffix[2])
+ assert half in (0, 1)
+ period = int(suffix[3])
+ assert period in (1, 2, 3, 4, 5, 6)
+
+ month = half * 6 + (period - 1)
+ month = ((month + 3) % 12) + 1 # Offset so Jan=1
+
+ # Jan/Feb/Mar are from the previous tax year
+ if month < 4:
+ year += 1
+ year += 1957
+
+ return (year, month)
diff --git a/core/data/schema/card.py b/core/data/schema/card.py
index 6205b4c..1865539 100644
--- a/core/data/schema/card.py
+++ b/core/data/schema/card.py
@@ -1,6 +1,6 @@
from typing import Dict, List, Optional
from sqlalchemy import Table, Column, UniqueConstraint
-from sqlalchemy.types import Integer, String, Boolean, TIMESTAMP, BIGINT
+from sqlalchemy.types import Integer, String, Boolean, TIMESTAMP, BIGINT, VARCHAR
from sqlalchemy.sql.schema import ForeignKey
from sqlalchemy.sql import func
from sqlalchemy.engine import Row
@@ -19,6 +19,7 @@ aime_card = Table(
Column("last_login_date", TIMESTAMP, onupdate=func.now()),
Column("is_locked", Boolean, server_default="0"),
Column("is_banned", Boolean, server_default="0"),
+ Column("memo", VARCHAR(16)),
UniqueConstraint("user", "access_code", name="aime_card_uk"),
mysql_charset="utf8mb4",
)
@@ -148,6 +149,11 @@ class CardData(BaseData):
if not result:
self.logger.error(f"Failed to change card access code from {old_ac} to {new_ac}")
+ async def set_memo_by_access_code(self, access_code: str, memo: str) -> None:
+ result = await self.execute(aime_card.update(aime_card.c.access_code == access_code).values(memo=memo))
+ if not result:
+ self.logger.error(f"Failed to add memo to card {access_code}")
+
def to_access_code(self, luid: str) -> str:
"""
Given a felica cards internal 16 hex character luid, convert it to a 0-padded 20 digit access code as a string
diff --git a/core/frontend.py b/core/frontend.py
index f15f58a..d070aa1 100644
--- a/core/frontend.py
+++ b/core/frontend.py
@@ -476,10 +476,10 @@ class FE_User(FE_Base):
card_data.append({
'access_code': ac,
'status': status,
- 'chip_id': "", #None if c['chip_id'] is None else f"{c['chip_id']:X}",
- 'idm': "",
+ 'chip_id': c['chip_id'],
+ 'idm': c['idm'],
'type': c_type,
- "memo": ""
+ "memo": c['memo']
})
if "e" in request.query_params:
@@ -516,7 +516,42 @@ class FE_User(FE_Base):
return resp
async def edit_card(self, request: Request) -> RedirectResponse:
- return RedirectResponse("/user/", 303)
+ frm = await request.form()
+ usr_sesh = self.validate_session(request)
+ if not usr_sesh or not self.test_perm(usr_sesh.permissions, PermissionOffset.USERMOD):
+ return RedirectResponse("/gate/", 303)
+
+ frm = await request.form()
+ ac = frm.get("add_access_code", None)
+ if not ac:
+ return RedirectResponse("/user/?e=999", 303)
+
+ card = await self.data.card.get_card_by_access_code(ac)
+ if not card:
+ return RedirectResponse("/user/?e=2", 303)
+
+ if card['user'] != usr_sesh.user_id and not self.test_perm_minimum(usr_sesh.permissions, PermissionOffset.USERMOD):
+ return RedirectResponse("/user/?e=11", 303)
+
+ if frm.get("add_memo", None):
+ memo = frm.get("add_memo")
+ if len(memo) > 16 or len(memo) == 0:
+ return RedirectResponse("/user/?e=4", 303)
+ await self.data.card.set_memo_by_access_code(ac, memo)
+
+ if frm.get("add_felica_idm", None):
+ idm = frm.get('add_felica_idm')
+ if not all(c in string.hexdigits for c in idm):
+ return RedirectResponse("/user/?e=4", 303)
+ await self.data.card.set_idm_by_access_code(ac, idm)
+
+ if frm.get("add_mifare_chip_id", None):
+ chip_id: str = frm.get('add_mifare_chip_id')
+ if not all(c in string.hexdigits for c in idm):
+ return RedirectResponse("/user/?e=4", 303)
+ await self.data.card.set_chip_id_by_access_code(ac, int(chip_id, 16))
+
+ return RedirectResponse("/user/?s=4", 303)
async def add_card(self, request: Request) -> RedirectResponse:
return RedirectResponse("/user/", 303)
@@ -791,14 +826,19 @@ class FE_System(FE_Base):
return RedirectResponse("/sys/?e=4", 303)
if not serial:
- serial = self.data.arcade.format_serial("A69E", 1, random.randint(1, 9999))
+ append = self.data.arcade.get_keychip_suffix(datetime.now().year, datetime.now().month)
+ generated = await self.data.arcade.get_num_generated_keychips()
+ if not generated:
+ generated = 0
+ serial = self.data.arcade.format_serial("A69A", 1, "A", generated + 1, int(append))
+ serial_dash = self.data.arcade.format_serial("A69A", 1, "A", generated + 1, int(append), True)
cab_id = await self.data.arcade.create_machine(int(shopid), serial, None, game_code if game_code else None)
return Response(template.render(
title=f"{self.core_config.server.name} | System",
sesh=vars(usr_sesh),
- cabadd={"id": cab_id, "serial": serial},
+ cabadd={"id": cab_id, "serial": serial_dash},
), media_type="text/html; charset=utf-8")
async def render_logs(self, request: Request):
diff --git a/core/templates/sys/logs.jinja b/core/templates/sys/logs.jinja
index 9b7a987..2370f6f 100644
--- a/core/templates/sys/logs.jinja
+++ b/core/templates/sys/logs.jinja
@@ -46,7 +46,11 @@ var per_page = 0;
var page = 0;
function update_tbl() {
- if (TBL_DATA.length == 0) { return; }
+ if (TBL_DATA.length == 0) {
+ document.getElementById("btn_next").disabled = true;
+ document.getElementById("btn_prev").disabled = true;
+ return;
+ }
var tbl = document.getElementById("tbl_events");
for (var i = 0; i < per_page; i++) {
@@ -183,7 +187,7 @@ function chg_page(num) {
document.getElementById("btn_prev").disabled = true;
return;
} else if (page == 0) {
- document.getElementById("btn_next").disabled = false;
+ document.getElementById("btn_next").disabled = TBL_DATA.length == 0;
document.getElementById("btn_prev").disabled = true;
} else {
document.getElementById("btn_next").disabled = false;
diff --git a/core/templates/user/index.jinja b/core/templates/user/index.jinja
index 1b6ec1d..578702c 100644
--- a/core/templates/user/index.jinja
+++ b/core/templates/user/index.jinja
@@ -49,11 +49,14 @@ function prep_edit_form(access_code, chip_id, idm, card_type, u_memo) {
fidm.value = idm;
memo.value = u_memo;
- if (card_type == "AmusementIC") {
+ if (access_code.startsWith("3") || access_code.startsWith("010")) {
+ cid.disabled = false;
+ fidm.disabled = true;
+ } else if (access_code.startsWith("5")) {
cid.disabled = true;
fidm.disabled = false;
} else {
- cid.disabled = false;
+ cid.disabled = true;
fidm.disabled = true;
}
}
@@ -91,9 +94,14 @@ Card added successfully
+{% if success is defined and success == 4 %}
+
+Update successful
+
+{% endif %}
{% for c in cards %}
-- {{ c.access_code }} ({{ c.type}}): {{ c.status }} {% if c.status == 'Active'%}{% elif c.status == 'Locked' %}{% endif %}
+- {{ c.access_code }} ({{ c.type if c.memo is none or not c.memo else c.memo }}): {{ c.status }} {% if c.status == 'Active'%}{% elif c.status == 'Locked' %}{% endif %}
{% endfor %}
diff --git a/dbutils.py b/dbutils.py
index 955e509..21b5c9d 100644
--- a/dbutils.py
+++ b/dbutils.py
@@ -42,6 +42,8 @@ if __name__ == "__main__":
data = Data(cfg)
+ loop = asyncio.get_event_loop()
+
if args.action == "create":
data.create_database()
@@ -55,19 +57,15 @@ if __name__ == "__main__":
data.schema_downgrade(args.version)
elif args.action == "create-owner":
- loop = asyncio.get_event_loop()
loop.run_until_complete(data.create_owner(args.email, args.access_code))
elif args.action == "migrate":
- loop = asyncio.get_event_loop()
loop.run_until_complete(data.migrate())
elif args.action == "create-revision":
- loop = asyncio.get_event_loop()
loop.run_until_complete(data.create_revision(args.message))
elif args.action == "create-autorevision":
- loop = asyncio.get_event_loop()
loop.run_until_complete(data.create_revision_auto(args.message))
else:
diff --git a/docs/game_specific_info.md b/docs/game_specific_info.md
index 9fadf80..33d5710 100644
--- a/docs/game_specific_info.md
+++ b/docs/game_specific_info.md
@@ -269,10 +269,14 @@ the Shop, Modules and Customizations.
Config file is located in `config/diva.yaml`.
-| Option | Info |
-| -------------------- | ----------------------------------------------------------------------------------------------- |
-| `unlock_all_modules` | Unlocks all modules (costumes) by default, if set to `False` all modules need to be purchased |
-| `unlock_all_items` | Unlocks all items (customizations) by default, if set to `False` all items need to be purchased |
+| Option | Info |
+| -------------------- | ------------------------------------------------------------------------------------------------ |
+| `festa_enable` | Enable or disable the ingame festa |
+| `festa_add_VP` | Set the extra VP you get when clearing a song, if festa is not enabled no extra VP will be given |
+| `festa_multiply_VP` | Multiplier for festa add VP |
+| `festa_end_time` | Set the date time for when festa will end and not show up in game anymore |
+| `unlock_all_modules` | Unlocks all modules (costumes) by default, if set to `False` all modules need to be purchased |
+| `unlock_all_items` | Unlocks all items (customizations) by default, if set to `False` all items need to be purchased |
### Custom PV Lists (databanks)
diff --git a/example_config/diva.yaml b/example_config/diva.yaml
index ad1842a..7b8bdcb 100644
--- a/example_config/diva.yaml
+++ b/example_config/diva.yaml
@@ -1,6 +1,10 @@
server:
enable: True
loglevel: "info"
+ festa_enable: True
+ festa_add_VP: "20,5"
+ festa_multiply_VP: "1,2"
+ festa_end_time: "2029-01-01 00:00:00.0"
mods:
unlock_all_modules: True
diff --git a/titles/chuni/frontend.py b/titles/chuni/frontend.py
index 0dbefac..510ae08 100644
--- a/titles/chuni/frontend.py
+++ b/titles/chuni/frontend.py
@@ -234,11 +234,11 @@ class ChuniFrontend(FE_Base):
if usr_sesh.user_id > 0:
form_data = await request.form()
chunithm_version = form_data.get("version")
- self.logger.info(f"version change to: {chunithm_version}")
+ self.logger.debug(f"version change to: {chunithm_version}")
if(chunithm_version.isdigit()):
usr_sesh.chunithm_version=int(chunithm_version)
encoded_sesh = self.encode_session(usr_sesh)
- self.logger.info(f"Created session with JWT {encoded_sesh}")
+ self.logger.debug(f"Created session with JWT {encoded_sesh}")
resp = RedirectResponse("/game/chuni/", 303)
resp.set_cookie("ARTEMIS_SESH", encoded_sesh)
return resp
diff --git a/titles/diva/base.py b/titles/diva/base.py
index 3b96848..be7a241 100644
--- a/titles/diva/base.py
+++ b/titles/diva/base.py
@@ -264,6 +264,11 @@ class DivaBase:
return response
async def handle_festa_info_request(self, data: Dict) -> Dict:
+ if self.game_config.server.festa_enable:
+ festa_end_time = self.game_config.server.festa_end_time
+ else:
+ festa_end_time = (datetime.datetime.now() - datetime.timedelta(days=365)).strftime("%Y-%m-%d %H:%M:%S") + ".0"
+
encoded = "&"
params = {
"fi_id": "1,2",
@@ -273,10 +278,10 @@ class DivaBase:
"fi_difficulty": "-1,-1",
"fi_pv_id_lst": "ALL,ALL",
"fi_attr": "7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF,7FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
- "fi_add_vp": "20,5",
- "fi_mul_vp": "1,2",
+ "fi_add_vp": f"{self.game_config.server.festa_add_VP}",
+ "fi_mul_vp": f"{self.game_config.server.festa_multiply_VP}",
"fi_st": "2019-01-01 00:00:00.0,2019-01-01 00:00:00.0",
- "fi_et": "2029-01-01 00:00:00.0,2029-01-01 00:00:00.0",
+ "fi_et": f"{festa_end_time},{festa_end_time}",
"fi_lut": "{self.time_lut}",
}
diff --git a/titles/diva/config.py b/titles/diva/config.py
index efa327e..48b5c0f 100644
--- a/titles/diva/config.py
+++ b/titles/diva/config.py
@@ -19,6 +19,29 @@ class DivaServerConfig:
)
)
+ @property
+ def festa_enable(self) -> bool:
+ return CoreConfig.get_config_field(
+ self.__config, "diva", "server", "festa_enable", default=True
+ )
+
+ @property
+ def festa_add_VP(self) -> str:
+ return CoreConfig.get_config_field(
+ self.__config, "diva", "server", "festa_add_VP", default="20,5"
+ )
+
+ @property
+ def festa_multiply_VP(self) -> str:
+ return CoreConfig.get_config_field(
+ self.__config, "diva", "server", "festa_multiply_VP", default="1,2"
+ )
+
+ @property
+ def festa_end_time(self) -> str:
+ return CoreConfig.get_config_field(
+ self.__config, "diva", "server", "festa_end_time", default="2029-01-01 00:00:00.0"
+ )
class DivaModsConfig:
def __init__(self, parent_config: "DivaConfig") -> None:
diff --git a/titles/mai2/frontend.py b/titles/mai2/frontend.py
index 635c2fa..976e2c4 100644
--- a/titles/mai2/frontend.py
+++ b/titles/mai2/frontend.py
@@ -5,8 +5,9 @@ from starlette.responses import Response, RedirectResponse
from os import path
import yaml
import jinja2
+from datetime import datetime
-from core.frontend import FE_Base, UserSession
+from core.frontend import FE_Base, UserSession, PermissionOffset
from core.config import CoreConfig
from .database import Mai2Data
from .config import Mai2Config
@@ -32,6 +33,12 @@ class Mai2Frontend(FE_Base):
Route("/", self.render_GET_playlog, methods=['GET']),
Route("/{index}", self.render_GET_playlog, methods=['GET']),
]),
+ Mount("/events", routes=[
+ Route("/", self.render_events, methods=['GET']),
+ Route("/{event_id:int}", self.render_event_edit, methods=['GET']),
+ Route("/update", self.update_event, methods=['POST']),
+ Route("/version.change", self.version_change, methods=['POST']),
+ ]),
Route("/update.name", self.update_name, methods=['POST']),
Route("/version.change", self.version_change, methods=['POST']),
]
@@ -43,13 +50,15 @@ class Mai2Frontend(FE_Base):
usr_sesh = self.validate_session(request)
if not usr_sesh:
usr_sesh = UserSession()
+
+ incoming_ver = usr_sesh.maimai_version
if usr_sesh.user_id > 0:
versions = await self.data.profile.get_all_profile_versions(usr_sesh.user_id)
profile = []
if versions:
# maimai_version is -1 means it is not initialized yet, select a default version from existing.
- if usr_sesh.maimai_version < 0:
+ if incoming_ver < 0:
usr_sesh.maimai_version = versions[0]['version']
profile = await self.data.profile.get_profile_detail(usr_sesh.user_id, usr_sesh.maimai_version)
versions = [x['version'] for x in versions]
@@ -65,7 +74,7 @@ class Mai2Frontend(FE_Base):
cur_version=usr_sesh.maimai_version
), media_type="text/html; charset=utf-8")
- if usr_sesh.maimai_version >= 0:
+ if incoming_ver < 0:
encoded_sesh = self.encode_session(usr_sesh)
resp.delete_cookie("ARTEMIS_SESH")
resp.set_cookie("ARTEMIS_SESH", encoded_sesh)
@@ -80,12 +89,10 @@ class Mai2Frontend(FE_Base):
)
usr_sesh = self.validate_session(request)
if not usr_sesh:
- print("wtf")
usr_sesh = UserSession()
if usr_sesh.user_id > 0:
if usr_sesh.maimai_version < 0:
- print(usr_sesh.maimai_version)
return RedirectResponse("/game/mai2/", 303)
path_index = request.path_params.get('index')
if not path_index or int(path_index) < 1:
@@ -175,6 +182,11 @@ class Mai2Frontend(FE_Base):
if not usr_sesh:
usr_sesh = UserSession()
+ if "/events/" in request.url.path:
+ resp = RedirectResponse("/game/mai2/events/", 303)
+ else:
+ resp = RedirectResponse("/game/mai2/", 303)
+
if usr_sesh.user_id > 0:
form_data = await request.form()
maimai_version = form_data.get("version")
@@ -182,9 +194,108 @@ class Mai2Frontend(FE_Base):
if(maimai_version.isdigit()):
usr_sesh.maimai_version=int(maimai_version)
encoded_sesh = self.encode_session(usr_sesh)
- self.logger.info(f"Created session with JWT {encoded_sesh}")
- resp = RedirectResponse("/game/mai2/", 303)
+ self.logger.debug(f"Created session with JWT {encoded_sesh}")
resp.set_cookie("ARTEMIS_SESH", encoded_sesh)
return resp
else:
- return RedirectResponse("/gate/", 303)
\ No newline at end of file
+ return RedirectResponse("/gate/", 303)
+
+ async def render_events(self, request: Request) -> Response:
+ usr_sesh = self.validate_session(request)
+ if not usr_sesh:
+ return RedirectResponse("/gate/", 303)
+
+ if not self.test_perm(usr_sesh.permissions, PermissionOffset.SYSADMIN):
+ return RedirectResponse("/game/mai2/", 303)
+
+ template = self.environment.get_template(
+ "titles/mai2/templates/events/mai2_events.jinja"
+ )
+
+ incoming_ver = usr_sesh.maimai_version
+ evts = []
+
+ if incoming_ver < 0:
+ usr_sesh.maimai_version = Mai2Constants.VER_MAIMAI_DX
+
+ event_list = await self.data.static.get_game_events(usr_sesh.maimai_version)
+ self.logger.info(f"Get events for v{usr_sesh.maimai_version}")
+
+ for event in event_list:
+ evts.append({
+ "id": event['id'],
+ "version": event['version'],
+ "eventId": event['eventId'],
+ "eventType": event['type'],
+ "name": event['name'],
+ "startDate": event['startDate'].strftime("%x %X"),
+ "enabled": "true" if event['enabled'] else "false",
+ })
+
+ resp = Response(template.render(
+ title=f"{self.core_config.server.name} | {self.nav_name} Events",
+ game_list=self.environment.globals["game_list"],
+ sesh=vars(usr_sesh),
+ version_list=Mai2Constants.VERSION_STRING,
+ events=evts
+ ), media_type="text/html; charset=utf-8")
+
+ if incoming_ver < 0:
+ encoded_sesh = self.encode_session(usr_sesh)
+ resp.delete_cookie("ARTEMIS_SESH")
+ resp.set_cookie("ARTEMIS_SESH", encoded_sesh)
+
+ return resp
+
+ async def render_event_edit(self, request: Request) -> Response:
+ usr_sesh = self.validate_session(request)
+ if not usr_sesh:
+ return RedirectResponse("/gate/", 303)
+
+ if not self.test_perm(usr_sesh.permissions, PermissionOffset.SYSADMIN):
+ return RedirectResponse("/game/mai2/", 303)
+
+ template = self.environment.get_template(
+ "titles/mai2/templates/events/mai2_event_edit.jinja"
+ )
+
+ evt_id = request.path_params.get("event_id")
+
+ event_id = await self.data.static.get_event_by_id(evt_id)
+ if not event_id:
+ return RedirectResponse("/game/mai2/events/", 303)
+
+ return Response(template.render(
+ title=f"{self.core_config.server.name} | {self.nav_name} Edit Event {evt_id}",
+ game_list=self.environment.globals["game_list"],
+ sesh=vars(usr_sesh),
+ user_id=usr_sesh.user_id,
+ version_list=Mai2Constants.VERSION_STRING,
+ cur_version=usr_sesh.maimai_version,
+ event=event_id._asdict()
+ ), media_type="text/html; charset=utf-8")
+
+ async def update_event(self, request: Request) -> RedirectResponse:
+ usr_sesh = self.validate_session(request)
+ if not usr_sesh:
+ return RedirectResponse("/gate/", 303)
+
+ if not self.test_perm(usr_sesh.permissions, PermissionOffset.SYSADMIN):
+ return RedirectResponse("/game/mai2/", 303)
+
+ form_data = await request.form()
+ print(form_data)
+ event_id: int = form_data.get("evtId", None)
+ new_enabled: bool = bool(form_data.get("evtEnabled", False))
+ try:
+ new_start_date: datetime = datetime.strptime(form_data.get("evtStart", None), "%Y-%m-%dT%H:%M:%S")
+ except:
+ new_start_date = None
+
+ print(f"{event_id} {new_enabled} {new_start_date}")
+ if event_id is None or new_start_date is None:
+ return RedirectResponse("/game/mai2/events/?e=4", 303)
+
+ await self.data.static.update_event_by_id(int(event_id), new_enabled, new_start_date)
+
+ return RedirectResponse("/game/mai2/events/?s=1", 303)
diff --git a/titles/mai2/schema/static.py b/titles/mai2/schema/static.py
index e33e4ec..ddba0f8 100644
--- a/titles/mai2/schema/static.py
+++ b/titles/mai2/schema/static.py
@@ -7,6 +7,7 @@ from sqlalchemy.schema import ForeignKey
from sqlalchemy.sql import func, select
from sqlalchemy.engine import Row
from sqlalchemy.dialects.mysql import insert
+from datetime import datetime
event = Table(
"mai2_static_event",
@@ -248,3 +249,18 @@ class Mai2StaticData(BaseData):
if result is None:
return None
return result.fetchall()
+
+ async def get_event_by_id(self, table_id: int) -> Optional[Row]:
+ result = await self.execute(event.select(event.c.id == table_id))
+ if result:
+ return result.fetchone()
+
+ async def get_events_by_event_id(self, event_id: int) -> Optional[List[Row]]:
+ result = await self.execute(event.select(event.c.eventId == event_id))
+ if result:
+ return result.fetchall()
+
+ async def update_event_by_id(self, table_id: int, is_enable: bool, start_date: datetime) -> None:
+ result = await self.execute(event.update(event.c.id == table_id).values(enabled=is_enable, startDate = start_date))
+ if not result:
+ self.logger.error(f"Failed to update event {table_id} - {is_enable} {start_date}")
diff --git a/titles/mai2/templates/events/mai2_event_edit.jinja b/titles/mai2/templates/events/mai2_event_edit.jinja
new file mode 100644
index 0000000..df061f5
--- /dev/null
+++ b/titles/mai2/templates/events/mai2_event_edit.jinja
@@ -0,0 +1,16 @@
+{% extends "core/templates/index.jinja" %}
+{% block content %}
+
+
+{% endblock content %}
diff --git a/titles/mai2/templates/events/mai2_events.jinja b/titles/mai2/templates/events/mai2_events.jinja
new file mode 100644
index 0000000..a2e9325
--- /dev/null
+++ b/titles/mai2/templates/events/mai2_events.jinja
@@ -0,0 +1,156 @@
+{% extends "core/templates/index.jinja" %}
+{% block content %}
+Events
+
+
+ Viewing all events
+
+
+ ID |
+ Version |
+ Event ID |
+ Event Type |
+ Name |
+ Start Date |
+ Enabled |
+ Actions |
+
+
+ {% if events is not defined or events|length == 0 %}
+
+ No Events |
+
+ {% endif %}
+
+
+
+
+
+
+
+
+{% endblock content %}
\ No newline at end of file
diff --git a/titles/mai2/templates/mai2_header.jinja b/titles/mai2/templates/mai2_header.jinja
index 4a4cb86..f226fbe 100644
--- a/titles/mai2/templates/mai2_header.jinja
+++ b/titles/mai2/templates/mai2_header.jinja
@@ -3,6 +3,9 @@
- PROFILE
- RECORD
+ {% if sesh is defined and sesh is not none and "{:08b}".format(sesh.permissions)[4] == "1" %}
+ - EVENTS
+ {% endif %}
\ No newline at end of file