Arrange precedence of config loading
Closes https://git.autonomic.zone/decentral1se/xbotlib/issues/14.
This commit is contained in:
parent
fbd72b55fe
commit
5b43a2cde2
@ -3,6 +3,9 @@
|
||||
# xbotlib 0.8.0 (2021-01-14)
|
||||
|
||||
- Support not providing response implementation ([#18](https://git.autonomic.zone/decentral1se/xbotlib/issues/18))
|
||||
- Arrange precedence logic for config loading ([#14](https://git.autonomic.zone/decentral1se/xbotlib/issues/14))
|
||||
- Remove `--no-input` option and detect it automatically ([#14](https://git.autonomic.zone/decentral1se/xbotlib/issues/14))
|
||||
- Refer to `jid` as `account` from now on both internally and externally ([#14](https://git.autonomic.zone/decentral1se/xbotlib/issues/14))
|
||||
|
||||
# xbotlib 0.7.1 (2021-01-13)
|
||||
|
||||
|
40
README.md
40
README.md
@ -22,8 +22,10 @@ $ pip install xbotlib
|
||||
|
||||
## Example
|
||||
|
||||
Put the following in a `echo.py` file. `xbotlib` provides a number of example
|
||||
bots which you can use to get moving fast and try things out.
|
||||
Put the following in a `echo.py` file.
|
||||
|
||||
`xbotlib` provides a number of example bots which you can use to get moving
|
||||
fast and try things out.
|
||||
|
||||
```python
|
||||
from xbotlib import EchoBot
|
||||
@ -31,10 +33,10 @@ from xbotlib import EchoBot
|
||||
EchotBot()
|
||||
```
|
||||
|
||||
And then `python echo.py`. You will be asked a few questions like which account
|
||||
details your bot will be using.
|
||||
|
||||
This will generate a `bot.conf` file in the same working directory for further use.
|
||||
And then `python echo.py`. You will be asked a few questions in order to load
|
||||
the account details that your bot will be using. This will generate a
|
||||
`bot.conf` file in the same working directory for further use. See the
|
||||
[configuration](#configure-your-bot) section for more.
|
||||
|
||||
Here's the code for the `EchoBot`.
|
||||
|
||||
@ -95,15 +97,29 @@ Attributes:
|
||||
|
||||
## Configure your bot
|
||||
|
||||
All the ways you can pass configuration details to your bot.
|
||||
|
||||
### Using the bot.conf
|
||||
|
||||
If you run simply run your Python script which contains the bot then `xbotlib`
|
||||
will generate a configuration for you by asking a few questions. This is the
|
||||
simplest way to run your bot locally.
|
||||
|
||||
### Using the command-line interface
|
||||
|
||||
Every bot accepts a number of comand-line arguments to load configuration. You
|
||||
can use the `--help` option to see what is available (e.g. `python bot.py --help`).
|
||||
|
||||
### Using the environment
|
||||
|
||||
You can pass the `--no-input` option to your script invocation (e.g. `python bot.py --no-input`).
|
||||
`xbotlib` will try to read the following configuration values from the
|
||||
environment if it cannot read them from a configuration file or the
|
||||
command-line interface. This can be useful when doing remote server
|
||||
deployments.
|
||||
|
||||
`xbotlib` will try to read the following configuration values from the environment.
|
||||
|
||||
- **XBOT_JID**: The username of the bot account
|
||||
- **XBOT_PASSWORD**: The password of the bot account
|
||||
- **XBOT_NICK**: The nickname that the bot uses
|
||||
- **XBOT_ACCOUNT**: The bot account
|
||||
- **XBOT_PASSWORD**: The bot password
|
||||
- **XBOT_NICK**: The bot nickname
|
||||
|
||||
## Roadmap
|
||||
|
||||
|
123
xbotlib.py
123
xbotlib.py
@ -1,12 +1,13 @@
|
||||
"""XMPP bots for humans."""
|
||||
|
||||
from argparse import ArgumentParser, BooleanOptionalAction
|
||||
from argparse import ArgumentParser
|
||||
from configparser import ConfigParser
|
||||
from getpass import getpass
|
||||
from logging import DEBUG, INFO, basicConfig, getLogger
|
||||
from os import environ
|
||||
from os.path import exists
|
||||
from pathlib import Path
|
||||
from sys import exit, stdout
|
||||
|
||||
from slixmpp import ClientXMPP
|
||||
|
||||
@ -42,7 +43,29 @@ class SimpleMessage:
|
||||
return self.message["type"]
|
||||
|
||||
|
||||
class Config:
|
||||
"""Bot file configuration."""
|
||||
|
||||
def __init__(self, config):
|
||||
self.config = config
|
||||
self.section = config["bot"] if "bot" in config else {}
|
||||
|
||||
@property
|
||||
def account(self):
|
||||
return self.section.get("account", None)
|
||||
|
||||
@property
|
||||
def password(self):
|
||||
return self.section.get("password", None)
|
||||
|
||||
@property
|
||||
def nick(self):
|
||||
return self.section.get("nick", None)
|
||||
|
||||
|
||||
class Bot(ClientXMPP):
|
||||
"""XMPP bots for humans."""
|
||||
|
||||
CONFIG_FILE = "bot.conf"
|
||||
|
||||
def __init__(self):
|
||||
@ -56,22 +79,36 @@ class Bot(ClientXMPP):
|
||||
|
||||
def parse_arguments(self):
|
||||
"""Parse command-line arguments."""
|
||||
self.parser = ArgumentParser()
|
||||
self.parser.add_argument(
|
||||
"--input",
|
||||
help="Read configuration from environment",
|
||||
action=BooleanOptionalAction,
|
||||
default=True,
|
||||
)
|
||||
self.parser = ArgumentParser(description="XMPP bots for humans")
|
||||
|
||||
self.parser.add_argument(
|
||||
"-d",
|
||||
"--debug",
|
||||
help="Set logging to DEBUG",
|
||||
help="Enable verbose debug logs",
|
||||
action="store_const",
|
||||
dest="log_level",
|
||||
const=DEBUG,
|
||||
default=INFO,
|
||||
)
|
||||
self.parser.add_argument(
|
||||
"-a",
|
||||
"--account",
|
||||
dest="account",
|
||||
help="Account for the bot account (foo@example.com)",
|
||||
)
|
||||
self.parser.add_argument(
|
||||
"-p",
|
||||
"--password",
|
||||
dest="password",
|
||||
help="Password for the bot account",
|
||||
)
|
||||
self.parser.add_argument(
|
||||
"-n",
|
||||
"--nick",
|
||||
dest="nick",
|
||||
help="Nickname for the bot account",
|
||||
)
|
||||
|
||||
self.args = self.parser.parse_args()
|
||||
|
||||
def setup_logging(self):
|
||||
@ -83,31 +120,26 @@ class Bot(ClientXMPP):
|
||||
|
||||
def read_config(self):
|
||||
"""Read configuration for running bot."""
|
||||
self.config = ConfigParser()
|
||||
config = ConfigParser()
|
||||
|
||||
config_file_path = Path(self.CONFIG_FILE).absolute()
|
||||
if not exists(config_file_path) and self.args.input:
|
||||
|
||||
if not exists(config_file_path) and stdout.isatty():
|
||||
self.generate_config_interactively()
|
||||
|
||||
if exists(config_file_path):
|
||||
self.config.read(config_file_path)
|
||||
config.read(config_file_path)
|
||||
|
||||
if self.args.input is False:
|
||||
self.read_config_from_env()
|
||||
|
||||
if "bot" not in self.config:
|
||||
raise RuntimeError("Failed to configure bot")
|
||||
self.config = Config(config)
|
||||
|
||||
def generate_config_interactively(self):
|
||||
"""Generate bot configuration."""
|
||||
jid = input("XMPP address (e.g. foo@bar.com): ") or "foo@bar.com"
|
||||
password = (
|
||||
getpass("Password (e.g. my-cool-password): ") or "my-cool-password"
|
||||
)
|
||||
nick = input("Nickname (e.g. lurkbot): ")
|
||||
account = input("Account: ")
|
||||
password = getpass("Password: ")
|
||||
nick = input("Nickname: ")
|
||||
|
||||
config = ConfigParser()
|
||||
config["bot"] = {"jid": jid, "password": password}
|
||||
config["bot"] = {"account": account, "password": password}
|
||||
|
||||
if nick:
|
||||
config["bot"]["nick"] = nick
|
||||
@ -115,18 +147,37 @@ class Bot(ClientXMPP):
|
||||
with open("bot.conf", "w") as file_handle:
|
||||
config.write(file_handle)
|
||||
|
||||
def read_config_from_env(self):
|
||||
"""Read configuration from the environment."""
|
||||
self.config["bot"] = {}
|
||||
self.config["bot"]["jid"] = environ.get("XBOT_JID")
|
||||
self.config["bot"]["password"] = environ.get("XBOT_PASSWORD")
|
||||
self.config["bot"]["nick"] = environ.get("XBOT_NICK", "")
|
||||
|
||||
def init_bot(self):
|
||||
"""Initialise bot with connection details."""
|
||||
jid = self.config["bot"]["jid"]
|
||||
password = self.config["bot"]["password"]
|
||||
ClientXMPP.__init__(self, jid, password)
|
||||
account = (
|
||||
self.args.account
|
||||
or self.config.account
|
||||
or environ.get("XBOT_ACCOUNT", None)
|
||||
)
|
||||
password = (
|
||||
self.args.password
|
||||
or self.config.password
|
||||
or environ.get("XBOT_PASSWORD", None)
|
||||
)
|
||||
nick = (
|
||||
self.args.nick or self.config.nick or environ.get("XBOT_NICK", None)
|
||||
)
|
||||
|
||||
if not account:
|
||||
self.log.error("Unable to discover account")
|
||||
exit(1)
|
||||
if not password:
|
||||
self.log.error("Unable to discover password")
|
||||
exit(1)
|
||||
if not nick:
|
||||
self.log.error("Unable to discover nick")
|
||||
exit(1)
|
||||
|
||||
ClientXMPP.__init__(self, account, password)
|
||||
|
||||
self.account = account
|
||||
self.password = password
|
||||
self.nick = nick
|
||||
|
||||
def register_xmpp_event_handlers(self):
|
||||
"""Register functions against specific XMPP event handlers."""
|
||||
@ -150,14 +201,12 @@ class Bot(ClientXMPP):
|
||||
|
||||
def group_invite(self, message):
|
||||
"""Accept invites to group chats."""
|
||||
self.plugin["xep_0045"].join_muc(
|
||||
message["from"], self.config["bot"]["nick"]
|
||||
)
|
||||
self.plugin["xep_0045"].join_muc(message["from"], self.config.nick)
|
||||
|
||||
def group_message(self, message):
|
||||
"""Handle groupchat_message event."""
|
||||
if message["type"] in ("groupchat", "normal"):
|
||||
if message["mucnick"] != self.config["bot"]["nick"]:
|
||||
if message["mucnick"] != self.config.nick:
|
||||
try:
|
||||
self.group(SimpleMessage(message))
|
||||
except AttributeError:
|
||||
|
Reference in New Issue
Block a user