Initial commit of mtgonline project

This commit is contained in:
2026-07-18 04:57:40 +00:00
commit 86c12376f8
1870 changed files with 547994 additions and 0 deletions
+133
View File
@@ -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
+170
View File
@@ -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
+232
View File
@@ -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