mudpuppy
The mudpuppy
module offers helpful decorators built on top of mudpuppy_core
.
These decorators can be used to set up handlers for events, GMCP messages, and to create triggers, aliases and timers automatically at session creation time.
In most cases each global decorator has a variant that allows you to accomplish the
same task, but for one or more specific mudpuppy_core.Mud
names. For example,
on_event()
and on_mud_event()
, or on_connected()
and on_mud_connected()
.
Decorator to register an async mudpuppy_core.EventHandler
function as an event handler
for one or more event types.
The decorated function will be called with a mudpuppy_core.Event
object when the
specified mudpuppy_core.EventType
's are received.
The decorated function must be async or an error will be produced by the decorator.
If module
is None
, then EventHandler.__module__
will be used. The module
name is used only for unregistering handlers when the module is to be reloaded.
See unload_handlers()
for more information.
If you want to register an event handler only for a specific set of MUDs, use
on_mud_event()
instead. If you require more control over the event handler
registration process you may prefer to manually use mudpuppy_core.event_handlers
.
Example:
@on_event(mudpuppy_core.EventType.Subnegotiation)
async def telnet_subneg_receive(event: mudpuppy_core.Event):
assert isinstance(event, mudpuppy_core.Event.Subnegotiation)
if event.option != 42:
return
print(f"telnet CHARSET subneg. for session {event.id}: {event.data.hex()}")
Equivalent to on_event()
but will only be invoked when the event
occurs for a session with mudpuppy_core.SessionInfo.mud_name
equal to one
of the specified mud_name
's.
Example:
@on_mud_event(["Test (TLS)", "Test (Telnet)"], mudpuppy_core.EventType.Prompt)
async def prompt_handler(event: mudpuppy_core.Event):
assert isinstance(event, mudpuppy_core.Event.Prompt)
print(f"Test MUD received prompt: {str(event.prompt)}")
Decorator to register an async mudpuppy_core.EventHandler
function as a handler for the
mudpuppy_core.EventType.NewSession
event.
The decorated function will be called with a mudpuppy_core.Event.NewSession
object when a new session is created.
The decorated function must be async or an error will be produced by the decorator.
If module
is None
, then EventHandler.__module__
will be used. The module
name is used only for unregistering handlers when the module is to be reloaded.
See unload_handlers()
for more information.
Example:
@on_new_session()
async def new_session_handler(event: mudpuppy_core.Event):
assert instance(event, mudpuppy_core.Event.NewSession)
print(f"new {event.mud} session created: {event.id}")
The same as on_new_session()
but will only be invoked when the event
occurs for a session with mudpuppy_core.SessionInfo.mud_name
equal to one
of the specified mud_name
's.
Example:
@on_mud_new_session("Test (TLS)")
async def new_session_handler(event: mudpuppy_core.Event):
assert isinstance(event, mudpuppy_core.Event.NewSession)
assert event.mud.name == "Test (TLS)"
print(f"new {event.mud} session created: {event.id}")
Decorator to register an async mudpuppy_core.EventHandler
function as a handler for both
mudpuppy_core.EventType.NewSession
and mudpuppy_core.EventType.ResumeSession
events.
The decorated function will be called with a mudpuppy_core.Event.NewSession
or mudpuppy_core.Event.ResumeSession
object when a new session is created,
or when the module is reloaded for existing sessions.
The decorated function must be async or an error will be produced by the decorator.
If module
is None
, then EventHandler.__module__
will be used. The module
name is used only for unregistering handlers when the module is to be reloaded.
See unload_handlers()
for more information.
Example:
@on_new_session_or_reload()
async def new_or_resumed_handler(event: mudpuppy_core.Event):
if isinstance(event, mudpuppy_core.Event.ResumeSession):
print(f"resuming session: {event.id}")
elif isinstance(event, mudpuppy_core.Event.NewSession):
print(f"new {event.mud} session created: {event.id}")
The same as on_new_session_or_reload()
but will only be invoked when the event
occurs for a session with mudpuppy_core.SessionInfo.mud_name
equal to one
of the specified mud_name
's.
Decorator to register an async mudpuppy_core.EventHandler
function as a handler for the
mudpuppy_core.EventType.Connection
events that indicate the new connection
mudpuppy_core.Status
is mudpuppy_core.Status.Connected
.
The decorated function will be called with a mudpuppy_core.Event.Connection
object when a session is connected.
The decorated function must be async or an error will be produced by the decorator.
If module
is None
, then EventHandler.__module__
will be used. The module
name is used only for unregistering handlers when the module is to be reloaded.
See unload_handlers()
for more information.
Example:
@on_connected()
async def connected_handler(event: mudpuppy_core.Event):
assert isinstance(event, mudpuppy_core.Event.Connection)
assert isinstance(event.status, mudpuppy_core.Status.Connected)
print(f"session {event.id} connected: {event.status}")
The same as on_connected()
but will only be invoked when the event
occurs for a session with mudpuppy_core.SessionInfo.mud_name
equal to one
of the specified mud_name
's.
Decorator to register an async mudpuppy_core.EventHandler
function as a handler for the
mudpuppy_core.EventType.Connection
events that indicate the new connection
mudpuppy_core.Status
is mudpuppy_core.Status.Disconnected
.
The decorated function will be called with a mudpuppy_core.Event.Connection
object when a session is disconnected.
The decorated function must be async or an error will be produced by the decorator.
If module
is None
, then EventHandler.__module__
will be used. The module
name is used only for unregistering handlers when the module is to be reloaded.
See unload_handlers()
for more information.
Example:
@on_disconnected()
async def disconnected_handler(event: mudpuppy_core.Event):
assert isinstance(event, mudpuppy_core.Event.Connection)
assert isinstance(event.status, mudpuppy_core.Status.Disconnected)
print(f"session {event.id} disconnected")
The same as on_disconnected()
but will only be invoked when the event
occurs for a session with mudpuppy_core.SessionInfo.mud_name
equal to one
of the specified mud_name
's.
An async function that receives an int
session ID and GMCP data as its arguments.
Example:
async def my_gmcp_handler(session_id: int, data: Any):
print(f"my_gmcp_handler session {session_id} got GMCP data {data}")
Decorator to register a GmcpHandler
as a handler for a specific GMCP package.
You will typically need to register intent for that GMCP package using
mudpuppy_core.MudpuppyCore.gmcp_register()
after checking GMCP has been negotiated with
mudpuppy_core.MudpuppyCore.gmcp_enabled()
, or in response to a
mudpuppy_core.EventType.GmcpEnabled
event.
The decorated function will be called with a int
session ID and the
GMCP message data whenever a mudpuppy_core.EventType.GmcpMessage
event for the
specified package is received.
If module
is None
, then GmcpHandler.__module__
will be used. The module
name is used only for unregistering handlers when the module is to be reloaded.
See unload_handlers()
for more information.
Example:
# Make sure the "Char" package is registered
@on_event(mudpuppy_core.EventType.GmcpEnabled):
async def gmcp_ready(event: mudpuppy_core.Event):
await mudpuppy_core.gmcp_register(event.id, "Char")
# Handle the "Char.Vitals" GMCP message
@on_gmcp("Char.Vitals")
async def gmcp_vitals(session_id: int, data: Any):
hp = data.get("hp", 0)
printf(f"gmcp_vitals: session {session_id} character has {hp} hp")
Decorator to register an async mudpuppy_core.AliasCallable
function as an alias handler for
a specific pattern. A mudpuppy_core.Alias
will be automatically created for the decorated
function when mudpuppy_core.EventType.NewSession
and
mudpuppy_core.EventType.ResumeSession
events occur. If no name
is provided, the name of
the decorated function is used as the alias name.
When input is provided matching the compiled regexp pattern
the decorated function
will be invoked with the int
session ID of the session that received the
input, the int
alias ID of the mudpuppy_core.Alias
that matched, the
input line that matched, and a list of captured groups from the pattern (if any).
The pattern
must be a valid regular expression. You can read more about the allowed
syntax in the regexp
crate docs. Note
that for performance reasons the Python regex
module is not used - the pattern
is used to create a Rust regular expression object. By using group syntax you will
receive the matched groups as a separate list argument to the decorated function.
An optional expansion
string may be provided. When the alias matches the
expansion
will be sent to the MUD as input. This is a convenient shorthand
for writing await mudpuppy_core.MudpuppyCore.send_line(session_id, expansion)
in the body of your mudpuppy_core.AliasCallable
.
An optional max_hits
integer may be provided. If set, the alias will only be
invoked max_hits
times before being automatically disabled with
mudpuppy_core.MudpuppyCore.disable_alias()
.
If a mud_name
, or list of mud_name
's are provided then the alias will only be
registered for sessions with the specified mud_name
's.
If module
is None
, then AliasCallable.__module__
will be used. The module
name is used only for unregistering handlers when the module is to be reloaded.
See unload_handlers()
for more information.
If you want more control over the alias creation process (for example, because
you want to add an alias sometime after session creation or wish to store the
int
alias ID of the created alias) you should prefer to instantiate
your own mudpuppy_core.AliasConfig
to use with
mudpuppy_core.MudpuppyCore.new_alias()
.
Example:
import asyncio
from mudpuppy_core import mudpuppy_core
@alias(mud_name="Test (TLS)", pattern="^kill (.*)$")
async def start_combat(session_id: int, _alias_id: int, _line: str, groups: list[str]):
assert len(groups) == 1
target = groups[0]
await mudpuppy_core.send(session_id, f"backstab {target}")
await asyncio.sleep(10)
await mudpuppy_core.send(session_id, f"steal from {target}")
Decorator to register an async mudpuppy_core.TriggerCallable
function as a trigger handler for
a specific pattern. A mudpuppy_core.Trigger
will be automatically created for the decorated
function when mudpuppy_core.EventType.NewSession
and
mudpuppy_core.EventType.ResumeSession
events occur. If no name
is provided, the name of
the decorated function is used as the trigger name.
When output is received matching the compiled regexp pattern
the decorated function
will be invoked with the int
session ID of the session that received the
output, the int
trigger ID of the mudpuppy_core.Trigger
that matched, the
output line that matched, and a list of captured groups from the pattern (if any).
The pattern
must be a valid regular expression. You can read more about the allowed
syntax in the regexp
crate docs. Note
that for performance reasons the Python regex
module is not used - the pattern
is used to create a Rust regular expression object. By using group syntax you will
receive the matched groups as a separate list argument to the decorated function.
If gag
is True
, then the line that matched the trigger's pattern
will be
gagged and not displayed in the MUD output area. This is useful for suppressing
text from the MUD server you don't want to see (e.g. because it's spammy, or because
you're post-processing it into something easier to understand that you'll output
manually).
If strip_ansi
is True
(default), then the line that matched the trigger's pattern
will have ANSI escape codes stripped before being passed to the decorated function.
You typically want strip_ansi
enabled so that your pattern
regexp doesn't have to
match the ANSI colour codes that may be decorating output. If you do want to write
a pattern that only matches text in a specific colour you can set strip_ansi=False
.
If prompt
is True
, then the trigger will only match if the line was detected as
a prompt. The pattern
must also match. Writing triggers with prompt=True
is an
alternative to registering a mudpuppy_core.EventType.Prompt
event handlers and can
be useful if you want to gag a prompt by setting both prompt=True
and gag=True
and writing a matching pattern
.
An optional expansion
string may be provided. When the trigger matches the
expansion
will be sent to the MUD as input. This is a convenient shorthand
for writing await mudpuppy_core.MudpuppyCore.send_line(session_id, expansion)
in the body of your mudpuppy_core.TriggerCallable
.
An optional max_hits
integer may be provided. If set, the trigger will only be
invoked max_hits
times before being automatically disabled with
mudpuppy_core.MudpuppyCore.disable_trigger()
.
If a mud_name
, or list of mud_name
's are provided then the trigger will only be
registered for sessions with the specified mud_name
's.
If module
is None
, then TriggerCallable.__module__
will be used. The module
name is used only for unregistering handlers when the module is to be reloaded.
See unload_handlers()
for more information.
If you want more control over the trigger creation process (for example, because
you want to add a trigger sometime after session creation or wish to store the
int
trigger ID of the created trigger) you should prefer to instantiate
your own mudpuppy_core.TriggerConfig
to use with
mudpuppy_core.MudpuppyCore.new_trigger()
.
Example:
from mudpuppy_core import mudpuppy_core
from mudpuppy import trigger
@trigger(pattern="^You wear (.*)$")
async def auto_keep(
session_id: int, _trigger_id: int, _line: str, groups: list[str]
):
await mudpuppy_core.send_line(session_id, f"keep {groups[0]}")
Decorator to register a non-async mudpuppy_core.HighlightCallable
function as a highlight
handler for a specific pattern. A mudpuppy_core.Trigger
for the highlight
will be automatically created for the decorated function when
mudpuppy_core.EventType.NewSession
and mudpuppy_core.EventType.ResumeSession
events occur. If no name
is provided, the name of the decorated function is used as
the highlight trigger name.
When output is received matching the compiled regexp pattern
the decorated function
will be invoked with the mudpuppy_core.MudLine
that matched the pattern
,
and a list of captured groups from the pattern (if any).
The pattern
must be a valid regular expression. You can read more about the allowed
syntax in the regexp
crate docs. Note
that for performance reasons the Python regex
module is not used - the pattern
is used to create a Rust regular expression object. By using group syntax you will
receive the matched groups as a separate list argument to the decorated function.
If strip_ansi
is True
(default), then the line that matched the highlight
trigger's pattern
will have ANSI escape codes stripped before being passed to the
decorated function. You typically want strip_ansi
enabled so that your pattern
regexp doesn't have to match the ANSI colour codes that may be decorating output.
If you do want to write a pattern that only matches text in a specific colour
you can set strip_ansi=False
.
If a mud_name
, or list of mud_name
's are provided then the trigger will only be
registered for sessions with the specified mud_name
's.
If module
is None
, then HighlightCallable.__module__
will be used. The module
name is used only for unregistering handlers when the module is to be reloaded.
See unload_handlers()
for more information.
If you want more control over the trigger creation process (for example, because
you want to add a trigger sometime after session creation or wish to store the
int
trigger ID of the created trigger) you should prefer to instantiate
your own mudpuppy_core.TriggerConfig
to use with
mudpuppy_core.MudpuppyCore.add_trigger()
.
Example:
from mudpuppy import highlight
@highlight(pattern=r"^(\d+) solaris$")
def solaris_highlight(line: mudpuppy_core.MudLine, groups: list[str]):
assert len(groups) == 1
# Highlight the total in bold
hilight = line.__str__().replace(
groups[0], cformat(f"<bold><yellow>{groups[0]}<reset><yellow>")
)
# And then overall line in yellow
hilight = cformat(f"<yellow>{hilight}<reset>")
line.set(hilight)
return line
An async function that is called when a timer expires. See mudpuppy.timer()
for the
associated @timer()
decorator.
The handler is called with:
- the
int
timer ID of the timer that expired. - either
None
, or aint
session ID if themudpuppy_core.Timer
was created for a specific session.
Example:
from mudpuppy_core import mudpuppy_core
async def my_timer_handler(timer_id: int, sesh: Optional[int]):
timer: mudpuppy_core.Timer = await mudpuppy_core.get_timer(sesh, timer_id)
print(f"trigger {timer.config.name} has fired")
if randint(1, 3) == 1:
print(f"stopping timer {timer_id}")
await mudpuppy_core.stop_timer(sesh, timer_id)
Decorator to register an async TimerCallable
function as a timer handler run every
specified duration. A mudpuppy_core.Timer
will be automatically created for the decorated
function when mudpuppy_core.EventType.NewSession
and
mudpuppy_core.EventType.ResumeSession
events occur. If no name
is provided, the name of
the decorated function is used as the timer name.
A duration is calculated based on the milliseconds
, seconds
, minutes
, and hours
arguments. After the duration has expired the TimerCallable
is invoked. This will
happen over and over until the timer is stopped with mudpuppy_core.MudpuppyCore.stop_timer()
or until the optional max_ticks
value is reached.
If a mud_name
, or list of mud_name
's are provided then the timer will only be
registered for sessions with the specified mud_name
's.
If module
is None
, then TimerCallable.__module__
will be used. The module
name is used only for unregistering handlers when the module is to be reloaded.
See unload_handlers()
for more information.
If you want more control over the timer creation process (for example, because
you want to add a timer sometime after session creation or wish to store the
int
timer ID of the created timer) you should prefer to instantiate
your own mudpuppy_core.TimerConfig
to use with
mudpuppy_core.MudpuppyCore.new_timer()
.
Example:
from mudpuppy_core import mudpuppy_core
from mudpuppy import timer
@timer(minutes=10, seconds=30)
async def auto_save(
_timer_id: int, session_id: Optional[int]
):
assert session_id is not None
await mudpuppy_core.send_line(session_id, "save")
Wrap a mudpuppy_core.AliasCallable
to limit the number of times it can be invoked.
Example:
from mudpuppy_core import mudpuppy_core, AliasConfig
from mudpuppy import alias_max_hits
async def my_alias_handler(_session_id: int, alias_id: int, line: str, _groups: list[str]):
print(f"alias {alias_id} matched line: {line}")
# Wrap the handler w/ mudpuppy.alias_max_hits to limit to 3 hits
alias_config = AliasConfig("^test$", "test alias", handler=alias_max_hits(handler=my_alias_handler, max_hits=3))
alias_id = await mudpuppy_core.new_alias(alias_config)
Wrap a mudpuppy_core.TriggerCallable
to limit the number of times it can be invoked.
Example:
from mudpuppy_core import mudpuppy_core, TriggerConfig
from mudpuppy import trigger_max_hits
async def my_trigger_handler(_session_id: int, trigger_id: int, line: str, _groups: list[str]):
print(f"trigger {trigger_id} matched line: {line}")
# Wrap the handler w/ mudpuppy.trigger_max_hits to limit to 3 hits
trigger_config = TriggerConfig("^test$", "test trigger", handler=trigger_max_hits(handler=my_trigger_handler, max_hits=3))
trigger_id = await mudpuppy_core.new_trigger(trigger_config)
Unregister all event handlers, GMCP handlers, triggers, aliases, and timers registered by the specified module.
This is useful to call ahead of a module reload to ensure that no handlers are left registered for the old module.
The module
name should be the same as the module
argument passed to the
various handler decorators at the time of registration.