From 589d4c0b9db5ef6f95eadd3da679918ba385d6d1 Mon Sep 17 00:00:00 2001 From: Marek Grzyb <marek@grzyb.dev> Date: Sun, 22 Sep 2024 15:42:10 +0200 Subject: [PATCH] Added message system and all known plasma transactions --- Dockerfile | 2 + bfbc2_masterserver/database.py | 191 ++++++++++++- bfbc2_masterserver/dataclasses/Manager.py | 1 + .../dataclasses/plasma/Service.py | 2 + .../enumerators/fesl/FESLTransaction.py | 41 +++ .../plasma/AssocationUpdateOperation.py | 6 + .../enumerators/plasma/ListFullBehavior.py | 6 + .../enumerators/plasma/StatUpdateType.py | 7 + .../messages/plasma/account/GameSpyPreAuth.py | 9 + .../plasma/account/NuCreateEncryptedToken.py | 12 + .../messages/plasma/account/NuGetAccount.py | 28 ++ .../plasma/account/NuGetAccountByNuid.py | 13 + .../plasma/account/NuGetAccountByPS3Ticket.py | 13 + .../plasma/account/NuGetEntitlementCount.py | 26 ++ .../plasma/account/NuPS3AddAccount.py | 12 + .../messages/plasma/account/NuPS3Login.py | 14 + .../messages/plasma/account/NuSearchOwners.py | 11 + .../plasma/account/NuSuggestPersonas.py | 12 + .../plasma/account/NuUpdateAccount.py | 29 ++ .../plasma/account/NuUpdatePassword.py | 11 + .../plasma/account/NuXBL360AddAccount.py | 12 + .../messages/plasma/account/NuXBL360Login.py | 13 + .../plasma/assocation/AddAssocations.py | 15 +- .../plasma/assocation/DeleteAssociations.py | 23 ++ .../plasma/assocation/GetAssociationsCount.py | 18 ++ .../assocation/NotifyAssociationUpdate.py | 16 ++ .../messages/plasma/connect/Suicide.py | 9 + .../plasma/message/AsyncMessageEvent.py | 6 + .../plasma/message/AsyncPurgedEvent.py | 5 + .../messages/plasma/message/DeleteMessages.py | 10 + .../plasma/message/GetMessageAttachments.py | 11 + .../messages/plasma/message/PurgeMessages.py | 10 + .../messages/plasma/message/SendMessage.py | 17 ++ .../presence/AsyncPresenceStatusEvent.py | 8 + .../plasma/presence/PresenceSubscribe.py | 10 + .../plasma/presence/PresenceUnsubscribe.py | 10 + .../messages/plasma/ranking/GetDateRange.py | 14 + .../plasma/ranking/GetStatsForOwners.py | 12 + .../messages/plasma/ranking/GetTopN.py | 12 + .../messages/plasma/ranking/GetTopNAndMe.py | 12 + .../messages/plasma/ranking/UpdateStats.py | 10 + .../messages/plasma/record/AddRecord.py | 12 + .../messages/plasma/record/AddRecordAsMap.py | 13 + .../messages/plasma/record/UpdateRecord.py | 12 + .../plasma/record/UpdateRecordAsMap.py | 13 + .../models/plasma/Association.py | 17 +- .../models/plasma/Attachment.py | 7 + bfbc2_masterserver/models/plasma/Attribute.py | 6 + bfbc2_masterserver/models/plasma/Message.py | 2 +- bfbc2_masterserver/models/plasma/Presence.py | 12 + bfbc2_masterserver/models/plasma/Stats.py | 13 + bfbc2_masterserver/models/plasma/Status.py | 6 + .../models/plasma/database/Message.py | 16 +- .../plasma/database/MessageAttachment.py | 2 +- bfbc2_masterserver/plasma.py | 11 +- bfbc2_masterserver/services/plasma/account.py | 219 +++++++++++++++ .../services/plasma/association.py | 250 +++++++++++++++++- bfbc2_masterserver/services/plasma/connect.py | 27 +- bfbc2_masterserver/services/plasma/message.py | 237 +++++++++++++++-- bfbc2_masterserver/services/plasma/playnow.py | 6 +- .../services/plasma/presence.py | 117 +++++++- bfbc2_masterserver/services/plasma/ranking.py | 70 +++++ bfbc2_masterserver/services/plasma/record.py | 88 ++++++ docker-compose.yml | 5 + 64 files changed, 1793 insertions(+), 67 deletions(-) create mode 100644 bfbc2_masterserver/enumerators/plasma/AssocationUpdateOperation.py create mode 100644 bfbc2_masterserver/enumerators/plasma/ListFullBehavior.py create mode 100644 bfbc2_masterserver/enumerators/plasma/StatUpdateType.py create mode 100644 bfbc2_masterserver/messages/plasma/account/GameSpyPreAuth.py create mode 100644 bfbc2_masterserver/messages/plasma/account/NuCreateEncryptedToken.py create mode 100644 bfbc2_masterserver/messages/plasma/account/NuGetAccount.py create mode 100644 bfbc2_masterserver/messages/plasma/account/NuGetAccountByNuid.py create mode 100644 bfbc2_masterserver/messages/plasma/account/NuGetAccountByPS3Ticket.py create mode 100644 bfbc2_masterserver/messages/plasma/account/NuGetEntitlementCount.py create mode 100644 bfbc2_masterserver/messages/plasma/account/NuPS3AddAccount.py create mode 100644 bfbc2_masterserver/messages/plasma/account/NuPS3Login.py create mode 100644 bfbc2_masterserver/messages/plasma/account/NuSearchOwners.py create mode 100644 bfbc2_masterserver/messages/plasma/account/NuSuggestPersonas.py create mode 100644 bfbc2_masterserver/messages/plasma/account/NuUpdateAccount.py create mode 100644 bfbc2_masterserver/messages/plasma/account/NuUpdatePassword.py create mode 100644 bfbc2_masterserver/messages/plasma/account/NuXBL360AddAccount.py create mode 100644 bfbc2_masterserver/messages/plasma/account/NuXBL360Login.py create mode 100644 bfbc2_masterserver/messages/plasma/assocation/DeleteAssociations.py create mode 100644 bfbc2_masterserver/messages/plasma/assocation/GetAssociationsCount.py create mode 100644 bfbc2_masterserver/messages/plasma/assocation/NotifyAssociationUpdate.py create mode 100644 bfbc2_masterserver/messages/plasma/connect/Suicide.py create mode 100644 bfbc2_masterserver/messages/plasma/message/AsyncMessageEvent.py create mode 100644 bfbc2_masterserver/messages/plasma/message/AsyncPurgedEvent.py create mode 100644 bfbc2_masterserver/messages/plasma/message/DeleteMessages.py create mode 100644 bfbc2_masterserver/messages/plasma/message/GetMessageAttachments.py create mode 100644 bfbc2_masterserver/messages/plasma/message/PurgeMessages.py create mode 100644 bfbc2_masterserver/messages/plasma/message/SendMessage.py create mode 100644 bfbc2_masterserver/messages/plasma/presence/AsyncPresenceStatusEvent.py create mode 100644 bfbc2_masterserver/messages/plasma/presence/PresenceSubscribe.py create mode 100644 bfbc2_masterserver/messages/plasma/presence/PresenceUnsubscribe.py create mode 100644 bfbc2_masterserver/messages/plasma/ranking/GetDateRange.py create mode 100644 bfbc2_masterserver/messages/plasma/ranking/GetStatsForOwners.py create mode 100644 bfbc2_masterserver/messages/plasma/ranking/GetTopN.py create mode 100644 bfbc2_masterserver/messages/plasma/ranking/GetTopNAndMe.py create mode 100644 bfbc2_masterserver/messages/plasma/ranking/UpdateStats.py create mode 100644 bfbc2_masterserver/messages/plasma/record/AddRecord.py create mode 100644 bfbc2_masterserver/messages/plasma/record/AddRecordAsMap.py create mode 100644 bfbc2_masterserver/messages/plasma/record/UpdateRecord.py create mode 100644 bfbc2_masterserver/messages/plasma/record/UpdateRecordAsMap.py create mode 100644 bfbc2_masterserver/models/plasma/Attachment.py create mode 100644 bfbc2_masterserver/models/plasma/Attribute.py create mode 100644 bfbc2_masterserver/models/plasma/Presence.py create mode 100644 bfbc2_masterserver/models/plasma/Status.py diff --git a/Dockerfile b/Dockerfile index af90526..69ecc0b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -21,6 +21,8 @@ RUN poetry install --no-root # Production image, copy all the files and run next FROM base AS runner +RUN apk add libpq --no-cache + WORKDIR /app RUN addgroup --system --gid 1001 bfbc2emu diff --git a/bfbc2_masterserver/database.py b/bfbc2_masterserver/database.py index f4d7cec..2b58ef5 100644 --- a/bfbc2_masterserver/database.py +++ b/bfbc2_masterserver/database.py @@ -2,6 +2,7 @@ import secrets import uuid from datetime import datetime +from decimal import Decimal from typing import Optional from uu import Error @@ -14,6 +15,7 @@ from bfbc2_masterserver.enumerators.ErrorCode import ErrorCode from bfbc2_masterserver.enumerators.plasma.AssocationType import AssocationType from bfbc2_masterserver.enumerators.plasma.RecordName import RecordName +from bfbc2_masterserver.enumerators.plasma.StatUpdateType import StatUpdateType from bfbc2_masterserver.enumerators.theater.GameType import GameType from bfbc2_masterserver.enumerators.theater.JoinMode import JoinMode from bfbc2_masterserver.messages.plasma.account.NuGrantEntitlement import ( @@ -29,6 +31,9 @@ from bfbc2_masterserver.models.plasma.database.Association import Association from bfbc2_masterserver.models.plasma.database.Entitlement import Entitlement from bfbc2_masterserver.models.plasma.database.Message import Message +from bfbc2_masterserver.models.plasma.database.MessageAttachment import ( + MessageAttachment, +) from bfbc2_masterserver.models.plasma.database.Persona import Persona from bfbc2_masterserver.models.plasma.database.Ranking import Ranking from bfbc2_masterserver.models.plasma.database.Record import Record @@ -376,6 +381,16 @@ def persona_get_by_name(self, persona_name: str) -> Persona | ErrorCode: return persona + def persona_get_owner_id(self, persona_id: int) -> int | ErrorCode: + with Session(self._engine) as session: + persona_query = select(Persona).where(Persona.id == persona_id) + persona = session.exec(persona_query).one_or_none() + + if not persona: + return ErrorCode.USER_NOT_FOUND + + return persona.owner.id + def persona_add(self, account_id: int, persona: Persona) -> bool | ErrorCode: with Session(self._engine) as session: account_query = select(Account).where(Account.id == account_id) @@ -407,7 +422,26 @@ def persona_delete(self, account_id: int, persona_id: int) -> bool | ErrorCode: return True - def assocation_get( + def persona_search(self, search: str) -> list[Persona]: + with Session(self._engine) as session: + search = search.replace("*", "%").replace("_", "") + personas_query = select(Persona).filter(Persona.name.ilike(search)) # type: ignore + personas = session.exec(personas_query).all() + + return list(personas) + + def association_get(self, persona_id, target_id, association_type: AssocationType): + with Session(self._engine) as session: + association_query = select(Association).where( + Association.owner_id == persona_id, + Association.target_id == target_id, + Association.type == association_type, + ) + association = session.exec(association_query).one_or_none() + + return association + + def association_get_all( self, persona_id: int, assocation_type: AssocationType ) -> list[Association] | ErrorCode: with Session(self._engine) as session: @@ -422,9 +456,52 @@ def assocation_get( for association in persona.associations: if association.type == assocation_type: assocations_list.append(association) + # Preload the target persona + association.target return assocations_list + def association_delete(self, association_id: int) -> bool | ErrorCode: + with Session(self._engine) as session: + association_query = select(Association).where( + Association.id == association_id + ) + association = session.exec(association_query).one_or_none() + + if not association: + return ErrorCode.SYSTEM_ERROR + + session.delete(association) + session.commit() + + return True + + def association_add( + self, owner_id: int, target_id: int, type: AssocationType + ) -> Association | ErrorCode: + with Session(self._engine) as session: + association_query = select(Association).where( + Association.owner_id == owner_id, + Association.target_id == target_id, + Association.type == type, + ) + existing_association = session.exec(association_query).one_or_none() + + if existing_association: + return ErrorCode.SYSTEM_ERROR + + association = Association( + owner_id=owner_id, + target_id=target_id, + type=type, + ) + + session.add(association) + session.commit() + session.refresh(association) + + return association + def ranking_get(self, persona_id: int, keys: list[str]) -> list[Ranking]: with Session(self._engine) as session: stats_query = select(Ranking).where(Ranking.owner_id == persona_id) @@ -483,6 +560,28 @@ def ranking_leaderboard_get( stats = session.exec(stats_query).all() return list(stats) + def ranking_set( + self, persona_id: int, key: str, value: Decimal, update_type: StatUpdateType + ) -> bool: + with Session(self._engine) as session: + stat_query = select(Ranking).where( + Ranking.owner_id == persona_id, Ranking.key == key + ) + stat = session.exec(stat_query).one_or_none() + + if not stat: + stat = Ranking(owner_id=persona_id, key=key, value=value) + else: + if update_type == StatUpdateType.RelativeValue: + stat.value += value + else: + stat.value = value + + session.add(stat) + session.commit() + + return True + def record_get(self, persona_id: int, type: RecordName) -> list[Record]: with Session(self._engine) as session: record_query = select(Record).where(Record.owner_id == persona_id) @@ -492,13 +591,92 @@ def record_get(self, persona_id: int, type: RecordName) -> list[Record]: return list(records) - def message_get(self, persona_id: int) -> list[Message]: + def record_add( + self, persona_id: int, type: RecordName, key: str, value: str + ) -> bool: + with Session(self._engine) as session: + record = Record(owner_id=persona_id, type=type, key=key, value=value) + session.add(record) + session.commit() + + return True + + def record_update( + self, persona_id: int, type: RecordName, key: str, value: str + ) -> bool: + with Session(self._engine) as session: + record_query = select(Record).where( + Record.owner_id == persona_id, Record.type == type, Record.key == key + ) + record = session.exec(record_query).one_or_none() + + if not record: + return False + + record.value = value + + session.add(record) + session.commit() + + return True + + def message_get(self, message_id: int) -> Message | None: + with Session(self._engine) as session: + message_query = select(Message).where(Message.id == message_id) + message = session.exec(message_query).one_or_none() + + return message + + def message_get_all(self, persona_id: int) -> list[Message]: with Session(self._engine) as session: message_query = select(Message).where(Message.recipient_id == persona_id) messages = session.exec(message_query).all() return list(messages) + def message_add(self, messageData: Message) -> Message: + with Session(self._engine) as session: + attachments = [] + + for attachment in messageData.attachments: + attachment = MessageAttachment( + key=attachment.key, type=attachment.type, data=attachment.data + ) + + session.add(attachment) + session.commit() + + attachments.append(attachment) + + message = Message( + sender_id=messageData.sender_id, + recipient_id=messageData.recipient.id, + messageType=messageData.messageType, + deliveryType=messageData.deliveryType, + purgeStrategy=messageData.purgeStrategy, + expiration=messageData.expiration, + attachments=attachments, + ) + + session.add(message) + session.commit() + session.refresh(message) + + return message + + def message_delete(self, message_id: int) -> bool: + with Session(self._engine) as session: + message_query = select(Message).where(Message.id == message_id) + message = session.exec(message_query).one_or_none() + + if not message: + return False + + session.delete(message) + session.commit() + + return True + def lobby_get(self, lobby_id: int) -> Lobby | None: with Session(self._engine) as session: lobby_query = select(Lobby).where(Lobby.id == lobby_id) @@ -709,3 +887,12 @@ def game_get(self, lobby_id: int, game_id: int) -> Game | None: game = session.exec(game_query).one_or_none() return game + + def game_find(self, gameType: GameType, level: int) -> Game | None: + with Session(self._engine) as session: + game_query = select(Game).where( + Game.gameType == gameType, Game.gameLevel == level + ) + game = session.exec(game_query).one_or_none() + + return game diff --git a/bfbc2_masterserver/dataclasses/Manager.py b/bfbc2_masterserver/dataclasses/Manager.py index d82e81f..6c1f507 100644 --- a/bfbc2_masterserver/dataclasses/Manager.py +++ b/bfbc2_masterserver/dataclasses/Manager.py @@ -1,4 +1,5 @@ from redis import Redis + from bfbc2_masterserver.database import DatabaseAPI from bfbc2_masterserver.dataclasses.Client import Client diff --git a/bfbc2_masterserver/dataclasses/plasma/Service.py b/bfbc2_masterserver/dataclasses/plasma/Service.py index a031efb..8acb573 100644 --- a/bfbc2_masterserver/dataclasses/plasma/Service.py +++ b/bfbc2_masterserver/dataclasses/plasma/Service.py @@ -31,6 +31,8 @@ def __init__(self, plasma: BasePlasmaHandler) -> None: self.connection = plasma.client.connection self.database = plasma.manager.database + self.manager = plasma.manager + self.redis = plasma.manager.redis self.plasma = plasma @abstractmethod diff --git a/bfbc2_masterserver/enumerators/fesl/FESLTransaction.py b/bfbc2_masterserver/enumerators/fesl/FESLTransaction.py index 5ceaa45..32d3199 100644 --- a/bfbc2_masterserver/enumerators/fesl/FESLTransaction.py +++ b/bfbc2_masterserver/enumerators/fesl/FESLTransaction.py @@ -2,12 +2,16 @@ class FESLTransaction(Enum): + # Universal + TransactionException = "TransactionException" + # Plasma (Connect) Hello = "Hello" MemCheck = "MemCheck" GetPingSites = "GetPingSites" Ping = "Ping" Goodbye = "Goodbye" + # Never called during my tests, but implemented in game code Suicide = "Suicide" # Plasma (Account) @@ -26,14 +30,38 @@ class FESLTransaction(Enum): NuEntitleUser = "NuEntitleUser" NuLookupUserInfo = "NuLookupUserInfo" NuGrantEntitlement = "NuGrantEntitlement" + NuSearchOwners = "NuSearchOwners" + # Never called during my tests, but implemented in game code + NuCreateEncryptedToken = "NuCreateEncryptedToken" + NuSuggestPersonas = "NuSuggestPersonas" + NuUpdatePassword = "NuUpdatePassword" + NuGetAccount = "NuGetAccount" + NuGetAccountByNuid = "NuGetAccountByNuid" + NuGetAccountByPS3Ticket = "NuGetAccountByPS3Ticket" # PS3 Only? + NuUpdateAccount = "NuUpdateAccount" + GameSpyPreAuth = "GameSpyPreAuth" # GameSpy leftover? + NuXBL360Login = "NuXBL360Login" # Xbox 360 Only? + NuXBL360AddAccount = "NuXBL360AddAccount" # Xbox 360 Only? + NuPS3Login = "NuPS3Login" # PS3 Only? + NuPS3AddAccount = "NuPS3AddAccount" # PS3 Only? + NuGetEntitlementCount = "NuGetEntitlementCount" # Plasma (Assocation) GetAssociations = "GetAssociations" AddAssociations = "AddAssociations" + NotifyAssociationUpdate = "NotifyAssociationUpdate" + DeleteAssociations = "DeleteAssociations" + GetAssociationsCount = "GetAssociationsCount" # Plasma (Extensible Message) ModifySettings = "ModifySettings" GetMessages = "GetMessages" + SendMessage = "SendMessage" + AsyncMessageEvent = "AsyncMessageEvent" + GetMessageAttachments = "GetMessageAttachments" + DeleteMessages = "DeleteMessages" + PurgeMessages = "PurgeMessages" + AsyncPurgedEvent = "AsyncPurgedEvent" # Plasma (PlayNow) Start = "Start" @@ -41,13 +69,26 @@ class FESLTransaction(Enum): # Plasma (Presence) SetPresenceStatus = "SetPresenceStatus" + PresenceSubscribe = "PresenceSubscribe" + PresenceUnsubscribe = "PresenceUnsubscribe" + AsyncPresenceStatusEvent = "AsyncPresenceStatusEvent" # Plasma (Ranking) GetStats = "GetStats" GetRankedStatsForOwners = "GetRankedStatsForOwners" GetRankedStats = "GetRankedStats" GetTopNAndStats = "GetTopNAndStats" + UpdateStats = "UpdateStats" + # Never called during my tests, but implemented in game code + GetStatsForOwners = "GetStatsForOwners" + GetTopN = "GetTopN" + GetTopNAndMe = "GetTopNAndMe" + GetDateRange = "GetDateRange" # Plasma (Record) GetRecordAsMap = "GetRecordAsMap" GetRecord = "GetRecord" + AddRecord = "AddRecord" + UpdateRecord = "UpdateRecord" + AddRecordAsMap = "AddRecordAsMap" + UpdateRecordAsMap = "UpdateRecordAsMap" diff --git a/bfbc2_masterserver/enumerators/plasma/AssocationUpdateOperation.py b/bfbc2_masterserver/enumerators/plasma/AssocationUpdateOperation.py new file mode 100644 index 0000000..35ee788 --- /dev/null +++ b/bfbc2_masterserver/enumerators/plasma/AssocationUpdateOperation.py @@ -0,0 +1,6 @@ +from enum import Enum + + +class AssocationUpdateOperation(Enum): + ADD = "add" + DEL = "del" diff --git a/bfbc2_masterserver/enumerators/plasma/ListFullBehavior.py b/bfbc2_masterserver/enumerators/plasma/ListFullBehavior.py new file mode 100644 index 0000000..481564f --- /dev/null +++ b/bfbc2_masterserver/enumerators/plasma/ListFullBehavior.py @@ -0,0 +1,6 @@ +from enum import Enum + + +class ListFullBehavior(Enum): + ReturnError = "ReturnError" + RollLeastRecentlyModified = "RollLeastRecentlyModified" diff --git a/bfbc2_masterserver/enumerators/plasma/StatUpdateType.py b/bfbc2_masterserver/enumerators/plasma/StatUpdateType.py new file mode 100644 index 0000000..58da931 --- /dev/null +++ b/bfbc2_masterserver/enumerators/plasma/StatUpdateType.py @@ -0,0 +1,7 @@ +from enum import Enum + + +class StatUpdateType(Enum): + AbsoluteValueRounded = 0 + AbsoluteValue = 1 + RelativeValue = 3 diff --git a/bfbc2_masterserver/messages/plasma/account/GameSpyPreAuth.py b/bfbc2_masterserver/messages/plasma/account/GameSpyPreAuth.py new file mode 100644 index 0000000..ea272f9 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/account/GameSpyPreAuth.py @@ -0,0 +1,9 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction + + +class GameSpyPreAuthRequest(PlasmaTransaction): + pass + + +class GameSpyPreAuthResponse(PlasmaTransaction): + pass diff --git a/bfbc2_masterserver/messages/plasma/account/NuCreateEncryptedToken.py b/bfbc2_masterserver/messages/plasma/account/NuCreateEncryptedToken.py new file mode 100644 index 0000000..2e1affb --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/account/NuCreateEncryptedToken.py @@ -0,0 +1,12 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Attribute import Attribute + + +class NuCreateEncryptedTokenRequest(PlasmaTransaction): + expires: int + attributes: list[Attribute] + + +class NuCreateEncryptedTokenResponse(PlasmaTransaction): + # Couldn't find any response for this transaction in my poor RE effort + pass diff --git a/bfbc2_masterserver/messages/plasma/account/NuGetAccount.py b/bfbc2_masterserver/messages/plasma/account/NuGetAccount.py new file mode 100644 index 0000000..b77e624 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/account/NuGetAccount.py @@ -0,0 +1,28 @@ +from typing import Optional + +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Entitlement import Entitlement + + +class NuGetAccountRequest(PlasmaTransaction): + nuid: Optional[str] + + +class NuGetAccountResponse(PlasmaTransaction): + nuid: str + parentalEmail: Optional[str] + firstName: Optional[str] + lastName: Optional[str] + street: Optional[str] + street2: Optional[str] + city: Optional[str] + state: Optional[str] + zip: Optional[str] + country: Optional[str] + language: Optional[str] + userId: int + dOBMonth: Optional[int] + dOBDay: Optional[int] + dOBYear: Optional[int] + globalCommOptin: Optional[str] + thirdPartyMailFlag: Optional[str] diff --git a/bfbc2_masterserver/messages/plasma/account/NuGetAccountByNuid.py b/bfbc2_masterserver/messages/plasma/account/NuGetAccountByNuid.py new file mode 100644 index 0000000..d53f637 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/account/NuGetAccountByNuid.py @@ -0,0 +1,13 @@ +from typing import Optional + +from bfbc2_masterserver.messages.plasma.account.NuGetAccount import NuGetAccountResponse +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Entitlement import Entitlement + + +class NuGetAccountByNuidRequest(PlasmaTransaction): + nuid: str + + +class NuGetAccountByNuidResponse(NuGetAccountResponse): + pass diff --git a/bfbc2_masterserver/messages/plasma/account/NuGetAccountByPS3Ticket.py b/bfbc2_masterserver/messages/plasma/account/NuGetAccountByPS3Ticket.py new file mode 100644 index 0000000..5d383f5 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/account/NuGetAccountByPS3Ticket.py @@ -0,0 +1,13 @@ +from typing import Optional + +from bfbc2_masterserver.messages.plasma.account.NuGetAccount import NuGetAccountResponse +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Entitlement import Entitlement + + +class NuGetAccountByPS3TicketRequest(PlasmaTransaction): + ticket: bytes + + +class NuGetAccountByPS3TicketResponse(NuGetAccountResponse): + pass diff --git a/bfbc2_masterserver/messages/plasma/account/NuGetEntitlementCount.py b/bfbc2_masterserver/messages/plasma/account/NuGetEntitlementCount.py new file mode 100644 index 0000000..742dd6f --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/account/NuGetEntitlementCount.py @@ -0,0 +1,26 @@ +from typing import Optional + +from pydantic import Field + +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction + + +class NuGetEntitlementCountRequest(PlasmaTransaction): + entitlementId: Optional[str] + entitlementTag: Optional[str] + masterUserId: Optional[int] + userId: Optional[int] + global_: Optional[str] = Field(alias="global") + status: Optional[str] + groupName: Optional[str] + productCatalog: Optional[str] + productId: Optional[str] + grantStartDate: Optional[str] + grantEndDate: Optional[str] + projectId: Optional[str] + entitlementType: Optional[str] + devicePhysicalId: Optional[str] + + +class NuGetEntitlementCountResponse(PlasmaTransaction): + entitlementCount: int diff --git a/bfbc2_masterserver/messages/plasma/account/NuPS3AddAccount.py b/bfbc2_masterserver/messages/plasma/account/NuPS3AddAccount.py new file mode 100644 index 0000000..c17d421 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/account/NuPS3AddAccount.py @@ -0,0 +1,12 @@ +from bfbc2_masterserver.messages.plasma.account.NuAddAccount import ( + NuAddAccountRequest, + NuAddAccountResponse, +) + + +class NuPS3AddAccountRequest(NuAddAccountRequest): + pass + + +class NuPS3AddAccountResponse(NuAddAccountResponse): + pass diff --git a/bfbc2_masterserver/messages/plasma/account/NuPS3Login.py b/bfbc2_masterserver/messages/plasma/account/NuPS3Login.py new file mode 100644 index 0000000..55cf829 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/account/NuPS3Login.py @@ -0,0 +1,14 @@ +from typing import Optional + +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction + + +class NuPS3LoginRequest(PlasmaTransaction): + ticket: bytes + macAddr: str + consoleId: str + tosVersion: Optional[str] + + +class NuPS3LoginResponse(PlasmaTransaction): + pass diff --git a/bfbc2_masterserver/messages/plasma/account/NuSearchOwners.py b/bfbc2_masterserver/messages/plasma/account/NuSearchOwners.py new file mode 100644 index 0000000..190baf1 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/account/NuSearchOwners.py @@ -0,0 +1,11 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Owner import Owner + + +class NuSearchOwnersRequest(PlasmaTransaction): + screenName: str + + +class NuSearchOwnersResponse(PlasmaTransaction): + users: list[Owner] + nameSpaceId: str # "battlefield" diff --git a/bfbc2_masterserver/messages/plasma/account/NuSuggestPersonas.py b/bfbc2_masterserver/messages/plasma/account/NuSuggestPersonas.py new file mode 100644 index 0000000..ce7fccd --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/account/NuSuggestPersonas.py @@ -0,0 +1,12 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.UserInfo import UserInfo, UserInfoRequest + + +class NuSuggestPersonasRequest(PlasmaTransaction): + name: str + maxSuggestions: int + keywords: list[str] + + +class NuSuggestPersonasResponse(PlasmaTransaction): + names: list[str] diff --git a/bfbc2_masterserver/messages/plasma/account/NuUpdateAccount.py b/bfbc2_masterserver/messages/plasma/account/NuUpdateAccount.py new file mode 100644 index 0000000..a0c74bd --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/account/NuUpdateAccount.py @@ -0,0 +1,29 @@ +from typing import Optional + +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction + + +class NuUpdateAccountRequest(PlasmaTransaction): + nuid: str + password: str + globalOptin: str + thirdPartyOptin: str + + parentalEmail: Optional[str] + DOBDay: Optional[int] + DOBMonth: Optional[int] + DOBYear: Optional[int] + first_Name: Optional[str] + last_Name: Optional[str] + gender: Optional[str] + street: Optional[str] + street2: Optional[str] + city: Optional[str] + state: Optional[str] + zipCode: Optional[str] + country: Optional[str] + language: Optional[str] + + +class NuUpdateAccountResponse(PlasmaTransaction): + pass diff --git a/bfbc2_masterserver/messages/plasma/account/NuUpdatePassword.py b/bfbc2_masterserver/messages/plasma/account/NuUpdatePassword.py new file mode 100644 index 0000000..6fb70ac --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/account/NuUpdatePassword.py @@ -0,0 +1,11 @@ +from typing import Optional + +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction + + +class NuUpdatePasswordRequest(PlasmaTransaction): + newPassword: str + + +class NuUpdatePasswordResponse(PlasmaTransaction): + encryptedLoginInfo: Optional[str] diff --git a/bfbc2_masterserver/messages/plasma/account/NuXBL360AddAccount.py b/bfbc2_masterserver/messages/plasma/account/NuXBL360AddAccount.py new file mode 100644 index 0000000..3dedb43 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/account/NuXBL360AddAccount.py @@ -0,0 +1,12 @@ +from bfbc2_masterserver.messages.plasma.account.NuAddAccount import ( + NuAddAccountRequest, + NuAddAccountResponse, +) + + +class NuXBL360AddAccountRequest(NuAddAccountRequest): + pass + + +class NuXBL360AddAccountResponse(NuAddAccountResponse): + pass diff --git a/bfbc2_masterserver/messages/plasma/account/NuXBL360Login.py b/bfbc2_masterserver/messages/plasma/account/NuXBL360Login.py new file mode 100644 index 0000000..f2fb6af --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/account/NuXBL360Login.py @@ -0,0 +1,13 @@ +from typing import Optional + +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction + + +class NuXBL360LoginRequest(PlasmaTransaction): + macAddr: str + consoleId: str + tosVersion: Optional[str] + + +class NuXBL360LoginResponse(PlasmaTransaction): + pass diff --git a/bfbc2_masterserver/messages/plasma/assocation/AddAssocations.py b/bfbc2_masterserver/messages/plasma/assocation/AddAssocations.py index acbbd17..d217db7 100644 --- a/bfbc2_masterserver/messages/plasma/assocation/AddAssocations.py +++ b/bfbc2_masterserver/messages/plasma/assocation/AddAssocations.py @@ -1,11 +1,22 @@ from bfbc2_masterserver.enumerators.plasma.AssocationType import AssocationType +from bfbc2_masterserver.enumerators.plasma.ListFullBehavior import ListFullBehavior from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction -from bfbc2_masterserver.models.plasma.Association import AssociationRequest +from bfbc2_masterserver.models.plasma.Association import ( + AssociationRequest, + AssociationResult, +) from bfbc2_masterserver.models.plasma.DomainPartition import DomainPartition class AddAssociationsRequest(PlasmaTransaction): domainPartition: DomainPartition type: AssocationType - listFullBehavior: str + listFullBehavior: ListFullBehavior addRequests: list[AssociationRequest] + + +class AddAssociationsResponse(PlasmaTransaction): + domainPartition: DomainPartition + maxListSize: int + result: list[AssociationResult] + type: AssocationType diff --git a/bfbc2_masterserver/messages/plasma/assocation/DeleteAssociations.py b/bfbc2_masterserver/messages/plasma/assocation/DeleteAssociations.py new file mode 100644 index 0000000..e937a6a --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/assocation/DeleteAssociations.py @@ -0,0 +1,23 @@ +from bfbc2_masterserver.enumerators.plasma.AssocationType import AssocationType +from bfbc2_masterserver.enumerators.plasma.ListFullBehavior import ListFullBehavior +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Association import ( + AssociationRequest, + AssociationResult, + AssociationReturn, +) +from bfbc2_masterserver.models.plasma.DomainPartition import DomainPartition +from bfbc2_masterserver.models.plasma.Owner import Owner + + +class DeleteAssociationsRequest(PlasmaTransaction): + domainPartition: DomainPartition + type: AssocationType + deleteRequests: list[AssociationRequest] + + +class DeleteAssociationsResponse(PlasmaTransaction): + domainPartition: DomainPartition + maxListSize: int + result: list[AssociationResult] + type: AssocationType diff --git a/bfbc2_masterserver/messages/plasma/assocation/GetAssociationsCount.py b/bfbc2_masterserver/messages/plasma/assocation/GetAssociationsCount.py new file mode 100644 index 0000000..4faf564 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/assocation/GetAssociationsCount.py @@ -0,0 +1,18 @@ +from bfbc2_masterserver.enumerators.plasma.AssocationType import AssocationType +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Association import AssociationReturn +from bfbc2_masterserver.models.plasma.DomainPartition import DomainPartition +from bfbc2_masterserver.models.plasma.Owner import Owner + + +class GetAssociationsCountRequest(PlasmaTransaction): + domainPartition: DomainPartition + type: AssocationType + owner: Owner + + +class GetAssociationsCountResponse(PlasmaTransaction): + domainPartition: DomainPartition + maxListSize: int + count: int + owner: Owner diff --git a/bfbc2_masterserver/messages/plasma/assocation/NotifyAssociationUpdate.py b/bfbc2_masterserver/messages/plasma/assocation/NotifyAssociationUpdate.py new file mode 100644 index 0000000..f5dbe5a --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/assocation/NotifyAssociationUpdate.py @@ -0,0 +1,16 @@ +from bfbc2_masterserver.enumerators.plasma.AssocationUpdateOperation import ( + AssocationUpdateOperation, +) +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Association import AssociationReturn +from bfbc2_masterserver.models.plasma.DomainPartition import DomainPartition +from bfbc2_masterserver.models.plasma.Owner import Owner + + +class NotifyAssociationUpdate(PlasmaTransaction): + domainPartition: DomainPartition + listSize: int + member: AssociationReturn + operation: AssocationUpdateOperation + owner: Owner + type: str diff --git a/bfbc2_masterserver/messages/plasma/connect/Suicide.py b/bfbc2_masterserver/messages/plasma/connect/Suicide.py new file mode 100644 index 0000000..add1b71 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/connect/Suicide.py @@ -0,0 +1,9 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction + + +class SuicideRequest(PlasmaTransaction): + pass + + +class SuicideResponse(PlasmaTransaction): + pass diff --git a/bfbc2_masterserver/messages/plasma/message/AsyncMessageEvent.py b/bfbc2_masterserver/messages/plasma/message/AsyncMessageEvent.py new file mode 100644 index 0000000..3cfc83b --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/message/AsyncMessageEvent.py @@ -0,0 +1,6 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Message import MessageResponse + + +class AsyncMessageEvent(PlasmaTransaction, MessageResponse): + pass diff --git a/bfbc2_masterserver/messages/plasma/message/AsyncPurgedEvent.py b/bfbc2_masterserver/messages/plasma/message/AsyncPurgedEvent.py new file mode 100644 index 0000000..a9f8890 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/message/AsyncPurgedEvent.py @@ -0,0 +1,5 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction + + +class AsyncPurgedEvent(PlasmaTransaction): + messageIds: list[int] diff --git a/bfbc2_masterserver/messages/plasma/message/DeleteMessages.py b/bfbc2_masterserver/messages/plasma/message/DeleteMessages.py new file mode 100644 index 0000000..687c7f2 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/message/DeleteMessages.py @@ -0,0 +1,10 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Message import MessageResponse + + +class DeleteMessagesRequest(PlasmaTransaction): + messageIds: list[int] + + +class DeleteMessagesResponse(PlasmaTransaction): + pass diff --git a/bfbc2_masterserver/messages/plasma/message/GetMessageAttachments.py b/bfbc2_masterserver/messages/plasma/message/GetMessageAttachments.py new file mode 100644 index 0000000..a174ccf --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/message/GetMessageAttachments.py @@ -0,0 +1,11 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Message import MessageResponse + + +class GetMessageAttachmentsRequest(PlasmaTransaction): + messageId: int + keys: list[str] + + +class GetMessageAttachmentsResponse(PlasmaTransaction): + pass diff --git a/bfbc2_masterserver/messages/plasma/message/PurgeMessages.py b/bfbc2_masterserver/messages/plasma/message/PurgeMessages.py new file mode 100644 index 0000000..6161da9 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/message/PurgeMessages.py @@ -0,0 +1,10 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Message import MessageResponse + + +class PurgeMessagesRequest(PlasmaTransaction): + messageIds: list[int] + + +class PurgeMessagesResponse(PlasmaTransaction): + pass diff --git a/bfbc2_masterserver/messages/plasma/message/SendMessage.py b/bfbc2_masterserver/messages/plasma/message/SendMessage.py new file mode 100644 index 0000000..d6198a3 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/message/SendMessage.py @@ -0,0 +1,17 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Attachment import Attachment +from bfbc2_masterserver.models.plasma.Status import Status + + +class SendMessageRequest(PlasmaTransaction): + attachments: list[Attachment] + to: list[int] + expires: int + deliveryType: str + messageType: str + purgeStrategy: str + + +class SendMessageResponse(PlasmaTransaction): + messageId: int + status: list[Status] diff --git a/bfbc2_masterserver/messages/plasma/presence/AsyncPresenceStatusEvent.py b/bfbc2_masterserver/messages/plasma/presence/AsyncPresenceStatusEvent.py new file mode 100644 index 0000000..fef41f6 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/presence/AsyncPresenceStatusEvent.py @@ -0,0 +1,8 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Owner import Owner + + +class AsyncPresenceStatusEvent(PlasmaTransaction): + initial: bool + owner: Owner + status: dict diff --git a/bfbc2_masterserver/messages/plasma/presence/PresenceSubscribe.py b/bfbc2_masterserver/messages/plasma/presence/PresenceSubscribe.py new file mode 100644 index 0000000..462207d --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/presence/PresenceSubscribe.py @@ -0,0 +1,10 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Presence import PresenceRequest, PresenceResponse + + +class PresenceSubscribeRequest(PlasmaTransaction): + requests: list[PresenceRequest] + + +class PresenceSubscribeResponse(PlasmaTransaction): + responses: list[PresenceResponse] diff --git a/bfbc2_masterserver/messages/plasma/presence/PresenceUnsubscribe.py b/bfbc2_masterserver/messages/plasma/presence/PresenceUnsubscribe.py new file mode 100644 index 0000000..3ed5ac9 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/presence/PresenceUnsubscribe.py @@ -0,0 +1,10 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Presence import PresenceRequest, PresenceResponse + + +class PresenceUnsubscribeRequest(PlasmaTransaction): + requests: list[PresenceRequest] + + +class PresenceUnsubscribeResponse(PlasmaTransaction): + responses: list[PresenceResponse] diff --git a/bfbc2_masterserver/messages/plasma/ranking/GetDateRange.py b/bfbc2_masterserver/messages/plasma/ranking/GetDateRange.py new file mode 100644 index 0000000..af7f7a5 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/ranking/GetDateRange.py @@ -0,0 +1,14 @@ +from datetime import datetime + +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Leaderboard import Leaderboard + + +class GetDateRangeRequest(PlasmaTransaction): + key: str + periodId: int + + +class GetDateRangeResponse(PlasmaTransaction): + startDate: datetime + endDate: datetime diff --git a/bfbc2_masterserver/messages/plasma/ranking/GetStatsForOwners.py b/bfbc2_masterserver/messages/plasma/ranking/GetStatsForOwners.py new file mode 100644 index 0000000..d404cf5 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/ranking/GetStatsForOwners.py @@ -0,0 +1,12 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Owner import RankedOwner +from bfbc2_masterserver.models.plasma.Stats import Stat + + +class GetStatsForOwnersRequest(PlasmaTransaction): + owners: list[RankedOwner] + keys: list[str] + + +class GetStatsForOwnersResponse(PlasmaTransaction): + stats: list[Stat] diff --git a/bfbc2_masterserver/messages/plasma/ranking/GetTopN.py b/bfbc2_masterserver/messages/plasma/ranking/GetTopN.py new file mode 100644 index 0000000..8b8056c --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/ranking/GetTopN.py @@ -0,0 +1,12 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Leaderboard import Leaderboard + + +class GetTopNRequest(PlasmaTransaction): + key: str + minRank: int + maxRank: int + + +class GetTopNResponse(PlasmaTransaction): + stats: list[Leaderboard] diff --git a/bfbc2_masterserver/messages/plasma/ranking/GetTopNAndMe.py b/bfbc2_masterserver/messages/plasma/ranking/GetTopNAndMe.py new file mode 100644 index 0000000..eddc754 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/ranking/GetTopNAndMe.py @@ -0,0 +1,12 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Leaderboard import Leaderboard + + +class GetTopNAndMeRequest(PlasmaTransaction): + key: str + minRank: int + maxRank: int + + +class GetTopNAndMeResponse(PlasmaTransaction): + stats: list[Leaderboard] diff --git a/bfbc2_masterserver/messages/plasma/ranking/UpdateStats.py b/bfbc2_masterserver/messages/plasma/ranking/UpdateStats.py new file mode 100644 index 0000000..67964d9 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/ranking/UpdateStats.py @@ -0,0 +1,10 @@ +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Stats import RankedOwnerStat, UserUpdateRequest + + +class UpdateStatsRequest(PlasmaTransaction): + u: list[UserUpdateRequest] + + +class UpdateStatsResponse(PlasmaTransaction): + pass diff --git a/bfbc2_masterserver/messages/plasma/record/AddRecord.py b/bfbc2_masterserver/messages/plasma/record/AddRecord.py new file mode 100644 index 0000000..66f818d --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/record/AddRecord.py @@ -0,0 +1,12 @@ +from bfbc2_masterserver.enumerators.plasma.RecordName import RecordName +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Record import Record + + +class AddRecordRequest(PlasmaTransaction): + recordName: RecordName + values: list[Record] + + +class AddRecordResponse(PlasmaTransaction): + pass diff --git a/bfbc2_masterserver/messages/plasma/record/AddRecordAsMap.py b/bfbc2_masterserver/messages/plasma/record/AddRecordAsMap.py new file mode 100644 index 0000000..dc352c3 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/record/AddRecordAsMap.py @@ -0,0 +1,13 @@ +from datetime import datetime + +from bfbc2_masterserver.enumerators.plasma.RecordName import RecordName +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction + + +class AddRecordAsMapRequest(PlasmaTransaction): + recordName: RecordName + values: dict[str, str] + + +class AddRecordAsMapResponse(PlasmaTransaction): + pass diff --git a/bfbc2_masterserver/messages/plasma/record/UpdateRecord.py b/bfbc2_masterserver/messages/plasma/record/UpdateRecord.py new file mode 100644 index 0000000..915df93 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/record/UpdateRecord.py @@ -0,0 +1,12 @@ +from bfbc2_masterserver.enumerators.plasma.RecordName import RecordName +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.models.plasma.Record import Record + + +class UpdateRecordRequest(PlasmaTransaction): + recordName: RecordName + values: list[Record] + + +class UpdateRecordResponse(PlasmaTransaction): + pass diff --git a/bfbc2_masterserver/messages/plasma/record/UpdateRecordAsMap.py b/bfbc2_masterserver/messages/plasma/record/UpdateRecordAsMap.py new file mode 100644 index 0000000..cc895c8 --- /dev/null +++ b/bfbc2_masterserver/messages/plasma/record/UpdateRecordAsMap.py @@ -0,0 +1,13 @@ +from datetime import datetime + +from bfbc2_masterserver.enumerators.plasma.RecordName import RecordName +from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction + + +class UpdateRecordAsMapRequest(PlasmaTransaction): + recordName: RecordName + values: dict[str, str] + + +class UpdateRecordAsMapResponse(PlasmaTransaction): + pass diff --git a/bfbc2_masterserver/models/plasma/Association.py b/bfbc2_masterserver/models/plasma/Association.py index 7340491..3a20834 100644 --- a/bfbc2_masterserver/models/plasma/Association.py +++ b/bfbc2_masterserver/models/plasma/Association.py @@ -1,19 +1,28 @@ from datetime import datetime +from typing import Optional from pydantic import BaseModel from bfbc2_masterserver.models.plasma.Owner import Owner +class AssociationRequest(BaseModel): + owner: Owner + member: Owner + mutual: bool + + class AssociationReturn(BaseModel): id: int name: str type: int - created: datetime - modified: datetime + created: Optional[datetime] + modified: Optional[datetime] -class AssociationRequest(BaseModel): +class AssociationResult(BaseModel): + member: AssociationReturn owner: Owner - member: Owner mutual: bool + outcome: int + listSize: int diff --git a/bfbc2_masterserver/models/plasma/Attachment.py b/bfbc2_masterserver/models/plasma/Attachment.py new file mode 100644 index 0000000..46839ec --- /dev/null +++ b/bfbc2_masterserver/models/plasma/Attachment.py @@ -0,0 +1,7 @@ +from pydantic import BaseModel + + +class Attachment(BaseModel): + key: str + type: str + data: str diff --git a/bfbc2_masterserver/models/plasma/Attribute.py b/bfbc2_masterserver/models/plasma/Attribute.py new file mode 100644 index 0000000..74ef625 --- /dev/null +++ b/bfbc2_masterserver/models/plasma/Attribute.py @@ -0,0 +1,6 @@ +from pydantic import BaseModel + + +class Attribute(BaseModel): + name: str + value: str diff --git a/bfbc2_masterserver/models/plasma/Message.py b/bfbc2_masterserver/models/plasma/Message.py index 373948a..fd90727 100644 --- a/bfbc2_masterserver/models/plasma/Message.py +++ b/bfbc2_masterserver/models/plasma/Message.py @@ -21,7 +21,7 @@ class MessageResponse(BaseModel): messageId: int messageType: str purgeStrategy: str - from_: Target = Field(validation_alias="from") + from_: Target = Field(alias="from") to: list[Target] timeSent: datetime expiration: datetime diff --git a/bfbc2_masterserver/models/plasma/Presence.py b/bfbc2_masterserver/models/plasma/Presence.py new file mode 100644 index 0000000..1231754 --- /dev/null +++ b/bfbc2_masterserver/models/plasma/Presence.py @@ -0,0 +1,12 @@ +from pydantic import BaseModel + +from bfbc2_masterserver.models.plasma.Owner import Owner + + +class PresenceRequest(BaseModel): + userId: int + + +class PresenceResponse(BaseModel): + owner: Owner + outcome: int diff --git a/bfbc2_masterserver/models/plasma/Stats.py b/bfbc2_masterserver/models/plasma/Stats.py index 16ee58a..ff0e22c 100644 --- a/bfbc2_masterserver/models/plasma/Stats.py +++ b/bfbc2_masterserver/models/plasma/Stats.py @@ -2,6 +2,8 @@ from pydantic import BaseModel +from bfbc2_masterserver.enumerators.plasma.StatUpdateType import StatUpdateType + class Stat(BaseModel): key: str @@ -16,3 +18,14 @@ class RankedOwnerStat(BaseModel): rankedStats: list[RankedStat] ownerId: int ownerType: int + + +class StatUpdate(BaseModel): + ut: StatUpdateType + k: str + v: Decimal + + +class UserUpdateRequest(BaseModel): + o: int + s: list[StatUpdate] diff --git a/bfbc2_masterserver/models/plasma/Status.py b/bfbc2_masterserver/models/plasma/Status.py new file mode 100644 index 0000000..5b16676 --- /dev/null +++ b/bfbc2_masterserver/models/plasma/Status.py @@ -0,0 +1,6 @@ +from pydantic import BaseModel + + +class Status(BaseModel): + userid: int + status: int diff --git a/bfbc2_masterserver/models/plasma/database/Message.py b/bfbc2_masterserver/models/plasma/database/Message.py index df97316..c60c1cc 100644 --- a/bfbc2_masterserver/models/plasma/database/Message.py +++ b/bfbc2_masterserver/models/plasma/database/Message.py @@ -17,17 +17,23 @@ class Message(SQLModel, table=True): sender: "Persona" = Relationship( back_populates="messagesSent", - sa_relationship_kwargs=dict(foreign_keys="[Message.sender_id]"), + sa_relationship_kwargs=dict( + foreign_keys="[Message.sender_id]", lazy="selectin" + ), ) - sender_id: int = Field(default=None, foreign_key="persona.id") + sender_id: int | None = Field(default=None, foreign_key="persona.id") recipient: "Persona" = Relationship( back_populates="messages", - sa_relationship_kwargs=dict(foreign_keys="[Message.recipient_id]"), + sa_relationship_kwargs=dict( + foreign_keys="[Message.recipient_id]", lazy="selectin" + ), ) - recipient_id: int = Field(default=None, foreign_key="persona.id") + recipient_id: int | None = Field(default=None, foreign_key="persona.id") - attachments: list["MessageAttachment"] = Relationship(back_populates="message") + attachments: list["MessageAttachment"] = Relationship( + back_populates="message", sa_relationship_kwargs=dict(lazy="selectin") + ) deliveryType: str messageType: str diff --git a/bfbc2_masterserver/models/plasma/database/MessageAttachment.py b/bfbc2_masterserver/models/plasma/database/MessageAttachment.py index 5e531df..cca8b31 100644 --- a/bfbc2_masterserver/models/plasma/database/MessageAttachment.py +++ b/bfbc2_masterserver/models/plasma/database/MessageAttachment.py @@ -10,7 +10,7 @@ class MessageAttachment(SQLModel, table=True): id: int = Field(default=None, primary_key=True) message: "Message" = Relationship(back_populates="attachments") - message_id: int = Field(default=None, foreign_key="message.id") + message_id: int | None = Field(default=None, foreign_key="message.id") key: str type: str diff --git a/bfbc2_masterserver/plasma.py b/bfbc2_masterserver/plasma.py index 7851de9..63cfc0f 100644 --- a/bfbc2_masterserver/plasma.py +++ b/bfbc2_masterserver/plasma.py @@ -4,6 +4,7 @@ from pydoc import resolve from fastapi import WebSocket +from fastapi.websockets import WebSocketState from pydantic import ValidationError from bfbc2_masterserver.dataclasses.Client import Client @@ -308,10 +309,16 @@ async def __send(self, message: Message) -> None: ) # Send the fragment - await self.websocket.send_bytes(message_fragment.compile()) + try: + await self.websocket.send_bytes(message_fragment.compile()) + except RuntimeError as e: + self.on_disconnect(str(e), None) else: # If the response fits into one message, send it - await self.websocket.send_bytes(response_bytes) + try: + await self.websocket.send_bytes(response_bytes) + except RuntimeError as e: + self.on_disconnect(str(e), None) def disconnect(self, reason: int) -> None: self.start_transaction( diff --git a/bfbc2_masterserver/services/plasma/account.py b/bfbc2_masterserver/services/plasma/account.py index c064f7e..5753f40 100644 --- a/bfbc2_masterserver/services/plasma/account.py +++ b/bfbc2_masterserver/services/plasma/account.py @@ -13,6 +13,10 @@ from bfbc2_masterserver.enumerators.ErrorCode import ErrorCode from bfbc2_masterserver.enumerators.fesl.FESLTransaction import FESLTransaction from bfbc2_masterserver.error import TransactionError +from bfbc2_masterserver.messages.plasma.account.GameSpyPreAuth import ( + GameSpyPreAuthRequest, + GameSpyPreAuthResponse, +) from bfbc2_masterserver.messages.plasma.account.GetCountryList import ( GetCountryListRequest, GetCountryListResponse, @@ -33,6 +37,10 @@ NuAddPersonaRequest, NuAddPersonaResponse, ) +from bfbc2_masterserver.messages.plasma.account.NuCreateEncryptedToken import ( + NuCreateEncryptedTokenRequest, + NuCreateEncryptedTokenResponse, +) from bfbc2_masterserver.messages.plasma.account.NuDisablePersona import ( NuDisablePersonaRequest, NuDisablePersonaResponse, @@ -45,6 +53,22 @@ NuEntitleUserRequest, NuEntitleUserResponse, ) +from bfbc2_masterserver.messages.plasma.account.NuGetAccount import ( + NuGetAccountRequest, + NuGetAccountResponse, +) +from bfbc2_masterserver.messages.plasma.account.NuGetAccountByNuid import ( + NuGetAccountByNuidRequest, + NuGetAccountByNuidResponse, +) +from bfbc2_masterserver.messages.plasma.account.NuGetAccountByPS3Ticket import ( + NuGetAccountByPS3TicketRequest, + NuGetAccountByPS3TicketResponse, +) +from bfbc2_masterserver.messages.plasma.account.NuGetEntitlementCount import ( + NuGetEntitlementCountRequest, + NuGetEntitlementCountResponse, +) from bfbc2_masterserver.messages.plasma.account.NuGetEntitlements import ( NuGetEntitlementsRequest, NuGetEntitlementsResponse, @@ -73,9 +97,42 @@ NuLookupUserInfoRequest, NuLookupUserInfoResponse, ) +from bfbc2_masterserver.messages.plasma.account.NuPS3AddAccount import ( + NuPS3AddAccountRequest, + NuPS3AddAccountResponse, +) +from bfbc2_masterserver.messages.plasma.account.NuPS3Login import ( + NuPS3LoginRequest, + NuPS3LoginResponse, +) +from bfbc2_masterserver.messages.plasma.account.NuSearchOwners import ( + NuSearchOwnersRequest, + NuSearchOwnersResponse, +) +from bfbc2_masterserver.messages.plasma.account.NuSuggestPersonas import ( + NuSuggestPersonasRequest, + NuSuggestPersonasResponse, +) +from bfbc2_masterserver.messages.plasma.account.NuUpdateAccount import ( + NuUpdateAccountRequest, + NuUpdateAccountResponse, +) +from bfbc2_masterserver.messages.plasma.account.NuUpdatePassword import ( + NuUpdatePasswordRequest, + NuUpdatePasswordResponse, +) +from bfbc2_masterserver.messages.plasma.account.NuXBL360AddAccount import ( + NuXBL360AddAccountRequest, + NuXBL360AddAccountResponse, +) +from bfbc2_masterserver.messages.plasma.account.NuXBL360Login import ( + NuXBL360LoginRequest, + NuXBL360LoginResponse, +) from bfbc2_masterserver.models.plasma.database.Account import Account from bfbc2_masterserver.models.plasma.database.Persona import Persona from bfbc2_masterserver.models.plasma.Entitlement import Entitlement +from bfbc2_masterserver.models.plasma.Owner import Owner from bfbc2_masterserver.models.plasma.UserInfo import UserInfo from bfbc2_masterserver.tools.country_list import COUNTRY_LIST, getLocalizedCountryList from bfbc2_masterserver.tools.terms_of_service import getLocalizedTOS @@ -159,6 +216,71 @@ def __init__(self, plasma) -> None: NuGrantEntitlementRequest, ) + self.resolvers[FESLTransaction.NuCreateEncryptedToken] = ( + self.__handle_nu_create_encrypted_token, + NuCreateEncryptedTokenRequest, + ) + + self.resolvers[FESLTransaction.NuUpdatePassword] = ( + self.__handle_nu_update_password, + NuUpdatePasswordRequest, + ) + + self.resolvers[FESLTransaction.NuGetAccount] = ( + self.__handle_nu_get_account, + NuGetAccountRequest, + ) + + self.resolvers[FESLTransaction.NuGetAccountByNuid] = ( + self.__handle_nu_get_account_by_nuid, + NuGetAccountByNuidRequest, + ) + + self.resolvers[FESLTransaction.NuGetAccountByPS3Ticket] = ( + self.__handle_nu_get_account_by_ps3_ticket, + NuGetAccountByPS3TicketRequest, + ) + + self.resolvers[FESLTransaction.NuUpdateAccount] = ( + self.__handle_nu_update_account, + NuUpdateAccountRequest, + ) + + self.resolvers[FESLTransaction.GameSpyPreAuth] = ( + self.__handle_gamespy_pre_auth, + GameSpyPreAuthRequest, + ) + + self.resolvers[FESLTransaction.NuXBL360Login] = ( + self.__handle_nu_xbl360_login, + NuXBL360LoginRequest, + ) + + self.resolvers[FESLTransaction.NuXBL360AddAccount] = ( + self.__handle_nu_xbl360_add_account, + NuXBL360AddAccountRequest, + ) + + self.resolvers[FESLTransaction.NuPS3Login] = ( + self.__handle_nu_ps3_login, + NuPS3LoginRequest, + ) + + self.resolvers[FESLTransaction.NuPS3AddAccount] = ( + self.__handle_nu_ps3_add_account, + NuPS3AddAccountRequest, + ) + + self.resolvers[FESLTransaction.NuSearchOwners] = ( + self.__handle_nu_search_owners, + NuSearchOwnersRequest, + ) + + self.resolvers[FESLTransaction.NuGetEntitlementCount] = ( + self.__handle_nu_get_entitlement_count, + NuGetEntitlementCountRequest, + ) + def _get_resolver(self, txn): """ Gets the resolver for a given transaction. @@ -641,3 +763,100 @@ def __handle_nu_grant_entitlement( ) -> NuGrantEntitlementResponse | TransactionError: self.database.account_grant_entitlement(data) return NuGrantEntitlementResponse() + + def __handle_nu_create_encrypted_token( + self, data: NuCreateEncryptedTokenRequest + ) -> NuCreateEncryptedTokenResponse | TransactionError: + # I don't know what is the expected response for this transaction + # Name suggest that it should create new session? But I don't think it's ever called by the client + raise NotImplementedError("NuCreateEncryptedToken is not implemented") + + def __handle_nu_suggest_personas( + self, data: NuSuggestPersonasRequest + ) -> NuSuggestPersonasResponse | TransactionError: + # Is this ever called from the client? + + # Not sure what "name" is here, but I assume it's the name of the currently logged persona + if not self.connection.persona: + return TransactionError(ErrorCode.SESSION_NOT_AUTHORIZED) + + if self.connection.persona.name != data.name: + return TransactionError(ErrorCode.SYSTEM_ERROR) + + # I don't know what is the expected response for this transaction + raise NotImplementedError("NuSuggestPersonas is not implemented") + + def __handle_nu_update_password( + self, data: NuUpdatePasswordRequest + ) -> NuUpdatePasswordResponse | TransactionError: + # Is this ever called from the client? + raise NotImplementedError("NuUpdatePassword is not implemented") + + def __handle_nu_get_account( + self, data: NuGetAccountRequest + ) -> NuGetAccountResponse | TransactionError: + # Is this ever called from the client? + raise NotImplementedError("NuGetAccount is not implemented") + + def __handle_nu_get_account_by_nuid( + self, data: NuGetAccountByNuidRequest + ) -> NuGetAccountByNuidResponse | TransactionError: + # Is this ever called from the client? + raise NotImplementedError("NuGetAccountByNuid is not implemented") + + def __handle_nu_get_account_by_ps3_ticket( + self, data: NuGetAccountByPS3TicketRequest + ) -> NuGetAccountByPS3TicketResponse | TransactionError: + # Is this ever called from the client? + raise NotImplementedError("NuGetAccountByPS3Ticket is not implemented") + + def __handle_nu_update_account( + self, data: NuUpdateAccountRequest + ) -> NuUpdateAccountResponse | TransactionError: + # Is this ever called from the client? + raise NotImplementedError("NuUpdateAccount is not implemented") + + def __handle_gamespy_pre_auth( + self, data: GameSpyPreAuthRequest + ) -> GameSpyPreAuthResponse | TransactionError: + # Is this ever called from the client? + raise NotImplementedError("GameSpyPreAuth is not implemented") + + def __handle_nu_xbl360_login( + self, data: NuXBL360LoginRequest + ) -> NuXBL360LoginResponse | TransactionError: + # Is this ever called from the client? + raise NotImplementedError("NuXBL360Login is not implemented") + + def __handle_nu_xbl360_add_account( + self, data: NuXBL360AddAccountRequest + ) -> NuXBL360AddAccountResponse | TransactionError: + # Is this ever called from the client? + raise NotImplementedError("NuXBL360AddAccount is not implemented") + + def __handle_nu_ps3_login( + self, data: NuPS3LoginRequest + ) -> NuPS3LoginResponse | TransactionError: + # Is this ever called from the client? + raise NotImplementedError("NuPS3Login is not implemented") + + def __handle_nu_ps3_add_account( + self, data: NuPS3AddAccountRequest + ) -> NuPS3AddAccountResponse | TransactionError: + # Is this ever called from the client? + raise NotImplementedError("NuPS3AddAccount is not implemented") + + def __handle_nu_search_owners( + self, data: NuSearchOwnersRequest + ) -> NuSearchOwnersResponse | TransactionError: + owners = self.database.persona_search(data.screenName) + return NuSearchOwnersResponse( + users=[Owner(id=owner.id, name=owner.name, type=1) for owner in owners], + nameSpaceId="battlefield", + ) + + def __handle_nu_get_entitlement_count( + self, data: NuGetEntitlementCountRequest + ) -> NuGetEntitlementCountResponse | TransactionError: + # Is this ever called from the client? + raise NotImplementedError("NuGetEntitlementCount is not implemented") diff --git a/bfbc2_masterserver/services/plasma/association.py b/bfbc2_masterserver/services/plasma/association.py index d5a4e1d..a3b8db6 100644 --- a/bfbc2_masterserver/services/plasma/association.py +++ b/bfbc2_masterserver/services/plasma/association.py @@ -1,20 +1,35 @@ -from typing import Tuple +from ast import Delete from bfbc2_masterserver.dataclasses.plasma.Service import PlasmaService from bfbc2_masterserver.enumerators.ErrorCode import ErrorCode +from bfbc2_masterserver.enumerators.fesl.FESLService import FESLService from bfbc2_masterserver.enumerators.fesl.FESLTransaction import FESLTransaction from bfbc2_masterserver.enumerators.plasma.AssocationType import AssocationType +from bfbc2_masterserver.enumerators.plasma.AssocationUpdateOperation import ( + AssocationUpdateOperation, +) +from bfbc2_masterserver.enumerators.plasma.ListFullBehavior import ListFullBehavior from bfbc2_masterserver.error import TransactionError from bfbc2_masterserver.messages.plasma.assocation.AddAssocations import ( AddAssociationsRequest, + AddAssociationsResponse, +) +from bfbc2_masterserver.messages.plasma.assocation.DeleteAssociations import ( + DeleteAssociationsRequest, + DeleteAssociationsResponse, ) from bfbc2_masterserver.messages.plasma.assocation.GetAssociations import ( GetAssociationsRequest, GetAssociationsResponse, ) -from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction +from bfbc2_masterserver.messages.plasma.assocation.GetAssociationsCount import ( + GetAssociationsCountRequest, +) +from bfbc2_masterserver.messages.plasma.assocation.NotifyAssociationUpdate import ( + NotifyAssociationUpdate, +) from bfbc2_masterserver.models.plasma.Association import ( - AssociationRequest, + AssociationResult, AssociationReturn, ) from bfbc2_masterserver.models.plasma.database.Association import Association @@ -36,6 +51,20 @@ def __init__(self, plasma) -> None: AddAssociationsRequest, ) + self.resolvers[FESLTransaction.DeleteAssociations] = ( + self.__handle_delete_associations, + DeleteAssociationsRequest, + ) + + self.resolvers[FESLTransaction.GetAssociationsCount] = ( + self.__handle_get_associations_count, + GetAssociationsCountRequest, + ) + + self.generators[FESLTransaction.NotifyAssociationUpdate] = ( + self.__notify_association_update + ) + def _get_resolver(self, txn): """ Gets the resolver for a given transaction. @@ -63,7 +92,7 @@ def _get_generator(self, txn): def __handle_get_associations( self, data: GetAssociationsRequest ) -> GetAssociationsResponse | TransactionError: - assocations: list[Association] | ErrorCode = self.database.assocation_get( + assocations: list[Association] | ErrorCode = self.database.association_get_all( data.owner.id, data.type ) @@ -96,5 +125,214 @@ def __handle_get_associations( return response def __handle_add_associations(self, data: AddAssociationsRequest): - # TODO - return PlasmaTransaction() + if self.connection.persona is None: + return TransactionError(ErrorCode.SESSION_NOT_AUTHORIZED) + + maxAssociations = 20 if data.type != AssocationType.PlasmaRecentPlayers else 100 + result = [] + + for addRequest in data.addRequests: + outcome = 0 + + associations = self.database.association_get_all( + addRequest.owner.id, data.type + ) + + if isinstance(associations, ErrorCode): + return TransactionError(ErrorCode.TRANSACTION_DATA_NOT_FOUND) + + isListFull = len(associations) >= maxAssociations + + if isListFull: + if data.listFullBehavior == ListFullBehavior.ReturnError: + outcome = 23005 + elif ( + data.listFullBehavior == ListFullBehavior.RollLeastRecentlyModified + ): + # Order by oldest first + associations.sort(key=lambda x: x.updatedAt) + self.database.association_delete(associations[0].id) + + # Add new member + assoResult = self.database.association_add( + owner_id=addRequest.owner.id, + target_id=addRequest.member.id, + type=data.type, + ) + + if isinstance(assoResult, ErrorCode): + outcome = 23005 + + memberAccountId = self.database.persona_get_owner_id(addRequest.member.id) + memberPersona = self.database.persona_get_by_id(addRequest.member.id) + + if isinstance(memberAccountId, ErrorCode) or isinstance( + memberPersona, ErrorCode + ): + continue + + externalClient = self.plasma.manager.CLIENTS.get(memberAccountId) + + if externalClient: + externalClient.plasma.start_transaction( + FESLService.AssociationService, + FESLTransaction.NotifyAssociationUpdate, + NotifyAssociationUpdate( + domainPartition=data.domainPartition, + listSize=len(associations) + 1, + member=AssociationReturn( + id=addRequest.owner.id, + name=self.connection.persona.name, + type=1, + created=( + assoResult.createdAt + if not isinstance(assoResult, ErrorCode) + else None + ), + modified=( + assoResult.updatedAt + if not isinstance(assoResult, ErrorCode) + else None + ), + ), + operation=AssocationUpdateOperation.ADD, + owner=Owner( + id=addRequest.member.id, + name=addRequest.member.name, + type=addRequest.member.type, + ), + type=data.type.value, + ), + ) + + result.append( + AssociationResult( + member=AssociationReturn( + id=addRequest.owner.id, + name=self.connection.persona.name, + type=1, + created=( + assoResult.createdAt + if not isinstance(assoResult, ErrorCode) + else None + ), + modified=( + assoResult.updatedAt + if not isinstance(assoResult, ErrorCode) + else None + ), + ), + owner=addRequest.owner, + mutual=data.type != AssocationType.PlasmaRecentPlayers, + outcome=outcome, + listSize=len(associations) + 1, + ) + ) + + response = AddAssociationsResponse( + domainPartition=data.domainPartition, + maxListSize=maxAssociations, + result=result, + type=data.type, + ) + + return response + + def __handle_delete_associations(self, data: DeleteAssociationsRequest): + if self.connection.persona is None: + return TransactionError(ErrorCode.SESSION_NOT_AUTHORIZED) + + maxAssociations = 20 if data.type != AssocationType.PlasmaRecentPlayers else 100 + result = [] + + for deleteRequest in data.deleteRequests: + outcome = 0 + + association = self.database.association_get( + deleteRequest.owner.id, deleteRequest.member.id, data.type + ) + + associations = self.database.association_get_all( + deleteRequest.owner.id, data.type + ) + + if isinstance(associations, ErrorCode): + return TransactionError(ErrorCode.TRANSACTION_DATA_NOT_FOUND) + + if not association: + outcome = 23005 + else: + self.database.association_delete(association.id) + + memberAccountId = self.database.persona_get_owner_id( + deleteRequest.member.id + ) + memberPersona = self.database.persona_get_by_id(deleteRequest.member.id) + + if isinstance(memberAccountId, ErrorCode) or isinstance( + memberPersona, ErrorCode + ): + continue + + externalClient = self.plasma.manager.CLIENTS.get(memberAccountId) + + if externalClient and association: + externalClient.plasma.start_transaction( + FESLService.AssociationService, + FESLTransaction.NotifyAssociationUpdate, + NotifyAssociationUpdate( + domainPartition=data.domainPartition, + listSize=len(associations), + member=AssociationReturn( + id=deleteRequest.owner.id, + name=self.connection.persona.name, + type=1, + created=association.createdAt, + modified=association.updatedAt, + ), + operation=AssocationUpdateOperation.DEL, + owner=Owner( + id=deleteRequest.member.id, + name=deleteRequest.member.name, + type=deleteRequest.member.type, + ), + type=data.type.value, + ), + ) + + if association: + result.append( + AssociationResult( + member=AssociationReturn( + id=deleteRequest.member.id, + name=memberPersona.name, + type=1, + created=association.createdAt, + modified=association.updatedAt, + ), + owner=deleteRequest.owner, + mutual=data.type != AssocationType.PlasmaRecentPlayers, + outcome=outcome, + listSize=len(associations), + ) + ) + + response = DeleteAssociationsResponse( + domainPartition=data.domainPartition, + maxListSize=maxAssociations, + result=result, + type=data.type, + ) + + return response + + def __notify_association_update(self, data: NotifyAssociationUpdate): + return data + + def __handle_get_associations_count(self, data: GetAssociationsCountRequest): + associations = self.database.association_get_all(data.owner.id, data.type) + + if isinstance(associations, ErrorCode): + return TransactionError(ErrorCode.TRANSACTION_DATA_NOT_FOUND) + + return len(associations) diff --git a/bfbc2_masterserver/services/plasma/connect.py b/bfbc2_masterserver/services/plasma/connect.py index fbb1aac..55f5999 100644 --- a/bfbc2_masterserver/services/plasma/connect.py +++ b/bfbc2_masterserver/services/plasma/connect.py @@ -21,6 +21,10 @@ MemCheckResult, ) from bfbc2_masterserver.messages.plasma.connect.Ping import PingRequest, PingResponse +from bfbc2_masterserver.messages.plasma.connect.Suicide import ( + SuicideRequest, + SuicideResponse, +) from bfbc2_masterserver.models.general.PlasmaTransaction import PlasmaTransaction from bfbc2_masterserver.models.plasma.DomainPartition import DomainPartition from bfbc2_masterserver.models.plasma.MemCheck import MemCheck @@ -167,15 +171,10 @@ def __handle_hello(self, data: HelloRequest) -> HelloResponse | TransactionError self.__make_memcheck() return response - def __create_memcheck(self, data: dict) -> MemCheckRequest: + def __create_memcheck(self, request: MemCheckRequest) -> MemCheckRequest: """ Creates a memcheck request """ - request = MemCheckRequest( - memcheck=MemCheck(), - type=0, - salt="".join(random.choice(string.digits) for _ in range(10)), - ) return request @@ -188,7 +187,7 @@ def __handle_memcheck(self, data: MemCheckResult) -> TransactionSkip: def __make_ping(self) -> None: self.plasma.start_transaction( - FESLService.ConnectService, FESLTransaction.Ping, PlasmaTransaction() + FESLService.ConnectService, FESLTransaction.Ping, PingRequest() ) loop = asyncio.get_event_loop() @@ -203,7 +202,13 @@ def __make_ping(self) -> None: def __make_memcheck(self) -> None: self.plasma.start_transaction( - FESLService.ConnectService, FESLTransaction.MemCheck, PlasmaTransaction() + FESLService.ConnectService, + FESLTransaction.MemCheck, + MemCheckRequest( + memcheck=MemCheck(), + type=0, + salt="".join(random.choice(string.digits) for _ in range(10)), + ), ) loop = asyncio.get_event_loop() @@ -283,13 +288,13 @@ def __handle_goodbye(self, data: GoodbyeRequest) -> TransactionSkip: return TransactionSkip() - def __handle_suicide(self, data: dict): + def __handle_suicide(self, data: SuicideRequest) -> SuicideResponse: """ Client support this message, but I'm not sure what this Transaction is supposed to do, we ignore it - never captured this packet from original master server so I suppose it's another leftover. """ - raise NotImplementedError() + raise NotImplementedError("Suicide not implemented") - def __create_goodbye(self, data: GoodbyeRequest): + def __create_goodbye(self, data: GoodbyeRequest) -> GoodbyeRequest: return data diff --git a/bfbc2_masterserver/services/plasma/message.py b/bfbc2_masterserver/services/plasma/message.py index 71748b0..dd18c96 100644 --- a/bfbc2_masterserver/services/plasma/message.py +++ b/bfbc2_masterserver/services/plasma/message.py @@ -1,7 +1,22 @@ +from datetime import datetime, timedelta + from bfbc2_masterserver.dataclasses.plasma.Service import PlasmaService from bfbc2_masterserver.enumerators.ErrorCode import ErrorCode +from bfbc2_masterserver.enumerators.fesl.FESLService import FESLService from bfbc2_masterserver.enumerators.fesl.FESLTransaction import FESLTransaction from bfbc2_masterserver.error import TransactionError +from bfbc2_masterserver.messages.plasma.message.AsyncMessageEvent import ( + AsyncMessageEvent, +) +from bfbc2_masterserver.messages.plasma.message.AsyncPurgedEvent import AsyncPurgedEvent +from bfbc2_masterserver.messages.plasma.message.DeleteMessages import ( + DeleteMessagesRequest, + DeleteMessagesResponse, +) +from bfbc2_masterserver.messages.plasma.message.GetMessageAttachments import ( + GetMessageAttachmentsRequest, + GetMessageAttachmentsResponse, +) from bfbc2_masterserver.messages.plasma.message.GetMessages import ( GetMessagesRequest, GetMessagesResponse, @@ -10,8 +25,20 @@ ModifySettingsRequest, ModifySettingsResponse, ) +from bfbc2_masterserver.messages.plasma.message.PurgeMessages import ( + PurgeMessagesRequest, + PurgeMessagesResponse, +) +from bfbc2_masterserver.messages.plasma.message.SendMessage import ( + SendMessageRequest, + SendMessageResponse, +) from bfbc2_masterserver.models.plasma.database.Message import Message +from bfbc2_masterserver.models.plasma.database.MessageAttachment import ( + MessageAttachment, +) from bfbc2_masterserver.models.plasma.Message import Attachment, MessageResponse, Target +from bfbc2_masterserver.models.plasma.Status import Status class ExtensibleMessageService(PlasmaService): @@ -29,6 +56,33 @@ def __init__(self, plasma) -> None: GetMessagesRequest, ) + self.resolvers[FESLTransaction.SendMessage] = ( + self.__handle_send_message, + SendMessageRequest, + ) + + self.resolvers[FESLTransaction.GetMessageAttachments] = ( + self.__handle_get_message_attachments, + GetMessageAttachmentsRequest, + ) + + self.resolvers[FESLTransaction.DeleteMessages] = ( + self.__handle_delete_messages, + DeleteMessagesRequest, + ) + + self.resolvers[FESLTransaction.PurgeMessages] = ( + self.__handle_purge_messages, + PurgeMessagesRequest, + ) + + self.generators[FESLTransaction.AsyncMessageEvent] = ( + self.__create_async_message_event + ) + self.generators[FESLTransaction.AsyncPurgedEvent] = ( + self.__create_async_purged_event + ) + def _get_resolver(self, txn): """ Gets the resolver for a given transaction. @@ -64,38 +118,171 @@ def __handle_get_messages( if self.connection.persona is None: return TransactionError(ErrorCode.SYSTEM_ERROR) - messages_db: list[Message] = self.database.message_get( + messages_db: list[Message] = self.database.message_get_all( self.connection.persona.id ) messages: list[MessageResponse] = [ - MessageResponse( - attachments=[ - Attachment( - key=attachment.key, type=attachment.type, data=attachment.data - ) - for attachment in message.attachments - ], - deliveryType=message.deliveryType, - messageId=message.id, - messageType=message.messageType, - purgeStrategy=message.purgeStrategy, - from_=Target( - name=message.sender.name, - id=message.sender.id, - type=1, - ), - to=[ - Target( - name=message.recipient.name, - id=message.recipient.id, + MessageResponse.model_validate( + { + "attachments": [ + Attachment( + key=attachment.key, + type=attachment.type, + data=attachment.data, + ) + for attachment in message.attachments + ], + "deliveryType": message.deliveryType, + "messageId": message.id, + "messageType": message.messageType, + "purgeStrategy": message.purgeStrategy, + "from": Target( + name=message.sender.name, + id=message.sender.id, type=1, - ) - ], - timeSent=message.timeSent, - expiration=message.expiration, + ), + "to": [ + Target( + name=message.recipient.name, + id=message.recipient.id, + type=1, + ) + ], + "timeSent": message.timeSent, + "expiration": message.expiration, + } ) for message in messages_db ] return GetMessagesResponse(messages=messages) + + def __handle_send_message( + self, data: SendMessageRequest + ) -> SendMessageResponse | TransactionError: + if not self.connection.persona: + return TransactionError(ErrorCode.SESSION_NOT_AUTHORIZED) + + expiration_date = datetime.now() + timedelta(seconds=data.expires) + + # While the game code only allows one recipient, the message schema allows for multiple recipients + targetPersona = self.database.persona_get_by_id(data.to[0]) + + if isinstance(targetPersona, ErrorCode): + return TransactionError(targetPersona) + + message = Message( + attachments=[ + MessageAttachment( + key=attachment.key, + type=attachment.type, + data=attachment.data, + ) + for attachment in data.attachments + ], + deliveryType=data.deliveryType, + messageType=data.messageType, + purgeStrategy=data.purgeStrategy, + sender_id=self.connection.persona.id, + recipient=targetPersona, + expiration=expiration_date, + ) + + result = self.database.message_add(message) + + accountId = self.database.persona_get_owner_id(targetPersona.id) + + if isinstance(accountId, ErrorCode): + return TransactionError(accountId) + + receiverSession = self.plasma.manager.CLIENTS.get(accountId) + + if receiverSession: + receiverSession.plasma.start_transaction( + FESLService.MessageService, + FESLTransaction.AsyncMessageEvent, + AsyncMessageEvent.model_validate( + { + "attachments": [ + Attachment( + key=attachment.key, + type=attachment.type, + data=attachment.data, + ) + for attachment in message.attachments + ], + "deliveryType": message.deliveryType, + "messageId": result.id, + "messageType": message.messageType, + "purgeStrategy": message.purgeStrategy, + "from": Target( + name=self.connection.persona.name, + id=self.connection.persona.id, + type=1, + ), + "to": [ + Target( + name=targetPersona.name, + id=targetPersona.id, + type=1, + ) + ], + "timeSent": result.timeSent, + "expiration": message.expiration, + } + ), + ), + + return SendMessageResponse( + messageId=result.id, status=[Status(userid=data.to[0], status=0)] + ) + + def __handle_get_message_attachments( + self, data: GetMessageAttachmentsRequest + ) -> GetMessageAttachmentsResponse | TransactionError: + # Is this ever called from the client? + raise NotImplementedError("GetMessageAttachments is not implemented.") + + def __handle_delete_messages( + self, data: DeleteMessagesRequest + ) -> DeleteMessagesResponse | TransactionError: + for messageId in data.messageIds: + message = self.database.message_get(messageId) + self.database.message_delete(messageId) + + if message and (message.sender_id and message.recipient_id): + senderSession = self.plasma.manager.CLIENTS.get(message.sender_id) + receiverSession = self.plasma.manager.CLIENTS.get(message.recipient_id) + + if senderSession: + senderSession.plasma.start_transaction( + FESLService.MessageService, + FESLTransaction.AsyncPurgedEvent, + AsyncPurgedEvent(messageIds=[messageId]), + ) + + if receiverSession: + receiverSession.plasma.start_transaction( + FESLService.MessageService, + FESLTransaction.AsyncPurgedEvent, + AsyncPurgedEvent(messageIds=[messageId]), + ) + + return DeleteMessagesResponse() + + def __handle_purge_messages( + self, data: PurgeMessagesRequest + ) -> PurgeMessagesResponse: + # Is this ever called from the client? + raise NotImplementedError("PurgeMessages is not implemented.") + + def __create_async_message_event( + self, request: AsyncMessageEvent + ) -> AsyncMessageEvent: + return request + + def __create_async_purged_event( + self, request: AsyncPurgedEvent + ) -> AsyncPurgedEvent: + return request diff --git a/bfbc2_masterserver/services/plasma/playnow.py b/bfbc2_masterserver/services/plasma/playnow.py index 8b1e61f..4bb53b6 100644 --- a/bfbc2_masterserver/services/plasma/playnow.py +++ b/bfbc2_masterserver/services/plasma/playnow.py @@ -73,7 +73,7 @@ async def __matchmaking(self, matchmakingId, params): prefGamemode = params.get("prefGamemode") prefLevel = params.get("prefLevel") - game = self.database.find_game(prefGamemode, prefLevel) + game = self.database.game_find(prefGamemode, prefLevel) if game: self.plasma.start_transaction( @@ -81,8 +81,8 @@ async def __matchmaking(self, matchmakingId, params): FESLTransaction.Status, StatusRequest( id=matchmakingId, - gid=game.GID, - lid=game.LID, + gid=game.id, + lid=game.lobbyId, ), ) else: diff --git a/bfbc2_masterserver/services/plasma/presence.py b/bfbc2_masterserver/services/plasma/presence.py index d7d3559..4ad9911 100644 --- a/bfbc2_masterserver/services/plasma/presence.py +++ b/bfbc2_masterserver/services/plasma/presence.py @@ -1,14 +1,28 @@ import json -from base64 import b64encode +from base64 import b64decode, b64encode from bfbc2_masterserver.dataclasses.plasma.Service import PlasmaService from bfbc2_masterserver.enumerators.ErrorCode import ErrorCode +from bfbc2_masterserver.enumerators.fesl.FESLService import FESLService from bfbc2_masterserver.enumerators.fesl.FESLTransaction import FESLTransaction from bfbc2_masterserver.error import TransactionError +from bfbc2_masterserver.messages.plasma.presence.AsyncPresenceStatusEvent import ( + AsyncPresenceStatusEvent, +) +from bfbc2_masterserver.messages.plasma.presence.PresenceSubscribe import ( + PresenceSubscribeRequest, + PresenceSubscribeResponse, +) +from bfbc2_masterserver.messages.plasma.presence.PresenceUnsubscribe import ( + PresenceUnsubscribeRequest, + PresenceUnsubscribeResponse, +) from bfbc2_masterserver.messages.plasma.presence.SetPresenceStatus import ( SetPresenceStatusRequest, SetPresenceStatusResponse, ) +from bfbc2_masterserver.models.plasma.Owner import Owner +from bfbc2_masterserver.models.plasma.Presence import PresenceResponse class PresenceService(PlasmaService): @@ -21,6 +35,20 @@ def __init__(self, plasma) -> None: SetPresenceStatusRequest, ) + self.resolvers[FESLTransaction.PresenceSubscribe] = ( + self.__handle_presence_subscribe, + PresenceSubscribeRequest, + ) + + self.resolvers[FESLTransaction.PresenceUnsubscribe] = ( + self.__handle_presence_unsubscribe, + PresenceUnsubscribeRequest, + ) + + self.generators[FESLTransaction.AsyncPresenceStatusEvent] = ( + self.__create_async_presence_status_event + ) + def _get_resolver(self, txn): """ Gets the resolver for a given transaction. @@ -54,8 +82,87 @@ def __handle_set_presence_status( if not self.connection.persona: return TransactionError(ErrorCode.SYSTEM_ERROR) - self.plasma.manager.redis.set( - f"presence:{self.connection.persona.id}", statusEncoded - ) - + self.redis.set(f"presence:{self.connection.persona.id}", statusEncoded) return SetPresenceStatusResponse() + + def __handle_presence_subscribe( + self, data: PresenceSubscribeRequest + ) -> PresenceSubscribeResponse | TransactionError: + if self.connection.persona is None: + return TransactionError(ErrorCode.SESSION_NOT_AUTHORIZED) + + responses = [] + + for request in data.requests: + owner = self.database.persona_get_by_id(request.userId) + + if isinstance(owner, ErrorCode): + return TransactionError(owner) + + responses.append( + PresenceResponse( + owner=Owner(id=owner.id, name=owner.name, type=0), outcome=0 + ) + ) + + targetUserStatus = self.redis.get(f"presence:{request.userId}") + accountId = self.database.persona_get_owner_id(owner.id) + + if isinstance(accountId, ErrorCode): + return TransactionError(accountId) + + receiverSession = self.plasma.manager.CLIENTS.get(accountId) + + if targetUserStatus and receiverSession: + self.plasma.start_transaction( + FESLService.PresenceService, + FESLTransaction.AsyncPresenceStatusEvent, + AsyncPresenceStatusEvent( + initial=True, + owner=Owner(id=owner.id, name=owner.name, type=0), + status=json.loads(str(b64decode(targetUserStatus).decode("utf-8"))), # type: ignore + ), + ) + + receiverSession.plasma.start_transaction( + FESLService.PresenceService, + FESLTransaction.AsyncPresenceStatusEvent, + AsyncPresenceStatusEvent( + initial=True, + owner=Owner( + id=self.connection.persona.id, + name=self.connection.persona.name, + type=0, + ), + status=json.loads(str(b64decode(targetUserStatus).decode("utf-8"))), # type: ignore + ), + ) + + return PresenceSubscribeResponse(responses=responses) + + def __handle_presence_unsubscribe( + self, data: PresenceUnsubscribeRequest + ) -> PresenceUnsubscribeResponse | TransactionError: + if self.connection.persona is None: + return TransactionError(ErrorCode.SESSION_NOT_AUTHORIZED) + + responses = [] + + for request in data.requests: + owner = self.database.persona_get_by_id(request.userId) + + if isinstance(owner, ErrorCode): + return TransactionError(owner) + + responses.append( + PresenceResponse( + owner=Owner(id=owner.id, name=owner.name, type=0), outcome=0 + ) + ) + + return PresenceUnsubscribeResponse(responses=responses) + + def __create_async_presence_status_event( + self, request: AsyncPresenceStatusEvent + ) -> AsyncPresenceStatusEvent: + return request diff --git a/bfbc2_masterserver/services/plasma/ranking.py b/bfbc2_masterserver/services/plasma/ranking.py index 1ec29c8..06985cf 100644 --- a/bfbc2_masterserver/services/plasma/ranking.py +++ b/bfbc2_masterserver/services/plasma/ranking.py @@ -4,6 +4,10 @@ from bfbc2_masterserver.enumerators.ErrorCode import ErrorCode from bfbc2_masterserver.enumerators.fesl.FESLTransaction import FESLTransaction from bfbc2_masterserver.error import TransactionError +from bfbc2_masterserver.messages.plasma.ranking.GetDateRange import ( + GetDateRangeRequest, + GetDateRangeResponse, +) from bfbc2_masterserver.messages.plasma.ranking.GetRankedStatsForOwners import ( GetRankedStatsForOwnersRequest, GetRankedStatsForOwnersResponse, @@ -16,11 +20,27 @@ GetStatsRequest, GetStatsResponse, ) +from bfbc2_masterserver.messages.plasma.ranking.GetStatsForOwners import ( + GetStatsForOwnersRequest, + GetStatsForOwnersResponse, +) +from bfbc2_masterserver.messages.plasma.ranking.GetTopN import ( + GetTopNRequest, + GetTopNResponse, +) +from bfbc2_masterserver.messages.plasma.ranking.GetTopNAndMe import ( + GetTopNAndMeRequest, + GetTopNAndMeResponse, +) from bfbc2_masterserver.messages.plasma.ranking.GetTopNAndStats import ( GetTopNAndStatsRequest, GetTopNAndStatsResponse, Leaderboard, ) +from bfbc2_masterserver.messages.plasma.ranking.UpdateStats import ( + UpdateStatsRequest, + UpdateStatsResponse, +) from bfbc2_masterserver.models.plasma.database.Ranking import Ranking from bfbc2_masterserver.models.plasma.Stats import RankedOwnerStat, RankedStat, Stat @@ -50,6 +70,31 @@ def __init__(self, plasma) -> None: GetTopNAndStatsRequest, ) + self.resolvers[FESLTransaction.UpdateStats] = ( + self.__handle_update_stats, + UpdateStatsRequest, + ) + + self.resolvers[FESLTransaction.GetStatsForOwners] = ( + self.__handle_get_stats_for_owners, + GetStatsForOwnersRequest, + ) + + self.resolvers[FESLTransaction.GetTopN] = ( + self.__handle_get_top_n, + GetTopNRequest, + ) + + self.resolvers[FESLTransaction.GetTopNAndMe] = ( + self.__handle_get_top_n_and_me, + GetTopNAndMeRequest, + ) + + self.resolvers[FESLTransaction.GetDateRange] = ( + self.__handle_get_date_range, + GetDateRangeRequest, + ) + def _get_resolver(self, txn): """ Gets the resolver for a given transaction. @@ -157,3 +202,28 @@ def __handle_get_top_n_and_stats( ) return GetTopNAndStatsResponse(stats=leaderboard) + + def __handle_update_stats(self, data: UpdateStatsRequest) -> UpdateStatsResponse: + for request in data.u: + for stat in request.s: + self.database.ranking_set(request.o, stat.k, stat.v, stat.ut) + + return UpdateStatsResponse() + + def __handle_get_stats_for_owners( + self, data: GetStatsForOwnersRequest + ) -> GetStatsForOwnersResponse: + raise NotImplementedError("GetStatsForOwners is not implemented") + + def __handle_get_top_n(self, data: GetTopNRequest) -> GetTopNResponse: + raise NotImplementedError("GetTopN is not implemented") + + def __handle_get_top_n_and_me( + self, data: GetTopNAndMeRequest + ) -> GetTopNAndMeResponse: + raise NotImplementedError("GetTopNAndMe is not implemented") + + def __handle_get_date_range( + self, data: GetDateRangeRequest + ) -> GetDateRangeResponse: + raise NotImplementedError("GetDateRange is not implemented") diff --git a/bfbc2_masterserver/services/plasma/record.py b/bfbc2_masterserver/services/plasma/record.py index c7a0167..2abe784 100644 --- a/bfbc2_masterserver/services/plasma/record.py +++ b/bfbc2_masterserver/services/plasma/record.py @@ -2,6 +2,14 @@ from bfbc2_masterserver.enumerators.ErrorCode import ErrorCode from bfbc2_masterserver.enumerators.fesl.FESLTransaction import FESLTransaction from bfbc2_masterserver.error import TransactionError +from bfbc2_masterserver.messages.plasma.record.AddRecord import ( + AddRecordRequest, + AddRecordResponse, +) +from bfbc2_masterserver.messages.plasma.record.AddRecordAsMap import ( + AddRecordAsMapRequest, + AddRecordAsMapResponse, +) from bfbc2_masterserver.messages.plasma.record.GetRecord import ( GetRecordRequest, GetRecordResponse, @@ -10,6 +18,14 @@ GetRecordAsMapRequest, GetRecordAsMapResponse, ) +from bfbc2_masterserver.messages.plasma.record.UpdateRecord import ( + UpdateRecordRequest, + UpdateRecordResponse, +) +from bfbc2_masterserver.messages.plasma.record.UpdateRecordAsMap import ( + UpdateRecordAsMapRequest, + UpdateRecordAsMapResponse, +) from bfbc2_masterserver.models.plasma.Record import Record @@ -28,6 +44,26 @@ def __init__(self, plasma) -> None: GetRecordRequest, ) + self.resolvers[FESLTransaction.AddRecord] = ( + self.__handle_add_record, + AddRecordRequest, + ) + + self.resolvers[FESLTransaction.UpdateRecord] = ( + self.__handle_update_record, + UpdateRecordRequest, + ) + + self.resolvers[FESLTransaction.AddRecordAsMap] = ( + self.__handle_add_record_as_map, + AddRecordAsMapRequest, + ) + + self.resolvers[FESLTransaction.UpdateRecordAsMap] = ( + self.__handle_update_record_as_map, + UpdateRecordAsMapRequest, + ) + def _get_resolver(self, txn): """ Gets the resolver for a given transaction. @@ -84,3 +120,55 @@ def __handle_get_record( values = [Record(key=record.key, value=record.value) for record in records] return GetRecordResponse(values=values) + + def __handle_add_record( + self, data: AddRecordRequest + ) -> AddRecordResponse | TransactionError: + if self.connection.persona is None: + return TransactionError(ErrorCode.SYSTEM_ERROR) + + for record in data.values: + self.database.record_add( + self.connection.persona.id, data.recordName, record.key, record.value + ) + + return AddRecordResponse() + + def __handle_update_record( + self, data: UpdateRecordRequest + ) -> UpdateRecordResponse | TransactionError: + if self.connection.persona is None: + return TransactionError(ErrorCode.SYSTEM_ERROR) + + for record in data.values: + self.database.record_update( + self.connection.persona.id, data.recordName, record.key, record.value + ) + + return UpdateRecordResponse() + + def __handle_add_record_as_map( + self, data: AddRecordAsMapRequest + ) -> AddRecordAsMapResponse | TransactionError: + if self.connection.persona is None: + return TransactionError(ErrorCode.SYSTEM_ERROR) + + for key, value in data.values.items(): + self.database.record_add( + self.connection.persona.id, data.recordName, key, value + ) + + return AddRecordAsMapResponse() + + def __handle_update_record_as_map( + self, data: UpdateRecordAsMapRequest + ) -> UpdateRecordAsMapResponse | TransactionError: + if self.connection.persona is None: + return TransactionError(ErrorCode.SYSTEM_ERROR) + + for key, value in data.values.items(): + self.database.record_update( + self.connection.persona.id, data.recordName, key, value + ) + + return UpdateRecordAsMapResponse() diff --git a/docker-compose.yml b/docker-compose.yml index e76e6b2..d1c22f7 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,3 +16,8 @@ services: REDIS_HOST: redis ports: - "8000:8000" + depends_on: + - database + - redis + volumes: + - ./static:/app/static