Module trappedbot.cmd
Expand source code
#!/usr/bin/env python
import argparse
import asyncio
import getpass
import logging
import os
import sys
import traceback
import typing
import requests
from trappedbot import appconfig, util
from trappedbot.applogger import LOGGER
from trappedbot.botclient import botloop
from trappedbot.configparser import parse_config
from trappedbot.constants import HELP_TRAPPED_MSG
from trappedbot.tasks.builtin import BUILTIN_TASKS
from trappedbot.version import version_cute
def ExistingResolvedPath(path):
if os.path.exists(path):
return os.path.abspath(path)
else:
raise FileNotFoundError(f"Path {path} does not exist")
def access_token(
homeserver: str, user: str, password: str, deviceid: str, devicename: str
) -> str:
"""Retrieve a bearer token from a Matrix homeserver"""
data = {
"identifier": {
"type": "m.id.user",
"user": user,
},
"password": password,
"type": "m.login.password",
"device_id": deviceid,
"initial_device_display_name": devicename,
}
uri = f"{homeserver}/_matrix/client/r0/login"
result = requests.post(uri, json=data)
# A successful request will return something like this:
# '{"user_id":"@me:micahrl.com","access_token":"...ELIDED...","home_server":"micahrl.com","device_id":"ifrit_get_mx_users","well_known":{"m.homeserver":{"base_url":"https://matrix.micahrl.com/"}}}'
# In case the request was not successful, raise an error
result.raise_for_status()
jresult = result.json()
return jresult["access_token"]
def parseargs(arguments: typing.List[str]) -> argparse.Namespace:
"""Parse command-line arguments"""
parser = argparse.ArgumentParser(
description=HELP_TRAPPED_MSG,
)
parser.add_argument(
"--debug",
"-d",
action="store_true",
help="Include verbose/debug messages, and enter the debugger on unhandled exceptions",
)
parser.add_argument(
"--verbose", "-v", action="store_true", help="Include verbose/debug messages"
)
subparsers = parser.add_subparsers(dest="action")
subparsers.required = True
# sub_version =
subparsers.add_parser("version", help="Show version")
sub_bot = subparsers.add_parser("bot", help="Start the bot")
sub_bot.add_argument(
"configpath", type=ExistingResolvedPath, help="Path to the config file"
)
# sub_builtins =
subparsers.add_parser("builtin-tasks", help="List built in tasks")
sub_acctok = subparsers.add_parser(
"access-token", help="Get an access token from a Matrix server"
)
sub_acctok.add_argument(
"homeserver",
help="The matrix homeserver to use, like 'https://matrix.example.com'",
)
sub_acctok.add_argument(
"username",
help="The username to retrieve a token for. Just the name, not a full mxid. You will be prompted for a password.",
)
sub_acctok.add_argument(
"deviceid",
help="A device ID to use. Arbitrary, but helpful in identifying the purpose of an access token later.",
)
sub_acctok.add_argument(
"--devicename", help="A human-readable device name; defaults to the device ID"
)
sub_acctok.add_argument(
"--password",
help="Password for the account. Will be prompted for this if not passed.",
)
return parser.parse_args(arguments)
def main(arguments: typing.List[str] = sys.argv[1:]):
"""The command-line main function"""
parsed = parseargs(arguments)
# force_log_debug is an not beautiful and a bit repetitive, but it lets me
# ensure that the command line flags can override settings in the config
# file if used, and the logger gets set up properly if it wasn't.
force_log_debug = False
if parsed.verbose or parsed.debug:
LOGGER.setLevel(logging.DEBUG)
force_log_debug = True
if parsed.debug:
sys.excepthook = util.idb_excepthook
if parsed.action == "version":
print(version_cute())
sys.exit(0)
elif parsed.action == "bot":
appconfig.set(parse_config(parsed.configpath, force_log_debug))
try:
asyncio.get_event_loop().run_until_complete(botloop())
except KeyboardInterrupt:
LOGGER.debug("Received keyboard interrupt, exiting...")
sys.exit(0)
except Exception as exc:
LOGGER.error(
f"Encountered exception: {exc}\nTraceback:\n{traceback.format_exc()}"
)
sys.exit(1)
elif parsed.action == "builtin-tasks":
print("The following tasks are built-in to the bot:")
for k, v in BUILTIN_TASKS.items():
print(f"- {k}")
elif parsed.action == "access-token":
password = parsed.password or getpass.getpass()
devicename = parsed.devicename or parsed.deviceid
token = access_token(
parsed.homeserver, parsed.username, password, parsed.deviceid, devicename
)
print(token)
else:
raise Exception(f"Unknown action {parsed.action}")
Functions
def ExistingResolvedPath(path)
-
Expand source code
def ExistingResolvedPath(path): if os.path.exists(path): return os.path.abspath(path) else: raise FileNotFoundError(f"Path {path} does not exist")
def access_token(homeserver: str, user: str, password: str, deviceid: str, devicename: str) ‑> str
-
Retrieve a bearer token from a Matrix homeserver
Expand source code
def access_token( homeserver: str, user: str, password: str, deviceid: str, devicename: str ) -> str: """Retrieve a bearer token from a Matrix homeserver""" data = { "identifier": { "type": "m.id.user", "user": user, }, "password": password, "type": "m.login.password", "device_id": deviceid, "initial_device_display_name": devicename, } uri = f"{homeserver}/_matrix/client/r0/login" result = requests.post(uri, json=data) # A successful request will return something like this: # '{"user_id":"@me:micahrl.com","access_token":"...ELIDED...","home_server":"micahrl.com","device_id":"ifrit_get_mx_users","well_known":{"m.homeserver":{"base_url":"https://matrix.micahrl.com/"}}}' # In case the request was not successful, raise an error result.raise_for_status() jresult = result.json() return jresult["access_token"]
def main(arguments: List[str] = ['--html', 'trappedbot'])
-
The command-line main function
Expand source code
def main(arguments: typing.List[str] = sys.argv[1:]): """The command-line main function""" parsed = parseargs(arguments) # force_log_debug is an not beautiful and a bit repetitive, but it lets me # ensure that the command line flags can override settings in the config # file if used, and the logger gets set up properly if it wasn't. force_log_debug = False if parsed.verbose or parsed.debug: LOGGER.setLevel(logging.DEBUG) force_log_debug = True if parsed.debug: sys.excepthook = util.idb_excepthook if parsed.action == "version": print(version_cute()) sys.exit(0) elif parsed.action == "bot": appconfig.set(parse_config(parsed.configpath, force_log_debug)) try: asyncio.get_event_loop().run_until_complete(botloop()) except KeyboardInterrupt: LOGGER.debug("Received keyboard interrupt, exiting...") sys.exit(0) except Exception as exc: LOGGER.error( f"Encountered exception: {exc}\nTraceback:\n{traceback.format_exc()}" ) sys.exit(1) elif parsed.action == "builtin-tasks": print("The following tasks are built-in to the bot:") for k, v in BUILTIN_TASKS.items(): print(f"- {k}") elif parsed.action == "access-token": password = parsed.password or getpass.getpass() devicename = parsed.devicename or parsed.deviceid token = access_token( parsed.homeserver, parsed.username, password, parsed.deviceid, devicename ) print(token) else: raise Exception(f"Unknown action {parsed.action}")
def parseargs(arguments: List[str]) ‑> argparse.Namespace
-
Parse command-line arguments
Expand source code
def parseargs(arguments: typing.List[str]) -> argparse.Namespace: """Parse command-line arguments""" parser = argparse.ArgumentParser( description=HELP_TRAPPED_MSG, ) parser.add_argument( "--debug", "-d", action="store_true", help="Include verbose/debug messages, and enter the debugger on unhandled exceptions", ) parser.add_argument( "--verbose", "-v", action="store_true", help="Include verbose/debug messages" ) subparsers = parser.add_subparsers(dest="action") subparsers.required = True # sub_version = subparsers.add_parser("version", help="Show version") sub_bot = subparsers.add_parser("bot", help="Start the bot") sub_bot.add_argument( "configpath", type=ExistingResolvedPath, help="Path to the config file" ) # sub_builtins = subparsers.add_parser("builtin-tasks", help="List built in tasks") sub_acctok = subparsers.add_parser( "access-token", help="Get an access token from a Matrix server" ) sub_acctok.add_argument( "homeserver", help="The matrix homeserver to use, like 'https://matrix.example.com'", ) sub_acctok.add_argument( "username", help="The username to retrieve a token for. Just the name, not a full mxid. You will be prompted for a password.", ) sub_acctok.add_argument( "deviceid", help="A device ID to use. Arbitrary, but helpful in identifying the purpose of an access token later.", ) sub_acctok.add_argument( "--devicename", help="A human-readable device name; defaults to the device ID" ) sub_acctok.add_argument( "--password", help="Password for the account. Will be prompted for this if not passed.", ) return parser.parse_args(arguments)