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)
|
# xbotlib 0.8.0 (2021-01-14)
|
||||||
|
|
||||||
- Support not providing response implementation ([#18](https://git.autonomic.zone/decentral1se/xbotlib/issues/18))
|
- 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)
|
# xbotlib 0.7.1 (2021-01-13)
|
||||||
|
|
||||||
|
40
README.md
40
README.md
@ -22,8 +22,10 @@ $ pip install xbotlib
|
|||||||
|
|
||||||
## Example
|
## Example
|
||||||
|
|
||||||
Put the following in a `echo.py` file. `xbotlib` provides a number of example
|
Put the following in a `echo.py` file.
|
||||||
bots which you can use to get moving fast and try things out.
|
|
||||||
|
`xbotlib` provides a number of example bots which you can use to get moving
|
||||||
|
fast and try things out.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from xbotlib import EchoBot
|
from xbotlib import EchoBot
|
||||||
@ -31,10 +33,10 @@ from xbotlib import EchoBot
|
|||||||
EchotBot()
|
EchotBot()
|
||||||
```
|
```
|
||||||
|
|
||||||
And then `python echo.py`. You will be asked a few questions like which account
|
And then `python echo.py`. You will be asked a few questions in order to load
|
||||||
details your bot will be using.
|
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
|
||||||
This will generate a `bot.conf` file in the same working directory for further use.
|
[configuration](#configure-your-bot) section for more.
|
||||||
|
|
||||||
Here's the code for the `EchoBot`.
|
Here's the code for the `EchoBot`.
|
||||||
|
|
||||||
@ -95,15 +97,29 @@ Attributes:
|
|||||||
|
|
||||||
## Configure your bot
|
## 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
|
### 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_ACCOUNT**: The bot account
|
||||||
|
- **XBOT_PASSWORD**: The bot password
|
||||||
- **XBOT_JID**: The username of the bot account
|
- **XBOT_NICK**: The bot nickname
|
||||||
- **XBOT_PASSWORD**: The password of the bot account
|
|
||||||
- **XBOT_NICK**: The nickname that the bot uses
|
|
||||||
|
|
||||||
## Roadmap
|
## Roadmap
|
||||||
|
|
||||||
|
123
xbotlib.py
123
xbotlib.py
@ -1,12 +1,13 @@
|
|||||||
"""XMPP bots for humans."""
|
"""XMPP bots for humans."""
|
||||||
|
|
||||||
from argparse import ArgumentParser, BooleanOptionalAction
|
from argparse import ArgumentParser
|
||||||
from configparser import ConfigParser
|
from configparser import ConfigParser
|
||||||
from getpass import getpass
|
from getpass import getpass
|
||||||
from logging import DEBUG, INFO, basicConfig, getLogger
|
from logging import DEBUG, INFO, basicConfig, getLogger
|
||||||
from os import environ
|
from os import environ
|
||||||
from os.path import exists
|
from os.path import exists
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from sys import exit, stdout
|
||||||
|
|
||||||
from slixmpp import ClientXMPP
|
from slixmpp import ClientXMPP
|
||||||
|
|
||||||
@ -42,7 +43,29 @@ class SimpleMessage:
|
|||||||
return self.message["type"]
|
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):
|
class Bot(ClientXMPP):
|
||||||
|
"""XMPP bots for humans."""
|
||||||
|
|
||||||
CONFIG_FILE = "bot.conf"
|
CONFIG_FILE = "bot.conf"
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@ -56,22 +79,36 @@ class Bot(ClientXMPP):
|
|||||||
|
|
||||||
def parse_arguments(self):
|
def parse_arguments(self):
|
||||||
"""Parse command-line arguments."""
|
"""Parse command-line arguments."""
|
||||||
self.parser = ArgumentParser()
|
self.parser = ArgumentParser(description="XMPP bots for humans")
|
||||||
self.parser.add_argument(
|
|
||||||
"--input",
|
|
||||||
help="Read configuration from environment",
|
|
||||||
action=BooleanOptionalAction,
|
|
||||||
default=True,
|
|
||||||
)
|
|
||||||
self.parser.add_argument(
|
self.parser.add_argument(
|
||||||
"-d",
|
"-d",
|
||||||
"--debug",
|
"--debug",
|
||||||
help="Set logging to DEBUG",
|
help="Enable verbose debug logs",
|
||||||
action="store_const",
|
action="store_const",
|
||||||
dest="log_level",
|
dest="log_level",
|
||||||
const=DEBUG,
|
const=DEBUG,
|
||||||
default=INFO,
|
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()
|
self.args = self.parser.parse_args()
|
||||||
|
|
||||||
def setup_logging(self):
|
def setup_logging(self):
|
||||||
@ -83,31 +120,26 @@ class Bot(ClientXMPP):
|
|||||||
|
|
||||||
def read_config(self):
|
def read_config(self):
|
||||||
"""Read configuration for running bot."""
|
"""Read configuration for running bot."""
|
||||||
self.config = ConfigParser()
|
config = ConfigParser()
|
||||||
|
|
||||||
config_file_path = Path(self.CONFIG_FILE).absolute()
|
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()
|
self.generate_config_interactively()
|
||||||
|
|
||||||
if exists(config_file_path):
|
if exists(config_file_path):
|
||||||
self.config.read(config_file_path)
|
config.read(config_file_path)
|
||||||
|
|
||||||
if self.args.input is False:
|
self.config = Config(config)
|
||||||
self.read_config_from_env()
|
|
||||||
|
|
||||||
if "bot" not in self.config:
|
|
||||||
raise RuntimeError("Failed to configure bot")
|
|
||||||
|
|
||||||
def generate_config_interactively(self):
|
def generate_config_interactively(self):
|
||||||
"""Generate bot configuration."""
|
"""Generate bot configuration."""
|
||||||
jid = input("XMPP address (e.g. foo@bar.com): ") or "foo@bar.com"
|
account = input("Account: ")
|
||||||
password = (
|
password = getpass("Password: ")
|
||||||
getpass("Password (e.g. my-cool-password): ") or "my-cool-password"
|
nick = input("Nickname: ")
|
||||||
)
|
|
||||||
nick = input("Nickname (e.g. lurkbot): ")
|
|
||||||
|
|
||||||
config = ConfigParser()
|
config = ConfigParser()
|
||||||
config["bot"] = {"jid": jid, "password": password}
|
config["bot"] = {"account": account, "password": password}
|
||||||
|
|
||||||
if nick:
|
if nick:
|
||||||
config["bot"]["nick"] = nick
|
config["bot"]["nick"] = nick
|
||||||
@ -115,18 +147,37 @@ class Bot(ClientXMPP):
|
|||||||
with open("bot.conf", "w") as file_handle:
|
with open("bot.conf", "w") as file_handle:
|
||||||
config.write(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):
|
def init_bot(self):
|
||||||
"""Initialise bot with connection details."""
|
"""Initialise bot with connection details."""
|
||||||
jid = self.config["bot"]["jid"]
|
account = (
|
||||||
password = self.config["bot"]["password"]
|
self.args.account
|
||||||
ClientXMPP.__init__(self, jid, password)
|
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):
|
def register_xmpp_event_handlers(self):
|
||||||
"""Register functions against specific XMPP event handlers."""
|
"""Register functions against specific XMPP event handlers."""
|
||||||
@ -150,14 +201,12 @@ class Bot(ClientXMPP):
|
|||||||
|
|
||||||
def group_invite(self, message):
|
def group_invite(self, message):
|
||||||
"""Accept invites to group chats."""
|
"""Accept invites to group chats."""
|
||||||
self.plugin["xep_0045"].join_muc(
|
self.plugin["xep_0045"].join_muc(message["from"], self.config.nick)
|
||||||
message["from"], self.config["bot"]["nick"]
|
|
||||||
)
|
|
||||||
|
|
||||||
def group_message(self, message):
|
def group_message(self, message):
|
||||||
"""Handle groupchat_message event."""
|
"""Handle groupchat_message event."""
|
||||||
if message["type"] in ("groupchat", "normal"):
|
if message["type"] in ("groupchat", "normal"):
|
||||||
if message["mucnick"] != self.config["bot"]["nick"]:
|
if message["mucnick"] != self.config.nick:
|
||||||
try:
|
try:
|
||||||
self.group(SimpleMessage(message))
|
self.group(SimpleMessage(message))
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
|
Reference in New Issue
Block a user