Files
azul/front/oscar/foodgroups/buddy.py
T
Athena Funderburg 21f38ee3e1 production init
2026-05-26 16:41:23 +00:00

112 lines
4.6 KiB
Python

import struct
import time
from core.models import Contact
from util.misc import Logger
from ..proto.snac import OSCARClient, OSCARContext, SNACMessage, Foodgroup, Subgroup
from ..proto.tlv import TLV, unmarshal_tlvs
# should be piped to .ctrl.send_snac()
def build_presence_notif(contact: Contact) -> SNACMessage:
# Packet dump from NINA's servers when a contact goes online:
# ===
# 0000 04 64 6b 61 79 00 00 00 07 00 30 00 04 67 19 67 .dkay.....0..g.g
# 0010 4c 00 0d 00 c0 09 46 13 45 4c 7f 11 d1 82 22 44 L.....F.EL...."D
# 0020 45 53 54 00 00 09 46 01 ff 4c 7f 11 d1 82 22 44 EST...F..L...."D
# 0030 45 53 54 00 00 74 8f 24 20 62 87 11 d1 82 22 44 EST..t.$ b...."D
# 0040 45 53 54 00 00 09 46 13 43 4c 7f 11 d1 82 22 44 EST...F.CL...."D
# 0050 45 53 54 00 00 09 46 13 41 4c 7f 11 d1 82 22 44 EST...F.AL...."D
# 0060 45 53 54 00 00 09 46 01 04 4c 7f 11 d1 82 22 44 EST...F..L...."D
# 0070 45 53 54 00 00 09 46 01 05 4c 7f 11 d1 82 22 44 EST...F..L...."D
# 0080 45 53 54 00 00 09 46 01 02 4c 7f 11 d1 82 22 44 EST...F..L...."D
# 0090 45 53 54 00 00 09 46 01 03 4c 7f 11 d1 82 22 44 EST...F..L...."D
# 00a0 45 53 54 00 00 09 46 01 01 4c 7f 11 d1 82 22 44 EST...F..L...."D
# 00b0 45 53 54 00 00 09 46 13 4a 4c 7f 11 d1 82 22 44 EST...F.JL...."D
# 00c0 45 53 54 00 00 09 46 13 46 4c 7f 11 d1 82 22 44 EST...F.FL...."D
# 00d0 45 53 54 00 00 00 1d 00 12 00 01 00 05 02 01 d2 EST.............
# 00e0 04 72 00 00 00 05 02 01 d2 04 72 00 01 00 02 00 .r........r.....
# 00f0 08 00 03 00 04 67 19 67 4b 00 0f 00 04 00 00 00 .....g.gK.......
# 0100 02 00 05 00 04 62 7c 7a cd .....b|z.
#
# Buddy length: 0x04
# Buddy name: dkay
# TLVs (7):
# - TLV 0x3000: unknown (data = 67 19 67 4C)
# - TLV 0x000D: capability list
# - TLV 0x001D: BART info (data = 00 01 00 05 02 01 D2 04 72 00 00 00 05 02 01 D2 04 72
# - TLV 0x0001: user class (data = 00 00 00 08)
# - TLV 0x0003: account signon time (value = 1729718091, in UNIX time)
# - TLV 0x000F: session length (value = 2)
# - TLV 0x0005: account creation time (value = 1652325069, in UNIX time)
#
msg = SNACMessage(0x0003, 0x000B if not contact.status.is_offlineish() else 0x000C)
msg.write_string_u8(contact.head.username)
msg.write_u16(0)
if contact.status.is_offlineish():
msg.write_tlv(TLV(0x0001, struct.pack('>L', 0x0008))) # User class (bitfield)
else:
capabilities = [
"{09461345-4C7F-11D1-8222-444553540000}", # Direct ICBM
"{094601FF-4C7F-11D1-8222-444553540000}", # Smart caps
"{748F2420-6287-11D1-8222-444553540000}", # Chat
"{09461343-4C7F-11D1-8222-444553540000}", # File transfer
"{09461341-4C7F-11D1-8222-444553540000}", # Voice chat
"{09460104-4C7F-11D1-8222-444553540000}", # RTC audio
"{09460105-4C7F-11D1-8222-444553540000}", # Unknown
"{09460102-4C7F-11D1-8222-444553540000}", # Camera
"{09460103-4C7F-11D1-8222-444553540000}", # Microphone
"{09460101-4C7F-11D1-8222-444553540000}", # RTC video
"{0946134A-4C7F-11D1-8222-444553540000}", # Games
"{09461346-4C7F-11D1-8222-444553540000}" # BART
]
capabilities_bytes = b''
for capability in capabilities:
capabilities_bytes += bytes.fromhex(capability
.lstrip('{')
.rstrip('}')
.replace('-', ''))
date_created_unix = int(time.mktime(contact.head.date_created.timetuple()))
date_login_unix = int(time.mktime(contact.head.date_login.timetuple())) if contact.head.date_login else 0
# usually would include 0x001D (BART info), but since CrossTalk doesn't support
# it rn I've decided to omit it
msg.write_tlv_block([
TLV(0x3000, struct.pack('>L', 0x6719674C)), # Unknown
TLV(0x000D, capabilities_bytes), # Capability info
TLV(0x0001, struct.pack('>H', 0x0008)), # User class (bitfield)
TLV(0x0003, struct.pack('>L', date_login_unix)), # Account signon time (unix time_t)
TLV(0x000F, struct.pack('>L', 0)), # Session length
TLV(0x0005, struct.pack('>L', date_created_unix)), # Account creation time (unix time_t)
])
print('Subgroup:', hex(msg.subgroup))
print('TLVs:', unmarshal_tlvs(msg.data))
return msg
@Foodgroup(0x0003)
class BuddyFoodgroup:
logger: Logger
@Subgroup(0x0002)
def rights_query(self, client: OSCARClient, context: OSCARContext, message: SNACMessage) -> None:
self.logger.info('[Client] BUDDY_RIGHTS_QUERY')
response_msg = SNACMessage(0x0003, 0x0003)
response_msg.write_tlvs([
TLV(0x0001, struct.pack('>H', 1000)), # max buddies
TLV(0x0002, struct.pack('>H', 3000)), # max watchers
TLV(0x0004, struct.pack('>H', 160)) # max temp buddies
])
self.logger.info('[Server] BUDDY_RIGHTS_REPLY')
client.send_snac(response_msg)