-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathclient.py
More file actions
executable file
·202 lines (162 loc) · 6.5 KB
/
client.py
File metadata and controls
executable file
·202 lines (162 loc) · 6.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/usr/bin/env python3
"""
Cocapn MUD Client — Programmatic interface for agents.
Usage:
python3 client.py --name oracle1 --role lighthouse
Or as a library:
async with MUDClient("oracle1") as mud:
await mud.say("Anyone want to hash out ISA v3?")
"""
import asyncio
import sys
import argparse
class MUDClient:
"""Async MUD client for programmatic agent access.
Provides a Python interface for connecting to a MUD server
and executing commands programmatically, useful for bots,
automated agents, and testing scripts.
Example usage:
async with MUDClient("oracle1") as mud:
await mud.say("Anyone want to hash out ISA v3?")
"""
def __init__(self, name: str, role: str = "", host: str = "localhost", port: int = 7777) -> None:
"""Initialize MUD client.
Args:
name: Agent name for login
role: Agent role (optional)
host: MUD server host (default: localhost)
port: MUD server port (default: 7777)
"""
self.name = name
self.role = role
self.host = host
self.port = port
self.reader = None
self.writer = None
self._buffer: List[str] = []
async def __aenter__(self):
self.reader, self.writer = await asyncio.open_connection(self.host, self.port)
# Read welcome prompt
await self.reader.readline() # "Welcome..."
await self.reader.readline() # "What is your name?"
# Send name
self.writer.write(f"{self.name}\n".encode())
await self.writer.drain()
# Read role prompt
await self.reader.readline() # "Role?"
self.writer.write(f"{self.role}\n".encode())
await self.writer.drain()
return self
async def __aexit__(self, *args):
if self.writer:
self.writer.write(b"quit\n")
await self.writer.drain()
self.writer.close()
async def _send(self, cmd: str) -> str:
"""Send a command and return the response.
Args:
cmd: Command string to send to MUD
Returns:
str: The response from the server
Raises:
ConnectionError: If connection is lost
"""
if not self.writer:
raise ConnectionError("Not connected to MUD server")
self.writer.write(f"{cmd}\n".encode())
await self.writer.drain()
# Read response(s) until prompt or timeout
lines: List[str] = []
try:
while True:
line = await asyncio.wait_for(self.reader.readline(), timeout=1.0)
if not line:
break
decoded = line.decode().strip()
if decoded:
lines.append(decoded)
except asyncio.TimeoutError:
pass
except ConnectionResetError:
raise ConnectionError("Connection reset by server")
return "\n".join(lines)
async def say(self, text: str) -> str:
"""Send a say command (room-wide message)."""
return await self._send(f'say {text}')
async def tell(self, target: str, text: str) -> str:
"""Send a tell command (private message to an agent)."""
return await self._send(f'tell {target} {text}')
async def gossip(self, text: str) -> str:
"""Send a gossip command (ship-wide broadcast)."""
return await self._send(f'gossip {text}')
async def ooc(self, text: str) -> str:
"""Send an OOC command (out-of-character message)."""
return await self._send(f'ooc {text}')
async def emote(self, action: str) -> str:
"""Send an emote command (roleplay action)."""
return await self._send(f'emote {action}')
async def go(self, exit_name: str) -> str:
"""Move to an adjacent room via an exit."""
return await self._send(f'go {exit_name}')
async def look(self) -> str:
"""Look around the current room."""
return await self._send('look')
async def build(self, name: str, desc: str) -> str:
"""Create a new room."""
return await self._send(f'build "{name}" -desc "{desc}"')
async def write_note(self, text: str) -> str:
"""Write a note on the room's wall."""
return await self._send(f'write {text}')
async def read_notes(self) -> str:
"""Read notes from the room's wall."""
return await self._send('read')
async def mask(self, name: str, desc: str = "") -> str:
"""Put on a character mask."""
cmd = f'mask "{name}"'
if desc:
cmd += f' -desc "{desc}"'
return await self._send(cmd)
async def unmask(self) -> str:
"""Remove your character mask."""
return await self._send('unmask')
async def spawn_npc(self, name: str, role: str = "", topic: str = "") -> str:
"""Spawn a constructed NPC."""
cmd = f'spawn "{name}"'
if role:
cmd += f' -role "{role}"'
if topic:
cmd += f' -topic "{topic}"'
return await self._send(cmd)
async def dismiss_npc(self, name: str) -> str:
"""Dismiss a constructed NPC."""
return await self._send(f'dismiss {name}')
async def who(self) -> str:
"""List all connected agents."""
return await self._send('who')
async def interactive(name: str, role: str, host: str, port: int) -> None:
"""Run an interactive session (like telnet but with name auto-filled).
Args:
name: Agent name to use
role: Agent role
host: MUD server host
port: MUD server port
"""
async with MUDClient(name, role, host, port) as mud:
print(f"Connected as {name}. Type 'help' for commands.")
loop = asyncio.get_event_loop()
async def read_input():
while True:
line = await loop.run_in_executor(None, input, "")
if line.strip():
result = await mud._send(line.strip())
if result:
print(result)
await read_input()
if __name__ == "__main__":
parser = argparse.ArgumentParser(description="Cocapn MUD Client")
parser.add_argument("--name", required=True)
parser.add_argument("--role", default="")
parser.add_argument("--host", default="localhost")
parser.add_argument("--port", type=int, default=7777)
args = parser.parse_args()
asyncio.run(interactive(args.name, args.role, args.host, args.port))