Previously, messages weren't stripped at all. This caused most bots to break and send replies similar to "I didn't understand your command". Nobody noticed, because the tests were only validating that replies were sent, but not the content in them. Thus, this commit also adds tests to avoid further regressions.
151 lines
5.8 KiB
Python
151 lines
5.8 KiB
Python
import configparser
|
|
import logging
|
|
import json
|
|
import os
|
|
|
|
from flask import Flask, request
|
|
from importlib import import_module
|
|
from typing import Any, Dict, Union, List, Optional
|
|
from werkzeug.exceptions import BadRequest
|
|
|
|
from zulip import Client
|
|
from zulip_bots import lib
|
|
from zulip_botserver.input_parameters import parse_args
|
|
|
|
|
|
def read_config_file(config_file_path: str, bot_name: Optional[str]=None) -> Dict[str, Dict[str, str]]:
|
|
parser = parse_config_file(config_file_path)
|
|
|
|
bots_config = {} # type: Dict[str, Dict[str, str]]
|
|
for section in parser.sections():
|
|
section_info = {
|
|
"email": parser.get(section, 'email'),
|
|
"key": parser.get(section, 'key'),
|
|
"site": parser.get(section, 'site'),
|
|
}
|
|
if bot_name is not None:
|
|
logging.warning("Single bot mode is enabled")
|
|
if bots_config:
|
|
logging.warning("'{}' bot will be ignored".format(section))
|
|
else:
|
|
bots_config[bot_name] = section_info
|
|
logging.warning(
|
|
"First bot name in the config list was changed from '{}' to '{}'".format(section, bot_name)
|
|
)
|
|
else:
|
|
bots_config[section] = section_info
|
|
return bots_config
|
|
|
|
|
|
def parse_config_file(config_file_path: str) -> configparser.ConfigParser:
|
|
config_file_path = os.path.abspath(os.path.expanduser(config_file_path))
|
|
if not os.path.isfile(config_file_path):
|
|
raise IOError("Could not read config file {}: File not found.".format(config_file_path))
|
|
parser = configparser.ConfigParser()
|
|
parser.read(config_file_path)
|
|
return parser
|
|
|
|
|
|
def load_lib_modules(available_bots: List[str]) -> Dict[str, Any]:
|
|
bots_lib_module = {}
|
|
for bot in available_bots:
|
|
try:
|
|
module_name = 'zulip_bots.bots.{bot}.{bot}'.format(bot=bot)
|
|
lib_module = import_module(module_name)
|
|
bots_lib_module[bot] = lib_module
|
|
except ImportError:
|
|
raise ImportError(
|
|
"\nImport Error: Bot \"{}\" doesn't exists. "
|
|
"Please make sure you have set up the flaskbotrc file correctly.\n".format(bot)
|
|
)
|
|
return bots_lib_module
|
|
|
|
|
|
def load_bot_handlers(
|
|
available_bots: List[str],
|
|
bots_config: Dict[str, Dict[str, str]],
|
|
third_party_bot_conf: Optional[configparser.ConfigParser]=None,
|
|
) -> Dict[str, lib.ExternalBotHandler]:
|
|
bot_handlers = {}
|
|
for bot in available_bots:
|
|
client = Client(email=bots_config[bot]["email"],
|
|
api_key=bots_config[bot]["key"],
|
|
site=bots_config[bot]["site"])
|
|
bot_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'bots', bot)
|
|
bot_handler = lib.ExternalBotHandler(
|
|
client,
|
|
bot_dir,
|
|
bot_details={},
|
|
bot_config_parser=third_party_bot_conf
|
|
)
|
|
|
|
bot_handlers[bot] = bot_handler
|
|
return bot_handlers
|
|
|
|
|
|
def init_message_handlers(
|
|
available_bots: List[str],
|
|
bots_lib_modules: Dict[str, Any],
|
|
bot_handlers: Dict[str, lib.ExternalBotHandler],
|
|
) -> Dict[str, Any]:
|
|
message_handlers = {}
|
|
for bot in available_bots:
|
|
bot_lib_module = bots_lib_modules[bot]
|
|
bot_handler = bot_handlers[bot]
|
|
message_handler = lib.prepare_message_handler(bot, bot_handler, bot_lib_module)
|
|
message_handlers[bot] = message_handler
|
|
return message_handlers
|
|
|
|
|
|
app = Flask(__name__)
|
|
bots_config = {} # type: Dict[str, Dict[str, str]]
|
|
|
|
|
|
@app.route('/', methods=['POST'])
|
|
def handle_bot() -> Union[str, BadRequest]:
|
|
event = request.get_json(force=True)
|
|
bot = None
|
|
for bot_name, config in bots_config.items():
|
|
if config['email'] == event['bot_email']:
|
|
bot = bot_name
|
|
if bot is None:
|
|
return BadRequest("Cannot find a bot with email {} in the bot server "
|
|
"configuration file. Do the emails in your flaskbotrc "
|
|
"match the bot emails on the server?".format(event['bot_email']))
|
|
else:
|
|
lib_module = app.config.get("BOTS_LIB_MODULES", {})[bot]
|
|
bot_handler = app.config.get("BOT_HANDLERS", {})[bot]
|
|
message_handler = app.config.get("MESSAGE_HANDLERS", {})[bot]
|
|
is_mentioned = event['trigger'] == "mention"
|
|
is_private_message = event['trigger'] == "private_message"
|
|
message = event["message"]
|
|
message['full_content'] = message['content']
|
|
# Strip at-mention botname from the message
|
|
if is_mentioned:
|
|
# message['content'] will be None when the bot's @-mention is not at the beginning.
|
|
# In that case, the message shall not be handled.
|
|
message['content'] = lib.extract_query_without_mention(message=message, client=bot_handler)
|
|
if message['content'] is None:
|
|
return json.dumps("")
|
|
|
|
if is_private_message or is_mentioned:
|
|
message_handler.handle_message(message=message, bot_handler=bot_handler)
|
|
return json.dumps("")
|
|
|
|
|
|
def main() -> None:
|
|
options = parse_args()
|
|
global bots_config
|
|
bots_config = read_config_file(options.config_file, options.bot_name)
|
|
available_bots = list(bots_config.keys())
|
|
bots_lib_modules = load_lib_modules(available_bots)
|
|
third_party_bot_conf = parse_config_file(options.bot_config_file) if options.bot_config_file is not None else None
|
|
bot_handlers = load_bot_handlers(available_bots, bots_config, third_party_bot_conf)
|
|
message_handlers = init_message_handlers(available_bots, bots_lib_modules, bot_handlers)
|
|
app.config["BOTS_LIB_MODULES"] = bots_lib_modules
|
|
app.config["BOT_HANDLERS"] = bot_handlers
|
|
app.config["MESSAGE_HANDLERS"] = message_handlers
|
|
app.run(host=options.hostname, port=int(options.port), debug=True)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|