bacommon.cloud
Functionality related to cloud functionality.
1# Released under the MIT License. See LICENSE for details. 2# 3"""Functionality related to cloud functionality.""" 4 5from __future__ import annotations 6 7import datetime 8from enum import Enum 9from dataclasses import dataclass, field 10from typing import TYPE_CHECKING, Annotated, override 11 12from efro.message import Message, Response 13from efro.dataclassio import ioprepped, IOAttrs 14from bacommon.transfer import DirectoryManifest 15from bacommon.login import LoginType 16 17if TYPE_CHECKING: 18 pass 19 20 21class WebLocation(Enum): 22 """Set of places we can be directed on ballistica.net.""" 23 24 ACCOUNT_EDITOR = 'e' 25 ACCOUNT_DELETE_SECTION = 'd' 26 27 28@ioprepped 29@dataclass 30class LoginProxyRequestMessage(Message): 31 """Request send to the cloud to ask for a login-proxy.""" 32 33 @override 34 @classmethod 35 def get_response_types(cls) -> list[type[Response] | None]: 36 return [LoginProxyRequestResponse] 37 38 39@ioprepped 40@dataclass 41class LoginProxyRequestResponse(Response): 42 """Response to a request for a login proxy.""" 43 44 # URL to direct the user to for sign in. 45 url: Annotated[str, IOAttrs('u')] 46 47 # URL to use for overlay-web-browser sign ins. 48 url_overlay: Annotated[str, IOAttrs('uo')] 49 50 # Proxy-Login id for querying results. 51 proxyid: Annotated[str, IOAttrs('p')] 52 53 # Proxy-Login key for querying results. 54 proxykey: Annotated[str, IOAttrs('k')] 55 56 57@ioprepped 58@dataclass 59class LoginProxyStateQueryMessage(Message): 60 """Soo.. how is that login proxy going?""" 61 62 proxyid: Annotated[str, IOAttrs('p')] 63 proxykey: Annotated[str, IOAttrs('k')] 64 65 @override 66 @classmethod 67 def get_response_types(cls) -> list[type[Response] | None]: 68 return [LoginProxyStateQueryResponse] 69 70 71@ioprepped 72@dataclass 73class LoginProxyStateQueryResponse(Response): 74 """Here's the info on that login-proxy you asked about, boss.""" 75 76 class State(Enum): 77 """States a login-proxy can be in.""" 78 79 WAITING = 'waiting' 80 SUCCESS = 'success' 81 FAIL = 'fail' 82 83 state: Annotated[State, IOAttrs('s')] 84 85 # On success, these will be filled out. 86 credentials: Annotated[str | None, IOAttrs('tk')] 87 88 89@ioprepped 90@dataclass 91class LoginProxyCompleteMessage(Message): 92 """Just so you know, we're done with this proxy.""" 93 94 proxyid: Annotated[str, IOAttrs('p')] 95 96 97@ioprepped 98@dataclass 99class PingMessage(Message): 100 """Standard ping.""" 101 102 @override 103 @classmethod 104 def get_response_types(cls) -> list[type[Response] | None]: 105 return [PingResponse] 106 107 108@ioprepped 109@dataclass 110class PingResponse(Response): 111 """pong.""" 112 113 114@ioprepped 115@dataclass 116class TestMessage(Message): 117 """Can I get some of that workspace action?""" 118 119 testfoo: Annotated[int, IOAttrs('f')] 120 121 @override 122 @classmethod 123 def get_response_types(cls) -> list[type[Response] | None]: 124 return [TestResponse] 125 126 127@ioprepped 128@dataclass 129class TestResponse(Response): 130 """Here's that workspace you asked for, boss.""" 131 132 testfoo: Annotated[int, IOAttrs('f')] 133 134 135@ioprepped 136@dataclass 137class SendInfoMessage(Message): 138 """User is using the send-info function""" 139 140 description: Annotated[str, IOAttrs('c')] 141 142 @override 143 @classmethod 144 def get_response_types(cls) -> list[type[Response] | None]: 145 return [SendInfoResponse] 146 147 148@ioprepped 149@dataclass 150class SendInfoResponse(Response): 151 """Response to sending into the server.""" 152 153 handled: Annotated[bool, IOAttrs('v')] 154 message: Annotated[str | None, IOAttrs('m', store_default=False)] = None 155 legacy_code: Annotated[str | None, IOAttrs('l', store_default=False)] = None 156 157 158@ioprepped 159@dataclass 160class WorkspaceFetchState: 161 """Common state data for a workspace fetch.""" 162 163 manifest: Annotated[DirectoryManifest, IOAttrs('m')] 164 iteration: Annotated[int, IOAttrs('i')] = 0 165 total_deletes: Annotated[int, IOAttrs('tdels')] = 0 166 total_downloads: Annotated[int, IOAttrs('tdlds')] = 0 167 total_up_to_date: Annotated[int | None, IOAttrs('tunmd')] = None 168 169 170@ioprepped 171@dataclass 172class WorkspaceFetchMessage(Message): 173 """Can I get some of that workspace action?""" 174 175 workspaceid: Annotated[str, IOAttrs('w')] 176 state: Annotated[WorkspaceFetchState, IOAttrs('s')] 177 178 @override 179 @classmethod 180 def get_response_types(cls) -> list[type[Response] | None]: 181 return [WorkspaceFetchResponse] 182 183 184@ioprepped 185@dataclass 186class WorkspaceFetchResponse(Response): 187 """Here's that workspace you asked for, boss.""" 188 189 state: Annotated[WorkspaceFetchState, IOAttrs('s')] 190 deletes: Annotated[list[str], IOAttrs('dlt', store_default=False)] = field( 191 default_factory=list 192 ) 193 downloads_inline: Annotated[ 194 dict[str, bytes], IOAttrs('dinl', store_default=False) 195 ] = field(default_factory=dict) 196 197 done: Annotated[bool, IOAttrs('d')] = False 198 199 200@ioprepped 201@dataclass 202class MerchAvailabilityMessage(Message): 203 """Can we show merch link?""" 204 205 @override 206 @classmethod 207 def get_response_types(cls) -> list[type[Response] | None]: 208 return [MerchAvailabilityResponse] 209 210 211@ioprepped 212@dataclass 213class MerchAvailabilityResponse(Response): 214 """About that merch...""" 215 216 url: Annotated[str | None, IOAttrs('u')] 217 218 219@ioprepped 220@dataclass 221class SignInMessage(Message): 222 """Can I sign in please?""" 223 224 login_type: Annotated[LoginType, IOAttrs('l')] 225 sign_in_token: Annotated[str, IOAttrs('t')] 226 227 # For debugging. Can remove soft_default once build 20988+ is ubiquitous. 228 description: Annotated[str, IOAttrs('d', soft_default='-')] 229 apptime: Annotated[float, IOAttrs('at', soft_default=-1.0)] 230 231 @override 232 @classmethod 233 def get_response_types(cls) -> list[type[Response] | None]: 234 return [SignInResponse] 235 236 237@ioprepped 238@dataclass 239class SignInResponse(Response): 240 """Here's that sign-in result you asked for, boss.""" 241 242 credentials: Annotated[str | None, IOAttrs('c')] 243 244 245@ioprepped 246@dataclass 247class ManageAccountMessage(Message): 248 """Message asking for a manage-account url.""" 249 250 weblocation: Annotated[WebLocation, IOAttrs('l')] = ( 251 WebLocation.ACCOUNT_EDITOR 252 ) 253 254 @override 255 @classmethod 256 def get_response_types(cls) -> list[type[Response] | None]: 257 return [ManageAccountResponse] 258 259 260@ioprepped 261@dataclass 262class ManageAccountResponse(Response): 263 """Here's that sign-in result you asked for, boss.""" 264 265 url: Annotated[str | None, IOAttrs('u')] 266 267 268@ioprepped 269@dataclass 270class StoreQueryMessage(Message): 271 """Message asking about purchasable stuff and store related state.""" 272 273 @override 274 @classmethod 275 def get_response_types(cls) -> list[type[Response] | None]: 276 return [StoreQueryResponse] 277 278 279@ioprepped 280@dataclass 281class StoreQueryResponse(Response): 282 """Here's that store info you asked for, boss.""" 283 284 class Result(Enum): 285 """Our overall result.""" 286 287 SUCCESS = 's' 288 ERROR = 'e' 289 290 @dataclass 291 class Purchase: 292 """Info about a purchasable thing.""" 293 294 purchaseid: Annotated[str, IOAttrs('id')] 295 296 # Overall result; all data is undefined if not SUCCESS. 297 result: Annotated[Result, IOAttrs('r')] 298 299 tokens: Annotated[int, IOAttrs('t')] 300 gold_pass: Annotated[bool, IOAttrs('g')] 301 302 available_purchases: Annotated[list[Purchase], IOAttrs('p')] 303 token_info_url: Annotated[str, IOAttrs('tiu')] 304 305 306@ioprepped 307@dataclass 308class BSPrivatePartyMessage(Message): 309 """Message asking about info we need for private-party UI.""" 310 311 need_datacode: Annotated[bool, IOAttrs('d')] 312 313 @override 314 @classmethod 315 def get_response_types(cls) -> list[type[Response] | None]: 316 return [BSPrivatePartyResponse] 317 318 319@ioprepped 320@dataclass 321class BSPrivatePartyResponse(Response): 322 """Here's that private party UI info you asked for, boss.""" 323 324 success: Annotated[bool, IOAttrs('s')] 325 tokens: Annotated[int, IOAttrs('t')] 326 gold_pass: Annotated[bool, IOAttrs('g')] 327 datacode: Annotated[str | None, IOAttrs('d')] 328 329 330class BSClassicChestAppearance(Enum): 331 """Appearances bombsquad classic chests can have.""" 332 333 UNKNOWN = 'u' 334 DEFAULT = 'd' 335 336 337@ioprepped 338@dataclass 339class BSClassicAccountLiveData: 340 """Account related data kept up to date live for classic app mode.""" 341 342 @dataclass 343 class Chest: 344 """A lovely chest.""" 345 346 appearance: Annotated[ 347 BSClassicChestAppearance, 348 IOAttrs('a', enum_fallback=BSClassicChestAppearance.UNKNOWN), 349 ] 350 unlock_time: Annotated[datetime.datetime, IOAttrs('t')] 351 ad_allow_time: Annotated[datetime.datetime | None, IOAttrs('at')] 352 353 class LeagueType(Enum): 354 """Type of league we are in.""" 355 356 BRONZE = 'b' 357 SILVER = 's' 358 GOLD = 'g' 359 DIAMOND = 'd' 360 361 tickets: Annotated[int, IOAttrs('ti')] 362 363 tokens: Annotated[int, IOAttrs('to')] 364 gold_pass: Annotated[bool, IOAttrs('g')] 365 366 achievements: Annotated[int, IOAttrs('a')] 367 achievements_total: Annotated[int, IOAttrs('at')] 368 369 league_type: Annotated[LeagueType | None, IOAttrs('lt')] 370 league_num: Annotated[int | None, IOAttrs('ln')] 371 league_rank: Annotated[int | None, IOAttrs('lr')] 372 373 level: Annotated[int, IOAttrs('lv')] 374 xp: Annotated[int, IOAttrs('xp')] 375 xpmax: Annotated[int, IOAttrs('xpm')] 376 377 inbox_count: Annotated[int, IOAttrs('ibc')] 378 inbox_count_is_max: Annotated[bool, IOAttrs('ibcm')] 379 380 chests: Annotated[dict[str, Chest], IOAttrs('c')] 381 382 383class BSInboxEntryType(Enum): 384 """Types of entries that can be in an inbox.""" 385 386 UNKNOWN = 'u' # Entry types we don't support will be this. 387 SIMPLE = 's' 388 CLAIM = 'c' 389 CLAIM_DISCARD = 'cd' 390 391 392@ioprepped 393@dataclass 394class BSInboxEntry: 395 """Single message in an inbox.""" 396 397 type: Annotated[ 398 BSInboxEntryType, IOAttrs('t', enum_fallback=BSInboxEntryType.UNKNOWN) 399 ] 400 id: Annotated[str, IOAttrs('i')] 401 createtime: Annotated[datetime.datetime, IOAttrs('c')] 402 403 # If clients don't support format_version of a message they will 404 # display 'app needs to be updated to show this'. 405 format_version: Annotated[int, IOAttrs('f', soft_default=1)] 406 407 # These have soft defaults so can be removed in the future if desired. 408 message: Annotated[str, IOAttrs('m', soft_default='(invalid message)')] 409 subs: Annotated[list[str], IOAttrs('s', soft_default_factory=list)] 410 411 412@ioprepped 413@dataclass 414class BSInboxRequestMessage(Message): 415 """Message requesting our inbox.""" 416 417 @override 418 @classmethod 419 def get_response_types(cls) -> list[type[Response] | None]: 420 return [BSInboxRequestResponse] 421 422 423@ioprepped 424@dataclass 425class BSInboxRequestResponse(Response): 426 """Here's that inbox contents you asked for, boss.""" 427 428 entries: Annotated[list[BSInboxEntry], IOAttrs('m')] 429 430 # Printable error if something goes wrong. 431 error: Annotated[str | None, IOAttrs('e')] = None 432 433 434@ioprepped 435@dataclass 436class BSChestInfoMessage(Message): 437 """Request info about a chest.""" 438 439 chest_id: Annotated[str, IOAttrs('i')] 440 441 @override 442 @classmethod 443 def get_response_types(cls) -> list[type[Response] | None]: 444 return [BSChestInfoResponse] 445 446 447@ioprepped 448@dataclass 449class BSChestInfoResponse(Response): 450 """Here's that inbox contents you asked for, boss.""" 451 452 @dataclass 453 class Chest: 454 """A lovely chest.""" 455 456 appearance: Annotated[ 457 BSClassicChestAppearance, 458 IOAttrs('a', enum_fallback=BSClassicChestAppearance.UNKNOWN), 459 ] 460 461 # How much to unlock *now*. 462 unlock_tokens: Annotated[int, IOAttrs('tk')] 463 464 # When unlocks on its own. 465 unlock_time: Annotated[datetime.datetime, IOAttrs('t')] 466 467 # Are ads allowed now? 468 ad_allow: Annotated[bool, IOAttrs('aa')] 469 470 chest: Annotated[Chest | None, IOAttrs('c')] 471 472 473@ioprepped 474@dataclass 475class BSChestActionMessage(Message): 476 """Request action about a chest.""" 477 478 class Action(Enum): 479 """Types of actions we can request.""" 480 481 # Unlocking (for free or with tokens). 482 UNLOCK = 'u' 483 484 # Watched an ad to reduce wait. 485 AD = 'ad' 486 487 action: Annotated[Action, IOAttrs('a')] 488 489 # Tokens we are paying (only applies to unlock). 490 token_payment: Annotated[int, IOAttrs('t')] 491 492 chest_id: Annotated[str, IOAttrs('i')] 493 494 @override 495 @classmethod 496 def get_response_types(cls) -> list[type[Response] | None]: 497 return [BSChestActionResponse] 498 499 500@ioprepped 501@dataclass 502class BSChestActionResponse(Response): 503 """Here's the results of that action you asked for, boss.""" 504 505 # If present, signifies the chest has been opened and we should show 506 # the user this stuff that was in it. 507 contents: Annotated[list[str] | None, IOAttrs('c')] = None 508 509 # Printable error if something goes wrong. 510 error: Annotated[str | None, IOAttrs('e')] = None 511 512 513class BSInboxEntryProcessType(Enum): 514 """Types of processing we can ask for.""" 515 516 POSITIVE = 'p' 517 NEGATIVE = 'n' 518 519 520@ioprepped 521@dataclass 522class BSInboxEntryProcessMessage(Message): 523 """Do something to an inbox entry.""" 524 525 id: Annotated[str, IOAttrs('i')] 526 process_type: Annotated[BSInboxEntryProcessType, IOAttrs('t')] 527 528 @override 529 @classmethod 530 def get_response_types(cls) -> list[type[Response] | None]: 531 return [BSInboxEntryProcessResponse] 532 533 534@ioprepped 535@dataclass 536class BSInboxEntryProcessResponse(Response): 537 """Did something to that inbox entry, boss.""" 538 539 # Printable error if something goes wrong. 540 error: Annotated[str | None, IOAttrs('e')] = None
22class WebLocation(Enum): 23 """Set of places we can be directed on ballistica.net.""" 24 25 ACCOUNT_EDITOR = 'e' 26 ACCOUNT_DELETE_SECTION = 'd'
Set of places we can be directed on ballistica.net.
29@ioprepped 30@dataclass 31class LoginProxyRequestMessage(Message): 32 """Request send to the cloud to ask for a login-proxy.""" 33 34 @override 35 @classmethod 36 def get_response_types(cls) -> list[type[Response] | None]: 37 return [LoginProxyRequestResponse]
Request send to the cloud to ask for a login-proxy.
40@ioprepped 41@dataclass 42class LoginProxyRequestResponse(Response): 43 """Response to a request for a login proxy.""" 44 45 # URL to direct the user to for sign in. 46 url: Annotated[str, IOAttrs('u')] 47 48 # URL to use for overlay-web-browser sign ins. 49 url_overlay: Annotated[str, IOAttrs('uo')] 50 51 # Proxy-Login id for querying results. 52 proxyid: Annotated[str, IOAttrs('p')] 53 54 # Proxy-Login key for querying results. 55 proxykey: Annotated[str, IOAttrs('k')]
Response to a request for a login proxy.
58@ioprepped 59@dataclass 60class LoginProxyStateQueryMessage(Message): 61 """Soo.. how is that login proxy going?""" 62 63 proxyid: Annotated[str, IOAttrs('p')] 64 proxykey: Annotated[str, IOAttrs('k')] 65 66 @override 67 @classmethod 68 def get_response_types(cls) -> list[type[Response] | None]: 69 return [LoginProxyStateQueryResponse]
Soo.. how is that login proxy going?
72@ioprepped 73@dataclass 74class LoginProxyStateQueryResponse(Response): 75 """Here's the info on that login-proxy you asked about, boss.""" 76 77 class State(Enum): 78 """States a login-proxy can be in.""" 79 80 WAITING = 'waiting' 81 SUCCESS = 'success' 82 FAIL = 'fail' 83 84 state: Annotated[State, IOAttrs('s')] 85 86 # On success, these will be filled out. 87 credentials: Annotated[str | None, IOAttrs('tk')]
Here's the info on that login-proxy you asked about, boss.
77 class State(Enum): 78 """States a login-proxy can be in.""" 79 80 WAITING = 'waiting' 81 SUCCESS = 'success' 82 FAIL = 'fail'
States a login-proxy can be in.
90@ioprepped 91@dataclass 92class LoginProxyCompleteMessage(Message): 93 """Just so you know, we're done with this proxy.""" 94 95 proxyid: Annotated[str, IOAttrs('p')]
Just so you know, we're done with this proxy.
98@ioprepped 99@dataclass 100class PingMessage(Message): 101 """Standard ping.""" 102 103 @override 104 @classmethod 105 def get_response_types(cls) -> list[type[Response] | None]: 106 return [PingResponse]
Standard ping.
pong.
115@ioprepped 116@dataclass 117class TestMessage(Message): 118 """Can I get some of that workspace action?""" 119 120 testfoo: Annotated[int, IOAttrs('f')] 121 122 @override 123 @classmethod 124 def get_response_types(cls) -> list[type[Response] | None]: 125 return [TestResponse]
Can I get some of that workspace action?
128@ioprepped 129@dataclass 130class TestResponse(Response): 131 """Here's that workspace you asked for, boss.""" 132 133 testfoo: Annotated[int, IOAttrs('f')]
Here's that workspace you asked for, boss.
136@ioprepped 137@dataclass 138class SendInfoMessage(Message): 139 """User is using the send-info function""" 140 141 description: Annotated[str, IOAttrs('c')] 142 143 @override 144 @classmethod 145 def get_response_types(cls) -> list[type[Response] | None]: 146 return [SendInfoResponse]
User is using the send-info function
149@ioprepped 150@dataclass 151class SendInfoResponse(Response): 152 """Response to sending into the server.""" 153 154 handled: Annotated[bool, IOAttrs('v')] 155 message: Annotated[str | None, IOAttrs('m', store_default=False)] = None 156 legacy_code: Annotated[str | None, IOAttrs('l', store_default=False)] = None
Response to sending into the server.
159@ioprepped 160@dataclass 161class WorkspaceFetchState: 162 """Common state data for a workspace fetch.""" 163 164 manifest: Annotated[DirectoryManifest, IOAttrs('m')] 165 iteration: Annotated[int, IOAttrs('i')] = 0 166 total_deletes: Annotated[int, IOAttrs('tdels')] = 0 167 total_downloads: Annotated[int, IOAttrs('tdlds')] = 0 168 total_up_to_date: Annotated[int | None, IOAttrs('tunmd')] = None
Common state data for a workspace fetch.
171@ioprepped 172@dataclass 173class WorkspaceFetchMessage(Message): 174 """Can I get some of that workspace action?""" 175 176 workspaceid: Annotated[str, IOAttrs('w')] 177 state: Annotated[WorkspaceFetchState, IOAttrs('s')] 178 179 @override 180 @classmethod 181 def get_response_types(cls) -> list[type[Response] | None]: 182 return [WorkspaceFetchResponse]
Can I get some of that workspace action?
185@ioprepped 186@dataclass 187class WorkspaceFetchResponse(Response): 188 """Here's that workspace you asked for, boss.""" 189 190 state: Annotated[WorkspaceFetchState, IOAttrs('s')] 191 deletes: Annotated[list[str], IOAttrs('dlt', store_default=False)] = field( 192 default_factory=list 193 ) 194 downloads_inline: Annotated[ 195 dict[str, bytes], IOAttrs('dinl', store_default=False) 196 ] = field(default_factory=dict) 197 198 done: Annotated[bool, IOAttrs('d')] = False
Here's that workspace you asked for, boss.
201@ioprepped 202@dataclass 203class MerchAvailabilityMessage(Message): 204 """Can we show merch link?""" 205 206 @override 207 @classmethod 208 def get_response_types(cls) -> list[type[Response] | None]: 209 return [MerchAvailabilityResponse]
Can we show merch link?
212@ioprepped 213@dataclass 214class MerchAvailabilityResponse(Response): 215 """About that merch...""" 216 217 url: Annotated[str | None, IOAttrs('u')]
About that merch...
220@ioprepped 221@dataclass 222class SignInMessage(Message): 223 """Can I sign in please?""" 224 225 login_type: Annotated[LoginType, IOAttrs('l')] 226 sign_in_token: Annotated[str, IOAttrs('t')] 227 228 # For debugging. Can remove soft_default once build 20988+ is ubiquitous. 229 description: Annotated[str, IOAttrs('d', soft_default='-')] 230 apptime: Annotated[float, IOAttrs('at', soft_default=-1.0)] 231 232 @override 233 @classmethod 234 def get_response_types(cls) -> list[type[Response] | None]: 235 return [SignInResponse]
Can I sign in please?
238@ioprepped 239@dataclass 240class SignInResponse(Response): 241 """Here's that sign-in result you asked for, boss.""" 242 243 credentials: Annotated[str | None, IOAttrs('c')]
Here's that sign-in result you asked for, boss.
246@ioprepped 247@dataclass 248class ManageAccountMessage(Message): 249 """Message asking for a manage-account url.""" 250 251 weblocation: Annotated[WebLocation, IOAttrs('l')] = ( 252 WebLocation.ACCOUNT_EDITOR 253 ) 254 255 @override 256 @classmethod 257 def get_response_types(cls) -> list[type[Response] | None]: 258 return [ManageAccountResponse]
Message asking for a manage-account url.
261@ioprepped 262@dataclass 263class ManageAccountResponse(Response): 264 """Here's that sign-in result you asked for, boss.""" 265 266 url: Annotated[str | None, IOAttrs('u')]
Here's that sign-in result you asked for, boss.
269@ioprepped 270@dataclass 271class StoreQueryMessage(Message): 272 """Message asking about purchasable stuff and store related state.""" 273 274 @override 275 @classmethod 276 def get_response_types(cls) -> list[type[Response] | None]: 277 return [StoreQueryResponse]
Message asking about purchasable stuff and store related state.
280@ioprepped 281@dataclass 282class StoreQueryResponse(Response): 283 """Here's that store info you asked for, boss.""" 284 285 class Result(Enum): 286 """Our overall result.""" 287 288 SUCCESS = 's' 289 ERROR = 'e' 290 291 @dataclass 292 class Purchase: 293 """Info about a purchasable thing.""" 294 295 purchaseid: Annotated[str, IOAttrs('id')] 296 297 # Overall result; all data is undefined if not SUCCESS. 298 result: Annotated[Result, IOAttrs('r')] 299 300 tokens: Annotated[int, IOAttrs('t')] 301 gold_pass: Annotated[bool, IOAttrs('g')] 302 303 available_purchases: Annotated[list[Purchase], IOAttrs('p')] 304 token_info_url: Annotated[str, IOAttrs('tiu')]
Here's that store info you asked for, boss.
Our overall result.
291 @dataclass 292 class Purchase: 293 """Info about a purchasable thing.""" 294 295 purchaseid: Annotated[str, IOAttrs('id')]
Info about a purchasable thing.
307@ioprepped 308@dataclass 309class BSPrivatePartyMessage(Message): 310 """Message asking about info we need for private-party UI.""" 311 312 need_datacode: Annotated[bool, IOAttrs('d')] 313 314 @override 315 @classmethod 316 def get_response_types(cls) -> list[type[Response] | None]: 317 return [BSPrivatePartyResponse]
Message asking about info we need for private-party UI.
320@ioprepped 321@dataclass 322class BSPrivatePartyResponse(Response): 323 """Here's that private party UI info you asked for, boss.""" 324 325 success: Annotated[bool, IOAttrs('s')] 326 tokens: Annotated[int, IOAttrs('t')] 327 gold_pass: Annotated[bool, IOAttrs('g')] 328 datacode: Annotated[str | None, IOAttrs('d')]
Here's that private party UI info you asked for, boss.
331class BSClassicChestAppearance(Enum): 332 """Appearances bombsquad classic chests can have.""" 333 334 UNKNOWN = 'u' 335 DEFAULT = 'd'
Appearances bombsquad classic chests can have.
338@ioprepped 339@dataclass 340class BSClassicAccountLiveData: 341 """Account related data kept up to date live for classic app mode.""" 342 343 @dataclass 344 class Chest: 345 """A lovely chest.""" 346 347 appearance: Annotated[ 348 BSClassicChestAppearance, 349 IOAttrs('a', enum_fallback=BSClassicChestAppearance.UNKNOWN), 350 ] 351 unlock_time: Annotated[datetime.datetime, IOAttrs('t')] 352 ad_allow_time: Annotated[datetime.datetime | None, IOAttrs('at')] 353 354 class LeagueType(Enum): 355 """Type of league we are in.""" 356 357 BRONZE = 'b' 358 SILVER = 's' 359 GOLD = 'g' 360 DIAMOND = 'd' 361 362 tickets: Annotated[int, IOAttrs('ti')] 363 364 tokens: Annotated[int, IOAttrs('to')] 365 gold_pass: Annotated[bool, IOAttrs('g')] 366 367 achievements: Annotated[int, IOAttrs('a')] 368 achievements_total: Annotated[int, IOAttrs('at')] 369 370 league_type: Annotated[LeagueType | None, IOAttrs('lt')] 371 league_num: Annotated[int | None, IOAttrs('ln')] 372 league_rank: Annotated[int | None, IOAttrs('lr')] 373 374 level: Annotated[int, IOAttrs('lv')] 375 xp: Annotated[int, IOAttrs('xp')] 376 xpmax: Annotated[int, IOAttrs('xpm')] 377 378 inbox_count: Annotated[int, IOAttrs('ibc')] 379 inbox_count_is_max: Annotated[bool, IOAttrs('ibcm')] 380 381 chests: Annotated[dict[str, Chest], IOAttrs('c')]
Account related data kept up to date live for classic app mode.
343 @dataclass 344 class Chest: 345 """A lovely chest.""" 346 347 appearance: Annotated[ 348 BSClassicChestAppearance, 349 IOAttrs('a', enum_fallback=BSClassicChestAppearance.UNKNOWN), 350 ] 351 unlock_time: Annotated[datetime.datetime, IOAttrs('t')] 352 ad_allow_time: Annotated[datetime.datetime | None, IOAttrs('at')]
A lovely chest.
354 class LeagueType(Enum): 355 """Type of league we are in.""" 356 357 BRONZE = 'b' 358 SILVER = 's' 359 GOLD = 'g' 360 DIAMOND = 'd'
Type of league we are in.
384class BSInboxEntryType(Enum): 385 """Types of entries that can be in an inbox.""" 386 387 UNKNOWN = 'u' # Entry types we don't support will be this. 388 SIMPLE = 's' 389 CLAIM = 'c' 390 CLAIM_DISCARD = 'cd'
Types of entries that can be in an inbox.
393@ioprepped 394@dataclass 395class BSInboxEntry: 396 """Single message in an inbox.""" 397 398 type: Annotated[ 399 BSInboxEntryType, IOAttrs('t', enum_fallback=BSInboxEntryType.UNKNOWN) 400 ] 401 id: Annotated[str, IOAttrs('i')] 402 createtime: Annotated[datetime.datetime, IOAttrs('c')] 403 404 # If clients don't support format_version of a message they will 405 # display 'app needs to be updated to show this'. 406 format_version: Annotated[int, IOAttrs('f', soft_default=1)] 407 408 # These have soft defaults so can be removed in the future if desired. 409 message: Annotated[str, IOAttrs('m', soft_default='(invalid message)')] 410 subs: Annotated[list[str], IOAttrs('s', soft_default_factory=list)]
Single message in an inbox.
413@ioprepped 414@dataclass 415class BSInboxRequestMessage(Message): 416 """Message requesting our inbox.""" 417 418 @override 419 @classmethod 420 def get_response_types(cls) -> list[type[Response] | None]: 421 return [BSInboxRequestResponse]
Message requesting our inbox.
424@ioprepped 425@dataclass 426class BSInboxRequestResponse(Response): 427 """Here's that inbox contents you asked for, boss.""" 428 429 entries: Annotated[list[BSInboxEntry], IOAttrs('m')] 430 431 # Printable error if something goes wrong. 432 error: Annotated[str | None, IOAttrs('e')] = None
Here's that inbox contents you asked for, boss.
435@ioprepped 436@dataclass 437class BSChestInfoMessage(Message): 438 """Request info about a chest.""" 439 440 chest_id: Annotated[str, IOAttrs('i')] 441 442 @override 443 @classmethod 444 def get_response_types(cls) -> list[type[Response] | None]: 445 return [BSChestInfoResponse]
Request info about a chest.
448@ioprepped 449@dataclass 450class BSChestInfoResponse(Response): 451 """Here's that inbox contents you asked for, boss.""" 452 453 @dataclass 454 class Chest: 455 """A lovely chest.""" 456 457 appearance: Annotated[ 458 BSClassicChestAppearance, 459 IOAttrs('a', enum_fallback=BSClassicChestAppearance.UNKNOWN), 460 ] 461 462 # How much to unlock *now*. 463 unlock_tokens: Annotated[int, IOAttrs('tk')] 464 465 # When unlocks on its own. 466 unlock_time: Annotated[datetime.datetime, IOAttrs('t')] 467 468 # Are ads allowed now? 469 ad_allow: Annotated[bool, IOAttrs('aa')] 470 471 chest: Annotated[Chest | None, IOAttrs('c')]
Here's that inbox contents you asked for, boss.
453 @dataclass 454 class Chest: 455 """A lovely chest.""" 456 457 appearance: Annotated[ 458 BSClassicChestAppearance, 459 IOAttrs('a', enum_fallback=BSClassicChestAppearance.UNKNOWN), 460 ] 461 462 # How much to unlock *now*. 463 unlock_tokens: Annotated[int, IOAttrs('tk')] 464 465 # When unlocks on its own. 466 unlock_time: Annotated[datetime.datetime, IOAttrs('t')] 467 468 # Are ads allowed now? 469 ad_allow: Annotated[bool, IOAttrs('aa')]
A lovely chest.
474@ioprepped 475@dataclass 476class BSChestActionMessage(Message): 477 """Request action about a chest.""" 478 479 class Action(Enum): 480 """Types of actions we can request.""" 481 482 # Unlocking (for free or with tokens). 483 UNLOCK = 'u' 484 485 # Watched an ad to reduce wait. 486 AD = 'ad' 487 488 action: Annotated[Action, IOAttrs('a')] 489 490 # Tokens we are paying (only applies to unlock). 491 token_payment: Annotated[int, IOAttrs('t')] 492 493 chest_id: Annotated[str, IOAttrs('i')] 494 495 @override 496 @classmethod 497 def get_response_types(cls) -> list[type[Response] | None]: 498 return [BSChestActionResponse]
Request action about a chest.
479 class Action(Enum): 480 """Types of actions we can request.""" 481 482 # Unlocking (for free or with tokens). 483 UNLOCK = 'u' 484 485 # Watched an ad to reduce wait. 486 AD = 'ad'
Types of actions we can request.
501@ioprepped 502@dataclass 503class BSChestActionResponse(Response): 504 """Here's the results of that action you asked for, boss.""" 505 506 # If present, signifies the chest has been opened and we should show 507 # the user this stuff that was in it. 508 contents: Annotated[list[str] | None, IOAttrs('c')] = None 509 510 # Printable error if something goes wrong. 511 error: Annotated[str | None, IOAttrs('e')] = None
Here's the results of that action you asked for, boss.
514class BSInboxEntryProcessType(Enum): 515 """Types of processing we can ask for.""" 516 517 POSITIVE = 'p' 518 NEGATIVE = 'n'
Types of processing we can ask for.
521@ioprepped 522@dataclass 523class BSInboxEntryProcessMessage(Message): 524 """Do something to an inbox entry.""" 525 526 id: Annotated[str, IOAttrs('i')] 527 process_type: Annotated[BSInboxEntryProcessType, IOAttrs('t')] 528 529 @override 530 @classmethod 531 def get_response_types(cls) -> list[type[Response] | None]: 532 return [BSInboxEntryProcessResponse]
Do something to an inbox entry.
535@ioprepped 536@dataclass 537class BSInboxEntryProcessResponse(Response): 538 """Did something to that inbox entry, boss.""" 539 540 # Printable error if something goes wrong. 541 error: Annotated[str | None, IOAttrs('e')] = None
Did something to that inbox entry, boss.