Skip to content

Commit 72982f7

Browse files
committed
Add Client.rules(), make Client.prefix public, make len_type mandatory
1 parent 1c8bcf1 commit 72982f7

File tree

2 files changed

+58
-16
lines changed

2 files changed

+58
-16
lines changed

main.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ async def main(*args: str) -> str | None:
2222
print('More than 100 players online, no info returned')
2323

2424
print('Uses open.mp:', await client.is_omp())
25+
print(await client.rules())
2526
return None
2627

2728

samp_query/__init__.py

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import trio
88

99

10-
def unpack_string(data: bytes, len_type: str = 'I') -> tuple[str, bytes]:
10+
def unpack_string(data: bytes, len_type: str) -> tuple[str, bytes]:
1111
format = f'<{len_type}'
1212
size = struct.calcsize(format)
1313
str_len, data = *struct.unpack_from(format, data), data[size:]
@@ -29,9 +29,9 @@ class ServerInfo:
2929
def from_data(cls, data: bytes) -> ServerInfo:
3030
password, players, max_players = struct.unpack_from('<?HH', data)
3131
data = data[5:] # _Bool + short + short, see above
32-
name, data = unpack_string(data)
33-
gamemode, data = unpack_string(data)
34-
language, data = unpack_string(data)
32+
name, data = unpack_string(data, 'I')
33+
gamemode, data = unpack_string(data, 'I')
34+
language, data = unpack_string(data, 'I')
3535

3636
assert not data # We consumed all the buffer
3737

@@ -81,14 +81,49 @@ def from_data(cls, data: bytes) -> PlayerList:
8181
return cls(players=players)
8282

8383

84+
@dataclass
85+
class Rule:
86+
name: str
87+
value: str
88+
89+
@classmethod
90+
def from_data(cls, data: bytes) -> tuple[Rule, bytes]:
91+
name, data = unpack_string(data, 'B')
92+
value, data = unpack_string(data, 'B')
93+
94+
return cls(
95+
name=name,
96+
value=value,
97+
), data
98+
99+
100+
@dataclass
101+
class RuleList:
102+
rules: list[Rule]
103+
104+
@classmethod
105+
def from_data(cls, data: bytes) -> RuleList:
106+
rule_count = struct.unpack_from('<H', data)[0]
107+
data = data[2:] # short, see above
108+
rules = []
109+
110+
for _ in range(rule_count):
111+
rule, data = Rule.from_data(data)
112+
rules.append(rule)
113+
114+
assert not data # We consumed all the buffer
115+
116+
return cls(rules=rules)
117+
118+
84119
@dataclass
85120
class Client:
86121
ip: str
87122
port: int
88123
rcon_password: str | None = field(default=None, repr=False)
89124

125+
prefix: bytes | None = field(default=None, repr=False)
90126
_socket: trio.socket.SocketType | None = field(default=None, repr=False)
91-
_prefix: bytes | None = field(default=None, repr=False)
92127

93128
async def connect(self) -> None:
94129
family, type, proto, _, (ip, *_) = (await trio.socket.getaddrinfo(
@@ -100,7 +135,7 @@ async def connect(self) -> None:
100135
self.ip = ip
101136
self._socket = socket = trio.socket.socket(family, type, proto)
102137
await socket.connect((self.ip, self.port))
103-
self._prefix = (
138+
self.prefix = (
104139
b'SAMP'
105140
+ trio.socket.inet_aton(self.ip)
106141
+ self.port.to_bytes(2, 'little')
@@ -110,8 +145,8 @@ async def send(self, opcode: bytes, payload: bytes = b'') -> None:
110145
if not self._socket:
111146
await self.connect()
112147

113-
assert self._socket and self._prefix
114-
await self._socket.send(self._prefix + opcode + payload)
148+
assert self._socket and self.prefix
149+
await self._socket.send(self.prefix + opcode + payload)
115150

116151
async def receive(self, header: bytes = b'') -> bytes:
117152
assert self._socket
@@ -126,8 +161,8 @@ async def ping(self) -> float:
126161
payload = random.getrandbits(32).to_bytes(4, 'little')
127162
start_time = trio.current_time()
128163
await self.send(b'p', payload)
129-
assert self._prefix
130-
data = await self.receive(header=self._prefix + b'p' + payload)
164+
assert self.prefix
165+
data = await self.receive(header=self.prefix + b'p' + payload)
131166
assert not data # No data beyond expected header
132167
return trio.current_time() - start_time
133168

@@ -138,20 +173,26 @@ async def is_omp(self) -> bool:
138173
# Assuming latency variance is less than 100%
139174
with trio.move_on_after(2 * ping):
140175
await self.send(b'o', payload)
141-
assert self._prefix
142-
await self.receive(header=self._prefix + b'o' + payload)
176+
assert self.prefix
177+
await self.receive(header=self.prefix + b'o' + payload)
143178
return True
144179

145180
return False
146181

147182
async def info(self) -> ServerInfo:
148183
await self.send(b'i')
149-
assert self._prefix
150-
data = await self.receive(header=self._prefix + b'i')
184+
assert self.prefix
185+
data = await self.receive(header=self.prefix + b'i')
151186
return ServerInfo.from_data(data)
152187

153188
async def players(self) -> PlayerList:
154189
await self.send(b'c')
155-
assert self._prefix
156-
data = await self.receive(header=self._prefix + b'c')
190+
assert self.prefix
191+
data = await self.receive(header=self.prefix + b'c')
157192
return PlayerList.from_data(data)
193+
194+
async def rules(self) -> RuleList:
195+
await self.send(b'r')
196+
assert self.prefix
197+
data = await self.receive(header=self.prefix + b'r')
198+
return RuleList.from_data(data)

0 commit comments

Comments
 (0)