Add docstrings to Bot functions
This commit is contained in:
parent
eb9cc4f9bd
commit
6a15e1a948
2 changed files with 180 additions and 1 deletions
|
|
@ -2,7 +2,8 @@
|
||||||
|
|
||||||
## Next version
|
## Next version
|
||||||
|
|
||||||
Nothing yet
|
- add docstrings to `Bot`
|
||||||
|
- change `KILL_REPLY` and `RESTART_REPLY` to be optional in `Bot`
|
||||||
|
|
||||||
## 1.1.3 (2019-04-19)
|
## 1.1.3 (2019-04-19)
|
||||||
|
|
||||||
|
|
|
||||||
178
yaboli/bot.py
178
yaboli/bot.py
|
|
@ -14,6 +14,36 @@ logger = logging.getLogger(__name__)
|
||||||
__all__ = ["Bot", "BotConstructor"]
|
__all__ = ["Bot", "BotConstructor"]
|
||||||
|
|
||||||
class Bot(Client):
|
class Bot(Client):
|
||||||
|
"""
|
||||||
|
A Bot is a Client that responds to commands and uses a config file to
|
||||||
|
automatically set its nick and join rooms.
|
||||||
|
|
||||||
|
The config file is loaded as a ConfigParser by the run() or run_modulebot()
|
||||||
|
functions and has the following structure:
|
||||||
|
|
||||||
|
A "general" section which contains:
|
||||||
|
- nick - the default nick of the bot (set to the empty string if you don't
|
||||||
|
want to set a nick)
|
||||||
|
- cookie_file (optional) - the file the cookie should be saved in
|
||||||
|
|
||||||
|
A "rooms" section which contains a list of rooms that the bot should
|
||||||
|
automatically join. This section is optional if you overwrite started().
|
||||||
|
The room list should have the format "roomname" or "roomname = password".
|
||||||
|
|
||||||
|
A bot has the following attributes:
|
||||||
|
- ALIASES - list of alternate nicks the bot responds to (see
|
||||||
|
process_commands())
|
||||||
|
- PING_REPLY - used by cmd_ping()
|
||||||
|
- HELP_GENERAL - used by cmd_help_general()
|
||||||
|
- HELP_SPECIFIC - used by cmd_help_specific()
|
||||||
|
- KILL_REPLY - used by cmd_kill()
|
||||||
|
- RESTART_REPLY - used by cmd_restart()
|
||||||
|
- GENERAL_SECTION - the name of the "general" section in the config file
|
||||||
|
(see above) (default: "general")
|
||||||
|
- ROOMS_SECTION - the name of the "rooms" section in the config file (see
|
||||||
|
above) (default: "rooms")
|
||||||
|
"""
|
||||||
|
|
||||||
ALIASES: List[str] = []
|
ALIASES: List[str] = []
|
||||||
|
|
||||||
PING_REPLY: str = "Pong!"
|
PING_REPLY: str = "Pong!"
|
||||||
|
|
@ -50,10 +80,26 @@ class Bot(Client):
|
||||||
self.start_time = datetime.datetime.now()
|
self.start_time = datetime.datetime.now()
|
||||||
|
|
||||||
def save_config(self) -> None:
|
def save_config(self) -> None:
|
||||||
|
"""
|
||||||
|
Save the current state of self.config to the file passed in __init__ as
|
||||||
|
the config_file parameter.
|
||||||
|
|
||||||
|
Usually, this is the file that self.config was loaded from (if you use
|
||||||
|
run or run_modulebot).
|
||||||
|
"""
|
||||||
|
|
||||||
with open(self.config_file, "w") as f:
|
with open(self.config_file, "w") as f:
|
||||||
self.config.write(f)
|
self.config.write(f)
|
||||||
|
|
||||||
async def started(self) -> None:
|
async def started(self) -> None:
|
||||||
|
"""
|
||||||
|
This Client function is overwritten in order to join all the rooms
|
||||||
|
listed in the "rooms" section of self.config.
|
||||||
|
|
||||||
|
If you need to overwrite this function but want to keep the auto-join
|
||||||
|
functionality, make sure to await super().started().
|
||||||
|
"""
|
||||||
|
|
||||||
for room, password in self.config[self.ROOMS_SECTION].items():
|
for room, password in self.config[self.ROOMS_SECTION].items():
|
||||||
if password is None:
|
if password is None:
|
||||||
await self.join(room)
|
await self.join(room)
|
||||||
|
|
@ -63,6 +109,12 @@ class Bot(Client):
|
||||||
# Registering commands
|
# Registering commands
|
||||||
|
|
||||||
def register(self, command: Command) -> None:
|
def register(self, command: Command) -> None:
|
||||||
|
"""
|
||||||
|
Register a Command (from the yaboli.command submodule).
|
||||||
|
|
||||||
|
Usually, you don't have to call this function yourself.
|
||||||
|
"""
|
||||||
|
|
||||||
self._commands.append(command)
|
self._commands.append(command)
|
||||||
|
|
||||||
def register_general(self,
|
def register_general(self,
|
||||||
|
|
@ -70,6 +122,23 @@ class Bot(Client):
|
||||||
cmdfunc: GeneralCommandFunction,
|
cmdfunc: GeneralCommandFunction,
|
||||||
args: bool = True
|
args: bool = True
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Register a function as general bot command (i. e. no @mention of the
|
||||||
|
bot nick after the !command). This function will be called by
|
||||||
|
process_commands() when the bot encounters a matching command.
|
||||||
|
|
||||||
|
name - the name of the command (If you want your command to be !hello,
|
||||||
|
the name is "hello".)
|
||||||
|
|
||||||
|
cmdfunc - the function that is called with the Room, LiveMessage and
|
||||||
|
ArgumentData when the bot encounters a matching command
|
||||||
|
|
||||||
|
args - whether the command may have arguments (If set to False, the
|
||||||
|
ArgumentData's has_args() function must also return False for the
|
||||||
|
command function to be called. If set to True, all ArgumentData is
|
||||||
|
valid.)
|
||||||
|
"""
|
||||||
|
|
||||||
command = GeneralCommand(name, cmdfunc, args)
|
command = GeneralCommand(name, cmdfunc, args)
|
||||||
self.register(command)
|
self.register(command)
|
||||||
|
|
||||||
|
|
@ -78,6 +147,21 @@ class Bot(Client):
|
||||||
cmdfunc: SpecificCommandFunction,
|
cmdfunc: SpecificCommandFunction,
|
||||||
args: bool = True
|
args: bool = True
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Register a function as specific bot command (i. e. @mention of the bot
|
||||||
|
nick after the !command is required). This function will be called by
|
||||||
|
process_commands() when the bot encounters a matching command.
|
||||||
|
|
||||||
|
name - the name of the command (see register_general() for an
|
||||||
|
explanation)
|
||||||
|
|
||||||
|
cmdfunc - the function that is called with the Room, LiveMessage and
|
||||||
|
SpecificArgumentData when the bot encounters a matching command
|
||||||
|
|
||||||
|
args - whether the command may have arguments (see register_general()
|
||||||
|
for an explanation)
|
||||||
|
"""
|
||||||
|
|
||||||
command = SpecificCommand(name, cmdfunc, args)
|
command = SpecificCommand(name, cmdfunc, args)
|
||||||
self.register(command)
|
self.register(command)
|
||||||
|
|
||||||
|
|
@ -88,6 +172,13 @@ class Bot(Client):
|
||||||
message: LiveMessage,
|
message: LiveMessage,
|
||||||
aliases: List[str] = []
|
aliases: List[str] = []
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
If the message contains a command, call all matching command functions
|
||||||
|
that were previously registered.
|
||||||
|
|
||||||
|
This function is usually called by the overwritten on_send() function.
|
||||||
|
"""
|
||||||
|
|
||||||
nicks = [room.session.nick] + aliases
|
nicks = [room.session.nick] + aliases
|
||||||
data = CommandData.from_string(message.content)
|
data = CommandData.from_string(message.content)
|
||||||
|
|
||||||
|
|
@ -97,11 +188,31 @@ class Bot(Client):
|
||||||
await command.run(room, message, nicks, data)
|
await command.run(room, message, nicks, data)
|
||||||
|
|
||||||
async def on_send(self, room: Room, message: LiveMessage) -> None:
|
async def on_send(self, room: Room, message: LiveMessage) -> None:
|
||||||
|
"""
|
||||||
|
This Client function is overwritten in order to automatically call
|
||||||
|
process_commands() with self.ALIASES.
|
||||||
|
|
||||||
|
If you need to overwrite this function, make sure to await
|
||||||
|
process_commands() with self.ALIASES somewhere in your function, or
|
||||||
|
await super().on_send().
|
||||||
|
"""
|
||||||
|
|
||||||
await self.process_commands(room, message, aliases=self.ALIASES)
|
await self.process_commands(room, message, aliases=self.ALIASES)
|
||||||
|
|
||||||
# Help util
|
# Help util
|
||||||
|
|
||||||
def format_help(self, room: Room, lines: List[str]) -> str:
|
def format_help(self, room: Room, lines: List[str]) -> str:
|
||||||
|
"""
|
||||||
|
Format a list of strings into a string, replacing certain placeholders
|
||||||
|
with the actual values.
|
||||||
|
|
||||||
|
This function uses the str.format() function to replace the following:
|
||||||
|
|
||||||
|
- {nick} - the bot's current nick
|
||||||
|
- {mention} - the bot's current nick, run through mention()
|
||||||
|
- {atmention} - the bot's current nick, run through atmention()
|
||||||
|
"""
|
||||||
|
|
||||||
text = "\n".join(lines)
|
text = "\n".join(lines)
|
||||||
params = {
|
params = {
|
||||||
"nick": room.session.nick,
|
"nick": room.session.nick,
|
||||||
|
|
@ -119,6 +230,36 @@ class Bot(Client):
|
||||||
kill: bool = False,
|
kill: bool = False,
|
||||||
restart: bool = False,
|
restart: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Register the commands necessary for the bot to conform to the botrulez
|
||||||
|
(https://github.com/jedevc/botrulez). Also includes a few optional
|
||||||
|
botrulez commands that are disabled by default.
|
||||||
|
|
||||||
|
- ping - register general and specific cmd_ping()
|
||||||
|
- help_ - register cmd_help_general() and cmd_help_specific()
|
||||||
|
- uptime - register specific cmd_uptime
|
||||||
|
- kill - register specific cmd_kill (disabled by default)
|
||||||
|
- uptime - register specific cmd_uptime (disabled by default)
|
||||||
|
|
||||||
|
All commands are registered with args=False.
|
||||||
|
|
||||||
|
If you want to implement your own versions of these commands, it is
|
||||||
|
recommended that you set the respective argument to False in your call
|
||||||
|
to register_botrulez(), overwrite the existing command functions or
|
||||||
|
create your own, and then register them manually.
|
||||||
|
|
||||||
|
For help, that might look something like this, if you've written a
|
||||||
|
custom specific help that takes extra arguments but are using the
|
||||||
|
botrulez general help:
|
||||||
|
|
||||||
|
self.register_botrulez(help_=False)
|
||||||
|
self.register_general("help", self.cmd_help_general, args=False)
|
||||||
|
self.register_specific("help", self.cmd_help_custom)
|
||||||
|
|
||||||
|
In case you're asking, the help_ parameter has an underscore at the end
|
||||||
|
so it doesn't overlap the help() function.
|
||||||
|
"""
|
||||||
|
|
||||||
if ping:
|
if ping:
|
||||||
self.register_general("ping", self.cmd_ping, args=False)
|
self.register_general("ping", self.cmd_ping, args=False)
|
||||||
self.register_specific("ping", self.cmd_ping, args=False)
|
self.register_specific("ping", self.cmd_ping, args=False)
|
||||||
|
|
@ -144,6 +285,10 @@ class Bot(Client):
|
||||||
message: LiveMessage,
|
message: LiveMessage,
|
||||||
args: ArgumentData
|
args: ArgumentData
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Reply with self.PING_REPLY.
|
||||||
|
"""
|
||||||
|
|
||||||
await message.reply(self.PING_REPLY)
|
await message.reply(self.PING_REPLY)
|
||||||
|
|
||||||
async def cmd_help_general(self,
|
async def cmd_help_general(self,
|
||||||
|
|
@ -151,6 +296,10 @@ class Bot(Client):
|
||||||
message: LiveMessage,
|
message: LiveMessage,
|
||||||
args: ArgumentData
|
args: ArgumentData
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Reply with self.HELP_GENERAL, if it is not None. Uses format_help().
|
||||||
|
"""
|
||||||
|
|
||||||
if self.HELP_GENERAL is not None:
|
if self.HELP_GENERAL is not None:
|
||||||
await message.reply(self.format_help(room, [self.HELP_GENERAL]))
|
await message.reply(self.format_help(room, [self.HELP_GENERAL]))
|
||||||
|
|
||||||
|
|
@ -159,6 +308,10 @@ class Bot(Client):
|
||||||
message: LiveMessage,
|
message: LiveMessage,
|
||||||
args: SpecificArgumentData
|
args: SpecificArgumentData
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Reply with self.HELP_SPECIFIC, if it is not None. Uses format_help().
|
||||||
|
"""
|
||||||
|
|
||||||
if self.HELP_SPECIFIC is not None:
|
if self.HELP_SPECIFIC is not None:
|
||||||
await message.reply(self.format_help(room, self.HELP_SPECIFIC))
|
await message.reply(self.format_help(room, self.HELP_SPECIFIC))
|
||||||
|
|
||||||
|
|
@ -167,6 +320,15 @@ class Bot(Client):
|
||||||
message: LiveMessage,
|
message: LiveMessage,
|
||||||
args: SpecificArgumentData
|
args: SpecificArgumentData
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Reply with the bot's uptime in the format specified by the botrulez.
|
||||||
|
|
||||||
|
This uses the time that the Bot was first started, not the time the
|
||||||
|
respective Room was created. A !restart (see register_botrulez()) will
|
||||||
|
reset the bot uptime, but leaving and re-joining a room or losing
|
||||||
|
connection won't.
|
||||||
|
"""
|
||||||
|
|
||||||
time = format_time(self.start_time)
|
time = format_time(self.start_time)
|
||||||
delta = format_delta(datetime.datetime.now() - self.start_time)
|
delta = format_delta(datetime.datetime.now() - self.start_time)
|
||||||
text = f"/me has been up since {time} UTC ({delta})"
|
text = f"/me has been up since {time} UTC ({delta})"
|
||||||
|
|
@ -177,6 +339,13 @@ class Bot(Client):
|
||||||
message: LiveMessage,
|
message: LiveMessage,
|
||||||
args: SpecificArgumentData
|
args: SpecificArgumentData
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Remove the bot from this room.
|
||||||
|
|
||||||
|
If self.KILL_REPLY is not None, replies with that before leaving the
|
||||||
|
room.
|
||||||
|
"""
|
||||||
|
|
||||||
logger.info(f"Killed in &{room.name} by {message.sender.atmention}")
|
logger.info(f"Killed in &{room.name} by {message.sender.atmention}")
|
||||||
|
|
||||||
if self.KILL_REPLY is not None:
|
if self.KILL_REPLY is not None:
|
||||||
|
|
@ -189,6 +358,15 @@ class Bot(Client):
|
||||||
message: LiveMessage,
|
message: LiveMessage,
|
||||||
args: SpecificArgumentData
|
args: SpecificArgumentData
|
||||||
) -> None:
|
) -> None:
|
||||||
|
"""
|
||||||
|
Restart the whole Bot.
|
||||||
|
|
||||||
|
This is done by stopping the Bot, since the run() or run_modulebot()
|
||||||
|
functions start the Bot in a while True loop.
|
||||||
|
|
||||||
|
If self.RESTART_REPLY is not None, replies with that before restarting.
|
||||||
|
"""
|
||||||
|
|
||||||
logger.info(f"Restarted in &{room.name} by {message.sender.atmention}")
|
logger.info(f"Restarted in &{room.name} by {message.sender.atmention}")
|
||||||
|
|
||||||
if self.RESTART_REPLY is not None:
|
if self.RESTART_REPLY is not None:
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue