mirror of
https://git.ugnet.gay/CrossTalk/azul.git
synced 2026-05-27 14:49:50 +00:00
648 lines
18 KiB
Python
648 lines
18 KiB
Python
from datetime import datetime
|
|
from typing import Dict, Optional, Set, List, Tuple, Any, TypeVar
|
|
from enum import Enum, IntEnum, IntFlag
|
|
|
|
class User:
|
|
__slots__ = (
|
|
'id',
|
|
'uuid',
|
|
'email',
|
|
'username',
|
|
'first_name',
|
|
'last_name',
|
|
'uin',
|
|
'verified_to_login',
|
|
'status',
|
|
'detail',
|
|
'settings',
|
|
'date_created',
|
|
'date_login',
|
|
'suspended',
|
|
'is_tester',
|
|
'is_mvp',
|
|
'show_in_dir',
|
|
'evil_permanent',
|
|
'evil_temporary',
|
|
'profile'
|
|
)
|
|
|
|
id: int
|
|
uuid: str
|
|
email: str
|
|
username: str
|
|
first_name: str
|
|
last_name: str
|
|
uin: int
|
|
verified_to_login: bool
|
|
status: 'UserStatus'
|
|
detail: Optional['UserDetail']
|
|
settings: Dict[str, Any]
|
|
date_created: datetime
|
|
date_login: datetime
|
|
suspended: bool
|
|
is_tester: bool
|
|
is_mvp: bool
|
|
show_in_dir: bool
|
|
evil_permanent: int
|
|
evil_temporary: int
|
|
profile: 'UserProfile'
|
|
|
|
def __init__(
|
|
self, id: int, uuid: str, email: str, username: str, first_name: str, last_name: str, uin: int, verified_to_login: bool, status: 'UserStatus',
|
|
settings: Dict[str, Any], date_created: datetime, date_login: datetime, suspended: bool, is_tester: bool, is_mvp: bool, show_in_dir: bool, evil_permanent: int, evil_temporary: int, profile: 'UserProfile'
|
|
) -> None:
|
|
self.id = id
|
|
self.uuid = uuid
|
|
self.email = email
|
|
self.username = username
|
|
self.first_name = first_name
|
|
self.last_name = last_name
|
|
self.uin = uin
|
|
self.verified_to_login = verified_to_login
|
|
# `status`: true status of user
|
|
self.status = status
|
|
self.detail = None
|
|
self.settings = settings
|
|
self.date_created = date_created
|
|
self.date_login = date_login
|
|
self.suspended = suspended
|
|
self.is_tester = is_tester
|
|
self.is_mvp = is_mvp
|
|
self.show_in_dir = show_in_dir
|
|
self.evil_permanent = evil_permanent
|
|
self.evil_temporary = evil_temporary
|
|
self.profile = profile
|
|
|
|
class Contact:
|
|
__slots__ = ('head', '_groups', 'lists', 'status', 'is_messenger_user', 'pending', 'detail')
|
|
|
|
head: User
|
|
_groups: Set['ContactGroupEntry']
|
|
lists: 'ContactList'
|
|
status: 'UserStatus'
|
|
is_messenger_user: bool
|
|
pending: bool
|
|
detail: 'ContactDetail'
|
|
|
|
def __init__(
|
|
self, user: User, groups: Set['ContactGroupEntry'], lists: 'ContactList', status: 'UserStatus', detail: 'ContactDetail', *,
|
|
is_messenger_user: Optional[bool] = None, pending: Optional[bool] = None,
|
|
) -> None:
|
|
self.head = user
|
|
self._groups = groups
|
|
self.lists = lists
|
|
# `status`: status as known by the contact
|
|
self.status = status
|
|
self.is_messenger_user = _default_if_none(is_messenger_user, True)
|
|
self.pending = _default_if_none(pending, False)
|
|
self.detail = detail
|
|
|
|
def compute_visible_status(self, to_user: User) -> None:
|
|
# Set Contact.status based on BLP and Contact.lists
|
|
# If not blocked, Contact.status == Contact.head.status
|
|
if self.head.detail is None or _is_blocking(self.head, to_user):
|
|
self.status.substatus = Substatus.Offline
|
|
return
|
|
true_status = self.head.status
|
|
self.status.substatus = true_status.substatus
|
|
self.status.name = true_status.name
|
|
self.status.message = true_status.message
|
|
self.status.media = true_status.media
|
|
|
|
def is_in_group_id(self, group_id: str) -> bool:
|
|
for group in self._groups:
|
|
if group.id == group_id:
|
|
return True
|
|
return False
|
|
|
|
def group_in_entry(self, grp: 'Group') -> bool:
|
|
for group in self._groups:
|
|
if group.id == grp.id or group.uuid == grp.uuid:
|
|
return True
|
|
return False
|
|
|
|
def add_group_to_entry(self, grp: 'Group') -> None:
|
|
self._groups.add(ContactGroupEntry(
|
|
self.head.uuid, grp.id, grp.uuid,
|
|
))
|
|
|
|
def remove_from_group(self, grp: 'Group') -> None:
|
|
found_group = None
|
|
for group in self._groups:
|
|
if group.id == grp.id or group.uuid == grp.uuid:
|
|
found_group = group
|
|
break
|
|
if found_group is not None:
|
|
self._groups.discard(group)
|
|
|
|
def _is_blocking(blocker: User, blockee: User) -> bool:
|
|
detail = blocker.detail
|
|
assert detail is not None
|
|
contact = detail.contacts.get(blockee.uuid)
|
|
lists = (contact and contact.lists or 0)
|
|
if lists & ContactList.BL: return True
|
|
if lists & ContactList.AL: return False
|
|
return (blocker.settings.get('BLP', 'AL') == 'BL')
|
|
|
|
class ContactDetail:
|
|
__slots__ = (
|
|
'index_id', 'birthdate', 'anniversary', 'notes', 'first_name', 'middle_name', 'last_name',
|
|
'nickname', 'primary_email_type', 'personal_email', 'work_email', 'im_email', 'other_email',
|
|
'home_phone', 'work_phone', 'fax_phone', 'pager_phone', 'mobile_phone', 'other_phone',
|
|
'personal_website', 'business_website', 'locations',
|
|
)
|
|
|
|
index_id: str
|
|
birthdate: Optional[datetime]
|
|
anniversary: Optional[datetime]
|
|
notes: Optional[str]
|
|
first_name: Optional[str]
|
|
middle_name: Optional[str]
|
|
last_name: Optional[str]
|
|
nickname: Optional[str]
|
|
primary_email_type: Optional[str]
|
|
personal_email: Optional[str]
|
|
work_email: Optional[str]
|
|
im_email: Optional[str]
|
|
other_email: Optional[str]
|
|
home_phone: Optional[str]
|
|
work_phone: Optional[str]
|
|
fax_phone: Optional[str]
|
|
pager_phone: Optional[str]
|
|
mobile_phone: Optional[str]
|
|
other_phone: Optional[str]
|
|
personal_website: Optional[str]
|
|
business_website: Optional[str]
|
|
locations: Dict[str, 'ContactLocation']
|
|
|
|
def __init__(
|
|
self, index_id: str, *, birthdate: Optional[datetime] = None, anniversary: Optional[datetime] = None,
|
|
notes: Optional[str] = None, first_name: Optional[str] = None, middle_name: Optional[str] = None,
|
|
last_name: Optional[str] = None, nickname: Optional[str] = None, primary_email_type: Optional[str] = None,
|
|
personal_email: Optional[str] = None, work_email: Optional[str] = None, im_email: Optional[str] = None,
|
|
other_email: Optional[str] = None, home_phone: Optional[str] = None, work_phone: Optional[str] = None,
|
|
fax_phone: Optional[str] = None, pager_phone: Optional[str] = None, mobile_phone: Optional[str] = None,
|
|
other_phone: Optional[str] = None, personal_website: Optional[str] = None, business_website: Optional[str] = None,
|
|
):
|
|
self.index_id = index_id
|
|
self.birthdate = birthdate
|
|
self.anniversary = anniversary
|
|
self.notes = notes
|
|
self.first_name = first_name
|
|
self.middle_name = middle_name
|
|
self.last_name = last_name
|
|
self.nickname = nickname
|
|
self.primary_email_type = primary_email_type
|
|
self.personal_email = personal_email
|
|
self.work_email = work_email
|
|
self.im_email = im_email
|
|
self.other_email = other_email
|
|
self.home_phone = home_phone
|
|
self.work_phone = work_phone
|
|
self.fax_phone = fax_phone
|
|
self.pager_phone = pager_phone
|
|
self.mobile_phone = mobile_phone
|
|
self.other_phone = other_phone
|
|
self.personal_website = personal_website
|
|
self.business_website = business_website
|
|
self.locations = {}
|
|
|
|
class ContactGroupEntry:
|
|
__slots__ = ('contact_uuid', 'id', 'uuid')
|
|
|
|
contact_uuid: str
|
|
id: str
|
|
uuid: str
|
|
|
|
def __init__(self, contact_uuid: str, id: str, uuid: str) -> None:
|
|
self.contact_uuid = contact_uuid
|
|
self.id = id
|
|
self.uuid = uuid
|
|
|
|
class ContactLocation:
|
|
__slots__ = ('type', 'name', 'street', 'city', 'state', 'country', 'zip_code')
|
|
|
|
type: str
|
|
name: Optional[str]
|
|
street: Optional[str]
|
|
city: Optional[str]
|
|
state: Optional[str]
|
|
country: Optional[str]
|
|
zip_code: Optional[str]
|
|
|
|
def __init__(
|
|
self, type: str, *, name: Optional[str] = None, street: Optional[str] = None, city: Optional[str] = None,
|
|
state: Optional[str] = None, country: Optional[str] = None, zip_code: Optional[str] = None,
|
|
) -> None:
|
|
self.type = type
|
|
self.name = name
|
|
self.street = street
|
|
self.city = city
|
|
self.state = state
|
|
self.country = country
|
|
self.zip_code = zip_code
|
|
|
|
class UserProfile:
|
|
__slots__ = ('user_id', 'bio', 'pronouns', 'website', 'socials', 'streetaddr', 'city', 'state', 'zip', 'country', 'interests', 'visibility')
|
|
user_id: User
|
|
bio: Optional[str]
|
|
pronouns: Optional[str]
|
|
website: Optional[str]
|
|
socials: Optional[Dict[str, Any]]
|
|
streetaddr: Optional[str]
|
|
city: Optional[str]
|
|
state: Optional[str]
|
|
zip: Optional[int]
|
|
country: Optional[str]
|
|
interests: Optional[Dict[str, Any]]
|
|
visibility: Optional[str]
|
|
|
|
def __init__(
|
|
self, user_id: User, bio: Optional[str], pronouns: Optional[str], website: Optional[str],
|
|
socials: Optional[Dict[str, Any]], streetaddr: Optional[str], city: Optional[str], state: Optional[str],
|
|
zip: Optional[int], country: Optional[str], interests: Optional[Dict[str, Any]], visibility: Optional[str]
|
|
):
|
|
self.user_id = user_id
|
|
self.bio = bio
|
|
self.pronouns = pronouns
|
|
self.website = website
|
|
self.socials = socials
|
|
self.streetaddr = streetaddr
|
|
self.city = city
|
|
self.state = state
|
|
self.zip = zip
|
|
self.country = country
|
|
self.interests = interests
|
|
self.visibility = visibility
|
|
|
|
class UserStatus:
|
|
__slots__ = ('substatus', 'name', 'message', 'media')
|
|
|
|
substatus: 'Substatus'
|
|
name: str
|
|
message: str
|
|
media: Optional[Any]
|
|
|
|
def __init__(self, name: str) -> None:
|
|
self.substatus = Substatus.Offline
|
|
self.name = name
|
|
self.message = ''
|
|
self.media = None
|
|
|
|
def is_offlineish(self) -> bool:
|
|
return self.substatus is Substatus.Offline or self.substatus is Substatus.Invisible
|
|
|
|
class UserDetail:
|
|
__slots__ = ('_groups_by_id', '_groups_by_uuid', 'contacts')
|
|
|
|
_groups_by_id: Dict[str, 'Group']
|
|
_groups_by_uuid: Dict[str, 'Group']
|
|
contacts: Dict[str, 'Contact']
|
|
|
|
def __init__(self) -> None:
|
|
self._groups_by_id = {}
|
|
self._groups_by_uuid = {}
|
|
self.contacts = {}
|
|
|
|
def get_contacts_by_list(self, lst: 'ContactList') -> Tuple[Contact, ...]:
|
|
return tuple([ctc for ctc in self.contacts.values() if ctc.lists & lst])
|
|
|
|
def insert_group(self, grp: 'Group') -> None:
|
|
self._groups_by_id[grp.id] = grp
|
|
self._groups_by_uuid[grp.uuid] = grp
|
|
|
|
def get_group_by_id(self, id: str) -> Optional['Group']:
|
|
group = None
|
|
|
|
group = self._groups_by_id.get(id)
|
|
if group is None:
|
|
group = self._groups_by_uuid.get(id)
|
|
|
|
return group
|
|
|
|
def get_groups_by_name(self, name: str) -> List['Group']:
|
|
groups = [] # type: ContactList[Group]
|
|
for group in self._groups_by_id.values():
|
|
if group.name == name:
|
|
if group not in groups: groups.append(group)
|
|
for group in self._groups_by_uuid.values():
|
|
if group.name == name:
|
|
if group not in groups: groups.append(group)
|
|
return groups
|
|
|
|
def delete_group(self, grp: 'Group') -> None:
|
|
if grp.id in self._groups_by_id:
|
|
del self._groups_by_id[grp.id]
|
|
if grp.uuid in self._groups_by_uuid:
|
|
del self._groups_by_uuid[grp.uuid]
|
|
|
|
class Group:
|
|
__slots__ = ('id', 'uuid', 'name', 'is_favorite')
|
|
|
|
id: str
|
|
uuid: str
|
|
name: str
|
|
is_favorite: bool
|
|
|
|
def __init__(self, id: str, uuid: str, name: str, is_favorite: bool) -> None:
|
|
self.id = id
|
|
self.uuid = uuid
|
|
self.name = name
|
|
self.is_favorite = is_favorite
|
|
|
|
class MessageType(Enum):
|
|
Chat = object()
|
|
Nudge = object()
|
|
Typing = object()
|
|
TypingDone = object()
|
|
Webcam = object()
|
|
Ink = object()
|
|
MSNP2P = object()
|
|
MSNP2PInvite = object()
|
|
MSNP2PTransBody = object()
|
|
Emoticon = object()
|
|
Ignored = object()
|
|
Other = object()
|
|
|
|
class MessageData:
|
|
__slots__ = ('sender', 'sender_pop_id', 'type', 'text', 'front_cache')
|
|
|
|
sender: User
|
|
sender_pop_id: Optional[str]
|
|
type: MessageType
|
|
text: Optional[str]
|
|
front_cache: Dict[str, Any]
|
|
|
|
def __init__(self, *, sender: User, sender_pop_id: Optional[str] = None, type: MessageType, text: Optional[str] = None) -> None:
|
|
self.sender = sender
|
|
self.sender_pop_id = sender_pop_id
|
|
self.type = type
|
|
self.text = text
|
|
self.front_cache = {}
|
|
|
|
class TextWithData:
|
|
__slots__ = ('text', 'yahoo_utf8')
|
|
|
|
text: str
|
|
yahoo_utf8: Any
|
|
|
|
def __init__(self, text: str, yahoo_utf8: Any) -> None:
|
|
self.text = text
|
|
self.yahoo_utf8 = yahoo_utf8
|
|
|
|
class RoamingInfo:
|
|
__slots__ = ('name', 'name_last_modified', 'message', 'message_last_modified')
|
|
|
|
name: Optional[str]
|
|
name_last_modified: datetime
|
|
message: Optional[str]
|
|
message_last_modified: datetime
|
|
|
|
def __init__(self, name: Optional[str], name_last_modified: datetime, message: Optional[str], message_last_modified: datetime) -> None: #, avatar: Optional[str]) -> None:
|
|
self.name = name
|
|
self.name_last_modified = name_last_modified
|
|
self.message = message
|
|
self.message_last_modified = message_last_modified
|
|
|
|
class Circle:
|
|
__slots__ = (
|
|
'chat_id', 'name', 'owner_id', 'owner_uuid', 'owner_friendly', 'membership_access',
|
|
'request_membership_option', 'memberships',
|
|
)
|
|
|
|
chat_id: str
|
|
name: str
|
|
owner_id: int
|
|
owner_uuid: str
|
|
owner_friendly: str
|
|
membership_access: int
|
|
request_membership_option: int
|
|
memberships: Dict[str, 'CircleMembership']
|
|
|
|
def __init__(
|
|
self, chat_id: str, name: str, owner_id: int, owner_uuid: str, owner_friendly: str,
|
|
membership_access: int, request_membership_option: int,
|
|
) -> None:
|
|
self.chat_id = chat_id
|
|
self.name = name
|
|
self.owner_id = owner_id
|
|
self.owner_uuid = owner_uuid
|
|
self.owner_friendly = owner_friendly
|
|
self.membership_access = membership_access
|
|
self.request_membership_option = request_membership_option
|
|
self.memberships = {}
|
|
|
|
class Chatroom:
|
|
__slots__ = (
|
|
'chat_id', 'name', 'owner', 'topic', 'role', 'is_public'
|
|
)
|
|
|
|
chat_id: str
|
|
name: str
|
|
owner: User
|
|
topic: Optional[str]
|
|
role: 'ChatroomRole' # will be used for setting IRC user modes, etc
|
|
is_public: bool
|
|
|
|
def __init__(
|
|
self, chat_id: str, name: str, owner: User, role: 'ChatroomRole', topic: Optional[str] = None, is_public: bool = False
|
|
):
|
|
self.chat_id = chat_id
|
|
self.name = name
|
|
self.owner = owner
|
|
self.topic = topic
|
|
self.role = role
|
|
self.is_public = is_public
|
|
|
|
class CircleMembership:
|
|
__slots__ = (
|
|
'chat_id', 'head', 'role', 'state', 'blocking', 'inviter_uuid', 'inviter_email', 'inviter_name', 'invite_message',
|
|
)
|
|
|
|
chat_id: str
|
|
head: User
|
|
role: 'CircleRole'
|
|
state: 'CircleState'
|
|
blocking: bool
|
|
inviter_uuid: Optional[str]
|
|
inviter_email: Optional[str]
|
|
inviter_name: Optional[str]
|
|
invite_message: Optional[str]
|
|
|
|
def __init__(
|
|
self, chat_id: str, head: User, role: 'CircleRole', state: 'CircleState', *,
|
|
blocking: bool = False, inviter_uuid: Optional[str] = None, inviter_email: Optional[str] = None,
|
|
inviter_name: Optional[str] = None, invite_message: Optional[str] = None,
|
|
):
|
|
self.chat_id = chat_id
|
|
self.head = head
|
|
self.role = role
|
|
self.state = state
|
|
self.blocking = blocking
|
|
self.inviter_uuid = inviter_uuid
|
|
self.inviter_email = inviter_email
|
|
self.inviter_name = inviter_name
|
|
self.invite_message = invite_message
|
|
|
|
class OIM:
|
|
__slots__ = (
|
|
'uuid', 'run_id', 'from_email', 'from_username', 'from_friendly', 'from_friendly_encoding', 'from_friendly_charset',
|
|
'from_user_id', 'to_email', 'sent', 'origin_ip', 'oim_proxy', 'headers', 'message', 'utf8',
|
|
)
|
|
|
|
uuid: str
|
|
run_id: str
|
|
from_email: str
|
|
from_username: str
|
|
from_friendly: str
|
|
from_friendly_encoding: str
|
|
from_friendly_charset: str
|
|
from_user_id: Optional[str]
|
|
to_email: str
|
|
sent: datetime
|
|
origin_ip: Optional[str]
|
|
oim_proxy: Optional[str]
|
|
headers: Dict[str, str]
|
|
message: str
|
|
utf8: bool
|
|
|
|
def __init__(
|
|
self, uuid: str, run_id: str, from_email: str, from_username: str, from_friendly: str, to_email: str, sent: datetime,
|
|
message: str, utf8: bool, *, headers: Optional[Dict[str, str]] = None, from_friendly_encoding: Optional[str] = None,
|
|
from_friendly_charset: Optional[str] = None, from_user_id: Optional[str] = None, origin_ip: Optional[str] = None,
|
|
oim_proxy: Optional[str] = None,
|
|
) -> None:
|
|
self.uuid = uuid
|
|
self.run_id = run_id
|
|
self.from_email = from_email
|
|
self.from_username = from_username
|
|
self.from_friendly = from_friendly
|
|
self.from_friendly_encoding = _default_if_none(from_friendly_encoding, 'B')
|
|
self.from_friendly_charset = _default_if_none(from_friendly_charset, 'utf-8')
|
|
self.from_user_id = from_user_id
|
|
self.to_email = to_email
|
|
self.sent = sent
|
|
self.origin_ip = origin_ip
|
|
self.oim_proxy = oim_proxy
|
|
self.headers = _default_if_none(headers, {})
|
|
self.message = message
|
|
self.utf8 = utf8
|
|
|
|
T = TypeVar('T')
|
|
def _default_if_none(x: Optional[T], default: T) -> T:
|
|
if x is None: return default
|
|
return x
|
|
|
|
class Substatus(Enum):
|
|
Offline = object()
|
|
Online = object()
|
|
Busy = object()
|
|
Idle = object()
|
|
BRB = object()
|
|
Away = object()
|
|
OnPhone = object()
|
|
OutToLunch = object()
|
|
Invisible = object()
|
|
NotAtHome = object()
|
|
NotAtDesk = object()
|
|
NotInOffice = object()
|
|
OnVacation = object()
|
|
SteppedOut = object()
|
|
|
|
def is_offlineish(self) -> bool:
|
|
return self is Substatus.Offline or self is Substatus.Invisible
|
|
|
|
# TODO: Put this in the ContactList class
|
|
MembershipLabels = {
|
|
# From further discovery, `FL` isn't used officially in any of the membership SOAPs. Skip to `AL`.
|
|
0x02: "Allow",
|
|
0x04: "Block",
|
|
0x08: "Reverse",
|
|
0x10: "Pending"
|
|
}
|
|
|
|
class ContactList(IntFlag):
|
|
Empty = 0x00
|
|
FL = 0x01
|
|
AL = 0x02
|
|
BL = 0x04
|
|
RL = 0x08
|
|
PL = 0x10
|
|
|
|
def __init__(self, id: int) -> None:
|
|
super().__init__()
|
|
self.label = MembershipLabels.get(id, "Undefined")
|
|
|
|
@classmethod
|
|
def Parse(cls, label: str) -> Optional['ContactList']:
|
|
if not hasattr(cls, '_MAP'):
|
|
label_map = {v.lower(): k for k, v in MembershipLabels.items()}
|
|
setattr(cls, '_MAP', label_map)
|
|
return cls._MAP.get(label.lower())
|
|
|
|
class NetworkID(IntEnum):
|
|
WINDOWS_LIVE = 0x01
|
|
OFFICE_COMMUNICATOR = 0x02 # E-mail in Skype too
|
|
ALIAS = 0x03
|
|
TELEPHONE = 0x04
|
|
DOMAIN = 0x05
|
|
SINK = 0x06
|
|
CONTACT = 0x07
|
|
MNI = 0x08 # Skype + Mobile Network Interop, used by Vodafone
|
|
CIRCLE = 0x09
|
|
TEMPORARYGROUP = 0x0A
|
|
CID = 0x0B
|
|
APPID = 0x0C
|
|
CONNECTUSER = 0x0D
|
|
CONNECTNETWORKS = 0x0E
|
|
SMTP = 0x10 # Jaguire, Japanese mobile interop
|
|
LIVEIDSINK = 0x11
|
|
MULTICAST = 0x12
|
|
THREAD = 0x13 # https://github.com/msndevs/protocol-docs/wiki/Threads-(Groupchats)
|
|
SHORTCIRCUIT = 0x14 # Unknown what this was used for
|
|
ONETOONETEXT = 0x15
|
|
GROUPTEXT = 0x16
|
|
BOT = 0x1C
|
|
YAHOO = 0x20
|
|
PUBSUBTOPIC = 0x21
|
|
PUBSUBSUBSCRIBER = 0x22
|
|
WNSSID = 0x23
|
|
|
|
class CircleRole(IntEnum):
|
|
Empty = 0
|
|
Admin = 1
|
|
AssistantAdmin = 2
|
|
Member = 3
|
|
StatePendingOutbound = 4
|
|
|
|
class ChatroomRole(IntEnum):
|
|
Member = 0
|
|
RoomOwner = 1
|
|
Admin = 2
|
|
Moderator = 3
|
|
Bot = 4 # maybe add user flags service-wide instead of doing this?
|
|
|
|
class CircleState(IntEnum):
|
|
Empty = 0
|
|
WaitingResponse = 1
|
|
Left = 2
|
|
Accepted = 3
|
|
Rejected = 4
|
|
|
|
class RelationshipType(IntEnum):
|
|
Circle = 5
|
|
|
|
class Service:
|
|
__slots__ = ('host', 'port')
|
|
|
|
host: str
|
|
port: int
|
|
|
|
def __init__(self, host: str, port: int) -> None:
|
|
self.host = host
|
|
self.port = port
|
|
|
|
class LoginOption(Enum):
|
|
BootOthers = object()
|
|
NotifyOthers = object()
|
|
Duplicate = object() |