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
class WebLocation(enum.Enum):
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.

ACCOUNT_EDITOR = <WebLocation.ACCOUNT_EDITOR: 'e'>
ACCOUNT_DELETE_SECTION = <WebLocation.ACCOUNT_DELETE_SECTION: 'd'>
@ioprepped
@dataclass
class LoginProxyRequestMessage(efro.message._message.Message):
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.

@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
34    @override
35    @classmethod
36    def get_response_types(cls) -> list[type[Response] | None]:
37        return [LoginProxyRequestResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

@ioprepped
@dataclass
class LoginProxyRequestResponse(efro.message._message.Response):
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.

LoginProxyRequestResponse( url: Annotated[str, <efro.dataclassio.IOAttrs object>], url_overlay: Annotated[str, <efro.dataclassio.IOAttrs object>], proxyid: Annotated[str, <efro.dataclassio.IOAttrs object>], proxykey: Annotated[str, <efro.dataclassio.IOAttrs object>])
url: Annotated[str, <efro.dataclassio.IOAttrs object at 0x1054fd490>]
url_overlay: Annotated[str, <efro.dataclassio.IOAttrs object at 0x1054fd910>]
proxyid: Annotated[str, <efro.dataclassio.IOAttrs object at 0x1054fda90>]
proxykey: Annotated[str, <efro.dataclassio.IOAttrs object at 0x1054fdc10>]
@ioprepped
@dataclass
class LoginProxyStateQueryMessage(efro.message._message.Message):
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?

LoginProxyStateQueryMessage( proxyid: Annotated[str, <efro.dataclassio.IOAttrs object>], proxykey: Annotated[str, <efro.dataclassio.IOAttrs object>])
proxyid: Annotated[str, <efro.dataclassio.IOAttrs object at 0x1054fe6f0>]
proxykey: Annotated[str, <efro.dataclassio.IOAttrs object at 0x1054fed80>]
@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
66    @override
67    @classmethod
68    def get_response_types(cls) -> list[type[Response] | None]:
69        return [LoginProxyStateQueryResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

@ioprepped
@dataclass
class LoginProxyStateQueryResponse(efro.message._message.Response):
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.

LoginProxyStateQueryResponse( state: Annotated[LoginProxyStateQueryResponse.State, <efro.dataclassio.IOAttrs object>], credentials: Annotated[str | None, <efro.dataclassio.IOAttrs object>])
state: Annotated[LoginProxyStateQueryResponse.State, <efro.dataclassio.IOAttrs object at 0x1054ffbf0>]
credentials: Annotated[str | None, <efro.dataclassio.IOAttrs object at 0x1054ffec0>]
class LoginProxyStateQueryResponse.State(enum.Enum):
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.

WAITING = <State.WAITING: 'waiting'>
SUCCESS = <State.SUCCESS: 'success'>
FAIL = <State.FAIL: 'fail'>
@ioprepped
@dataclass
class LoginProxyCompleteMessage(efro.message._message.Message):
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.

LoginProxyCompleteMessage(proxyid: Annotated[str, <efro.dataclassio.IOAttrs object>])
proxyid: Annotated[str, <efro.dataclassio.IOAttrs object at 0x1054ab230>]
@ioprepped
@dataclass
class PingMessage(efro.message._message.Message):
 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.

@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
103    @override
104    @classmethod
105    def get_response_types(cls) -> list[type[Response] | None]:
106        return [PingResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

@ioprepped
@dataclass
class PingResponse(efro.message._message.Response):
109@ioprepped
110@dataclass
111class PingResponse(Response):
112    """pong."""

pong.

@ioprepped
@dataclass
class TestMessage(efro.message._message.Message):
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?

TestMessage(testfoo: Annotated[int, <efro.dataclassio.IOAttrs object>])
testfoo: Annotated[int, <efro.dataclassio.IOAttrs object at 0x1049177a0>]
@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
122    @override
123    @classmethod
124    def get_response_types(cls) -> list[type[Response] | None]:
125        return [TestResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

@ioprepped
@dataclass
class TestResponse(efro.message._message.Response):
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.

TestResponse(testfoo: Annotated[int, <efro.dataclassio.IOAttrs object>])
testfoo: Annotated[int, <efro.dataclassio.IOAttrs object at 0x104c54a40>]
@ioprepped
@dataclass
class SendInfoMessage(efro.message._message.Message):
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

SendInfoMessage(description: Annotated[str, <efro.dataclassio.IOAttrs object>])
description: Annotated[str, <efro.dataclassio.IOAttrs object at 0x105514bc0>]
@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
143    @override
144    @classmethod
145    def get_response_types(cls) -> list[type[Response] | None]:
146        return [SendInfoResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

@ioprepped
@dataclass
class SendInfoResponse(efro.message._message.Response):
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.

SendInfoResponse( handled: Annotated[bool, <efro.dataclassio.IOAttrs object>], message: Annotated[str | None, <efro.dataclassio.IOAttrs object>] = None, legacy_code: Annotated[str | None, <efro.dataclassio.IOAttrs object>] = None)
handled: Annotated[bool, <efro.dataclassio.IOAttrs object at 0x1055160c0>]
message: Annotated[str | None, <efro.dataclassio.IOAttrs object at 0x105515850>] = None
legacy_code: Annotated[str | None, <efro.dataclassio.IOAttrs object at 0x105517cb0>] = None
@ioprepped
@dataclass
class WorkspaceFetchState:
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.

WorkspaceFetchState( manifest: Annotated[bacommon.transfer.DirectoryManifest, <efro.dataclassio.IOAttrs object>], iteration: Annotated[int, <efro.dataclassio.IOAttrs object>] = 0, total_deletes: Annotated[int, <efro.dataclassio.IOAttrs object>] = 0, total_downloads: Annotated[int, <efro.dataclassio.IOAttrs object>] = 0, total_up_to_date: Annotated[int | None, <efro.dataclassio.IOAttrs object>] = None)
manifest: Annotated[bacommon.transfer.DirectoryManifest, <efro.dataclassio.IOAttrs object at 0x105515220>]
iteration: Annotated[int, <efro.dataclassio.IOAttrs object at 0x1055153d0>] = 0
total_deletes: Annotated[int, <efro.dataclassio.IOAttrs object at 0x105515520>] = 0
total_downloads: Annotated[int, <efro.dataclassio.IOAttrs object at 0x105515670>] = 0
total_up_to_date: Annotated[int | None, <efro.dataclassio.IOAttrs object at 0x1055156d0>] = None
@ioprepped
@dataclass
class WorkspaceFetchMessage(efro.message._message.Message):
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?

WorkspaceFetchMessage( workspaceid: Annotated[str, <efro.dataclassio.IOAttrs object>], state: Annotated[WorkspaceFetchState, <efro.dataclassio.IOAttrs object>])
workspaceid: Annotated[str, <efro.dataclassio.IOAttrs object at 0x105516990>]
state: Annotated[WorkspaceFetchState, <efro.dataclassio.IOAttrs object at 0x1055177d0>]
@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
179    @override
180    @classmethod
181    def get_response_types(cls) -> list[type[Response] | None]:
182        return [WorkspaceFetchResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

@ioprepped
@dataclass
class WorkspaceFetchResponse(efro.message._message.Response):
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.

WorkspaceFetchResponse( state: Annotated[WorkspaceFetchState, <efro.dataclassio.IOAttrs object>], deletes: Annotated[list[str], <efro.dataclassio.IOAttrs object>] = <factory>, downloads_inline: Annotated[dict[str, bytes], <efro.dataclassio.IOAttrs object>] = <factory>, done: Annotated[bool, <efro.dataclassio.IOAttrs object>] = False)
state: Annotated[WorkspaceFetchState, <efro.dataclassio.IOAttrs object at 0x1056e9d30>]
deletes: Annotated[list[str], <efro.dataclassio.IOAttrs object at 0x1056e8560>]
downloads_inline: Annotated[dict[str, bytes], <efro.dataclassio.IOAttrs object at 0x1056e8680>]
done: Annotated[bool, <efro.dataclassio.IOAttrs object at 0x1056e8830>] = False
@ioprepped
@dataclass
class MerchAvailabilityMessage(efro.message._message.Message):
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?

@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
206    @override
207    @classmethod
208    def get_response_types(cls) -> list[type[Response] | None]:
209        return [MerchAvailabilityResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

@ioprepped
@dataclass
class MerchAvailabilityResponse(efro.message._message.Response):
212@ioprepped
213@dataclass
214class MerchAvailabilityResponse(Response):
215    """About that merch..."""
216
217    url: Annotated[str | None, IOAttrs('u')]

About that merch...

MerchAvailabilityResponse(url: Annotated[str | None, <efro.dataclassio.IOAttrs object>])
url: Annotated[str | None, <efro.dataclassio.IOAttrs object at 0x1056ebd10>]
@ioprepped
@dataclass
class SignInMessage(efro.message._message.Message):
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?

SignInMessage( login_type: Annotated[bacommon.login.LoginType, <efro.dataclassio.IOAttrs object>], sign_in_token: Annotated[str, <efro.dataclassio.IOAttrs object>], description: Annotated[str, <efro.dataclassio.IOAttrs object>], apptime: Annotated[float, <efro.dataclassio.IOAttrs object>])
login_type: Annotated[bacommon.login.LoginType, <efro.dataclassio.IOAttrs object at 0x1056eadb0>]
sign_in_token: Annotated[str, <efro.dataclassio.IOAttrs object at 0x1056eb0e0>]
description: Annotated[str, <efro.dataclassio.IOAttrs object at 0x1056eb140>]
apptime: Annotated[float, <efro.dataclassio.IOAttrs object at 0x1056eb200>]
@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
232    @override
233    @classmethod
234    def get_response_types(cls) -> list[type[Response] | None]:
235        return [SignInResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

@ioprepped
@dataclass
class SignInResponse(efro.message._message.Response):
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.

SignInResponse( credentials: Annotated[str | None, <efro.dataclassio.IOAttrs object>])
credentials: Annotated[str | None, <efro.dataclassio.IOAttrs object at 0x105761a30>]
@ioprepped
@dataclass
class ManageAccountMessage(efro.message._message.Message):
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.

ManageAccountMessage( weblocation: Annotated[WebLocation, <efro.dataclassio.IOAttrs object>] = <WebLocation.ACCOUNT_EDITOR: 'e'>)
weblocation: Annotated[WebLocation, <efro.dataclassio.IOAttrs object at 0x105762fc0>] = <WebLocation.ACCOUNT_EDITOR: 'e'>
@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
255    @override
256    @classmethod
257    def get_response_types(cls) -> list[type[Response] | None]:
258        return [ManageAccountResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

@ioprepped
@dataclass
class ManageAccountResponse(efro.message._message.Response):
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.

ManageAccountResponse(url: Annotated[str | None, <efro.dataclassio.IOAttrs object>])
url: Annotated[str | None, <efro.dataclassio.IOAttrs object at 0x1057635f0>]
@ioprepped
@dataclass
class StoreQueryMessage(efro.message._message.Message):
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.

@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
274    @override
275    @classmethod
276    def get_response_types(cls) -> list[type[Response] | None]:
277        return [StoreQueryResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

@ioprepped
@dataclass
class StoreQueryResponse(efro.message._message.Response):
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.

StoreQueryResponse( result: Annotated[StoreQueryResponse.Result, <efro.dataclassio.IOAttrs object>], tokens: Annotated[int, <efro.dataclassio.IOAttrs object>], gold_pass: Annotated[bool, <efro.dataclassio.IOAttrs object>], available_purchases: Annotated[list[StoreQueryResponse.Purchase], <efro.dataclassio.IOAttrs object>], token_info_url: Annotated[str, <efro.dataclassio.IOAttrs object>])
result: Annotated[StoreQueryResponse.Result, <efro.dataclassio.IOAttrs object at 0x105762ea0>]
tokens: Annotated[int, <efro.dataclassio.IOAttrs object at 0x105763380>]
gold_pass: Annotated[bool, <efro.dataclassio.IOAttrs object at 0x1057634d0>]
available_purchases: Annotated[list[StoreQueryResponse.Purchase], <efro.dataclassio.IOAttrs object at 0x105763530>]
token_info_url: Annotated[str, <efro.dataclassio.IOAttrs object at 0x105763770>]
class StoreQueryResponse.Result(enum.Enum):
285    class Result(Enum):
286        """Our overall result."""
287
288        SUCCESS = 's'
289        ERROR = 'e'

Our overall result.

SUCCESS = <Result.SUCCESS: 's'>
ERROR = <Result.ERROR: 'e'>
@dataclass
class StoreQueryResponse.Purchase:
291    @dataclass
292    class Purchase:
293        """Info about a purchasable thing."""
294
295        purchaseid: Annotated[str, IOAttrs('id')]

Info about a purchasable thing.

StoreQueryResponse.Purchase(purchaseid: Annotated[str, <efro.dataclassio.IOAttrs object>])
purchaseid: Annotated[str, <efro.dataclassio.IOAttrs object at 0x105796ed0>]
@ioprepped
@dataclass
class BSPrivatePartyMessage(efro.message._message.Message):
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.

BSPrivatePartyMessage( need_datacode: Annotated[bool, <efro.dataclassio.IOAttrs object>])
need_datacode: Annotated[bool, <efro.dataclassio.IOAttrs object at 0x105797a40>]
@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
314    @override
315    @classmethod
316    def get_response_types(cls) -> list[type[Response] | None]:
317        return [BSPrivatePartyResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

@ioprepped
@dataclass
class BSPrivatePartyResponse(efro.message._message.Response):
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.

BSPrivatePartyResponse( success: Annotated[bool, <efro.dataclassio.IOAttrs object>], tokens: Annotated[int, <efro.dataclassio.IOAttrs object>], gold_pass: Annotated[bool, <efro.dataclassio.IOAttrs object>], datacode: Annotated[str | None, <efro.dataclassio.IOAttrs object>])
success: Annotated[bool, <efro.dataclassio.IOAttrs object at 0x105796a20>]
tokens: Annotated[int, <efro.dataclassio.IOAttrs object at 0x105796d20>]
gold_pass: Annotated[bool, <efro.dataclassio.IOAttrs object at 0x105796e10>]
datacode: Annotated[str | None, <efro.dataclassio.IOAttrs object at 0x105796f00>]
class BSClassicChestAppearance(enum.Enum):
331class BSClassicChestAppearance(Enum):
332    """Appearances bombsquad classic chests can have."""
333
334    UNKNOWN = 'u'
335    DEFAULT = 'd'

Appearances bombsquad classic chests can have.

@ioprepped
@dataclass
class BSClassicAccountLiveData:
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.

BSClassicAccountLiveData( tickets: Annotated[int, <efro.dataclassio.IOAttrs object>], tokens: Annotated[int, <efro.dataclassio.IOAttrs object>], gold_pass: Annotated[bool, <efro.dataclassio.IOAttrs object>], achievements: Annotated[int, <efro.dataclassio.IOAttrs object>], achievements_total: Annotated[int, <efro.dataclassio.IOAttrs object>], league_type: Annotated[BSClassicAccountLiveData.LeagueType | None, <efro.dataclassio.IOAttrs object>], league_num: Annotated[int | None, <efro.dataclassio.IOAttrs object>], league_rank: Annotated[int | None, <efro.dataclassio.IOAttrs object>], level: Annotated[int, <efro.dataclassio.IOAttrs object>], xp: Annotated[int, <efro.dataclassio.IOAttrs object>], xpmax: Annotated[int, <efro.dataclassio.IOAttrs object>], inbox_count: Annotated[int, <efro.dataclassio.IOAttrs object>], inbox_count_is_max: Annotated[bool, <efro.dataclassio.IOAttrs object>], chests: Annotated[dict[str, BSClassicAccountLiveData.Chest], <efro.dataclassio.IOAttrs object>])
tickets: Annotated[int, <efro.dataclassio.IOAttrs object at 0x10577c9e0>]
tokens: Annotated[int, <efro.dataclassio.IOAttrs object at 0x10577cd10>]
gold_pass: Annotated[bool, <efro.dataclassio.IOAttrs object at 0x10577ce00>]
achievements: Annotated[int, <efro.dataclassio.IOAttrs object at 0x10577cf20>]
achievements_total: Annotated[int, <efro.dataclassio.IOAttrs object at 0x10577d040>]
league_type: Annotated[BSClassicAccountLiveData.LeagueType | None, <efro.dataclassio.IOAttrs object at 0x10577d0d0>]
league_num: Annotated[int | None, <efro.dataclassio.IOAttrs object at 0x10577d250>]
league_rank: Annotated[int | None, <efro.dataclassio.IOAttrs object at 0x10577d370>]
level: Annotated[int, <efro.dataclassio.IOAttrs object at 0x10577d4f0>]
xp: Annotated[int, <efro.dataclassio.IOAttrs object at 0x10577d610>]
xpmax: Annotated[int, <efro.dataclassio.IOAttrs object at 0x10577d790>]
inbox_count: Annotated[int, <efro.dataclassio.IOAttrs object at 0x10577d910>]
inbox_count_is_max: Annotated[bool, <efro.dataclassio.IOAttrs object at 0x10577da60>]
chests: Annotated[dict[str, BSClassicAccountLiveData.Chest], <efro.dataclassio.IOAttrs object at 0x10577d970>]
@dataclass
class BSClassicAccountLiveData.Chest:
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.

BSClassicAccountLiveData.Chest( appearance: Annotated[BSClassicChestAppearance, <efro.dataclassio.IOAttrs object>], unlock_time: Annotated[datetime.datetime, <efro.dataclassio.IOAttrs object>], ad_allow_time: Annotated[datetime.datetime | None, <efro.dataclassio.IOAttrs object>])
appearance: Annotated[BSClassicChestAppearance, <efro.dataclassio.IOAttrs object at 0x10577faa0>]
unlock_time: Annotated[datetime.datetime, <efro.dataclassio.IOAttrs object at 0x10577fc20>]
ad_allow_time: Annotated[datetime.datetime | None, <efro.dataclassio.IOAttrs object at 0x10577fc80>]
class BSClassicAccountLiveData.LeagueType(enum.Enum):
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.

BRONZE = <LeagueType.BRONZE: 'b'>
SILVER = <LeagueType.SILVER: 's'>
GOLD = <LeagueType.GOLD: 'g'>
DIAMOND = <LeagueType.DIAMOND: 'd'>
class BSInboxEntryType(enum.Enum):
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.

UNKNOWN = <BSInboxEntryType.UNKNOWN: 'u'>
SIMPLE = <BSInboxEntryType.SIMPLE: 's'>
CLAIM = <BSInboxEntryType.CLAIM: 'c'>
CLAIM_DISCARD = <BSInboxEntryType.CLAIM_DISCARD: 'cd'>
@ioprepped
@dataclass
class BSInboxEntry:
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.

BSInboxEntry( type: Annotated[BSInboxEntryType, <efro.dataclassio.IOAttrs object>], id: Annotated[str, <efro.dataclassio.IOAttrs object>], createtime: Annotated[datetime.datetime, <efro.dataclassio.IOAttrs object>], format_version: Annotated[int, <efro.dataclassio.IOAttrs object>], message: Annotated[str, <efro.dataclassio.IOAttrs object>], subs: Annotated[list[str], <efro.dataclassio.IOAttrs object>])
type: Annotated[BSInboxEntryType, <efro.dataclassio.IOAttrs object at 0x105802330>]
id: Annotated[str, <efro.dataclassio.IOAttrs object at 0x1058025a0>]
createtime: Annotated[datetime.datetime, <efro.dataclassio.IOAttrs object at 0x105802660>]
format_version: Annotated[int, <efro.dataclassio.IOAttrs object at 0x1058026f0>]
message: Annotated[str, <efro.dataclassio.IOAttrs object at 0x1058028d0>]
subs: Annotated[list[str], <efro.dataclassio.IOAttrs object at 0x1058029c0>]
@ioprepped
@dataclass
class BSInboxRequestMessage(efro.message._message.Message):
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.

@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
418    @override
419    @classmethod
420    def get_response_types(cls) -> list[type[Response] | None]:
421        return [BSInboxRequestResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

@ioprepped
@dataclass
class BSInboxRequestResponse(efro.message._message.Response):
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.

BSInboxRequestResponse( entries: Annotated[list[BSInboxEntry], <efro.dataclassio.IOAttrs object>], error: Annotated[str | None, <efro.dataclassio.IOAttrs object>] = None)
entries: Annotated[list[BSInboxEntry], <efro.dataclassio.IOAttrs object at 0x105779d30>]
error: Annotated[str | None, <efro.dataclassio.IOAttrs object at 0x105778590>] = None
@ioprepped
@dataclass
class BSChestInfoMessage(efro.message._message.Message):
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.

BSChestInfoMessage(chest_id: Annotated[str, <efro.dataclassio.IOAttrs object>])
chest_id: Annotated[str, <efro.dataclassio.IOAttrs object at 0x10577aed0>]
@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
442    @override
443    @classmethod
444    def get_response_types(cls) -> list[type[Response] | None]:
445        return [BSChestInfoResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

@ioprepped
@dataclass
class BSChestInfoResponse(efro.message._message.Response):
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.

BSChestInfoResponse( chest: Annotated[BSChestInfoResponse.Chest | None, <efro.dataclassio.IOAttrs object>])
chest: Annotated[BSChestInfoResponse.Chest | None, <efro.dataclassio.IOAttrs object at 0x10577bc20>]
@dataclass
class BSChestInfoResponse.Chest:
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.

BSChestInfoResponse.Chest( appearance: Annotated[BSClassicChestAppearance, <efro.dataclassio.IOAttrs object>], unlock_tokens: Annotated[int, <efro.dataclassio.IOAttrs object>], unlock_time: Annotated[datetime.datetime, <efro.dataclassio.IOAttrs object>], ad_allow: Annotated[bool, <efro.dataclassio.IOAttrs object>])
appearance: Annotated[BSClassicChestAppearance, <efro.dataclassio.IOAttrs object at 0x10577ad80>]
unlock_tokens: Annotated[int, <efro.dataclassio.IOAttrs object at 0x10577af60>]
unlock_time: Annotated[datetime.datetime, <efro.dataclassio.IOAttrs object at 0x10577aff0>]
ad_allow: Annotated[bool, <efro.dataclassio.IOAttrs object at 0x10577b230>]
@ioprepped
@dataclass
class BSChestActionMessage(efro.message._message.Message):
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.

BSChestActionMessage( action: Annotated[BSChestActionMessage.Action, <efro.dataclassio.IOAttrs object>], token_payment: Annotated[int, <efro.dataclassio.IOAttrs object>], chest_id: Annotated[str, <efro.dataclassio.IOAttrs object>])
action: Annotated[BSChestActionMessage.Action, <efro.dataclassio.IOAttrs object at 0x105815940>]
token_payment: Annotated[int, <efro.dataclassio.IOAttrs object at 0x105814320>]
chest_id: Annotated[str, <efro.dataclassio.IOAttrs object at 0x105814410>]
@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
495    @override
496    @classmethod
497    def get_response_types(cls) -> list[type[Response] | None]:
498        return [BSChestActionResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

class BSChestActionMessage.Action(enum.Enum):
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.

UNLOCK = <Action.UNLOCK: 'u'>
AD = <Action.AD: 'ad'>
@ioprepped
@dataclass
class BSChestActionResponse(efro.message._message.Response):
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.

BSChestActionResponse( contents: Annotated[list[str] | None, <efro.dataclassio.IOAttrs object>] = None, error: Annotated[str | None, <efro.dataclassio.IOAttrs object>] = None)
contents: Annotated[list[str] | None, <efro.dataclassio.IOAttrs object at 0x105815e80>] = None
error: Annotated[str | None, <efro.dataclassio.IOAttrs object at 0x105815d30>] = None
class BSInboxEntryProcessType(enum.Enum):
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.

@ioprepped
@dataclass
class BSInboxEntryProcessMessage(efro.message._message.Message):
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.

BSInboxEntryProcessMessage( id: Annotated[str, <efro.dataclassio.IOAttrs object>], process_type: Annotated[BSInboxEntryProcessType, <efro.dataclassio.IOAttrs object>])
id: Annotated[str, <efro.dataclassio.IOAttrs object at 0x105817950>]
process_type: Annotated[BSInboxEntryProcessType, <efro.dataclassio.IOAttrs object at 0x105817c80>]
@override
@classmethod
def get_response_types(cls) -> list[type[efro.message.Response] | None]:
529    @override
530    @classmethod
531    def get_response_types(cls) -> list[type[Response] | None]:
532        return [BSInboxEntryProcessResponse]

Return all Response types this Message can return when sent.

The default implementation specifies a None return type.

@ioprepped
@dataclass
class BSInboxEntryProcessResponse(efro.message._message.Response):
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.

BSInboxEntryProcessResponse( error: Annotated[str | None, <efro.dataclassio.IOAttrs object>] = None)
error: Annotated[str | None, <efro.dataclassio.IOAttrs object at 0x105862120>] = None