Initial commit of mtgonline project
This commit is contained in:
@@ -0,0 +1,133 @@
|
||||
"""Pydantic models for protocol buffer message conversion."""
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional, List, Dict, Any
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
class ProtoMessageBase(BaseModel):
|
||||
"""Base class for protocol buffer message models."""
|
||||
message_type: str
|
||||
timestamp: datetime = datetime.now()
|
||||
|
||||
|
||||
class SessionCommand(ProtoMessageBase):
|
||||
"""Session command message."""
|
||||
message_type: str = "SessionCommand"
|
||||
cmd_type: int
|
||||
cmd_id: int = 0
|
||||
data: Dict[str, Any] = {}
|
||||
|
||||
|
||||
class GameCommand(ProtoMessageBase):
|
||||
"""Game command message."""
|
||||
message_type: str = "GameCommand"
|
||||
cmd_type: int
|
||||
cmd_id: int = 0
|
||||
player_id: Optional[int] = None
|
||||
data: Dict[str, Any] = {}
|
||||
|
||||
|
||||
class GameEvent(ProtoMessageBase):
|
||||
"""Game event message."""
|
||||
message_type: str = "GameEvent"
|
||||
event_type: int
|
||||
player_id: Optional[int] = None
|
||||
data: Dict[str, Any] = {}
|
||||
|
||||
|
||||
class Response(ProtoMessageBase):
|
||||
"""Response message."""
|
||||
message_type: str = "Response"
|
||||
cmd_id: int
|
||||
response_code: int
|
||||
data: Dict[str, Any] = {}
|
||||
|
||||
|
||||
class ServerInfoUser(BaseModel):
|
||||
"""ServerInfo_User message."""
|
||||
id: int
|
||||
name: str
|
||||
user_level: int = 0
|
||||
address: Optional[str] = None
|
||||
real_name: Optional[str] = None
|
||||
country: Optional[str] = None
|
||||
avatar_bmp: Optional[bytes] = None
|
||||
server_id: Optional[int] = None
|
||||
session_id: Optional[int] = None
|
||||
accountage_secs: Optional[int] = None
|
||||
email: Optional[str] = None
|
||||
privlevel: Optional[str] = None
|
||||
|
||||
|
||||
class ServerInfoDeckStorageFile(BaseModel):
|
||||
"""ServerInfo_DeckStorage_File message."""
|
||||
creation_time: Optional[int] = None
|
||||
|
||||
|
||||
class ServerInfoDeckStorageFolder(BaseModel):
|
||||
"""ServerInfo_DeckStorage_Folder message."""
|
||||
items: List[Dict[str, Any]] = []
|
||||
|
||||
|
||||
class ServerInfoDeckStorageTreeItem(BaseModel):
|
||||
"""ServerInfo_DeckStorage_TreeItem message."""
|
||||
id: Optional[int] = None
|
||||
name: Optional[str] = None
|
||||
file: Optional[ServerInfoDeckStorageFile] = None
|
||||
folder: Optional[ServerInfoDeckStorageFolder] = None
|
||||
|
||||
|
||||
class ServerInfoCard(BaseModel):
|
||||
"""ServerInfo_Card message."""
|
||||
id: int
|
||||
name: str
|
||||
x: Optional[int] = None
|
||||
y: Optional[int] = None
|
||||
face_down: bool = False
|
||||
tapped: bool = False
|
||||
attacking: bool = False
|
||||
color: Optional[str] = None
|
||||
pt: Optional[str] = None
|
||||
annotation: Optional[str] = None
|
||||
destroy_on_zone_change: bool = False
|
||||
doesnt_untap: bool = False
|
||||
counter_list: List[Dict[str, Any]] = []
|
||||
attach_player_id: Optional[int] = None
|
||||
attach_zone: Optional[str] = None
|
||||
attach_card_id: Optional[int] = None
|
||||
provider_id: Optional[str] = None
|
||||
|
||||
|
||||
class ServerInfoZone(BaseModel):
|
||||
"""ServerInfo_Zone message."""
|
||||
name: str
|
||||
zone_type: int = 0 # PrivateZone=0, PublicZone=1, HiddenZone=2
|
||||
with_coords: bool = False
|
||||
card_count: int = 0
|
||||
card_list: List[ServerInfoCard] = []
|
||||
always_reveal_top_card: bool = False
|
||||
always_look_at_top_card: bool = False
|
||||
|
||||
|
||||
class ServerInfoGame(BaseModel):
|
||||
"""ServerInfo_Game message."""
|
||||
server_id: Optional[int] = None
|
||||
room_id: Optional[int] = None
|
||||
game_id: Optional[int] = None
|
||||
description: Optional[str] = None
|
||||
with_password: bool = False
|
||||
max_players: int = 4
|
||||
game_types: List[int] = []
|
||||
creator_info: Optional[ServerInfoUser] = None
|
||||
only_buddies: bool = False
|
||||
only_registered: bool = False
|
||||
spectators_allowed: bool = False
|
||||
spectators_need_password: bool = False
|
||||
spectators_can_chat: bool = False
|
||||
spectators_omniscient: bool = False
|
||||
share_decklists_on_load: bool = False
|
||||
player_count: int = 0
|
||||
spectators_count: int = 0
|
||||
started: bool = False
|
||||
start_time: Optional[int] = None
|
||||
closed: bool = False
|
||||
@@ -0,0 +1,170 @@
|
||||
"""Cockatrice protocol constants and message definitions."""
|
||||
import enum
|
||||
|
||||
|
||||
class SessionCommandType(enum.IntEnum):
|
||||
"""Session command types from session_commands.proto"""
|
||||
PING = 1000
|
||||
LOGIN = 1001
|
||||
MESSAGE = 1002
|
||||
LIST_USERS = 1003
|
||||
GET_GAMES_OF_USER = 1004
|
||||
GET_USER_INFO = 1005
|
||||
ADD_TO_LIST = 1006
|
||||
REMOVE_FROM_LIST = 1007
|
||||
DECK_LIST = 1008
|
||||
DECK_NEW_DIR = 1009
|
||||
DECK_DEL_DIR = 1010
|
||||
DECK_DEL = 1011
|
||||
DECK_DOWNLOAD = 1012
|
||||
DECK_UPLOAD = 1013
|
||||
LIST_ROOMS = 1014
|
||||
JOIN_ROOM = 1015
|
||||
REGISTER = 1016
|
||||
ACTIVATE = 1017
|
||||
ACCOUNT_EDIT = 1018
|
||||
ACCOUNT_IMAGE = 1019
|
||||
ACCOUNT_PASSWORD = 1020
|
||||
FORGOT_PASSWORD_REQUEST = 1021
|
||||
FORGOT_PASSWORD_RESET = 1022
|
||||
FORGOT_PASSWORD_CHALLENGE = 1023
|
||||
REQUEST_PASSWORD_SALT = 1024
|
||||
SET_CARD_ART_PARAMS = 1025
|
||||
REPLAY_LIST = 1100
|
||||
REPLAY_DOWNLOAD = 1101
|
||||
REPLAY_MODIFY_MATCH = 1102
|
||||
REPLAY_DELETE_MATCH = 1103
|
||||
REPLAY_GET_CODE = 1104
|
||||
REPLAY_SUBMIT_CODE = 1105
|
||||
|
||||
|
||||
class GameCommandType(enum.IntEnum):
|
||||
"""Game command types from game_commands.proto"""
|
||||
KICK_FROM_GAME = 1000
|
||||
LEAVE_GAME = 1001
|
||||
GAME_SAY = 1002
|
||||
SHUFFLE = 1003
|
||||
MULLIGAN = 1004
|
||||
ROLL_DIE = 1005
|
||||
DRAW_CARDS = 1006
|
||||
UNDO_DRAW = 1007
|
||||
FLIP_CARD = 1008
|
||||
ATTACH_CARD = 1009
|
||||
CREATE_TOKEN = 1010
|
||||
CREATE_ARROW = 1011
|
||||
DELETE_ARROW = 1012
|
||||
SET_CARD_ATTR = 1013
|
||||
SET_CARD_COUNTER = 1014
|
||||
INC_CARD_COUNTER = 1015
|
||||
READY_START = 1016
|
||||
CONCEDE = 1017
|
||||
INC_COUNTER = 1018
|
||||
CREATE_COUNTER = 1019
|
||||
SET_COUNTER = 1020
|
||||
DEL_COUNTER = 1021
|
||||
NEXT_TURN = 1022
|
||||
SET_ACTIVE_PHASE = 1023
|
||||
DUMP_ZONE = 1024
|
||||
REVEAL_CARDS = 1026
|
||||
MOVE_CARD = 1027
|
||||
SET_SIDEBOARD_PLAN = 1028
|
||||
DECK_SELECT = 1029
|
||||
SET_SIDEBOARD_LOCK = 1030
|
||||
CHANGE_ZONE_PROPERTIES = 1031
|
||||
UNCONCEDE = 1032
|
||||
JUDGE = 1033
|
||||
REVERSE_TURN = 1034
|
||||
|
||||
|
||||
class GameEventType(enum.IntEnum):
|
||||
"""Game event types from game_event.proto"""
|
||||
JOIN = 1000
|
||||
LEAVE = 1001
|
||||
GAME_CLOSED = 1002
|
||||
GAME_HOST_CHANGED = 1003
|
||||
KICKED = 1004
|
||||
GAME_STATE_CHANGED = 1005
|
||||
PLAYER_PROPERTIES_CHANGED = 1007
|
||||
GAME_SAY = 1009
|
||||
CREATE_ARROW = 2000
|
||||
DELETE_ARROW = 2001
|
||||
CREATE_COUNTER = 2002
|
||||
SET_COUNTER = 2003
|
||||
DEL_COUNTER = 2004
|
||||
DRAW_CARDS = 2005
|
||||
REVEAL_CARDS = 2006
|
||||
SHUFFLE = 2007
|
||||
ROLL_DIE = 2008
|
||||
MOVE_CARD = 2009
|
||||
FLIP_CARD = 2010
|
||||
DESTROY_CARD = 2011
|
||||
ATTACH_CARD = 2012
|
||||
CREATE_TOKEN = 2013
|
||||
SET_CARD_ATTR = 2014
|
||||
SET_CARD_COUNTER = 2015
|
||||
SET_ACTIVE_PLAYER = 2016
|
||||
SET_ACTIVE_PHASE = 2017
|
||||
DUMP_ZONE = 2018
|
||||
CHANGE_ZONE_PROPERTIES = 2020
|
||||
REVERSE_TURN = 2021
|
||||
GAME_LOG_NOTICE = 2022
|
||||
|
||||
|
||||
class ResponseCode(enum.IntEnum):
|
||||
"""Response codes from response.proto"""
|
||||
RespNotConnected = -1
|
||||
RespNothing = 0
|
||||
RespOk = 1
|
||||
RespNotInRoom = 2
|
||||
RespInternalError = 3
|
||||
RespInvalidCommand = 4
|
||||
RespInvalidData = 5
|
||||
RespNameNotFound = 6
|
||||
RespLoginNeeded = 7
|
||||
RespFunctionNotAllowed = 8
|
||||
RespGameNotStarted = 9
|
||||
RespGameFull = 10
|
||||
RespContextError = 11
|
||||
RespWrongPassword = 12
|
||||
RespSpectatorsNotAllowed = 13
|
||||
RespOnlyBuddies = 14
|
||||
RespUserLevelTooLow = 15
|
||||
RespInIgnoreList = 16
|
||||
RespWouldOverwriteOldSession = 17
|
||||
RespChatFlood = 18
|
||||
RespUserIsBanned = 19
|
||||
RespAccessDenied = 20
|
||||
RespUsernameInvalid = 21
|
||||
RespRegistrationRequired = 22
|
||||
RespRegistrationAccepted = 23
|
||||
RespUserAlreadyExists = 24
|
||||
RespEmailRequiredToRegister = 25
|
||||
RespTooManyRequests = 26
|
||||
RespPasswordTooShort = 27
|
||||
RespAccountNotActivated = 28
|
||||
RespRegistrationDisabled = 29
|
||||
RespRegistrationFailed = 30
|
||||
RespActivationAccepted = 31
|
||||
RespActivationFailed = 32
|
||||
RespRegistrationAcceptedNeedsActivation = 33
|
||||
RespClientIdRequired = 34
|
||||
RespClientUpdateRequired = 35
|
||||
RespServerFull = 36
|
||||
RespEmailBlackListed = 37
|
||||
|
||||
|
||||
class ZoneType(enum.IntEnum):
|
||||
"""Zone types from serverinfo_zone.proto"""
|
||||
PrivateZone = 0
|
||||
PublicZone = 1
|
||||
HiddenZone = 2
|
||||
|
||||
|
||||
class UserLevelFlag(enum.IntFlag):
|
||||
"""User level flags from serverinfo_user.proto"""
|
||||
IsNothing = 0
|
||||
IsUser = 1
|
||||
IsRegistered = 2
|
||||
IsModerator = 4
|
||||
IsAdmin = 8
|
||||
IsJudge = 16
|
||||
@@ -0,0 +1,232 @@
|
||||
"""
|
||||
Pydantic schemas for request/response validation.
|
||||
|
||||
Provides typed data structures for API endpoints.
|
||||
"""
|
||||
from pydantic import BaseModel, EmailStr, Field
|
||||
from typing import Optional, List
|
||||
from datetime import datetime
|
||||
|
||||
|
||||
# ===== Authentication Schemas =====
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
"""Login request payload."""
|
||||
username: str = Field(..., min_length=3, max_length=64)
|
||||
password: str = Field(..., min_length=6, max_length=128)
|
||||
|
||||
|
||||
class LoginResponse(BaseModel):
|
||||
"""Login response payload."""
|
||||
access_token: str
|
||||
refresh_token: str
|
||||
token_type: str = "bearer"
|
||||
user: dict
|
||||
|
||||
|
||||
class RefreshTokenRequest(BaseModel):
|
||||
"""Refresh token request."""
|
||||
refresh_token: str
|
||||
|
||||
|
||||
class TokenResponse(BaseModel):
|
||||
"""Token response."""
|
||||
access_token: str
|
||||
token_type: str = "bearer"
|
||||
|
||||
|
||||
# ===== User Schemas =====
|
||||
|
||||
class UserBase(BaseModel):
|
||||
"""User base fields."""
|
||||
username: str
|
||||
email: Optional[str] = None
|
||||
country: Optional[str] = Field(None, max_length=2)
|
||||
real_name: Optional[str] = None
|
||||
|
||||
|
||||
class UserCreate(UserBase):
|
||||
"""User registration fields."""
|
||||
password: str = Field(..., min_length=8)
|
||||
|
||||
class Config:
|
||||
json_schema_extra = {
|
||||
"example": {
|
||||
"username": "player123",
|
||||
"password": "securepassword123",
|
||||
"email": "player@example.com",
|
||||
"country": "US"
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
class UserUpdate(BaseModel):
|
||||
"""User update fields."""
|
||||
email: Optional[str] = None
|
||||
country: Optional[str] = None
|
||||
real_name: Optional[str] = None
|
||||
new_password: Optional[str] = Field(None, min_length=8, max_length=128)
|
||||
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
"""User response payload."""
|
||||
id: int
|
||||
username: str
|
||||
email: Optional[str]
|
||||
country: Optional[str]
|
||||
real_name: Optional[str]
|
||||
privlevel: str
|
||||
vip_status: int
|
||||
is_active: bool
|
||||
is_banned: bool
|
||||
ban_reason: Optional[str]
|
||||
creation_date: datetime
|
||||
last_login: Optional[datetime]
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ===== Deck Schemas =====
|
||||
|
||||
class DeckCreate(BaseModel):
|
||||
"""Deck creation request."""
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
content: str = Field(..., min_length=1)
|
||||
folder_id: Optional[int] = None
|
||||
format: str = Field("native", pattern="^(native|plain)$")
|
||||
|
||||
|
||||
class DeckUpdate(BaseModel):
|
||||
"""Deck update request."""
|
||||
name: Optional[str] = None
|
||||
content: Optional[str] = None
|
||||
folder_id: Optional[int] = None
|
||||
|
||||
|
||||
class DeckResponse(BaseModel):
|
||||
"""Deck response payload."""
|
||||
id: int
|
||||
name: str
|
||||
content: str
|
||||
format: str
|
||||
folder_id: Optional[int]
|
||||
owner_id: int
|
||||
creation_date: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
class FolderCreate(BaseModel):
|
||||
"""Folder creation request."""
|
||||
name: str = Field(..., min_length=1, max_length=255)
|
||||
parent_id: Optional[int] = None
|
||||
|
||||
|
||||
class FolderResponse(BaseModel):
|
||||
"""Folder response payload."""
|
||||
id: int
|
||||
name: str
|
||||
parent_id: Optional[int]
|
||||
owner_id: int
|
||||
creation_date: datetime
|
||||
children: List["FolderResponse"] = []
|
||||
files: List[DeckResponse] = []
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ===== Game Schemas =====
|
||||
|
||||
class GameCreate(BaseModel):
|
||||
"""Game creation request."""
|
||||
room_id: int
|
||||
game_type: Optional[str] = None
|
||||
description: Optional[str] = None
|
||||
password: Optional[str] = None
|
||||
|
||||
|
||||
class GameResponse(BaseModel):
|
||||
"""Game response payload."""
|
||||
id: int
|
||||
room_id: int
|
||||
game_type: Optional[str]
|
||||
description: Optional[str]
|
||||
with_password: bool
|
||||
max_players: int
|
||||
player_count: int
|
||||
started: bool
|
||||
creation_date: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ===== Room Schemas =====
|
||||
|
||||
class RoomResponse(BaseModel):
|
||||
"""Room response payload."""
|
||||
id: int
|
||||
name: str
|
||||
description: Optional[str]
|
||||
is_password_protected: bool
|
||||
game_types: List[str] = []
|
||||
player_count: int = 0
|
||||
creation_date: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ===== Ban Schemas =====
|
||||
|
||||
class BanCreate(BaseModel):
|
||||
"""Ban creation request."""
|
||||
user_id: int
|
||||
reason: str = Field(..., min_length=1, max_length=1000)
|
||||
expiration_time: Optional[datetime] = None
|
||||
|
||||
|
||||
class BanResponse(BaseModel):
|
||||
"""Ban response payload."""
|
||||
id: int
|
||||
user_id: int
|
||||
reason: str
|
||||
moderators: Optional[str]
|
||||
expiration_time: Optional[datetime]
|
||||
active: bool
|
||||
creation_date: datetime
|
||||
|
||||
class Config:
|
||||
from_attributes = True
|
||||
|
||||
|
||||
# ===== Auth Error Responses =====
|
||||
|
||||
class ErrorResponse(BaseModel):
|
||||
"""Standard error response."""
|
||||
detail: str
|
||||
|
||||
|
||||
class ValidationErrorResponse(BaseModel):
|
||||
"""Validation error response."""
|
||||
detail: List[dict] # Pydantic validation errors
|
||||
|
||||
|
||||
# ===== Pagination Schemas =====
|
||||
|
||||
class PaginationParams(BaseModel):
|
||||
"""Common pagination parameters."""
|
||||
page: int = Field(1, ge=1)
|
||||
page_size: int = Field(50, ge=1, le=100)
|
||||
|
||||
|
||||
class PaginatedResponse(BaseModel):
|
||||
"""Generic paginated response."""
|
||||
items: List[dict]
|
||||
total: int
|
||||
page: int
|
||||
page_size: int
|
||||
total_pages: int
|
||||
Reference in New Issue
Block a user