pyupgrade: Reformat with --py36-plus.
This includes mainly fixes of string literals using f-strings or .format(...), as well as unpacking of list comprehensions.
This commit is contained in:
parent
e27ac0ddbe
commit
9ce7c52a10
|
@ -13,7 +13,7 @@ whitespace_rules = [
|
||||||
] # type: List[Rule]
|
] # type: List[Rule]
|
||||||
|
|
||||||
markdown_whitespace_rules = list(
|
markdown_whitespace_rules = list(
|
||||||
[rule for rule in whitespace_rules if rule["pattern"] != r"\s+$"]
|
rule for rule in whitespace_rules if rule["pattern"] != r"\s+$"
|
||||||
) + [
|
) + [
|
||||||
# Two spaces trailing a line with other content is okay--it's a markdown line break.
|
# Two spaces trailing a line with other content is okay--it's a markdown line break.
|
||||||
# This rule finds one space trailing a non-space, three or more trailing spaces, and
|
# This rule finds one space trailing a non-space, three or more trailing spaces, and
|
||||||
|
|
24
tools/deploy
24
tools/deploy
|
@ -31,14 +31,14 @@ def pack(options: argparse.Namespace) -> None:
|
||||||
print("tools/deploy: No main bot file specified.")
|
print("tools/deploy: No main bot file specified.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if not os.path.isfile(options.config):
|
if not os.path.isfile(options.config):
|
||||||
print("pack: Config file not found at path: {}.".format(options.config))
|
print(f"pack: Config file not found at path: {options.config}.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if not os.path.isdir(options.path):
|
if not os.path.isdir(options.path):
|
||||||
print("pack: Bot folder not found at path: {}.".format(options.path))
|
print(f"pack: Bot folder not found at path: {options.path}.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
main_path = os.path.join(options.path, options.main)
|
main_path = os.path.join(options.path, options.main)
|
||||||
if not os.path.isfile(main_path):
|
if not os.path.isfile(main_path):
|
||||||
print("pack: Bot main file not found at path: {}.".format(main_path))
|
print(f"pack: Bot main file not found at path: {main_path}.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Main logic for packing the bot.
|
# Main logic for packing the bot.
|
||||||
|
@ -65,7 +65,7 @@ def pack(options: argparse.Namespace) -> None:
|
||||||
)
|
)
|
||||||
zip_file.writestr("config.ini", bot_config)
|
zip_file.writestr("config.ini", bot_config)
|
||||||
zip_file.close()
|
zip_file.close()
|
||||||
print("pack: Created zip file at: {}.".format(zip_file_path))
|
print(f"pack: Created zip file at: {zip_file_path}.")
|
||||||
|
|
||||||
|
|
||||||
def check_common_options(options: argparse.Namespace) -> None:
|
def check_common_options(options: argparse.Namespace) -> None:
|
||||||
|
@ -83,7 +83,7 @@ def handle_common_response_without_data(
|
||||||
return handle_common_response(
|
return handle_common_response(
|
||||||
response=response,
|
response=response,
|
||||||
operation=operation,
|
operation=operation,
|
||||||
success_handler=lambda r: print("{}: {}".format(operation, success_message)),
|
success_handler=lambda r: print(f"{operation}: {success_message}"),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -99,12 +99,12 @@ def handle_common_response(
|
||||||
print("{}: {}".format(operation, response_data["message"]))
|
print("{}: {}".format(operation, response_data["message"]))
|
||||||
return False
|
return False
|
||||||
else:
|
else:
|
||||||
print("{}: Unexpected success response format".format(operation))
|
print(f"{operation}: Unexpected success response format")
|
||||||
return False
|
return False
|
||||||
if response.status_code == requests.codes.unauthorized:
|
if response.status_code == requests.codes.unauthorized:
|
||||||
print("{}: Authentication error with the server. Aborting.".format(operation))
|
print(f"{operation}: Authentication error with the server. Aborting.")
|
||||||
else:
|
else:
|
||||||
print("{}: Error {}. Aborting.".format(operation, response.status_code))
|
print(f"{operation}: Error {response.status_code}. Aborting.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
@ -112,7 +112,7 @@ def upload(options: argparse.Namespace) -> None:
|
||||||
check_common_options(options)
|
check_common_options(options)
|
||||||
file_path = os.path.join(bots_dir, options.botname + ".zip")
|
file_path = os.path.join(bots_dir, options.botname + ".zip")
|
||||||
if not os.path.exists(file_path):
|
if not os.path.exists(file_path):
|
||||||
print("upload: Could not find bot package at {}.".format(file_path))
|
print(f"upload: Could not find bot package at {file_path}.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
files = {"file": open(file_path, "rb")}
|
files = {"file": open(file_path, "rb")}
|
||||||
headers = {"key": options.token}
|
headers = {"key": options.token}
|
||||||
|
@ -129,9 +129,9 @@ def clean(options: argparse.Namespace) -> None:
|
||||||
file_path = os.path.join(bots_dir, options.botname + ".zip")
|
file_path = os.path.join(bots_dir, options.botname + ".zip")
|
||||||
if os.path.exists(file_path):
|
if os.path.exists(file_path):
|
||||||
os.remove(file_path)
|
os.remove(file_path)
|
||||||
print("clean: Removed {}.".format(file_path))
|
print(f"clean: Removed {file_path}.")
|
||||||
else:
|
else:
|
||||||
print("clean: File '{}' not found.".format(file_path))
|
print(f"clean: File '{file_path}' not found.")
|
||||||
|
|
||||||
|
|
||||||
def process(options: argparse.Namespace) -> None:
|
def process(options: argparse.Namespace) -> None:
|
||||||
|
@ -341,7 +341,7 @@ To list user's bots, use:
|
||||||
if options.command in commands:
|
if options.command in commands:
|
||||||
commands[options.command](options)
|
commands[options.command](options)
|
||||||
else:
|
else:
|
||||||
print("tools/deploy: No command '{}' found.".format(options.command))
|
print(f"tools/deploy: No command '{options.command}' found.")
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|
|
@ -41,7 +41,7 @@ the Python version this command is executed with."""
|
||||||
# The output has the format "Python 1.2.3"
|
# The output has the format "Python 1.2.3"
|
||||||
py_version_list = py_version_output.split()[1].split(".")
|
py_version_list = py_version_output.split()[1].split(".")
|
||||||
py_version = tuple(int(num) for num in py_version_list[0:2])
|
py_version = tuple(int(num) for num in py_version_list[0:2])
|
||||||
venv_name = "zulip-api-py{}-venv".format(py_version[0])
|
venv_name = f"zulip-api-py{py_version[0]}-venv"
|
||||||
|
|
||||||
if py_version <= (3, 1) and (not options.force):
|
if py_version <= (3, 1) and (not options.force):
|
||||||
print(
|
print(
|
||||||
|
|
|
@ -62,11 +62,11 @@ def cleanup(package_dir):
|
||||||
build_dir = os.path.join(package_dir, "build")
|
build_dir = os.path.join(package_dir, "build")
|
||||||
temp_dir = os.path.join(package_dir, "temp")
|
temp_dir = os.path.join(package_dir, "temp")
|
||||||
dist_dir = os.path.join(package_dir, "dist")
|
dist_dir = os.path.join(package_dir, "dist")
|
||||||
egg_info = os.path.join(package_dir, "{}.egg-info".format(os.path.basename(package_dir)))
|
egg_info = os.path.join(package_dir, f"{os.path.basename(package_dir)}.egg-info")
|
||||||
|
|
||||||
def _rm_if_it_exists(directory):
|
def _rm_if_it_exists(directory):
|
||||||
if os.path.isdir(directory):
|
if os.path.isdir(directory):
|
||||||
print(crayons.green("Removing {}/*".format(directory), bold=True))
|
print(crayons.green(f"Removing {directory}/*", bold=True))
|
||||||
shutil.rmtree(directory)
|
shutil.rmtree(directory)
|
||||||
|
|
||||||
_rm_if_it_exists(build_dir)
|
_rm_if_it_exists(build_dir)
|
||||||
|
@ -91,7 +91,7 @@ def set_variable(fp, variable, value):
|
||||||
os.remove(fp)
|
os.remove(fp)
|
||||||
shutil.move(temp_abs_path, fp)
|
shutil.move(temp_abs_path, fp)
|
||||||
|
|
||||||
message = "Set {variable} in {fp} to {value}.".format(fp=fp, variable=variable, value=value)
|
message = f"Set {variable} in {fp} to {value}."
|
||||||
print(crayons.white(message, bold=True))
|
print(crayons.white(message, bold=True))
|
||||||
|
|
||||||
|
|
||||||
|
@ -122,8 +122,8 @@ def update_requirements_in_zulip_repo(zulip_repo_dir, version, hash_or_tag):
|
||||||
_edit_reqs_file(prod, zulip_bots_line, zulip_line)
|
_edit_reqs_file(prod, zulip_bots_line, zulip_line)
|
||||||
_edit_reqs_file(dev, zulip_bots_line, zulip_line)
|
_edit_reqs_file(dev, zulip_bots_line, zulip_line)
|
||||||
|
|
||||||
editable_zulip = '-e "{}"\n'.format(url_zulip.rstrip())
|
editable_zulip = f'-e "{url_zulip.rstrip()}"\n'
|
||||||
editable_zulip_bots = '-e "{}"\n'.format(url_zulip_bots.rstrip())
|
editable_zulip_bots = f'-e "{url_zulip_bots.rstrip()}"\n'
|
||||||
|
|
||||||
_edit_reqs_file(
|
_edit_reqs_file(
|
||||||
common,
|
common,
|
||||||
|
|
|
@ -35,7 +35,7 @@ def check_git_pristine() -> None:
|
||||||
def ensure_on_clean_master() -> None:
|
def ensure_on_clean_master() -> None:
|
||||||
branch = get_git_branch()
|
branch = get_git_branch()
|
||||||
if branch != "master":
|
if branch != "master":
|
||||||
exit("You are still on a feature branch: %s" % (branch,))
|
exit(f"You are still on a feature branch: {branch}")
|
||||||
check_git_pristine()
|
check_git_pristine()
|
||||||
run("git fetch upstream master")
|
run("git fetch upstream master")
|
||||||
run("git rebase upstream/master")
|
run("git rebase upstream/master")
|
||||||
|
@ -43,7 +43,7 @@ def ensure_on_clean_master() -> None:
|
||||||
|
|
||||||
def create_pull_branch(pull_id: int) -> None:
|
def create_pull_branch(pull_id: int) -> None:
|
||||||
run("git fetch upstream pull/%d/head" % (pull_id,))
|
run("git fetch upstream pull/%d/head" % (pull_id,))
|
||||||
run("git checkout -B review-%s FETCH_HEAD" % (pull_id,))
|
run(f"git checkout -B review-{pull_id} FETCH_HEAD")
|
||||||
run("git rebase upstream/master")
|
run("git rebase upstream/master")
|
||||||
run("git log upstream/master.. --oneline")
|
run("git log upstream/master.. --oneline")
|
||||||
run("git diff upstream/master.. --name-status")
|
run("git diff upstream/master.. --name-status")
|
||||||
|
|
|
@ -207,7 +207,7 @@ if args.quick:
|
||||||
# run mypy
|
# run mypy
|
||||||
status = 0
|
status = 0
|
||||||
for repo, python_files in repo_python_files.items():
|
for repo, python_files in repo_python_files.items():
|
||||||
print("Running mypy for `{}`.".format(repo), flush=True)
|
print(f"Running mypy for `{repo}`.", flush=True)
|
||||||
if python_files:
|
if python_files:
|
||||||
result = subprocess.call([mypy_command] + extra_args + python_files)
|
result = subprocess.call([mypy_command] + extra_args + python_files)
|
||||||
if result != 0:
|
if result != 0:
|
||||||
|
|
|
@ -11,7 +11,7 @@ os.chdir(os.path.dirname(TOOLS_DIR))
|
||||||
|
|
||||||
|
|
||||||
def handle_input_and_run_tests_for_package(package_name, path_list):
|
def handle_input_and_run_tests_for_package(package_name, path_list):
|
||||||
parser = argparse.ArgumentParser(description="Run tests for {}.".format(package_name))
|
parser = argparse.ArgumentParser(description=f"Run tests for {package_name}.")
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--coverage",
|
"--coverage",
|
||||||
nargs="?",
|
nargs="?",
|
||||||
|
@ -31,7 +31,7 @@ def handle_input_and_run_tests_for_package(package_name, path_list):
|
||||||
)
|
)
|
||||||
options = parser.parse_args()
|
options = parser.parse_args()
|
||||||
|
|
||||||
test_session_title = " Running tests for {} ".format(package_name)
|
test_session_title = f" Running tests for {package_name} "
|
||||||
header = test_session_title.center(shutil.get_terminal_size().columns, "#")
|
header = test_session_title.center(shutil.get_terminal_size().columns, "#")
|
||||||
print(header)
|
print(header)
|
||||||
|
|
||||||
|
|
|
@ -97,7 +97,7 @@ def main():
|
||||||
|
|
||||||
if options.pytest:
|
if options.pytest:
|
||||||
excluded_bots = ["merels"]
|
excluded_bots = ["merels"]
|
||||||
pytest_bots_to_test = sorted([bot for bot in bots_to_test if bot not in excluded_bots])
|
pytest_bots_to_test = sorted(bot for bot in bots_to_test if bot not in excluded_bots)
|
||||||
pytest_options = [
|
pytest_options = [
|
||||||
"-s", # show output from tests; this hides the progress bar though
|
"-s", # show output from tests; this hides the progress bar though
|
||||||
"-x", # stop on first test failure
|
"-x", # stop on first test failure
|
||||||
|
|
|
@ -46,7 +46,7 @@ class IRCBot(irc.bot.SingleServerIRCBot):
|
||||||
def check_subscription_or_die(self) -> None:
|
def check_subscription_or_die(self) -> None:
|
||||||
resp = self.zulip_client.get_subscriptions()
|
resp = self.zulip_client.get_subscriptions()
|
||||||
if resp["result"] != "success":
|
if resp["result"] != "success":
|
||||||
print("ERROR: %s" % (resp["msg"],))
|
print("ERROR: {}".format(resp["msg"]))
|
||||||
exit(1)
|
exit(1)
|
||||||
subs = [s["name"] for s in resp["subscriptions"]]
|
subs = [s["name"] for s in resp["subscriptions"]]
|
||||||
if self.stream not in subs:
|
if self.stream not in subs:
|
||||||
|
@ -61,7 +61,7 @@ class IRCBot(irc.bot.SingleServerIRCBot):
|
||||||
|
|
||||||
def on_welcome(self, c: ServerConnection, e: Event) -> None:
|
def on_welcome(self, c: ServerConnection, e: Event) -> None:
|
||||||
if len(self.nickserv_password) > 0:
|
if len(self.nickserv_password) > 0:
|
||||||
msg = "identify %s" % (self.nickserv_password,)
|
msg = f"identify {self.nickserv_password}"
|
||||||
c.privmsg("NickServ", msg)
|
c.privmsg("NickServ", msg)
|
||||||
c.join(self.channel)
|
c.join(self.channel)
|
||||||
|
|
||||||
|
@ -75,7 +75,7 @@ class IRCBot(irc.bot.SingleServerIRCBot):
|
||||||
in_the_specified_stream = msg["display_recipient"] == self.stream
|
in_the_specified_stream = msg["display_recipient"] == self.stream
|
||||||
at_the_specified_subject = msg["subject"].casefold() == self.topic.casefold()
|
at_the_specified_subject = msg["subject"].casefold() == self.topic.casefold()
|
||||||
if in_the_specified_stream and at_the_specified_subject:
|
if in_the_specified_stream and at_the_specified_subject:
|
||||||
msg["content"] = ("@**%s**: " % (msg["sender_full_name"],)) + msg["content"]
|
msg["content"] = ("@**{}**: ".format(msg["sender_full_name"])) + msg["content"]
|
||||||
send = lambda x: self.c.privmsg(self.channel, x)
|
send = lambda x: self.c.privmsg(self.channel, x)
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
@ -126,7 +126,7 @@ class IRCBot(irc.bot.SingleServerIRCBot):
|
||||||
"type": "stream",
|
"type": "stream",
|
||||||
"to": self.stream,
|
"to": self.stream,
|
||||||
"subject": self.topic,
|
"subject": self.topic,
|
||||||
"content": "**{}**: {}".format(sender, content),
|
"content": f"**{sender}**: {content}",
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
|
@ -77,7 +77,7 @@ def matrix_to_zulip(
|
||||||
"""
|
"""
|
||||||
content = get_message_content_from_event(event, no_noise)
|
content = get_message_content_from_event(event, no_noise)
|
||||||
|
|
||||||
zulip_bot_user = "@%s:matrix.org" % (matrix_config["username"],)
|
zulip_bot_user = "@{}:matrix.org".format(matrix_config["username"])
|
||||||
# We do this to identify the messages generated from Zulip -> Matrix
|
# We do this to identify the messages generated from Zulip -> Matrix
|
||||||
# and we make sure we don't forward it again to the Zulip stream.
|
# and we make sure we don't forward it again to the Zulip stream.
|
||||||
not_from_zulip_bot = event["sender"] != zulip_bot_user
|
not_from_zulip_bot = event["sender"] != zulip_bot_user
|
||||||
|
@ -228,9 +228,7 @@ def read_configuration(config_file: str) -> Dict[str, Dict[str, str]]:
|
||||||
|
|
||||||
def write_sample_config(target_path: str, zuliprc: Optional[str]) -> None:
|
def write_sample_config(target_path: str, zuliprc: Optional[str]) -> None:
|
||||||
if os.path.exists(target_path):
|
if os.path.exists(target_path):
|
||||||
raise Bridge_ConfigException(
|
raise Bridge_ConfigException(f"Path '{target_path}' exists; not overwriting existing file.")
|
||||||
"Path '{}' exists; not overwriting existing file.".format(target_path)
|
|
||||||
)
|
|
||||||
|
|
||||||
sample_dict = OrderedDict(
|
sample_dict = OrderedDict(
|
||||||
(
|
(
|
||||||
|
@ -262,7 +260,7 @@ def write_sample_config(target_path: str, zuliprc: Optional[str]) -> None:
|
||||||
|
|
||||||
if zuliprc is not None:
|
if zuliprc is not None:
|
||||||
if not os.path.exists(zuliprc):
|
if not os.path.exists(zuliprc):
|
||||||
raise Bridge_ConfigException("Zuliprc file '{}' does not exist.".format(zuliprc))
|
raise Bridge_ConfigException(f"Zuliprc file '{zuliprc}' does not exist.")
|
||||||
|
|
||||||
zuliprc_config = configparser.ConfigParser()
|
zuliprc_config = configparser.ConfigParser()
|
||||||
try:
|
try:
|
||||||
|
@ -293,10 +291,10 @@ def main() -> None:
|
||||||
try:
|
try:
|
||||||
write_sample_config(options.sample_config, options.zuliprc)
|
write_sample_config(options.sample_config, options.zuliprc)
|
||||||
except Bridge_ConfigException as exception:
|
except Bridge_ConfigException as exception:
|
||||||
print("Could not write sample config: {}".format(exception))
|
print(f"Could not write sample config: {exception}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
if options.zuliprc is None:
|
if options.zuliprc is None:
|
||||||
print("Wrote sample configuration to '{}'".format(options.sample_config))
|
print(f"Wrote sample configuration to '{options.sample_config}'")
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
"Wrote sample configuration to '{}' using zuliprc file '{}'".format(
|
"Wrote sample configuration to '{}' using zuliprc file '{}'".format(
|
||||||
|
@ -312,7 +310,7 @@ def main() -> None:
|
||||||
try:
|
try:
|
||||||
config = read_configuration(options.config)
|
config = read_configuration(options.config)
|
||||||
except Bridge_ConfigException as exception:
|
except Bridge_ConfigException as exception:
|
||||||
print("Could not parse config file: {}".format(exception))
|
print(f"Could not parse config file: {exception}")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# Get config for each client
|
# Get config for each client
|
||||||
|
@ -347,11 +345,11 @@ def main() -> None:
|
||||||
zulip_client.call_on_each_message(zulip_to_matrix(zulip_config, room))
|
zulip_client.call_on_each_message(zulip_to_matrix(zulip_config, room))
|
||||||
|
|
||||||
except Bridge_FatalMatrixException as exception:
|
except Bridge_FatalMatrixException as exception:
|
||||||
sys.exit("Matrix bridge error: {}".format(exception))
|
sys.exit(f"Matrix bridge error: {exception}")
|
||||||
except Bridge_ZulipFatalException as exception:
|
except Bridge_ZulipFatalException as exception:
|
||||||
sys.exit("Zulip bridge error: {}".format(exception))
|
sys.exit(f"Zulip bridge error: {exception}")
|
||||||
except zulip.ZulipError as exception:
|
except zulip.ZulipError as exception:
|
||||||
sys.exit("Zulip error: {}".format(exception))
|
sys.exit(f"Zulip error: {exception}")
|
||||||
except Exception:
|
except Exception:
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
backoff.fail()
|
backoff.fail()
|
||||||
|
|
|
@ -49,14 +49,14 @@ class MatrixBridgeScriptTests(TestCase):
|
||||||
output_lines = self.output_from_script([])
|
output_lines = self.output_from_script([])
|
||||||
expected_lines = [
|
expected_lines = [
|
||||||
"Options required: -c or --config to run, OR --write-sample-config.",
|
"Options required: -c or --config to run, OR --write-sample-config.",
|
||||||
"usage: {} [-h]".format(script_file),
|
f"usage: {script_file} [-h]",
|
||||||
]
|
]
|
||||||
for expected, output in zip(expected_lines, output_lines):
|
for expected, output in zip(expected_lines, output_lines):
|
||||||
self.assertIn(expected, output)
|
self.assertIn(expected, output)
|
||||||
|
|
||||||
def test_help_usage_and_description(self) -> None:
|
def test_help_usage_and_description(self) -> None:
|
||||||
output_lines = self.output_from_script(["-h"])
|
output_lines = self.output_from_script(["-h"])
|
||||||
usage = "usage: {} [-h]".format(script_file)
|
usage = f"usage: {script_file} [-h]"
|
||||||
description = "Script to bridge"
|
description = "Script to bridge"
|
||||||
self.assertIn(usage, output_lines[0])
|
self.assertIn(usage, output_lines[0])
|
||||||
blank_lines = [num for num, line in enumerate(output_lines) if line == ""]
|
blank_lines = [num for num, line in enumerate(output_lines) if line == ""]
|
||||||
|
@ -71,7 +71,7 @@ class MatrixBridgeScriptTests(TestCase):
|
||||||
with new_temp_dir() as tempdir:
|
with new_temp_dir() as tempdir:
|
||||||
path = os.path.join(tempdir, sample_config_path)
|
path = os.path.join(tempdir, sample_config_path)
|
||||||
output_lines = self.output_from_script(["--write-sample-config", path])
|
output_lines = self.output_from_script(["--write-sample-config", path])
|
||||||
self.assertEqual(output_lines, ["Wrote sample configuration to '{}'".format(path)])
|
self.assertEqual(output_lines, [f"Wrote sample configuration to '{path}'"])
|
||||||
|
|
||||||
with open(path) as sample_file:
|
with open(path) as sample_file:
|
||||||
self.assertEqual(sample_file.read(), sample_config_text)
|
self.assertEqual(sample_file.read(), sample_config_text)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
# -*- coding: utf-8 -*-
|
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import os
|
import os
|
||||||
|
|
|
@ -56,7 +56,7 @@ while len(json_implementations):
|
||||||
|
|
||||||
def make_api_call(path: str) -> Optional[List[Dict[str, Any]]]:
|
def make_api_call(path: str) -> Optional[List[Dict[str, Any]]]:
|
||||||
response = requests.get(
|
response = requests.get(
|
||||||
"https://api3.codebasehq.com/%s" % (path,),
|
f"https://api3.codebasehq.com/{path}",
|
||||||
auth=(config.CODEBASE_API_USERNAME, config.CODEBASE_API_KEY),
|
auth=(config.CODEBASE_API_USERNAME, config.CODEBASE_API_KEY),
|
||||||
params={"raw": "True"},
|
params={"raw": "True"},
|
||||||
headers={
|
headers={
|
||||||
|
@ -76,13 +76,13 @@ def make_api_call(path: str) -> Optional[List[Dict[str, Any]]]:
|
||||||
sys.exit(-1)
|
sys.exit(-1)
|
||||||
else:
|
else:
|
||||||
logging.warn(
|
logging.warn(
|
||||||
"Found non-success response status code: %s %s" % (response.status_code, response.text)
|
f"Found non-success response status code: {response.status_code} {response.text}"
|
||||||
)
|
)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def make_url(path: str) -> str:
|
def make_url(path: str) -> str:
|
||||||
return "%s/%s" % (config.CODEBASE_ROOT_URL, path)
|
return f"{config.CODEBASE_ROOT_URL}/{path}"
|
||||||
|
|
||||||
|
|
||||||
def handle_event(event: Dict[str, Any]) -> None:
|
def handle_event(event: Dict[str, Any]) -> None:
|
||||||
|
@ -102,11 +102,11 @@ def handle_event(event: Dict[str, Any]) -> None:
|
||||||
project_name = raw_props.get("name")
|
project_name = raw_props.get("name")
|
||||||
project_repo_type = raw_props.get("scm_type")
|
project_repo_type = raw_props.get("scm_type")
|
||||||
|
|
||||||
url = make_url("projects/%s" % (project_link,))
|
url = make_url(f"projects/{project_link}")
|
||||||
scm = "of type %s" % (project_repo_type,) if project_repo_type else ""
|
scm = f"of type {project_repo_type}" if project_repo_type else ""
|
||||||
|
|
||||||
subject = "Repository %s Created" % (project_name,)
|
subject = f"Repository {project_name} Created"
|
||||||
content = "%s created a new repository %s [%s](%s)" % (actor_name, scm, project_name, url)
|
content = f"{actor_name} created a new repository {scm} [{project_name}]({url})"
|
||||||
elif event_type == "push":
|
elif event_type == "push":
|
||||||
stream = config.ZULIP_COMMITS_STREAM_NAME
|
stream = config.ZULIP_COMMITS_STREAM_NAME
|
||||||
|
|
||||||
|
@ -117,14 +117,14 @@ def handle_event(event: Dict[str, Any]) -> None:
|
||||||
deleted_ref = raw_props.get("deleted_ref")
|
deleted_ref = raw_props.get("deleted_ref")
|
||||||
new_ref = raw_props.get("new_ref")
|
new_ref = raw_props.get("new_ref")
|
||||||
|
|
||||||
subject = "Push to %s on %s" % (branch, project)
|
subject = f"Push to {branch} on {project}"
|
||||||
|
|
||||||
if deleted_ref:
|
if deleted_ref:
|
||||||
content = "%s deleted branch %s from %s" % (actor_name, branch, project)
|
content = f"{actor_name} deleted branch {branch} from {project}"
|
||||||
else:
|
else:
|
||||||
if new_ref:
|
if new_ref:
|
||||||
branch = "new branch %s" % (branch,)
|
branch = f"new branch {branch}"
|
||||||
content = "%s pushed %s commit(s) to %s in project %s:\n\n" % (
|
content = "{} pushed {} commit(s) to {} in project {}:\n\n".format(
|
||||||
actor_name,
|
actor_name,
|
||||||
num_commits,
|
num_commits,
|
||||||
branch,
|
branch,
|
||||||
|
@ -132,11 +132,9 @@ def handle_event(event: Dict[str, Any]) -> None:
|
||||||
)
|
)
|
||||||
for commit in raw_props.get("commits"):
|
for commit in raw_props.get("commits"):
|
||||||
ref = commit.get("ref")
|
ref = commit.get("ref")
|
||||||
url = make_url(
|
url = make_url(f"projects/{project_link}/repositories/{repo_link}/commit/{ref}")
|
||||||
"projects/%s/repositories/%s/commit/%s" % (project_link, repo_link, ref)
|
|
||||||
)
|
|
||||||
message = commit.get("message")
|
message = commit.get("message")
|
||||||
content += "* [%s](%s): %s\n" % (ref, url, message)
|
content += f"* [{ref}]({url}): {message}\n"
|
||||||
elif event_type == "ticketing_ticket":
|
elif event_type == "ticketing_ticket":
|
||||||
stream = config.ZULIP_TICKETS_STREAM_NAME
|
stream = config.ZULIP_TICKETS_STREAM_NAME
|
||||||
|
|
||||||
|
@ -144,11 +142,11 @@ def handle_event(event: Dict[str, Any]) -> None:
|
||||||
name = raw_props.get("subject")
|
name = raw_props.get("subject")
|
||||||
assignee = raw_props.get("assignee")
|
assignee = raw_props.get("assignee")
|
||||||
priority = raw_props.get("priority")
|
priority = raw_props.get("priority")
|
||||||
url = make_url("projects/%s/tickets/%s" % (project_link, num))
|
url = make_url(f"projects/{project_link}/tickets/{num}")
|
||||||
|
|
||||||
if assignee is None:
|
if assignee is None:
|
||||||
assignee = "no one"
|
assignee = "no one"
|
||||||
subject = "#%s: %s" % (num, name)
|
subject = f"#{num}: {name}"
|
||||||
content = (
|
content = (
|
||||||
"""%s created a new ticket [#%s](%s) priority **%s** assigned to %s:\n\n~~~ quote\n %s"""
|
"""%s created a new ticket [#%s](%s) priority **%s** assigned to %s:\n\n~~~ quote\n %s"""
|
||||||
% (actor_name, num, url, priority, assignee, name)
|
% (actor_name, num, url, priority, assignee, name)
|
||||||
|
@ -161,12 +159,12 @@ def handle_event(event: Dict[str, Any]) -> None:
|
||||||
body = raw_props.get("content")
|
body = raw_props.get("content")
|
||||||
changes = raw_props.get("changes")
|
changes = raw_props.get("changes")
|
||||||
|
|
||||||
url = make_url("projects/%s/tickets/%s" % (project_link, num))
|
url = make_url(f"projects/{project_link}/tickets/{num}")
|
||||||
subject = "#%s: %s" % (num, name)
|
subject = f"#{num}: {name}"
|
||||||
|
|
||||||
content = ""
|
content = ""
|
||||||
if body is not None and len(body) > 0:
|
if body is not None and len(body) > 0:
|
||||||
content = "%s added a comment to ticket [#%s](%s):\n\n~~~ quote\n%s\n\n" % (
|
content = "{} added a comment to ticket [#{}]({}):\n\n~~~ quote\n{}\n\n".format(
|
||||||
actor_name,
|
actor_name,
|
||||||
num,
|
num,
|
||||||
url,
|
url,
|
||||||
|
@ -175,7 +173,7 @@ def handle_event(event: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
if "status_id" in changes:
|
if "status_id" in changes:
|
||||||
status_change = changes.get("status_id")
|
status_change = changes.get("status_id")
|
||||||
content += "Status changed from **%s** to **%s**\n\n" % (
|
content += "Status changed from **{}** to **{}**\n\n".format(
|
||||||
status_change[0],
|
status_change[0],
|
||||||
status_change[1],
|
status_change[1],
|
||||||
)
|
)
|
||||||
|
@ -184,10 +182,10 @@ def handle_event(event: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
name = raw_props.get("name")
|
name = raw_props.get("name")
|
||||||
identifier = raw_props.get("identifier")
|
identifier = raw_props.get("identifier")
|
||||||
url = make_url("projects/%s/milestone/%s" % (project_link, identifier))
|
url = make_url(f"projects/{project_link}/milestone/{identifier}")
|
||||||
|
|
||||||
subject = name
|
subject = name
|
||||||
content = "%s created a new milestone [%s](%s)" % (actor_name, name, url)
|
content = f"{actor_name} created a new milestone [{name}]({url})"
|
||||||
elif event_type == "comment":
|
elif event_type == "comment":
|
||||||
stream = config.ZULIP_COMMITS_STREAM_NAME
|
stream = config.ZULIP_COMMITS_STREAM_NAME
|
||||||
|
|
||||||
|
@ -198,12 +196,10 @@ def handle_event(event: Dict[str, Any]) -> None:
|
||||||
if commit:
|
if commit:
|
||||||
repo_link = raw_props.get("repository_permalink")
|
repo_link = raw_props.get("repository_permalink")
|
||||||
|
|
||||||
url = make_url(
|
url = make_url(f"projects/{project_link}/repositories/{repo_link}/commit/{commit}")
|
||||||
"projects/%s/repositories/%s/commit/%s" % (project_link, repo_link, commit)
|
|
||||||
)
|
|
||||||
|
|
||||||
subject = "%s commented on %s" % (actor_name, commit)
|
subject = f"{actor_name} commented on {commit}"
|
||||||
content = "%s commented on [%s](%s):\n\n~~~ quote\n%s" % (
|
content = "{} commented on [{}]({}):\n\n~~~ quote\n{}".format(
|
||||||
actor_name,
|
actor_name,
|
||||||
commit,
|
commit,
|
||||||
url,
|
url,
|
||||||
|
@ -215,13 +211,13 @@ def handle_event(event: Dict[str, Any]) -> None:
|
||||||
category = raw_props.get("category")
|
category = raw_props.get("category")
|
||||||
comment_content = raw_props.get("content")
|
comment_content = raw_props.get("content")
|
||||||
|
|
||||||
subject = "Discussion: %s" % (subj,)
|
subject = f"Discussion: {subj}"
|
||||||
|
|
||||||
if category:
|
if category:
|
||||||
format_str = "%s started a new discussion in %s:\n\n~~~ quote\n%s\n~~~"
|
format_str = "%s started a new discussion in %s:\n\n~~~ quote\n%s\n~~~"
|
||||||
content = format_str % (actor_name, category, comment_content)
|
content = format_str % (actor_name, category, comment_content)
|
||||||
else:
|
else:
|
||||||
content = "%s posted:\n\n~~~ quote\n%s\n~~~" % (actor_name, comment_content)
|
content = f"{actor_name} posted:\n\n~~~ quote\n{comment_content}\n~~~"
|
||||||
|
|
||||||
elif event_type == "deployment":
|
elif event_type == "deployment":
|
||||||
stream = config.ZULIP_COMMITS_STREAM_NAME
|
stream = config.ZULIP_COMMITS_STREAM_NAME
|
||||||
|
@ -233,19 +229,17 @@ def handle_event(event: Dict[str, Any]) -> None:
|
||||||
repo_link = raw_props.get("repository_permalink")
|
repo_link = raw_props.get("repository_permalink")
|
||||||
|
|
||||||
start_ref_url = make_url(
|
start_ref_url = make_url(
|
||||||
"projects/%s/repositories/%s/commit/%s" % (project_link, repo_link, start_ref)
|
f"projects/{project_link}/repositories/{repo_link}/commit/{start_ref}"
|
||||||
)
|
|
||||||
end_ref_url = make_url(
|
|
||||||
"projects/%s/repositories/%s/commit/%s" % (project_link, repo_link, end_ref)
|
|
||||||
)
|
)
|
||||||
|
end_ref_url = make_url(f"projects/{project_link}/repositories/{repo_link}/commit/{end_ref}")
|
||||||
between_url = make_url(
|
between_url = make_url(
|
||||||
"projects/%s/repositories/%s/compare/%s...%s"
|
"projects/%s/repositories/%s/compare/%s...%s"
|
||||||
% (project_link, repo_link, start_ref, end_ref)
|
% (project_link, repo_link, start_ref, end_ref)
|
||||||
)
|
)
|
||||||
|
|
||||||
subject = "Deployment to %s" % (environment,)
|
subject = f"Deployment to {environment}"
|
||||||
|
|
||||||
content = "%s deployed [%s](%s) [through](%s) [%s](%s) to the **%s** environment." % (
|
content = "{} deployed [{}]({}) [through]({}) [{}]({}) to the **{}** environment.".format(
|
||||||
actor_name,
|
actor_name,
|
||||||
start_ref,
|
start_ref,
|
||||||
start_ref_url,
|
start_ref_url,
|
||||||
|
@ -256,7 +250,7 @@ def handle_event(event: Dict[str, Any]) -> None:
|
||||||
)
|
)
|
||||||
if servers is not None:
|
if servers is not None:
|
||||||
content += "\n\nServers deployed to: %s" % (
|
content += "\n\nServers deployed to: %s" % (
|
||||||
", ".join(["`%s`" % (server,) for server in servers])
|
", ".join(f"`{server}`" for server in servers)
|
||||||
)
|
)
|
||||||
|
|
||||||
elif event_type == "named_tree":
|
elif event_type == "named_tree":
|
||||||
|
@ -270,7 +264,7 @@ def handle_event(event: Dict[str, Any]) -> None:
|
||||||
elif event_type == "sprint_ended":
|
elif event_type == "sprint_ended":
|
||||||
logging.warn("Sprint notifications not yet implemented")
|
logging.warn("Sprint notifications not yet implemented")
|
||||||
else:
|
else:
|
||||||
logging.info("Unknown event type %s, ignoring!" % (event_type,))
|
logging.info(f"Unknown event type {event_type}, ignoring!")
|
||||||
|
|
||||||
if subject and content:
|
if subject and content:
|
||||||
if len(subject) > 60:
|
if len(subject) > 60:
|
||||||
|
@ -280,9 +274,9 @@ def handle_event(event: Dict[str, Any]) -> None:
|
||||||
{"type": "stream", "to": stream, "subject": subject, "content": content}
|
{"type": "stream", "to": stream, "subject": subject, "content": content}
|
||||||
)
|
)
|
||||||
if res["result"] == "success":
|
if res["result"] == "success":
|
||||||
logging.info("Successfully sent Zulip with id: %s" % (res["id"],))
|
logging.info("Successfully sent Zulip with id: {}".format(res["id"]))
|
||||||
else:
|
else:
|
||||||
logging.warn("Failed to send Zulip: %s %s" % (res["result"], res["msg"]))
|
logging.warn("Failed to send Zulip: {} {}".format(res["result"], res["msg"]))
|
||||||
|
|
||||||
|
|
||||||
# the main run loop for this mirror script
|
# the main run loop for this mirror script
|
||||||
|
@ -300,7 +294,7 @@ def run_mirror() -> None:
|
||||||
else:
|
else:
|
||||||
since = datetime.fromtimestamp(float(timestamp), tz=pytz.utc)
|
since = datetime.fromtimestamp(float(timestamp), tz=pytz.utc)
|
||||||
except (ValueError, OSError) as e:
|
except (ValueError, OSError) as e:
|
||||||
logging.warn("Could not open resume file: %s" % (str(e),))
|
logging.warn(f"Could not open resume file: {str(e)}")
|
||||||
since = default_since()
|
since = default_since()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -339,9 +333,7 @@ def check_permissions() -> None:
|
||||||
try:
|
try:
|
||||||
open(config.RESUME_FILE, "a+")
|
open(config.RESUME_FILE, "a+")
|
||||||
except OSError as e:
|
except OSError as e:
|
||||||
sys.stderr.write(
|
sys.stderr.write(f"Could not open up the file {config.RESUME_FILE} for reading and writing")
|
||||||
"Could not open up the file %s for reading and writing" % (config.RESUME_FILE,)
|
|
||||||
)
|
|
||||||
sys.stderr.write(str(e))
|
sys.stderr.write(str(e))
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -41,14 +41,14 @@ def git_repository_name() -> str:
|
||||||
|
|
||||||
|
|
||||||
def git_commit_range(oldrev: str, newrev: str) -> str:
|
def git_commit_range(oldrev: str, newrev: str) -> str:
|
||||||
log_cmd = ["git", "log", "--reverse", "--pretty=%aE %H %s", "%s..%s" % (oldrev, newrev)]
|
log_cmd = ["git", "log", "--reverse", "--pretty=%aE %H %s", f"{oldrev}..{newrev}"]
|
||||||
commits = ""
|
commits = ""
|
||||||
for ln in subprocess.check_output(log_cmd, universal_newlines=True).splitlines():
|
for ln in subprocess.check_output(log_cmd, universal_newlines=True).splitlines():
|
||||||
author_email, commit_id, subject = ln.split(None, 2)
|
author_email, commit_id, subject = ln.split(None, 2)
|
||||||
if hasattr(config, "format_commit_message"):
|
if hasattr(config, "format_commit_message"):
|
||||||
commits += config.format_commit_message(author_email, subject, commit_id)
|
commits += config.format_commit_message(author_email, subject, commit_id)
|
||||||
else:
|
else:
|
||||||
commits += "!avatar(%s) %s\n" % (author_email, subject)
|
commits += f"!avatar({author_email}) {subject}\n"
|
||||||
return commits
|
return commits
|
||||||
|
|
||||||
|
|
||||||
|
@ -75,19 +75,19 @@ def send_bot_message(oldrev: str, newrev: str, refname: str) -> None:
|
||||||
removed = git_commit_range(newrev, oldrev)
|
removed = git_commit_range(newrev, oldrev)
|
||||||
|
|
||||||
if oldrev == "0000000000000000000000000000000000000000":
|
if oldrev == "0000000000000000000000000000000000000000":
|
||||||
message = "`%s` was pushed to new branch `%s`" % (new_head, branch)
|
message = f"`{new_head}` was pushed to new branch `{branch}`"
|
||||||
elif newrev == "0000000000000000000000000000000000000000":
|
elif newrev == "0000000000000000000000000000000000000000":
|
||||||
message = "branch `%s` was removed (was `%s`)" % (branch, old_head)
|
message = f"branch `{branch}` was removed (was `{old_head}`)"
|
||||||
elif removed:
|
elif removed:
|
||||||
message = "`%s` was pushed to `%s`, **REMOVING**:\n\n%s" % (new_head, branch, removed)
|
message = f"`{new_head}` was pushed to `{branch}`, **REMOVING**:\n\n{removed}"
|
||||||
if added:
|
if added:
|
||||||
message += "\n**and adding**:\n\n" + added
|
message += "\n**and adding**:\n\n" + added
|
||||||
message += "\n**A HISTORY REWRITE HAS OCCURRED!**"
|
message += "\n**A HISTORY REWRITE HAS OCCURRED!**"
|
||||||
message += "\n@everyone: Please check your local branches to deal with this."
|
message += "\n@everyone: Please check your local branches to deal with this."
|
||||||
elif added:
|
elif added:
|
||||||
message = "`%s` was deployed to `%s` with:\n\n%s" % (new_head, branch, added)
|
message = f"`{new_head}` was deployed to `{branch}` with:\n\n{added}"
|
||||||
else:
|
else:
|
||||||
message = "`%s` was pushed to `%s`... but nothing changed?" % (new_head, branch)
|
message = f"`{new_head}` was pushed to `{branch}`... but nothing changed?"
|
||||||
|
|
||||||
message_data = {
|
message_data = {
|
||||||
"type": "stream",
|
"type": "stream",
|
||||||
|
|
|
@ -25,7 +25,7 @@ ZULIP_API_KEY = "0123456789abcdef0123456789abcdef"
|
||||||
# And similarly for branch "test-post-receive" (for use when testing).
|
# And similarly for branch "test-post-receive" (for use when testing).
|
||||||
def commit_notice_destination(repo: str, branch: str, commit: str) -> Optional[Dict[str, str]]:
|
def commit_notice_destination(repo: str, branch: str, commit: str) -> Optional[Dict[str, str]]:
|
||||||
if branch in ["master", "test-post-receive"]:
|
if branch in ["master", "test-post-receive"]:
|
||||||
return dict(stream=STREAM_NAME, subject="%s" % (branch,))
|
return dict(stream=STREAM_NAME, subject=f"{branch}")
|
||||||
|
|
||||||
# Return None for cases where you don't want a notice sent
|
# Return None for cases where you don't want a notice sent
|
||||||
return None
|
return None
|
||||||
|
@ -37,7 +37,7 @@ def commit_notice_destination(repo: str, branch: str, commit: str) -> Optional[D
|
||||||
#
|
#
|
||||||
# return '!avatar(%s) [%s](https://example.com/commits/%s)\n' % (author, subject, commit_id)
|
# return '!avatar(%s) [%s](https://example.com/commits/%s)\n' % (author, subject, commit_id)
|
||||||
def format_commit_message(author: str, subject: str, commit_id: str) -> str:
|
def format_commit_message(author: str, subject: str, commit_id: str) -> str:
|
||||||
return "!avatar(%s) %s\n" % (author, subject)
|
return f"!avatar({author}) {subject}\n"
|
||||||
|
|
||||||
|
|
||||||
## If properly installed, the Zulip API should be in your import
|
## If properly installed, the Zulip API should be in your import
|
||||||
|
|
|
@ -174,9 +174,9 @@ def send_reminders() -> Optional[None]:
|
||||||
key = (id, start)
|
key = (id, start)
|
||||||
if key not in sent:
|
if key not in sent:
|
||||||
if start.hour == 0 and start.minute == 0:
|
if start.hour == 0 and start.minute == 0:
|
||||||
line = "%s is today." % (summary,)
|
line = f"{summary} is today."
|
||||||
else:
|
else:
|
||||||
line = "%s starts at %s" % (summary, start.strftime("%H:%M"))
|
line = "{} starts at {}".format(summary, start.strftime("%H:%M"))
|
||||||
print("Sending reminder:", line)
|
print("Sending reminder:", line)
|
||||||
messages.append(line)
|
messages.append(line)
|
||||||
keys.add(key)
|
keys.add(key)
|
||||||
|
|
|
@ -37,7 +37,7 @@ def format_summary_line(
|
||||||
revcount=revcount, s=plural, url=summary_url
|
revcount=revcount, s=plural, url=summary_url
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
formatted_commit_count = "{revcount} commit{s}".format(revcount=revcount, s=plural)
|
formatted_commit_count = f"{revcount} commit{plural}"
|
||||||
|
|
||||||
return "**{user}** pushed {commits} to **{branch}** (`{tip}:{node}`):\n\n".format(
|
return "**{user}** pushed {commits} to **{branch}** (`{tip}:{node}`):\n\n".format(
|
||||||
user=user, commits=formatted_commit_count, branch=branch, tip=tip, node=node[:12]
|
user=user, commits=formatted_commit_count, branch=branch, tip=tip, node=node[:12]
|
||||||
|
@ -60,9 +60,9 @@ def format_commit_lines(web_url: str, repo: repo, base: int, tip: int) -> str:
|
||||||
|
|
||||||
if web_url:
|
if web_url:
|
||||||
summary_url = rev_base_url + str(rev_ctx)
|
summary_url = rev_base_url + str(rev_ctx)
|
||||||
summary = "* [{summary}]({url})".format(summary=one_liner, url=summary_url)
|
summary = f"* [{one_liner}]({summary_url})"
|
||||||
else:
|
else:
|
||||||
summary = "* {summary}".format(summary=one_liner)
|
summary = f"* {one_liner}"
|
||||||
|
|
||||||
commit_summaries.append(summary)
|
commit_summaries.append(summary)
|
||||||
|
|
||||||
|
@ -95,7 +95,7 @@ def get_config(ui: ui, item: str) -> str:
|
||||||
# config returns configuration value.
|
# config returns configuration value.
|
||||||
return ui.config("zulip", item)
|
return ui.config("zulip", item)
|
||||||
except IndexError:
|
except IndexError:
|
||||||
ui.warn("Zulip: Could not find required item {} in hg config.".format(item))
|
ui.warn(f"Zulip: Could not find required item {item} in hg config.")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
||||||
|
@ -106,10 +106,10 @@ def hook(ui: ui, repo: repo, **kwargs: str) -> None:
|
||||||
hooktype = kwargs["hooktype"]
|
hooktype = kwargs["hooktype"]
|
||||||
node = kwargs["node"]
|
node = kwargs["node"]
|
||||||
|
|
||||||
ui.debug("Zulip: received {hooktype} event\n".format(hooktype=hooktype))
|
ui.debug(f"Zulip: received {hooktype} event\n")
|
||||||
|
|
||||||
if hooktype != "changegroup":
|
if hooktype != "changegroup":
|
||||||
ui.warn("Zulip: {hooktype} not supported\n".format(hooktype=hooktype))
|
ui.warn(f"Zulip: {hooktype} not supported\n")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
ctx = repo[node]
|
ctx = repo[node]
|
||||||
|
@ -123,14 +123,14 @@ def hook(ui: ui, repo: repo, **kwargs: str) -> None:
|
||||||
# Only send notifications on branches we are watching.
|
# Only send notifications on branches we are watching.
|
||||||
watched_branches = [b.lower().strip() for b in branch_whitelist.split(",")]
|
watched_branches = [b.lower().strip() for b in branch_whitelist.split(",")]
|
||||||
if branch.lower() not in watched_branches:
|
if branch.lower() not in watched_branches:
|
||||||
ui.debug("Zulip: ignoring event for {branch}\n".format(branch=branch))
|
ui.debug(f"Zulip: ignoring event for {branch}\n")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
if branch_blacklist:
|
if branch_blacklist:
|
||||||
# Don't send notifications for branches we've ignored.
|
# Don't send notifications for branches we've ignored.
|
||||||
ignored_branches = [b.lower().strip() for b in branch_blacklist.split(",")]
|
ignored_branches = [b.lower().strip() for b in branch_blacklist.split(",")]
|
||||||
if branch.lower() in ignored_branches:
|
if branch.lower() in ignored_branches:
|
||||||
ui.debug("Zulip: ignoring event for {branch}\n".format(branch=branch))
|
ui.debug(f"Zulip: ignoring event for {branch}\n")
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
# The first and final commits in the changeset.
|
# The first and final commits in the changeset.
|
||||||
|
|
|
@ -65,7 +65,7 @@ def jid_to_zulip(jid: JID) -> str:
|
||||||
suffix = ""
|
suffix = ""
|
||||||
if not jid.username.endswith("-bot"):
|
if not jid.username.endswith("-bot"):
|
||||||
suffix = options.zulip_email_suffix
|
suffix = options.zulip_email_suffix
|
||||||
return "%s%s@%s" % (jid.username, suffix, options.zulip_domain)
|
return f"{jid.username}{suffix}@{options.zulip_domain}"
|
||||||
|
|
||||||
|
|
||||||
def zulip_to_jid(email: str, jabber_domain: str) -> JID:
|
def zulip_to_jid(email: str, jabber_domain: str) -> JID:
|
||||||
|
@ -274,7 +274,7 @@ def get_rooms(zulipToJabber: ZulipToJabberBot) -> List[str]:
|
||||||
ret = method()
|
ret = method()
|
||||||
if ret.get("result") != "success":
|
if ret.get("result") != "success":
|
||||||
logging.error(str(ret))
|
logging.error(str(ret))
|
||||||
sys.exit("Could not get initial list of Zulip %s" % (key,))
|
sys.exit(f"Could not get initial list of Zulip {key}")
|
||||||
return ret[key]
|
return ret[key]
|
||||||
|
|
||||||
if options.mode == "public":
|
if options.mode == "public":
|
||||||
|
@ -291,7 +291,7 @@ def get_rooms(zulipToJabber: ZulipToJabberBot) -> List[str]:
|
||||||
|
|
||||||
|
|
||||||
def config_error(msg: str) -> None:
|
def config_error(msg: str) -> None:
|
||||||
sys.stderr.write("%s\n" % (msg,))
|
sys.stderr.write(f"{msg}\n")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
|
|
||||||
|
@ -442,10 +442,10 @@ option does not affect login credentials.""".replace(
|
||||||
try:
|
try:
|
||||||
jid = JID(options.jid)
|
jid = JID(options.jid)
|
||||||
except InvalidJID as e:
|
except InvalidJID as e:
|
||||||
config_error("Bad JID: %s: %s" % (options.jid, e.message))
|
config_error(f"Bad JID: {options.jid}: {e.message}")
|
||||||
|
|
||||||
if options.conference_domain is None:
|
if options.conference_domain is None:
|
||||||
options.conference_domain = "conference.%s" % (jid.domain,)
|
options.conference_domain = f"conference.{jid.domain}"
|
||||||
|
|
||||||
xmpp = JabberToZulipBot(jid, options.jabber_password, get_rooms(zulipToJabber))
|
xmpp = JabberToZulipBot(jid, options.jabber_password, get_rooms(zulipToJabber))
|
||||||
|
|
||||||
|
|
|
@ -46,12 +46,12 @@ def mkdir_p(path: str) -> None:
|
||||||
|
|
||||||
|
|
||||||
def send_log_zulip(file_name: str, count: int, lines: List[str], extra: str = "") -> None:
|
def send_log_zulip(file_name: str, count: int, lines: List[str], extra: str = "") -> None:
|
||||||
content = "%s new errors%s:\n```\n%s\n```" % (count, extra, "\n".join(lines))
|
content = "{} new errors{}:\n```\n{}\n```".format(count, extra, "\n".join(lines))
|
||||||
zulip_client.send_message(
|
zulip_client.send_message(
|
||||||
{
|
{
|
||||||
"type": "stream",
|
"type": "stream",
|
||||||
"to": "logs",
|
"to": "logs",
|
||||||
"subject": "%s on %s" % (file_name, platform.node()),
|
"subject": f"{file_name} on {platform.node()}",
|
||||||
"content": content,
|
"content": content,
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -84,7 +84,7 @@ def process_logs() -> None:
|
||||||
file_data = last_data.get(log_file, {})
|
file_data = last_data.get(log_file, {})
|
||||||
if not os.path.exists(log_file):
|
if not os.path.exists(log_file):
|
||||||
# If the file doesn't exist, log an error and then move on to the next file
|
# If the file doesn't exist, log an error and then move on to the next file
|
||||||
print("Log file does not exist or could not stat log file: %s" % (log_file,))
|
print(f"Log file does not exist or could not stat log file: {log_file}")
|
||||||
continue
|
continue
|
||||||
length = int(subprocess.check_output(["wc", "-l", log_file]).split()[0])
|
length = int(subprocess.check_output(["wc", "-l", log_file]).split()[0])
|
||||||
if file_data.get("last") is None:
|
if file_data.get("last") is None:
|
||||||
|
@ -95,7 +95,7 @@ def process_logs() -> None:
|
||||||
# a log file ends up at the same line length as before
|
# a log file ends up at the same line length as before
|
||||||
# immediately after rotation, this tool won't notice.
|
# immediately after rotation, this tool won't notice.
|
||||||
file_data["last"] = 1
|
file_data["last"] = 1
|
||||||
output = subprocess.check_output(["tail", "-n+%s" % (file_data["last"],), log_file])
|
output = subprocess.check_output(["tail", "-n+{}".format(file_data["last"]), log_file])
|
||||||
new_lines = output.decode("utf-8", errors="replace").split("\n")[:-1]
|
new_lines = output.decode("utf-8", errors="replace").split("\n")[:-1]
|
||||||
if len(new_lines) > 0:
|
if len(new_lines) > 0:
|
||||||
process_lines(new_lines, log_file)
|
process_lines(new_lines, log_file)
|
||||||
|
@ -124,7 +124,7 @@ if __name__ == "__main__":
|
||||||
try:
|
try:
|
||||||
log_files = json.loads(open(args.control_path).read())
|
log_files = json.loads(open(args.control_path).read())
|
||||||
except (json.JSONDecodeError, OSError):
|
except (json.JSONDecodeError, OSError):
|
||||||
print("Could not load control data from %s" % (args.control_path,))
|
print(f"Could not load control data from {args.control_path}")
|
||||||
traceback.print_exc()
|
traceback.print_exc()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
process_logs()
|
process_logs()
|
||||||
|
|
|
@ -31,16 +31,16 @@ msg = dict(type="stream", to=opts.stream) # type: Dict[str, Any]
|
||||||
if opts.service is None:
|
if opts.service is None:
|
||||||
# Host notification
|
# Host notification
|
||||||
thing = "host" # type: Text
|
thing = "host" # type: Text
|
||||||
msg["subject"] = "host %s" % (opts.host,)
|
msg["subject"] = f"host {opts.host}"
|
||||||
else:
|
else:
|
||||||
# Service notification
|
# Service notification
|
||||||
thing = "service"
|
thing = "service"
|
||||||
msg["subject"] = "service %s on %s" % (opts.service, opts.host)
|
msg["subject"] = f"service {opts.service} on {opts.host}"
|
||||||
|
|
||||||
if len(msg["subject"]) > 60:
|
if len(msg["subject"]) > 60:
|
||||||
msg["subject"] = msg["subject"][0:57].rstrip() + "..."
|
msg["subject"] = msg["subject"][0:57].rstrip() + "..."
|
||||||
# e.g. **PROBLEM**: service is CRITICAL
|
# e.g. **PROBLEM**: service is CRITICAL
|
||||||
msg["content"] = "**%s**: %s is %s" % (opts.type, thing, opts.state)
|
msg["content"] = f"**{opts.type}**: {thing} is {opts.state}"
|
||||||
|
|
||||||
# The "long output" can contain newlines represented by "\n" escape sequences.
|
# The "long output" can contain newlines represented by "\n" escape sequences.
|
||||||
# The Nagios mail command uses /usr/bin/printf "%b" to expand these.
|
# The Nagios mail command uses /usr/bin/printf "%b" to expand these.
|
||||||
|
|
|
@ -21,7 +21,7 @@ ZULIP_API_KEY = "0123456789abcdef0123456789abcdef"
|
||||||
# And similarly for branch "test-post-receive" (for use when testing).
|
# And similarly for branch "test-post-receive" (for use when testing).
|
||||||
def deployment_notice_destination(branch: str) -> Optional[Dict[str, str]]:
|
def deployment_notice_destination(branch: str) -> Optional[Dict[str, str]]:
|
||||||
if branch in ["master", "test-post-receive"]:
|
if branch in ["master", "test-post-receive"]:
|
||||||
return dict(stream="deployments", subject="%s" % (branch,))
|
return dict(stream="deployments", subject=f"{branch}")
|
||||||
|
|
||||||
# Return None for cases where you don't want a notice sent
|
# Return None for cases where you don't want a notice sent
|
||||||
return None
|
return None
|
||||||
|
@ -46,7 +46,7 @@ def format_deployment_message(
|
||||||
dep_id: str = "",
|
dep_id: str = "",
|
||||||
dep_time: str = "",
|
dep_time: str = "",
|
||||||
) -> str:
|
) -> str:
|
||||||
return "Deployed commit `%s` (%s) in [%s](%s)" % (commit_id, branch, app_name, url)
|
return f"Deployed commit `{commit_id}` ({branch}) in [{app_name}]({url})"
|
||||||
|
|
||||||
|
|
||||||
## If properly installed, the Zulip API should be in your import
|
## If properly installed, the Zulip API should be in your import
|
||||||
|
|
|
@ -37,7 +37,7 @@ def commit_notice_destination(path: str, changelist: int) -> Optional[Dict[str,
|
||||||
directory = dirs[2]
|
directory = dirs[2]
|
||||||
|
|
||||||
if directory not in ["evil-master-plan", "my-super-secret-repository"]:
|
if directory not in ["evil-master-plan", "my-super-secret-repository"]:
|
||||||
return dict(stream="%s-commits" % (directory,), subject=path)
|
return dict(stream=f"{directory}-commits", subject=path)
|
||||||
|
|
||||||
# Return None for cases where you don't want a notice sent
|
# Return None for cases where you don't want a notice sent
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -105,7 +105,7 @@ try:
|
||||||
mkdir_p(opts.data_dir)
|
mkdir_p(opts.data_dir)
|
||||||
except OSError:
|
except OSError:
|
||||||
# We can't write to the logfile, so just print and give up.
|
# We can't write to the logfile, so just print and give up.
|
||||||
print("Unable to store RSS data at %s." % (opts.data_dir,), file=sys.stderr)
|
print(f"Unable to store RSS data at {opts.data_dir}.", file=sys.stderr)
|
||||||
exit(1)
|
exit(1)
|
||||||
|
|
||||||
log_file = os.path.join(opts.data_dir, "rss-bot.log") # type: str
|
log_file = os.path.join(opts.data_dir, "rss-bot.log") # type: str
|
||||||
|
@ -170,7 +170,7 @@ def send_zulip(entry: Any, feed_name: str) -> Dict[str, Any]:
|
||||||
if opts.unwrap:
|
if opts.unwrap:
|
||||||
body = unwrap_text(body)
|
body = unwrap_text(body)
|
||||||
|
|
||||||
content = "**[%s](%s)**\n%s\n%s" % (
|
content = "**[{}]({})**\n{}\n{}".format(
|
||||||
entry.title,
|
entry.title,
|
||||||
entry.link,
|
entry.link,
|
||||||
strip_tags(body),
|
strip_tags(body),
|
||||||
|
@ -194,7 +194,7 @@ try:
|
||||||
with open(opts.feed_file) as f:
|
with open(opts.feed_file) as f:
|
||||||
feed_urls = [feed.strip() for feed in f.readlines()] # type: List[str]
|
feed_urls = [feed.strip() for feed in f.readlines()] # type: List[str]
|
||||||
except OSError:
|
except OSError:
|
||||||
log_error_and_exit("Unable to read feed file at %s." % (opts.feed_file,))
|
log_error_and_exit(f"Unable to read feed file at {opts.feed_file}.")
|
||||||
|
|
||||||
client = zulip.Client(
|
client = zulip.Client(
|
||||||
email=opts.zulip_email,
|
email=opts.zulip_email,
|
||||||
|
@ -245,7 +245,7 @@ for feed_url in feed_urls:
|
||||||
|
|
||||||
response = send_zulip(entry, feed_name) # type: Dict[str, Any]
|
response = send_zulip(entry, feed_name) # type: Dict[str, Any]
|
||||||
if response["result"] != "success":
|
if response["result"] != "success":
|
||||||
logger.error("Error processing %s" % (feed_url,))
|
logger.error(f"Error processing {feed_url}")
|
||||||
logger.error(str(response))
|
logger.error(str(response))
|
||||||
if first_message:
|
if first_message:
|
||||||
# This is probably some fundamental problem like the stream not
|
# This is probably some fundamental problem like the stream not
|
||||||
|
|
|
@ -21,7 +21,7 @@ ZULIP_API_KEY = "0123456789abcdef0123456789abcdef"
|
||||||
def commit_notice_destination(path: str, commit: str) -> Optional[Dict[str, str]]:
|
def commit_notice_destination(path: str, commit: str) -> Optional[Dict[str, str]]:
|
||||||
repo = path.split("/")[-1]
|
repo = path.split("/")[-1]
|
||||||
if repo not in ["evil-master-plan", "my-super-secret-repository"]:
|
if repo not in ["evil-master-plan", "my-super-secret-repository"]:
|
||||||
return dict(stream="commits", subject="%s" % (repo,))
|
return dict(stream="commits", subject=f"{repo}")
|
||||||
|
|
||||||
# Return None for cases where you don't want a notice sent
|
# Return None for cases where you don't want a notice sent
|
||||||
return None
|
return None
|
||||||
|
|
|
@ -38,7 +38,7 @@ client = zulip.Client(
|
||||||
|
|
||||||
|
|
||||||
def markdown_ticket_url(ticket: Any, heading: str = "ticket") -> str:
|
def markdown_ticket_url(ticket: Any, heading: str = "ticket") -> str:
|
||||||
return "[%s #%s](%s/%s)" % (heading, ticket.id, config.TRAC_BASE_TICKET_URL, ticket.id)
|
return f"[{heading} #{ticket.id}]({config.TRAC_BASE_TICKET_URL}/{ticket.id})"
|
||||||
|
|
||||||
|
|
||||||
def markdown_block(desc: str) -> str:
|
def markdown_block(desc: str) -> str:
|
||||||
|
@ -52,7 +52,7 @@ def truncate(string: str, length: int) -> str:
|
||||||
|
|
||||||
|
|
||||||
def trac_subject(ticket: Any) -> str:
|
def trac_subject(ticket: Any) -> str:
|
||||||
return truncate("#%s: %s" % (ticket.id, ticket.values.get("summary")), 60)
|
return truncate("#{}: {}".format(ticket.id, ticket.values.get("summary")), 60)
|
||||||
|
|
||||||
|
|
||||||
def send_update(ticket: Any, content: str) -> None:
|
def send_update(ticket: Any, content: str) -> None:
|
||||||
|
@ -71,7 +71,7 @@ class ZulipPlugin(Component):
|
||||||
|
|
||||||
def ticket_created(self, ticket: Any) -> None:
|
def ticket_created(self, ticket: Any) -> None:
|
||||||
"""Called when a ticket is created."""
|
"""Called when a ticket is created."""
|
||||||
content = "%s created %s in component **%s**, priority **%s**:\n" % (
|
content = "{} created {} in component **{}**, priority **{}**:\n".format(
|
||||||
ticket.values.get("reporter"),
|
ticket.values.get("reporter"),
|
||||||
markdown_ticket_url(ticket),
|
markdown_ticket_url(ticket),
|
||||||
ticket.values.get("component"),
|
ticket.values.get("component"),
|
||||||
|
@ -79,9 +79,9 @@ class ZulipPlugin(Component):
|
||||||
)
|
)
|
||||||
# Include the full subject if it will be truncated
|
# Include the full subject if it will be truncated
|
||||||
if len(ticket.values.get("summary")) > 60:
|
if len(ticket.values.get("summary")) > 60:
|
||||||
content += "**%s**\n" % (ticket.values.get("summary"),)
|
content += "**{}**\n".format(ticket.values.get("summary"))
|
||||||
if ticket.values.get("description") != "":
|
if ticket.values.get("description") != "":
|
||||||
content += "%s" % (markdown_block(ticket.values.get("description")),)
|
content += "{}".format(markdown_block(ticket.values.get("description")))
|
||||||
send_update(ticket, content)
|
send_update(ticket, content)
|
||||||
|
|
||||||
def ticket_changed(
|
def ticket_changed(
|
||||||
|
@ -98,26 +98,26 @@ class ZulipPlugin(Component):
|
||||||
):
|
):
|
||||||
return
|
return
|
||||||
|
|
||||||
content = "%s updated %s" % (author, markdown_ticket_url(ticket))
|
content = f"{author} updated {markdown_ticket_url(ticket)}"
|
||||||
if comment:
|
if comment:
|
||||||
content += " with comment: %s\n\n" % (markdown_block(comment),)
|
content += f" with comment: {markdown_block(comment)}\n\n"
|
||||||
else:
|
else:
|
||||||
content += ":\n\n"
|
content += ":\n\n"
|
||||||
field_changes = []
|
field_changes = []
|
||||||
for key, value in old_values.items():
|
for key, value in old_values.items():
|
||||||
if key == "description":
|
if key == "description":
|
||||||
content += "- Changed %s from %s\n\nto %s" % (
|
content += "- Changed {} from {}\n\nto {}".format(
|
||||||
key,
|
key,
|
||||||
markdown_block(value),
|
markdown_block(value),
|
||||||
markdown_block(ticket.values.get(key)),
|
markdown_block(ticket.values.get(key)),
|
||||||
)
|
)
|
||||||
elif old_values.get(key) == "":
|
elif old_values.get(key) == "":
|
||||||
field_changes.append("%s: => **%s**" % (key, ticket.values.get(key)))
|
field_changes.append(f"{key}: => **{ticket.values.get(key)}**")
|
||||||
elif ticket.values.get(key) == "":
|
elif ticket.values.get(key) == "":
|
||||||
field_changes.append('%s: **%s** => ""' % (key, old_values.get(key)))
|
field_changes.append(f'{key}: **{old_values.get(key)}** => ""')
|
||||||
else:
|
else:
|
||||||
field_changes.append(
|
field_changes.append(
|
||||||
"%s: **%s** => **%s**" % (key, old_values.get(key), ticket.values.get(key))
|
f"{key}: **{old_values.get(key)}** => **{ticket.values.get(key)}**"
|
||||||
)
|
)
|
||||||
content += ", ".join(field_changes)
|
content += ", ".join(field_changes)
|
||||||
|
|
||||||
|
@ -125,5 +125,5 @@ class ZulipPlugin(Component):
|
||||||
|
|
||||||
def ticket_deleted(self, ticket: Any) -> None:
|
def ticket_deleted(self, ticket: Any) -> None:
|
||||||
"""Called when a ticket is deleted."""
|
"""Called when a ticket is deleted."""
|
||||||
content = "%s was deleted." % (markdown_ticket_url(ticket, heading="Ticket"),)
|
content = "{} was deleted.".format(markdown_ticket_url(ticket, heading="Ticket"))
|
||||||
send_update(ticket, content)
|
send_update(ticket, content)
|
||||||
|
|
|
@ -25,7 +25,7 @@ def get_model_id(options):
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
|
||||||
trello_api_url = "https://api.trello.com/1/board/{}".format(options.trello_board_id)
|
trello_api_url = f"https://api.trello.com/1/board/{options.trello_board_id}"
|
||||||
|
|
||||||
params = {
|
params = {
|
||||||
"key": options.trello_api_key,
|
"key": options.trello_api_key,
|
||||||
|
@ -88,7 +88,7 @@ def create_webhook(options):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# first, we need to get the idModel
|
# first, we need to get the idModel
|
||||||
print("Getting Trello idModel for the {} board...".format(options.trello_board_name))
|
print(f"Getting Trello idModel for the {options.trello_board_name} board...")
|
||||||
|
|
||||||
id_model = get_model_id(options)
|
id_model = get_model_id(options)
|
||||||
|
|
||||||
|
|
|
@ -202,8 +202,8 @@ for status in statuses[::-1][: opts.limit_tweets]:
|
||||||
continue # Continue with the loop for the next tweet
|
continue # Continue with the loop for the next tweet
|
||||||
|
|
||||||
# https://twitter.com/eatevilpenguins/status/309995853408530432
|
# https://twitter.com/eatevilpenguins/status/309995853408530432
|
||||||
composed = "%s (%s)" % (status.user.name, status.user.screen_name)
|
composed = f"{status.user.name} ({status.user.screen_name})"
|
||||||
url = "https://twitter.com/%s/status/%s" % (status.user.screen_name, status.id)
|
url = f"https://twitter.com/{status.user.screen_name}/status/{status.id}"
|
||||||
# This contains all strings that could have caused the tweet to match our query.
|
# This contains all strings that could have caused the tweet to match our query.
|
||||||
text_to_check = [status.text, status.user.screen_name]
|
text_to_check = [status.text, status.user.screen_name]
|
||||||
text_to_check.extend(url.expanded_url for url in status.urls)
|
text_to_check.extend(url.expanded_url for url in status.urls)
|
||||||
|
|
|
@ -143,7 +143,7 @@ for tries in range(10):
|
||||||
missing = 0
|
missing = 0
|
||||||
for elt in zephyr_subs_to_add:
|
for elt in zephyr_subs_to_add:
|
||||||
if elt not in zephyr_subs:
|
if elt not in zephyr_subs:
|
||||||
logging.error("Failed to subscribe to %s" % (elt,))
|
logging.error(f"Failed to subscribe to {elt}")
|
||||||
missing += 1
|
missing += 1
|
||||||
if missing == 0:
|
if missing == 0:
|
||||||
actually_subscribed = True
|
actually_subscribed = True
|
||||||
|
@ -220,9 +220,7 @@ for key, (stream, test) in zhkeys.items():
|
||||||
)
|
)
|
||||||
print_status_and_exit(1)
|
print_status_and_exit(1)
|
||||||
else:
|
else:
|
||||||
logging.warning(
|
logging.warning(f"Replaced key {key} with {new_key} due to Zephyr server failure.")
|
||||||
"Replaced key %s with %s due to Zephyr server failure." % (key, new_key)
|
|
||||||
)
|
|
||||||
receive_zephyrs()
|
receive_zephyrs()
|
||||||
|
|
||||||
receive_zephyrs()
|
receive_zephyrs()
|
||||||
|
|
|
@ -9,19 +9,19 @@ api_key = sys.argv[2]
|
||||||
ccache_data_encoded = sys.argv[3]
|
ccache_data_encoded = sys.argv[3]
|
||||||
|
|
||||||
# Update the Kerberos ticket cache file
|
# Update the Kerberos ticket cache file
|
||||||
program_name = "zmirror-%s" % (short_user,)
|
program_name = f"zmirror-{short_user}"
|
||||||
with open("/home/zulip/ccache/%s" % (program_name,), "wb") as f:
|
with open(f"/home/zulip/ccache/{program_name}", "wb") as f:
|
||||||
f.write(base64.b64decode(ccache_data_encoded))
|
f.write(base64.b64decode(ccache_data_encoded))
|
||||||
|
|
||||||
# Setup API key
|
# Setup API key
|
||||||
api_key_path = "/home/zulip/api-keys/%s" % (program_name,)
|
api_key_path = f"/home/zulip/api-keys/{program_name}"
|
||||||
open(api_key_path, "w").write(api_key + "\n")
|
open(api_key_path, "w").write(api_key + "\n")
|
||||||
|
|
||||||
# Setup supervisord configuration
|
# Setup supervisord configuration
|
||||||
supervisor_path = "/etc/supervisor/conf.d/zulip/%s.conf" % (program_name,)
|
supervisor_path = f"/etc/supervisor/conf.d/zulip/{program_name}.conf"
|
||||||
template = os.path.join(os.path.dirname(__file__), "zmirror_private.conf.template")
|
template = os.path.join(os.path.dirname(__file__), "zmirror_private.conf.template")
|
||||||
template_data = open(template).read()
|
template_data = open(template).read()
|
||||||
session_path = "/home/zulip/zephyr_sessions/%s" % (program_name,)
|
session_path = f"/home/zulip/zephyr_sessions/{program_name}"
|
||||||
|
|
||||||
# Preserve mail zephyrs forwarding setting across rewriting the config file
|
# Preserve mail zephyrs forwarding setting across rewriting the config file
|
||||||
|
|
||||||
|
|
|
@ -42,7 +42,7 @@ if options.forward_class_messages and not options.noshard:
|
||||||
jobs = list("0123456789abcdef")
|
jobs = list("0123456789abcdef")
|
||||||
|
|
||||||
def run_job(shard: str) -> int:
|
def run_job(shard: str) -> int:
|
||||||
subprocess.call(args + ["--shard=%s" % (shard,)])
|
subprocess.call(args + [f"--shard={shard}"])
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
for (status, job) in run_parallel(run_job, jobs, threads=16):
|
for (status, job) in run_parallel(run_job, jobs, threads=16):
|
||||||
|
|
|
@ -54,7 +54,7 @@ def to_zephyr_username(zulip_username: str) -> str:
|
||||||
return user.lower() + "@ATHENA.MIT.EDU"
|
return user.lower() + "@ATHENA.MIT.EDU"
|
||||||
match_user = re.match(r"([a-zA-Z0-9_]+)\|(.+)", user)
|
match_user = re.match(r"([a-zA-Z0-9_]+)\|(.+)", user)
|
||||||
if not match_user:
|
if not match_user:
|
||||||
raise Exception("Could not parse Zephyr realm for cross-realm user %s" % (zulip_username,))
|
raise Exception(f"Could not parse Zephyr realm for cross-realm user {zulip_username}")
|
||||||
return match_user.group(1).lower() + "@" + match_user.group(2).upper()
|
return match_user.group(1).lower() + "@" + match_user.group(2).upper()
|
||||||
|
|
||||||
|
|
||||||
|
@ -134,10 +134,10 @@ def send_zulip(zeph: ZephyrDict) -> Dict[str, Any]:
|
||||||
# Forward messages sent to -c foo -i bar to stream bar subject "instance"
|
# Forward messages sent to -c foo -i bar to stream bar subject "instance"
|
||||||
if zeph["stream"] == "message":
|
if zeph["stream"] == "message":
|
||||||
message["to"] = zeph["subject"].lower()
|
message["to"] = zeph["subject"].lower()
|
||||||
message["subject"] = "instance %s" % (zeph["subject"],)
|
message["subject"] = "instance {}".format(zeph["subject"])
|
||||||
elif zeph["stream"] == "tabbott-test5":
|
elif zeph["stream"] == "tabbott-test5":
|
||||||
message["to"] = zeph["subject"].lower()
|
message["to"] = zeph["subject"].lower()
|
||||||
message["subject"] = "test instance %s" % (zeph["subject"],)
|
message["subject"] = "test instance {}".format(zeph["subject"])
|
||||||
else:
|
else:
|
||||||
message["to"] = zeph["stream"]
|
message["to"] = zeph["stream"]
|
||||||
else:
|
else:
|
||||||
|
@ -145,7 +145,7 @@ def send_zulip(zeph: ZephyrDict) -> Dict[str, Any]:
|
||||||
message["content"] = unwrap_lines(zeph["content"])
|
message["content"] = unwrap_lines(zeph["content"])
|
||||||
|
|
||||||
if options.test_mode and options.site == DEFAULT_SITE:
|
if options.test_mode and options.site == DEFAULT_SITE:
|
||||||
logger.debug("Message is: %s" % (str(message),))
|
logger.debug(f"Message is: {str(message)}")
|
||||||
return {"result": "success"}
|
return {"result": "success"}
|
||||||
|
|
||||||
return zulip_client.send_message(message)
|
return zulip_client.send_message(message)
|
||||||
|
@ -174,7 +174,7 @@ def zephyr_bulk_subscribe(subs: List[Tuple[str, str, str]]) -> None:
|
||||||
# retrying the next time the bot checks its subscriptions are
|
# retrying the next time the bot checks its subscriptions are
|
||||||
# up to date.
|
# up to date.
|
||||||
logger.exception("Error subscribing to streams (will retry automatically):")
|
logger.exception("Error subscribing to streams (will retry automatically):")
|
||||||
logger.warning("Streams were: %s" % ([cls for cls, instance, recipient in subs],))
|
logger.warning(f"Streams were: {[cls for cls, instance, recipient in subs]}")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
actual_zephyr_subs = [cls for (cls, _, _) in zephyr._z.getSubscriptions()]
|
actual_zephyr_subs = [cls for (cls, _, _) in zephyr._z.getSubscriptions()]
|
||||||
|
@ -186,7 +186,7 @@ def zephyr_bulk_subscribe(subs: List[Tuple[str, str, str]]) -> None:
|
||||||
return
|
return
|
||||||
for (cls, instance, recipient) in subs:
|
for (cls, instance, recipient) in subs:
|
||||||
if cls not in actual_zephyr_subs:
|
if cls not in actual_zephyr_subs:
|
||||||
logger.error("Zephyr failed to subscribe us to %s; will retry" % (cls,))
|
logger.error(f"Zephyr failed to subscribe us to {cls}; will retry")
|
||||||
try:
|
try:
|
||||||
# We'll retry automatically when we next check for
|
# We'll retry automatically when we next check for
|
||||||
# streams to subscribe to (within 15 seconds), but
|
# streams to subscribe to (within 15 seconds), but
|
||||||
|
@ -317,7 +317,7 @@ def parse_zephyr_body(zephyr_data: str, notice_format: str) -> Tuple[str, str]:
|
||||||
# Logic based off of owl_zephyr_get_message in barnowl
|
# Logic based off of owl_zephyr_get_message in barnowl
|
||||||
fields = body.split("\x00")
|
fields = body.split("\x00")
|
||||||
if len(fields) == 5:
|
if len(fields) == 5:
|
||||||
body = "New transaction [%s] entered in %s\nFrom: %s (%s)\nSubject: %s" % (
|
body = "New transaction [{}] entered in {}\nFrom: {} ({})\nSubject: {}".format(
|
||||||
fields[0],
|
fields[0],
|
||||||
fields[1],
|
fields[1],
|
||||||
fields[2],
|
fields[2],
|
||||||
|
@ -419,7 +419,7 @@ def process_notice(notice: "zephyr.ZNotice", log: Optional[IO[str]]) -> None:
|
||||||
if is_personal and not options.forward_personals:
|
if is_personal and not options.forward_personals:
|
||||||
return
|
return
|
||||||
if (zephyr_class not in current_zephyr_subs) and not is_personal:
|
if (zephyr_class not in current_zephyr_subs) and not is_personal:
|
||||||
logger.debug("Skipping ... %s/%s/%s" % (zephyr_class, notice.instance, is_personal))
|
logger.debug(f"Skipping ... {zephyr_class}/{notice.instance}/{is_personal}")
|
||||||
return
|
return
|
||||||
if notice.format.startswith("Zephyr error: See") or notice.format.endswith("@(@color(blue))"):
|
if notice.format.startswith("Zephyr error: See") or notice.format.endswith("@(@color(blue))"):
|
||||||
logger.debug("Skipping message we got from Zulip!")
|
logger.debug("Skipping message we got from Zulip!")
|
||||||
|
@ -471,23 +471,21 @@ def process_notice(notice: "zephyr.ZNotice", log: Optional[IO[str]]) -> None:
|
||||||
if notice.instance.strip() != "":
|
if notice.instance.strip() != "":
|
||||||
zeph["subject"] = notice.instance
|
zeph["subject"] = notice.instance
|
||||||
else:
|
else:
|
||||||
zeph["subject"] = '(instance "%s")' % (notice.instance,)
|
zeph["subject"] = f'(instance "{notice.instance}")'
|
||||||
|
|
||||||
# Add instances in for instanced personals
|
# Add instances in for instanced personals
|
||||||
if is_personal:
|
if is_personal:
|
||||||
if notice.cls.lower() != "message" and notice.instance.lower != "personal":
|
if notice.cls.lower() != "message" and notice.instance.lower != "personal":
|
||||||
heading = "[-c %s -i %s]\n" % (notice.cls, notice.instance)
|
heading = f"[-c {notice.cls} -i {notice.instance}]\n"
|
||||||
elif notice.cls.lower() != "message":
|
elif notice.cls.lower() != "message":
|
||||||
heading = "[-c %s]\n" % (notice.cls,)
|
heading = f"[-c {notice.cls}]\n"
|
||||||
elif notice.instance.lower() != "personal":
|
elif notice.instance.lower() != "personal":
|
||||||
heading = "[-i %s]\n" % (notice.instance,)
|
heading = f"[-i {notice.instance}]\n"
|
||||||
else:
|
else:
|
||||||
heading = ""
|
heading = ""
|
||||||
zeph["content"] = heading + zeph["content"]
|
zeph["content"] = heading + zeph["content"]
|
||||||
|
|
||||||
logger.info(
|
logger.info(f"Received a message on {zephyr_class}/{notice.instance} from {notice.sender}...")
|
||||||
"Received a message on %s/%s from %s..." % (zephyr_class, notice.instance, notice.sender)
|
|
||||||
)
|
|
||||||
if log is not None:
|
if log is not None:
|
||||||
log.write(json.dumps(zeph) + "\n")
|
log.write(json.dumps(zeph) + "\n")
|
||||||
log.flush()
|
log.flush()
|
||||||
|
@ -499,7 +497,7 @@ def process_notice(notice: "zephyr.ZNotice", log: Optional[IO[str]]) -> None:
|
||||||
try:
|
try:
|
||||||
res = send_zulip(zeph)
|
res = send_zulip(zeph)
|
||||||
if res.get("result") != "success":
|
if res.get("result") != "success":
|
||||||
logger.error("Error relaying zephyr:\n%s\n%s" % (zeph, res))
|
logger.error(f"Error relaying zephyr:\n{zeph}\n{res}")
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.exception("Error relaying zephyr:")
|
logger.exception("Error relaying zephyr:")
|
||||||
finally:
|
finally:
|
||||||
|
@ -630,7 +628,7 @@ def send_zephyr(zwrite_args: List[str], content: str) -> Tuple[int, str]:
|
||||||
logger.info("stdout: " + stdout)
|
logger.info("stdout: " + stdout)
|
||||||
elif stderr:
|
elif stderr:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"zwrite command '%s' printed the following warning:" % (" ".join(zwrite_args),)
|
"zwrite command '{}' printed the following warning:".format(" ".join(zwrite_args))
|
||||||
)
|
)
|
||||||
if stderr:
|
if stderr:
|
||||||
logger.warning("stderr: " + stderr)
|
logger.warning("stderr: " + stderr)
|
||||||
|
@ -712,7 +710,7 @@ Feedback button or at support@zulip.com."""
|
||||||
# Forward messages sent to '(instance "WHITESPACE")' back to the
|
# Forward messages sent to '(instance "WHITESPACE")' back to the
|
||||||
# appropriate WHITESPACE instance for bidirectional mirroring
|
# appropriate WHITESPACE instance for bidirectional mirroring
|
||||||
instance = match_whitespace_instance.group(1)
|
instance = match_whitespace_instance.group(1)
|
||||||
elif instance == "instance %s" % (zephyr_class,) or instance == "test instance %s" % (
|
elif instance == f"instance {zephyr_class}" or instance == "test instance {}".format(
|
||||||
zephyr_class,
|
zephyr_class,
|
||||||
):
|
):
|
||||||
# Forward messages to e.g. -c -i white-magic back from the
|
# Forward messages to e.g. -c -i white-magic back from the
|
||||||
|
@ -724,7 +722,7 @@ Feedback button or at support@zulip.com."""
|
||||||
instance = zephyr_class
|
instance = zephyr_class
|
||||||
zephyr_class = "message"
|
zephyr_class = "message"
|
||||||
zwrite_args.extend(["-c", zephyr_class, "-i", instance])
|
zwrite_args.extend(["-c", zephyr_class, "-i", instance])
|
||||||
logger.info("Forwarding message to class %s, instance %s" % (zephyr_class, instance))
|
logger.info(f"Forwarding message to class {zephyr_class}, instance {instance}")
|
||||||
elif message["type"] == "private":
|
elif message["type"] == "private":
|
||||||
if len(message["display_recipient"]) == 1:
|
if len(message["display_recipient"]) == 1:
|
||||||
recipient = to_zephyr_username(message["display_recipient"][0]["email"])
|
recipient = to_zephyr_username(message["display_recipient"][0]["email"])
|
||||||
|
@ -744,7 +742,7 @@ Feedback button or at support@zulip.com."""
|
||||||
to_zephyr_username(user["email"]).replace("@ATHENA.MIT.EDU", "")
|
to_zephyr_username(user["email"]).replace("@ATHENA.MIT.EDU", "")
|
||||||
for user in message["display_recipient"]
|
for user in message["display_recipient"]
|
||||||
]
|
]
|
||||||
logger.info("Forwarding message to %s" % (recipients,))
|
logger.info(f"Forwarding message to {recipients}")
|
||||||
zwrite_args.extend(recipients)
|
zwrite_args.extend(recipients)
|
||||||
|
|
||||||
if message.get("invite_only_stream"):
|
if message.get("invite_only_stream"):
|
||||||
|
@ -769,7 +767,7 @@ Zulip users (like you) received it, Zephyr users did not.
|
||||||
zwrite_args.extend(["-O", "crypt"])
|
zwrite_args.extend(["-O", "crypt"])
|
||||||
|
|
||||||
if options.test_mode:
|
if options.test_mode:
|
||||||
logger.debug("Would have forwarded: %s\n%s" % (zwrite_args, wrapped_content))
|
logger.debug(f"Would have forwarded: {zwrite_args}\n{wrapped_content}")
|
||||||
return
|
return
|
||||||
|
|
||||||
(code, stderr) = send_authed_zephyr(zwrite_args, wrapped_content)
|
(code, stderr) = send_authed_zephyr(zwrite_args, wrapped_content)
|
||||||
|
@ -849,7 +847,7 @@ def maybe_forward_to_zephyr(message: Dict[str, Any]) -> None:
|
||||||
timestamp_now = int(time.time())
|
timestamp_now = int(time.time())
|
||||||
if float(message["timestamp"]) < timestamp_now - 15:
|
if float(message["timestamp"]) < timestamp_now - 15:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
"Skipping out of order message: %s < %s" % (message["timestamp"], timestamp_now)
|
"Skipping out of order message: {} < {}".format(message["timestamp"], timestamp_now)
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
@ -932,7 +930,7 @@ def add_zulip_subscriptions(verbose: bool) -> None:
|
||||||
authorization_errors_fatal=False,
|
authorization_errors_fatal=False,
|
||||||
)
|
)
|
||||||
if res.get("result") != "success":
|
if res.get("result") != "success":
|
||||||
logger.error("Error subscribing to streams:\n%s" % (res["msg"],))
|
logger.error("Error subscribing to streams:\n{}".format(res["msg"]))
|
||||||
return
|
return
|
||||||
|
|
||||||
already = res.get("already_subscribed")
|
already = res.get("already_subscribed")
|
||||||
|
@ -940,10 +938,12 @@ def add_zulip_subscriptions(verbose: bool) -> None:
|
||||||
unauthorized = res.get("unauthorized")
|
unauthorized = res.get("unauthorized")
|
||||||
if verbose:
|
if verbose:
|
||||||
if already is not None and len(already) > 0:
|
if already is not None and len(already) > 0:
|
||||||
logger.info("\nAlready subscribed to: %s" % (", ".join(list(already.values())[0]),))
|
logger.info(
|
||||||
|
"\nAlready subscribed to: {}".format(", ".join(list(already.values())[0]))
|
||||||
|
)
|
||||||
if new is not None and len(new) > 0:
|
if new is not None and len(new) > 0:
|
||||||
logger.info(
|
logger.info(
|
||||||
"\nSuccessfully subscribed to: %s" % (", ".join(list(new.values())[0]),)
|
"\nSuccessfully subscribed to: {}".format(", ".join(list(new.values())[0]))
|
||||||
)
|
)
|
||||||
if unauthorized is not None and len(unauthorized) > 0:
|
if unauthorized is not None and len(unauthorized) > 0:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
@ -962,7 +962,7 @@ on these streams and already use Zulip. They can subscribe you to them via the
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
+ "\n\n %s" % (", ".join(unauthorized),)
|
+ "\n\n {}".format(", ".join(unauthorized))
|
||||||
)
|
)
|
||||||
|
|
||||||
if len(skipped) > 0:
|
if len(skipped) > 0:
|
||||||
|
@ -989,9 +989,9 @@ Zulip subscription to these lines in ~/.zephyr.subs:
|
||||||
for (cls, instance, recipient, reason) in skipped:
|
for (cls, instance, recipient, reason) in skipped:
|
||||||
if verbose:
|
if verbose:
|
||||||
if reason != "":
|
if reason != "":
|
||||||
logger.info(" [%s,%s,%s] (%s)" % (cls, instance, recipient, reason))
|
logger.info(f" [{cls},{instance},{recipient}] ({reason})")
|
||||||
else:
|
else:
|
||||||
logger.info(" [%s,%s,%s]" % (cls, instance, recipient))
|
logger.info(f" [{cls},{instance},{recipient}]")
|
||||||
if len(skipped) > 0:
|
if len(skipped) > 0:
|
||||||
if verbose:
|
if verbose:
|
||||||
logger.info(
|
logger.info(
|
||||||
|
@ -1032,11 +1032,11 @@ def parse_zephyr_subs(verbose: bool = False) -> Set[Tuple[str, str, str]]:
|
||||||
recipient = recipient.replace("%me%", options.user)
|
recipient = recipient.replace("%me%", options.user)
|
||||||
if not valid_stream_name(cls):
|
if not valid_stream_name(cls):
|
||||||
if verbose:
|
if verbose:
|
||||||
logger.error("Skipping subscription to unsupported class name: [%s]" % (line,))
|
logger.error(f"Skipping subscription to unsupported class name: [{line}]")
|
||||||
continue
|
continue
|
||||||
except Exception:
|
except Exception:
|
||||||
if verbose:
|
if verbose:
|
||||||
logger.error("Couldn't parse ~/.zephyr.subs line: [%s]" % (line,))
|
logger.error(f"Couldn't parse ~/.zephyr.subs line: [{line}]")
|
||||||
continue
|
continue
|
||||||
zephyr_subscriptions.add((cls.strip(), instance.strip(), recipient.strip()))
|
zephyr_subscriptions.add((cls.strip(), instance.strip(), recipient.strip()))
|
||||||
return zephyr_subscriptions
|
return zephyr_subscriptions
|
||||||
|
@ -1051,7 +1051,7 @@ def open_logger() -> logging.Logger:
|
||||||
else:
|
else:
|
||||||
log_file = "/var/log/zulip/mirror-log"
|
log_file = "/var/log/zulip/mirror-log"
|
||||||
else:
|
else:
|
||||||
f = tempfile.NamedTemporaryFile(prefix="zulip-log.%s." % (options.user,), delete=False)
|
f = tempfile.NamedTemporaryFile(prefix=f"zulip-log.{options.user}.", delete=False)
|
||||||
log_file = f.name
|
log_file = f.name
|
||||||
# Close the file descriptor, since the logging system will
|
# Close the file descriptor, since the logging system will
|
||||||
# reopen it anyway.
|
# reopen it anyway.
|
||||||
|
@ -1230,10 +1230,10 @@ or specify the --api-key-file option."""
|
||||||
pgrep_query = "python.*zephyr_mirror"
|
pgrep_query = "python.*zephyr_mirror"
|
||||||
if options.shard is not None:
|
if options.shard is not None:
|
||||||
# sharded class mirror
|
# sharded class mirror
|
||||||
pgrep_query = "%s.*--shard=%s" % (pgrep_query, options.shard)
|
pgrep_query = f"{pgrep_query}.*--shard={options.shard}"
|
||||||
elif options.user is not None:
|
elif options.user is not None:
|
||||||
# Personals mirror on behalf of another user.
|
# Personals mirror on behalf of another user.
|
||||||
pgrep_query = "%s.*--user=%s" % (pgrep_query, options.user)
|
pgrep_query = f"{pgrep_query}.*--user={options.user}"
|
||||||
proc = subprocess.Popen(
|
proc = subprocess.Popen(
|
||||||
["pgrep", "-U", os.environ["USER"], "-f", pgrep_query],
|
["pgrep", "-U", os.environ["USER"], "-f", pgrep_query],
|
||||||
stdout=subprocess.PIPE,
|
stdout=subprocess.PIPE,
|
||||||
|
@ -1245,7 +1245,7 @@ or specify the --api-key-file option."""
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# Another copy of zephyr_mirror.py! Kill it.
|
# Another copy of zephyr_mirror.py! Kill it.
|
||||||
logger.info("Killing duplicate zephyr_mirror process %s" % (pid,))
|
logger.info(f"Killing duplicate zephyr_mirror process {pid}")
|
||||||
try:
|
try:
|
||||||
os.kill(pid, signal.SIGINT)
|
os.kill(pid, signal.SIGINT)
|
||||||
except OSError:
|
except OSError:
|
||||||
|
@ -1261,7 +1261,7 @@ or specify the --api-key-file option."""
|
||||||
options.forward_mail_zephyrs = subscribed_to_mail_messages()
|
options.forward_mail_zephyrs = subscribed_to_mail_messages()
|
||||||
|
|
||||||
if options.session_path is None:
|
if options.session_path is None:
|
||||||
options.session_path = "/var/tmp/%s" % (options.user,)
|
options.session_path = f"/var/tmp/{options.user}"
|
||||||
|
|
||||||
if options.forward_from_zulip:
|
if options.forward_from_zulip:
|
||||||
child_pid = os.fork() # type: Optional[int]
|
child_pid = os.fork() # type: Optional[int]
|
||||||
|
@ -1278,7 +1278,7 @@ or specify the --api-key-file option."""
|
||||||
|
|
||||||
logger_name = "zephyr=>zulip"
|
logger_name = "zephyr=>zulip"
|
||||||
if options.shard is not None:
|
if options.shard is not None:
|
||||||
logger_name += "(%s)" % (options.shard,)
|
logger_name += f"({options.shard})"
|
||||||
configure_logger(logger, logger_name)
|
configure_logger(logger, logger_name)
|
||||||
# Have the kernel reap children for when we fork off processes to send Zulips
|
# Have the kernel reap children for when we fork off processes to send Zulips
|
||||||
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
|
signal.signal(signal.SIGCHLD, signal.SIG_IGN)
|
||||||
|
|
|
@ -102,7 +102,7 @@ class RandomExponentialBackoff(CountingBackoff):
|
||||||
# between x and 2x where x is growing exponentially
|
# between x and 2x where x is growing exponentially
|
||||||
delay_scale = int(2 ** (self.number_of_retries / 2.0 - 1)) + 1
|
delay_scale = int(2 ** (self.number_of_retries / 2.0 - 1)) + 1
|
||||||
delay = min(delay_scale + random.randint(1, delay_scale), self.delay_cap)
|
delay = min(delay_scale + random.randint(1, delay_scale), self.delay_cap)
|
||||||
message = "Sleeping for %ss [max %s] before retrying." % (delay, delay_scale * 2)
|
message = f"Sleeping for {delay}s [max {delay_scale * 2}] before retrying."
|
||||||
try:
|
try:
|
||||||
logger.warning(message)
|
logger.warning(message)
|
||||||
except NameError:
|
except NameError:
|
||||||
|
@ -124,7 +124,7 @@ def add_default_arguments(
|
||||||
|
|
||||||
def custom_error_handling(self: argparse.ArgumentParser, message: str) -> None:
|
def custom_error_handling(self: argparse.ArgumentParser, message: str) -> None:
|
||||||
self.print_help(sys.stderr)
|
self.print_help(sys.stderr)
|
||||||
self.exit(2, "{}: error: {}\n".format(self.prog, message))
|
self.exit(2, f"{self.prog}: error: {message}\n")
|
||||||
|
|
||||||
parser.error = types.MethodType(custom_error_handling, parser) # type: ignore # patching function
|
parser.error = types.MethodType(custom_error_handling, parser) # type: ignore # patching function
|
||||||
|
|
||||||
|
@ -200,22 +200,22 @@ def generate_option_group(parser: optparse.OptionParser, prefix: str = "") -> op
|
||||||
)
|
)
|
||||||
|
|
||||||
group = optparse.OptionGroup(parser, "Zulip API configuration")
|
group = optparse.OptionGroup(parser, "Zulip API configuration")
|
||||||
|
group.add_option(f"--{prefix}site", dest="zulip_site", help="Zulip server URI", default=None)
|
||||||
|
group.add_option(f"--{prefix}api-key", dest="zulip_api_key", action="store")
|
||||||
group.add_option(
|
group.add_option(
|
||||||
"--%ssite" % (prefix,), dest="zulip_site", help="Zulip server URI", default=None
|
f"--{prefix}user",
|
||||||
)
|
dest="zulip_email",
|
||||||
group.add_option("--%sapi-key" % (prefix,), dest="zulip_api_key", action="store")
|
help="Email address of the calling bot or user.",
|
||||||
group.add_option(
|
|
||||||
"--%suser" % (prefix,), dest="zulip_email", help="Email address of the calling bot or user."
|
|
||||||
)
|
)
|
||||||
group.add_option(
|
group.add_option(
|
||||||
"--%sconfig-file" % (prefix,),
|
f"--{prefix}config-file",
|
||||||
action="store",
|
action="store",
|
||||||
dest="zulip_config_file",
|
dest="zulip_config_file",
|
||||||
help="Location of an ini file containing the\nabove information. (default ~/.zuliprc)",
|
help="Location of an ini file containing the\nabove information. (default ~/.zuliprc)",
|
||||||
)
|
)
|
||||||
group.add_option("-v", "--verbose", action="store_true", help="Provide detailed output.")
|
group.add_option("-v", "--verbose", action="store_true", help="Provide detailed output.")
|
||||||
group.add_option(
|
group.add_option(
|
||||||
"--%sclient" % (prefix,),
|
f"--{prefix}client",
|
||||||
action="store",
|
action="store",
|
||||||
default=None,
|
default=None,
|
||||||
dest="zulip_client",
|
dest="zulip_client",
|
||||||
|
@ -428,7 +428,7 @@ class Client:
|
||||||
|
|
||||||
elif None in (api_key, email):
|
elif None in (api_key, email):
|
||||||
raise ConfigNotFoundError(
|
raise ConfigNotFoundError(
|
||||||
"api_key or email not specified and file %s does not exist" % (config_file,)
|
f"api_key or email not specified and file {config_file} does not exist"
|
||||||
)
|
)
|
||||||
|
|
||||||
assert api_key is not None and email is not None
|
assert api_key is not None and email is not None
|
||||||
|
@ -461,7 +461,7 @@ class Client:
|
||||||
self.tls_verification = False # type: Union[bool, str]
|
self.tls_verification = False # type: Union[bool, str]
|
||||||
elif cert_bundle is not None:
|
elif cert_bundle is not None:
|
||||||
if not os.path.isfile(cert_bundle):
|
if not os.path.isfile(cert_bundle):
|
||||||
raise ConfigNotFoundError("tls bundle '%s' does not exist" % (cert_bundle,))
|
raise ConfigNotFoundError(f"tls bundle '{cert_bundle}' does not exist")
|
||||||
self.tls_verification = cert_bundle
|
self.tls_verification = cert_bundle
|
||||||
else:
|
else:
|
||||||
# Default behavior: verify against system CA certificates
|
# Default behavior: verify against system CA certificates
|
||||||
|
@ -475,12 +475,10 @@ class Client:
|
||||||
)
|
)
|
||||||
else: # we have a client cert
|
else: # we have a client cert
|
||||||
if not os.path.isfile(client_cert):
|
if not os.path.isfile(client_cert):
|
||||||
raise ConfigNotFoundError("client cert '%s' does not exist" % (client_cert,))
|
raise ConfigNotFoundError(f"client cert '{client_cert}' does not exist")
|
||||||
if client_cert_key is not None:
|
if client_cert_key is not None:
|
||||||
if not os.path.isfile(client_cert_key):
|
if not os.path.isfile(client_cert_key):
|
||||||
raise ConfigNotFoundError(
|
raise ConfigNotFoundError(f"client cert key '{client_cert_key}' does not exist")
|
||||||
"client cert key '%s' does not exist" % (client_cert_key,)
|
|
||||||
)
|
|
||||||
self.client_cert = client_cert
|
self.client_cert = client_cert
|
||||||
self.client_cert_key = client_cert_key
|
self.client_cert_key = client_cert_key
|
||||||
|
|
||||||
|
@ -631,7 +629,7 @@ class Client:
|
||||||
|
|
||||||
# On 50x errors, try again after a short sleep
|
# On 50x errors, try again after a short sleep
|
||||||
if str(res.status_code).startswith("5"):
|
if str(res.status_code).startswith("5"):
|
||||||
if error_retry(" (server %s)" % (res.status_code,)):
|
if error_retry(f" (server {res.status_code})"):
|
||||||
continue
|
continue
|
||||||
# Otherwise fall through and process the python-requests error normally
|
# Otherwise fall through and process the python-requests error normally
|
||||||
except (requests.exceptions.Timeout, requests.exceptions.SSLError) as e:
|
except (requests.exceptions.Timeout, requests.exceptions.SSLError) as e:
|
||||||
|
@ -650,7 +648,7 @@ class Client:
|
||||||
else:
|
else:
|
||||||
end_error_retry(False)
|
end_error_retry(False)
|
||||||
return {
|
return {
|
||||||
"msg": "Connection error:\n%s" % (traceback.format_exc(),),
|
"msg": f"Connection error:\n{traceback.format_exc()}",
|
||||||
"result": "connection-error",
|
"result": "connection-error",
|
||||||
}
|
}
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
|
@ -665,13 +663,13 @@ class Client:
|
||||||
continue
|
continue
|
||||||
end_error_retry(False)
|
end_error_retry(False)
|
||||||
return {
|
return {
|
||||||
"msg": "Connection error:\n%s" % (traceback.format_exc(),),
|
"msg": f"Connection error:\n{traceback.format_exc()}",
|
||||||
"result": "connection-error",
|
"result": "connection-error",
|
||||||
}
|
}
|
||||||
except Exception:
|
except Exception:
|
||||||
# We'll split this out into more cases as we encounter new bugs.
|
# We'll split this out into more cases as we encounter new bugs.
|
||||||
return {
|
return {
|
||||||
"msg": "Unexpected error:\n%s" % (traceback.format_exc(),),
|
"msg": f"Unexpected error:\n{traceback.format_exc()}",
|
||||||
"result": "unexpected-error",
|
"result": "unexpected-error",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -737,7 +735,7 @@ class Client:
|
||||||
res = self.register(event_types, narrow, **kwargs)
|
res = self.register(event_types, narrow, **kwargs)
|
||||||
if "error" in res["result"]:
|
if "error" in res["result"]:
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print("Server returned error:\n%s" % (res["msg"],))
|
print("Server returned error:\n{}".format(res["msg"]))
|
||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
else:
|
else:
|
||||||
return (res["queue_id"], res["last_event_id"])
|
return (res["queue_id"], res["last_event_id"])
|
||||||
|
@ -762,7 +760,7 @@ class Client:
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if self.verbose:
|
if self.verbose:
|
||||||
print("Server returned error:\n%s" % (res["msg"],))
|
print("Server returned error:\n{}".format(res["msg"]))
|
||||||
# Eventually, we'll only want the
|
# Eventually, we'll only want the
|
||||||
# BAD_EVENT_QUEUE_ID check, but we check for the
|
# BAD_EVENT_QUEUE_ID check, but we check for the
|
||||||
# old string to support legacy Zulip servers. We
|
# old string to support legacy Zulip servers. We
|
||||||
|
@ -821,7 +819,7 @@ class Client:
|
||||||
"""
|
"""
|
||||||
See examples/get-raw-message for example usage
|
See examples/get-raw-message for example usage
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(url="messages/{}".format(message_id), method="GET")
|
return self.call_endpoint(url=f"messages/{message_id}", method="GET")
|
||||||
|
|
||||||
def send_message(self, message_data: Dict[str, Any]) -> Dict[str, Any]:
|
def send_message(self, message_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
|
@ -861,7 +859,7 @@ class Client:
|
||||||
"""
|
"""
|
||||||
See examples/delete-message for example usage.
|
See examples/delete-message for example usage.
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(url="messages/{}".format(message_id), method="DELETE")
|
return self.call_endpoint(url=f"messages/{message_id}", method="DELETE")
|
||||||
|
|
||||||
def update_message_flags(self, update_data: Dict[str, Any]) -> Dict[str, Any]:
|
def update_message_flags(self, update_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
|
@ -914,7 +912,7 @@ class Client:
|
||||||
"""
|
"""
|
||||||
See examples/message-history for example usage.
|
See examples/message-history for example usage.
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(url="messages/{}/history".format(message_id), method="GET")
|
return self.call_endpoint(url=f"messages/{message_id}/history", method="GET")
|
||||||
|
|
||||||
def add_reaction(self, reaction_data: Dict[str, Any]) -> Dict[str, Any]:
|
def add_reaction(self, reaction_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
|
@ -965,9 +963,7 @@ class Client:
|
||||||
>>> client.upload_custom_emoji(emoji_name, file_obj)
|
>>> client.upload_custom_emoji(emoji_name, file_obj)
|
||||||
{'result': 'success', 'msg': ''}
|
{'result': 'success', 'msg': ''}
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(f"realm/emoji/{emoji_name}", method="POST", files=[file_obj])
|
||||||
"realm/emoji/{}".format(emoji_name), method="POST", files=[file_obj]
|
|
||||||
)
|
|
||||||
|
|
||||||
def delete_custom_emoji(self, emoji_name: str) -> Dict[str, Any]:
|
def delete_custom_emoji(self, emoji_name: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
|
@ -977,7 +973,7 @@ class Client:
|
||||||
{'result': 'success', 'msg': ''}
|
{'result': 'success', 'msg': ''}
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(
|
||||||
url="realm/emoji/{}".format(emoji_name),
|
url=f"realm/emoji/{emoji_name}",
|
||||||
method="DELETE",
|
method="DELETE",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1027,7 +1023,7 @@ class Client:
|
||||||
{'result': 'success', 'msg': ''}
|
{'result': 'success', 'msg': ''}
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(
|
||||||
url="realm/filters/{}".format(filter_id),
|
url=f"realm/filters/{filter_id}",
|
||||||
method="DELETE",
|
method="DELETE",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1064,7 +1060,7 @@ class Client:
|
||||||
{'result': 'success', 'msg': ''}
|
{'result': 'success', 'msg': ''}
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(
|
||||||
url="realm/profile_fields/{}".format(field_id),
|
url=f"realm/profile_fields/{field_id}",
|
||||||
method="DELETE",
|
method="DELETE",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1089,7 +1085,7 @@ class Client:
|
||||||
{'result': 'success', 'msg': ''}
|
{'result': 'success', 'msg': ''}
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(
|
||||||
url="realm/profile_fields/{}".format(field_id),
|
url=f"realm/profile_fields/{field_id}",
|
||||||
method="PATCH",
|
method="PATCH",
|
||||||
request=request,
|
request=request,
|
||||||
)
|
)
|
||||||
|
@ -1181,7 +1177,7 @@ class Client:
|
||||||
{'presence': {'website': {'timestamp': 1486799122, 'status': 'active'}}, 'result': 'success', 'msg': ''}
|
{'presence': {'website': {'timestamp': 1486799122, 'status': 'active'}}, 'result': 'success', 'msg': ''}
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(
|
||||||
url="users/%s/presence" % (email,),
|
url=f"users/{email}/presence",
|
||||||
method="GET",
|
method="GET",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1240,7 +1236,7 @@ class Client:
|
||||||
See examples/delete-stream for example usage.
|
See examples/delete-stream for example usage.
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(
|
||||||
url="streams/{}".format(stream_id),
|
url=f"streams/{stream_id}",
|
||||||
method="DELETE",
|
method="DELETE",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1267,7 +1263,7 @@ class Client:
|
||||||
{'result': 'success', 'msg': '', 'user': [{...}, {...}]}
|
{'result': 'success', 'msg': '', 'user': [{...}, {...}]}
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(
|
||||||
url="users/{}".format(user_id),
|
url=f"users/{user_id}",
|
||||||
method="GET",
|
method="GET",
|
||||||
request=request,
|
request=request,
|
||||||
)
|
)
|
||||||
|
@ -1281,7 +1277,7 @@ class Client:
|
||||||
{'result': 'success', 'msg': ''}
|
{'result': 'success', 'msg': ''}
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(
|
||||||
url="users/{}".format(user_id),
|
url=f"users/{user_id}",
|
||||||
method="DELETE",
|
method="DELETE",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1294,7 +1290,7 @@ class Client:
|
||||||
{'result': 'success', 'msg': ''}
|
{'result': 'success', 'msg': ''}
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(
|
||||||
url="users/{}/reactivate".format(user_id),
|
url=f"users/{user_id}/reactivate",
|
||||||
method="POST",
|
method="POST",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1310,7 +1306,7 @@ class Client:
|
||||||
for key, value in request.items():
|
for key, value in request.items():
|
||||||
request[key] = json.dumps(value)
|
request[key] = json.dumps(value)
|
||||||
|
|
||||||
return self.call_endpoint(url="users/{}".format(user_id), method="PATCH", request=request)
|
return self.call_endpoint(url=f"users/{user_id}", method="PATCH", request=request)
|
||||||
|
|
||||||
def get_users(self, request: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
def get_users(self, request: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
|
@ -1399,7 +1395,7 @@ class Client:
|
||||||
{'result': 'success', 'msg': '', 'is_subscribed': False}
|
{'result': 'success', 'msg': '', 'is_subscribed': False}
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(
|
||||||
url="users/{}/subscriptions/{}".format(user_id, stream_id),
|
url=f"users/{user_id}/subscriptions/{stream_id}",
|
||||||
method="GET",
|
method="GET",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1456,7 +1452,7 @@ class Client:
|
||||||
Example usage: client.get_stream_id('devel')
|
Example usage: client.get_stream_id('devel')
|
||||||
"""
|
"""
|
||||||
stream_encoded = urllib.parse.quote(stream, safe="")
|
stream_encoded = urllib.parse.quote(stream, safe="")
|
||||||
url = "get_stream_id?stream=%s" % (stream_encoded,)
|
url = f"get_stream_id?stream={stream_encoded}"
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(
|
||||||
url=url,
|
url=url,
|
||||||
method="GET",
|
method="GET",
|
||||||
|
@ -1467,7 +1463,7 @@ class Client:
|
||||||
"""
|
"""
|
||||||
See examples/get-stream-topics for example usage.
|
See examples/get-stream-topics for example usage.
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(url="users/me/{}/topics".format(stream_id), method="GET")
|
return self.call_endpoint(url=f"users/me/{stream_id}/topics", method="GET")
|
||||||
|
|
||||||
def get_user_groups(self) -> Dict[str, Any]:
|
def get_user_groups(self) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
|
@ -1521,7 +1517,7 @@ class Client:
|
||||||
{'msg': '', 'result': 'success'}
|
{'msg': '', 'result': 'success'}
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(
|
||||||
url="user_groups/{}".format(group_id),
|
url=f"user_groups/{group_id}",
|
||||||
method="DELETE",
|
method="DELETE",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -1538,7 +1534,7 @@ class Client:
|
||||||
{'msg': '', 'result': 'success'}
|
{'msg': '', 'result': 'success'}
|
||||||
"""
|
"""
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(
|
||||||
url="user_groups/{}/members".format(user_group_id),
|
url=f"user_groups/{user_group_id}/members",
|
||||||
method="POST",
|
method="POST",
|
||||||
request=group_data,
|
request=group_data,
|
||||||
)
|
)
|
||||||
|
@ -1683,7 +1679,7 @@ class Client:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
if len(result["messages"]) <= 0:
|
if len(result["messages"]) <= 0:
|
||||||
return {"result": "error", "msg": 'No messages found in topic: "{}"'.format(topic)}
|
return {"result": "error", "msg": f'No messages found in topic: "{topic}"'}
|
||||||
|
|
||||||
message_id = result["messages"][0]["id"]
|
message_id = result["messages"][0]["id"]
|
||||||
|
|
||||||
|
@ -1696,7 +1692,7 @@ class Client:
|
||||||
"send_notification_to_new_thread": notify_new_topic,
|
"send_notification_to_new_thread": notify_new_topic,
|
||||||
}
|
}
|
||||||
return self.call_endpoint(
|
return self.call_endpoint(
|
||||||
url="messages/{}".format(message_id),
|
url=f"messages/{message_id}",
|
||||||
method="PATCH",
|
method="PATCH",
|
||||||
request=request,
|
request=request,
|
||||||
)
|
)
|
||||||
|
|
|
@ -15,7 +15,7 @@ Example: edit-stream --stream-id=3 --history-public-to-subscribers
|
||||||
|
|
||||||
|
|
||||||
def quote(string: str) -> str:
|
def quote(string: str) -> str:
|
||||||
return '"{}"'.format(string)
|
return f'"{string}"'
|
||||||
|
|
||||||
|
|
||||||
parser = zulip.add_default_arguments(argparse.ArgumentParser(usage=usage))
|
parser = zulip.add_default_arguments(argparse.ArgumentParser(usage=usage))
|
||||||
|
|
|
@ -38,4 +38,4 @@ response = client.upload_file(file)
|
||||||
try:
|
try:
|
||||||
print("File URI: {}".format(response["uri"]))
|
print("File URI: {}".format(response["uri"]))
|
||||||
except KeyError:
|
except KeyError:
|
||||||
print("Error! API response was: {}".format(response))
|
print(f"Error! API response was: {response}")
|
||||||
|
|
|
@ -22,7 +22,7 @@ def do_send_message(client: zulip.Client, message_data: Dict[str, Any]) -> bool:
|
||||||
% (message_data["to"], message_data["subject"])
|
% (message_data["to"], message_data["subject"])
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
log.info("Sending message to %s... " % (message_data["to"],))
|
log.info("Sending message to {}... ".format(message_data["to"]))
|
||||||
response = client.send_message(message_data)
|
response = client.send_message(message_data)
|
||||||
if response["result"] == "success":
|
if response["result"] == "success":
|
||||||
log.info("Message sent.")
|
log.info("Message sent.")
|
||||||
|
|
|
@ -90,11 +90,11 @@ except ImportError:
|
||||||
except (ImportError, AssertionError):
|
except (ImportError, AssertionError):
|
||||||
if version is not None:
|
if version is not None:
|
||||||
print(
|
print(
|
||||||
"{name}>={version} is not installed.".format(name=module_name, version=version),
|
f"{module_name}>={version} is not installed.",
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
print("{name} is not installed.".format(name=module_name), file=sys.stderr)
|
print(f"{module_name} is not installed.", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
check_dependency_manually("zulip")
|
check_dependency_manually("zulip")
|
||||||
|
|
|
@ -73,7 +73,7 @@ class BaremetricsHandler:
|
||||||
if content == ["list-commands"]:
|
if content == ["list-commands"]:
|
||||||
response = "**Available Commands:** \n"
|
response = "**Available Commands:** \n"
|
||||||
for command, description in zip(self.commands, self.descriptions):
|
for command, description in zip(self.commands, self.descriptions):
|
||||||
response += " - {} : {}\n".format(command, description)
|
response += f" - {command} : {description}\n"
|
||||||
|
|
||||||
bot_handler.send_reply(message, response)
|
bot_handler.send_reply(message, response)
|
||||||
return
|
return
|
||||||
|
@ -148,7 +148,7 @@ class BaremetricsHandler:
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def get_plans(self, source_id: str) -> str:
|
def get_plans(self, source_id: str) -> str:
|
||||||
url = "https://api.baremetrics.com/v1/{}/plans".format(source_id)
|
url = f"https://api.baremetrics.com/v1/{source_id}/plans"
|
||||||
plans_response = requests.get(url, headers=self.auth_header)
|
plans_response = requests.get(url, headers=self.auth_header)
|
||||||
|
|
||||||
plans_data = plans_response.json()
|
plans_data = plans_response.json()
|
||||||
|
@ -174,7 +174,7 @@ class BaremetricsHandler:
|
||||||
return "\n".join(response)
|
return "\n".join(response)
|
||||||
|
|
||||||
def get_customers(self, source_id: str) -> str:
|
def get_customers(self, source_id: str) -> str:
|
||||||
url = "https://api.baremetrics.com/v1/{}/customers".format(source_id)
|
url = f"https://api.baremetrics.com/v1/{source_id}/customers"
|
||||||
customers_response = requests.get(url, headers=self.auth_header)
|
customers_response = requests.get(url, headers=self.auth_header)
|
||||||
|
|
||||||
customers_data = customers_response.json()
|
customers_data = customers_response.json()
|
||||||
|
@ -203,7 +203,7 @@ class BaremetricsHandler:
|
||||||
return "\n".join(response)
|
return "\n".join(response)
|
||||||
|
|
||||||
def get_subscriptions(self, source_id: str) -> str:
|
def get_subscriptions(self, source_id: str) -> str:
|
||||||
url = "https://api.baremetrics.com/v1/{}/subscriptions".format(source_id)
|
url = f"https://api.baremetrics.com/v1/{source_id}/subscriptions"
|
||||||
subscriptions_response = requests.get(url, headers=self.auth_header)
|
subscriptions_response = requests.get(url, headers=self.auth_header)
|
||||||
|
|
||||||
subscriptions_data = subscriptions_response.json()
|
subscriptions_data = subscriptions_response.json()
|
||||||
|
@ -250,7 +250,7 @@ class BaremetricsHandler:
|
||||||
"interval_count": int(parameters[6]),
|
"interval_count": int(parameters[6]),
|
||||||
} # type: Any
|
} # type: Any
|
||||||
|
|
||||||
url = "https://api.baremetrics.com/v1/{}/plans".format(parameters[0])
|
url = f"https://api.baremetrics.com/v1/{parameters[0]}/plans"
|
||||||
create_plan_response = requests.post(url, data=data_header, headers=self.auth_header)
|
create_plan_response = requests.post(url, data=data_header, headers=self.auth_header)
|
||||||
if "error" not in create_plan_response.json():
|
if "error" not in create_plan_response.json():
|
||||||
return "Plan Created."
|
return "Plan Created."
|
||||||
|
|
|
@ -70,7 +70,7 @@ at syntax by: @mention-botname help"
|
||||||
r.status_code
|
r.status_code
|
||||||
) # Occures in case of unprocessable entity
|
) # Occures in case of unprocessable entity
|
||||||
else:
|
else:
|
||||||
datapoint_link = "https://www.beeminder.com/{}/{}".format(username, goalname)
|
datapoint_link = f"https://www.beeminder.com/{username}/{goalname}"
|
||||||
return "[Datapoint]({}) created.".format(
|
return "[Datapoint]({}) created.".format(
|
||||||
datapoint_link
|
datapoint_link
|
||||||
) # Handles the case of successful datapoint creation
|
) # Handles the case of successful datapoint creation
|
||||||
|
|
|
@ -376,7 +376,7 @@ def make_draw_response(reason: str) -> str:
|
||||||
|
|
||||||
Returns: The draw response string.
|
Returns: The draw response string.
|
||||||
"""
|
"""
|
||||||
return "It's a draw because of {}!".format(reason)
|
return f"It's a draw because of {reason}!"
|
||||||
|
|
||||||
|
|
||||||
def make_loss_response(board: chess.Board, reason: str) -> str:
|
def make_loss_response(board: chess.Board, reason: str) -> str:
|
||||||
|
@ -525,7 +525,7 @@ def make_str(board: chess.Board, is_white_on_bottom: bool) -> str:
|
||||||
replaced_and_guided_str if is_white_on_bottom else replaced_and_guided_str[::-1]
|
replaced_and_guided_str if is_white_on_bottom else replaced_and_guided_str[::-1]
|
||||||
)
|
)
|
||||||
trimmed_str = trim_whitespace_before_newline(properly_flipped_str)
|
trimmed_str = trim_whitespace_before_newline(properly_flipped_str)
|
||||||
monospaced_str = "```\n{}\n```".format(trimmed_str)
|
monospaced_str = f"```\n{trimmed_str}\n```"
|
||||||
|
|
||||||
return monospaced_str
|
return monospaced_str
|
||||||
|
|
||||||
|
|
|
@ -51,7 +51,7 @@ class DefineHandler:
|
||||||
if not to_define_lower:
|
if not to_define_lower:
|
||||||
return self.EMPTY_WORD_REQUEST_ERROR_MESSAGE
|
return self.EMPTY_WORD_REQUEST_ERROR_MESSAGE
|
||||||
else:
|
else:
|
||||||
response = "**{}**:\n".format(to_define)
|
response = f"**{to_define}**:\n"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
# Use OwlBot API to fetch definition.
|
# Use OwlBot API to fetch definition.
|
||||||
|
|
|
@ -36,7 +36,7 @@ def get_bot_result(message_content: str, config: Dict[str, str], sender_id: str)
|
||||||
return res_json["result"]["fulfillment"]["speech"]
|
return res_json["result"]["fulfillment"]["speech"]
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(str(e))
|
logging.exception(str(e))
|
||||||
return "Error. {}.".format(str(e))
|
return f"Error. {str(e)}."
|
||||||
|
|
||||||
|
|
||||||
class DialogFlowHandler:
|
class DialogFlowHandler:
|
||||||
|
|
|
@ -117,7 +117,7 @@ def syntax_help(cmd_name: str) -> str:
|
||||||
cmd = cmd_name + " " + arg_syntax
|
cmd = cmd_name + " " + arg_syntax
|
||||||
else:
|
else:
|
||||||
cmd = cmd_name
|
cmd = cmd_name
|
||||||
return "syntax: {}".format(cmd)
|
return f"syntax: {cmd}"
|
||||||
|
|
||||||
|
|
||||||
def dbx_help(client: Any, cmd_name: str) -> str:
|
def dbx_help(client: Any, cmd_name: str) -> str:
|
||||||
|
@ -197,7 +197,7 @@ def dbx_read(client: Any, fn: str) -> str:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
result = client.files_download(fn)
|
result = client.files_download(fn)
|
||||||
msg = "**{}** :\n{}".format(result[0].name, result[1].text)
|
msg = f"**{result[0].name}** :\n{result[1].text}"
|
||||||
except Exception:
|
except Exception:
|
||||||
msg = (
|
msg = (
|
||||||
"Please provide a correct file path\nUsage: `read <filename>` to read content of a file"
|
"Please provide a correct file path\nUsage: `read <filename>` to read content of a file"
|
||||||
|
|
|
@ -27,14 +27,14 @@ class FileUploaderHandler:
|
||||||
|
|
||||||
path = Path(os.path.expanduser(content))
|
path = Path(os.path.expanduser(content))
|
||||||
if not path.is_file():
|
if not path.is_file():
|
||||||
bot_handler.send_reply(message, "File `{}` not found".format(content))
|
bot_handler.send_reply(message, f"File `{content}` not found")
|
||||||
return
|
return
|
||||||
|
|
||||||
path = path.resolve()
|
path = path.resolve()
|
||||||
upload = bot_handler.upload_file_from_path(str(path))
|
upload = bot_handler.upload_file_from_path(str(path))
|
||||||
if upload["result"] != "success":
|
if upload["result"] != "success":
|
||||||
msg = upload["msg"]
|
msg = upload["msg"]
|
||||||
bot_handler.send_reply(message, "Failed to upload `{}` file: {}".format(path, msg))
|
bot_handler.send_reply(message, f"Failed to upload `{path}` file: {msg}")
|
||||||
return
|
return
|
||||||
|
|
||||||
uploaded_file_reply = "[{}]({})".format(path.name, upload["uri"])
|
uploaded_file_reply = "[{}]({})".format(path.name, upload["uri"])
|
||||||
|
|
|
@ -52,7 +52,7 @@ class FollowupHandler:
|
||||||
def get_bot_followup_response(self, message: Dict[str, str]) -> str:
|
def get_bot_followup_response(self, message: Dict[str, str]) -> str:
|
||||||
original_content = message["content"]
|
original_content = message["content"]
|
||||||
original_sender = message["sender_email"]
|
original_sender = message["sender_email"]
|
||||||
temp_content = "from %s: " % (original_sender,)
|
temp_content = f"from {original_sender}: "
|
||||||
new_content = temp_content + original_content
|
new_content = temp_content + original_content
|
||||||
|
|
||||||
return new_content
|
return new_content
|
||||||
|
|
|
@ -35,7 +35,7 @@ class FrontHandler:
|
||||||
def help(self, bot_handler: BotHandler) -> str:
|
def help(self, bot_handler: BotHandler) -> str:
|
||||||
response = ""
|
response = ""
|
||||||
for command, description in self.COMMANDS:
|
for command, description in self.COMMANDS:
|
||||||
response += "`{}` {}\n".format(command, description)
|
response += f"`{command}` {description}\n"
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
|
|
@ -78,9 +78,9 @@ class TestFrontBot(BotTestCase, DefaultTests):
|
||||||
def _test_command_error(self, command_name: str, command_arg: Optional[str] = None) -> None:
|
def _test_command_error(self, command_name: str, command_arg: Optional[str] = None) -> None:
|
||||||
bot_command = command_name
|
bot_command = command_name
|
||||||
if command_arg:
|
if command_arg:
|
||||||
bot_command += " {}".format(command_arg)
|
bot_command += f" {command_arg}"
|
||||||
with self.mock_config_info({"api_key": "TEST"}):
|
with self.mock_config_info({"api_key": "TEST"}):
|
||||||
with self.mock_http_conversation("{}_error".format(command_name)):
|
with self.mock_http_conversation(f"{command_name}_error"):
|
||||||
self.verify_reply(bot_command, "Something went wrong.")
|
self.verify_reply(bot_command, "Something went wrong.")
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
from typing import Any, Dict, List
|
from typing import Any, Dict, List
|
||||||
|
from unittest.mock import patch
|
||||||
from mock import patch
|
|
||||||
|
|
||||||
from zulip_bots.game_handler import GameInstance
|
from zulip_bots.game_handler import GameInstance
|
||||||
from zulip_bots.test_lib import BotTestCase, DefaultTests
|
from zulip_bots.test_lib import BotTestCase, DefaultTests
|
||||||
|
@ -66,8 +65,8 @@ class TestGameHandlerBot(BotTestCase, DefaultTests):
|
||||||
if bot is None:
|
if bot is None:
|
||||||
bot, bot_handler = self._get_handlers()
|
bot, bot_handler = self._get_handlers()
|
||||||
message = {
|
message = {
|
||||||
"sender_email": "{}@example.com".format(name),
|
"sender_email": f"{name}@example.com",
|
||||||
"sender_full_name": "{}".format(name),
|
"sender_full_name": f"{name}",
|
||||||
}
|
}
|
||||||
bot.add_user_to_cache(message)
|
bot.add_user_to_cache(message)
|
||||||
return bot
|
return bot
|
||||||
|
|
|
@ -42,8 +42,8 @@ class GithubHandler:
|
||||||
status = details["state"].title()
|
status = details["state"].title()
|
||||||
|
|
||||||
message_string = (
|
message_string = (
|
||||||
"**[{owner}/{repo}#{id}]".format(owner=owner, repo=repo, id=number),
|
f"**[{owner}/{repo}#{number}]",
|
||||||
"({link}) - {title}**\n".format(title=title, link=link),
|
f"({link}) - {title}**\n",
|
||||||
"Created by **[{author}](https://github.com/{author})**\n".format(author=author),
|
"Created by **[{author}](https://github.com/{author})**\n".format(author=author),
|
||||||
"Status - **{status}**\n```quote\n{description}\n```".format(
|
"Status - **{status}**\n```quote\n{description}\n```".format(
|
||||||
status=status, description=description
|
status=status, description=description
|
||||||
|
|
|
@ -32,7 +32,7 @@ def google_search(keywords: str) -> List[Dict[str, str]]:
|
||||||
if a.text.strip() == "Cached" and "webcache.googleusercontent.com" in a["href"]:
|
if a.text.strip() == "Cached" and "webcache.googleusercontent.com" in a["href"]:
|
||||||
continue
|
continue
|
||||||
# a.text: The name of the page
|
# a.text: The name of the page
|
||||||
result = {"url": "https://www.google.com{}".format(link), "name": a.text}
|
result = {"url": f"https://www.google.com{link}", "name": a.text}
|
||||||
results.append(result)
|
results.append(result)
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ def get_google_result(search_keywords: str) -> str:
|
||||||
return "Found Result: [{}]({})".format(results[0]["name"], results[0]["url"])
|
return "Found Result: [{}]({})".format(results[0]["name"], results[0]["url"])
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(str(e))
|
logging.exception(str(e))
|
||||||
return "Error: Search failed. {}.".format(e)
|
return f"Error: Search failed. {e}."
|
||||||
|
|
||||||
|
|
||||||
class GoogleSearchHandler:
|
class GoogleSearchHandler:
|
||||||
|
|
|
@ -107,12 +107,12 @@ def get_translate_bot_response(message_content, config_file, author, all_languag
|
||||||
text_to_translate, config_file["key"], target_language, source_language
|
text_to_translate, config_file["key"], target_language, source_language
|
||||||
)
|
)
|
||||||
except requests.exceptions.ConnectionError as conn_err:
|
except requests.exceptions.ConnectionError as conn_err:
|
||||||
return "Could not connect to Google Translate. {}.".format(conn_err)
|
return f"Could not connect to Google Translate. {conn_err}."
|
||||||
except TranslateError as tr_err:
|
except TranslateError as tr_err:
|
||||||
return "Translate Error. {}.".format(tr_err)
|
return f"Translate Error. {tr_err}."
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
return "Error. {}.".format(err)
|
return f"Error. {err}."
|
||||||
return "{} (from {})".format(translated_text, author)
|
return f"{translated_text} (from {author})"
|
||||||
|
|
||||||
|
|
||||||
handler_class = GoogleTranslateHandler
|
handler_class = GoogleTranslateHandler
|
||||||
|
|
|
@ -64,12 +64,12 @@ def api_list_team() -> List[Dict[str, str]]:
|
||||||
|
|
||||||
|
|
||||||
def api_show_team(hash_id: str) -> Dict[str, str]:
|
def api_show_team(hash_id: str) -> Dict[str, str]:
|
||||||
return make_API_request("/teams/{}".format(hash_id))
|
return make_API_request(f"/teams/{hash_id}")
|
||||||
|
|
||||||
|
|
||||||
# NOTE: This function is not currently used
|
# NOTE: This function is not currently used
|
||||||
def api_show_users(hash_id: str) -> Any:
|
def api_show_users(hash_id: str) -> Any:
|
||||||
return make_API_request("/teams/{}/members".format(hash_id))
|
return make_API_request(f"/teams/{hash_id}/members")
|
||||||
|
|
||||||
|
|
||||||
def api_list_entries(team_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
def api_list_entries(team_id: Optional[str] = None) -> List[Dict[str, Any]]:
|
||||||
|
@ -105,7 +105,7 @@ def team_info(team_name: str) -> str:
|
||||||
def entries_list(team_name: str) -> str:
|
def entries_list(team_name: str) -> str:
|
||||||
if team_name:
|
if team_name:
|
||||||
data = api_list_entries(get_team_hash(team_name))
|
data = api_list_entries(get_team_hash(team_name))
|
||||||
response = "Entries for {}:".format(team_name)
|
response = f"Entries for {team_name}:"
|
||||||
else:
|
else:
|
||||||
data = api_list_entries()
|
data = api_list_entries()
|
||||||
response = "Entries for all teams:"
|
response = "Entries for all teams:"
|
||||||
|
|
|
@ -39,7 +39,7 @@ class IncidentHandler:
|
||||||
bot_response = "Invalid answer format"
|
bot_response = "Invalid answer format"
|
||||||
bot_handler.send_reply(message, bot_response)
|
bot_handler.send_reply(message, bot_response)
|
||||||
return
|
return
|
||||||
bot_response = "Incident %s\n status = %s" % (ticket_id, answer)
|
bot_response = f"Incident {ticket_id}\n status = {answer}"
|
||||||
bot_handler.send_reply(message, bot_response)
|
bot_handler.send_reply(message, bot_response)
|
||||||
else:
|
else:
|
||||||
bot_response = 'type "new <description>" for a new incident'
|
bot_response = 'type "new <description>" for a new incident'
|
||||||
|
@ -125,15 +125,13 @@ def format_incident_for_widget(ticket_id: str, incident: Dict[str, Any]) -> str:
|
||||||
|
|
||||||
def format_incident_for_markdown(ticket_id: str, incident: Dict[str, Any]) -> str:
|
def format_incident_for_markdown(ticket_id: str, incident: Dict[str, Any]) -> str:
|
||||||
answer_list = "\n".join(
|
answer_list = "\n".join(
|
||||||
[
|
"* **{code}** {answer}".format(
|
||||||
"* **{code}** {answer}".format(
|
code=code,
|
||||||
code=code,
|
answer=ANSWERS[code],
|
||||||
answer=ANSWERS[code],
|
)
|
||||||
)
|
for code in "1234"
|
||||||
for code in "1234"
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
how_to_respond = """**reply**: answer {ticket_id} <code>""".format(ticket_id=ticket_id)
|
how_to_respond = f"""**reply**: answer {ticket_id} <code>"""
|
||||||
|
|
||||||
content = """
|
content = """
|
||||||
Incident: {incident}
|
Incident: {incident}
|
||||||
|
|
|
@ -183,7 +183,7 @@ class JiraHandler:
|
||||||
UNKNOWN_VAL = "*unknown*"
|
UNKNOWN_VAL = "*unknown*"
|
||||||
jira_response = requests.get(
|
jira_response = requests.get(
|
||||||
self.domain_with_protocol
|
self.domain_with_protocol
|
||||||
+ "/rest/api/2/search?jql={}&fields=key,summary,status".format(jql_query),
|
+ f"/rest/api/2/search?jql={jql_query}&fields=key,summary,status",
|
||||||
headers={"Authorization": self.auth},
|
headers={"Authorization": self.auth},
|
||||||
).json()
|
).json()
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ class JiraHandler:
|
||||||
if errors:
|
if errors:
|
||||||
response = "Oh no! Jira raised an error:\n > " + ", ".join(errors)
|
response = "Oh no! Jira raised an error:\n > " + ", ".join(errors)
|
||||||
else:
|
else:
|
||||||
response = "*Found {} results*\n\n".format(results)
|
response = f"*Found {results} results*\n\n"
|
||||||
for issue in jira_response.get("issues", []):
|
for issue in jira_response.get("issues", []):
|
||||||
fields = issue.get("fields", {})
|
fields = issue.get("fields", {})
|
||||||
summary = fields.get("summary", UNKNOWN_VAL)
|
summary = fields.get("summary", UNKNOWN_VAL)
|
||||||
|
@ -314,12 +314,12 @@ class JiraHandler:
|
||||||
response = "Issue *" + key + "* was edited! " + url
|
response = "Issue *" + key + "* was edited! " + url
|
||||||
elif search_match:
|
elif search_match:
|
||||||
search_term = search_match.group("search_term")
|
search_term = search_match.group("search_term")
|
||||||
search_results = self.jql_search("summary ~ {}".format(search_term))
|
search_results = self.jql_search(f"summary ~ {search_term}")
|
||||||
response = '**Search results for "{}"**\n\n{}'.format(search_term, search_results)
|
response = f'**Search results for "{search_term}"**\n\n{search_results}'
|
||||||
elif jql_match:
|
elif jql_match:
|
||||||
jql_query = jql_match.group("jql_query")
|
jql_query = jql_match.group("jql_query")
|
||||||
search_results = self.jql_search(jql_query)
|
search_results = self.jql_search(jql_query)
|
||||||
response = '**Search results for "{}"**\n\n{}'.format(jql_query, search_results)
|
response = f'**Search results for "{jql_query}"**\n\n{search_results}'
|
||||||
elif help_match:
|
elif help_match:
|
||||||
response = HELP_RESPONSE
|
response = HELP_RESPONSE
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -197,5 +197,5 @@ def check_win(topic_name, merels_storage):
|
||||||
win = mechanics.who_won(topic_name, merels_storage)
|
win = mechanics.who_won(topic_name, merels_storage)
|
||||||
if win != "None":
|
if win != "None":
|
||||||
merels.remove_game(topic_name)
|
merels.remove_game(topic_name)
|
||||||
return "{} wins the game!".format(win)
|
return f"{win} wins the game!"
|
||||||
return ""
|
return ""
|
||||||
|
|
|
@ -278,7 +278,7 @@ def create_room(topic_name, merels_storage):
|
||||||
|
|
||||||
if merels.create_new_game(topic_name):
|
if merels.create_new_game(topic_name):
|
||||||
response = ""
|
response = ""
|
||||||
response += "A room has been created in {0}. Starting game now.\n".format(topic_name)
|
response += f"A room has been created in {topic_name}. Starting game now.\n"
|
||||||
response += display_game(topic_name, merels_storage)
|
response += display_game(topic_name, merels_storage)
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
@ -404,7 +404,7 @@ def put_man(topic_name, v, h, merels_storage):
|
||||||
data.hill_uid,
|
data.hill_uid,
|
||||||
data.take_mode,
|
data.take_mode,
|
||||||
)
|
)
|
||||||
return "Put a man to ({}, {}) for {}.".format(v, h, data.turn)
|
return f"Put a man to ({v}, {h}) for {data.turn}."
|
||||||
else:
|
else:
|
||||||
raise BadMoveException("Failed: That's not a legal put. Please try again.")
|
raise BadMoveException("Failed: That's not a legal put. Please try again.")
|
||||||
|
|
||||||
|
@ -448,7 +448,7 @@ def take_man(topic_name, v, h, merels_storage):
|
||||||
data.hill_uid,
|
data.hill_uid,
|
||||||
data.take_mode,
|
data.take_mode,
|
||||||
)
|
)
|
||||||
return "Taken a man from ({}, {}) for {}.".format(v, h, data.turn)
|
return f"Taken a man from ({v}, {h}) for {data.turn}."
|
||||||
else:
|
else:
|
||||||
raise BadMoveException("Failed: That's not a legal take. Please try again.")
|
raise BadMoveException("Failed: That's not a legal take. Please try again.")
|
||||||
|
|
||||||
|
|
|
@ -44,7 +44,7 @@ class MerelsModel:
|
||||||
if self.storage.get(self.topic) == '["X", 0, 0, "NNNNNNNNNNNNNNNNNNNNNNNN", "", 0]':
|
if self.storage.get(self.topic) == '["X", 0, 0, "NNNNNNNNNNNNNNNNNNNNNNNN", "", 0]':
|
||||||
self.storage.put(
|
self.storage.put(
|
||||||
self.topic,
|
self.topic,
|
||||||
'["{}", 0, 0, "NNNNNNNNNNNNNNNNNNNNNNNN", "", 0]'.format(self.token[player_number]),
|
f'["{self.token[player_number]}", 0, 0, "NNNNNNNNNNNNNNNNNNNNNNNN", "", 0]',
|
||||||
)
|
)
|
||||||
self.current_board, same_player_move = game.beat(move, self.topic, self.storage)
|
self.current_board, same_player_move = game.beat(move, self.topic, self.storage)
|
||||||
if same_player_move != "":
|
if same_player_move != "":
|
||||||
|
|
|
@ -60,8 +60,8 @@ class TestMerelsBot(BotTestCase, DefaultTests):
|
||||||
if bot is None:
|
if bot is None:
|
||||||
bot, bot_handler = self._get_handlers()
|
bot, bot_handler = self._get_handlers()
|
||||||
message = {
|
message = {
|
||||||
"sender_email": "{}@example.com".format(name),
|
"sender_email": f"{name}@example.com",
|
||||||
"sender_full_name": "{}".format(name),
|
"sender_full_name": f"{name}",
|
||||||
}
|
}
|
||||||
bot.add_user_to_cache(message)
|
bot.add_user_to_cache(message)
|
||||||
return bot
|
return bot
|
||||||
|
|
|
@ -25,15 +25,15 @@ def compose(results: Dict) -> str:
|
||||||
|
|
||||||
response = ""
|
response = ""
|
||||||
|
|
||||||
response += "{}\n".format(print_status(results))
|
response += f"{print_status(results)}\n"
|
||||||
|
|
||||||
if "success" in response.lower():
|
if "success" in response.lower():
|
||||||
response += "{}".format(print_test_id(results))
|
response += f"{print_test_id(results)}"
|
||||||
return response
|
return response
|
||||||
|
|
||||||
response += "{}\n".format(print_enabled_checkers(results))
|
response += f"{print_enabled_checkers(results)}\n"
|
||||||
response += "{}\n".format(print_failures_checkers(results))
|
response += f"{print_failures_checkers(results)}\n"
|
||||||
response += "{}".format(print_more_info_url(results))
|
response += f"{print_more_info_url(results)}"
|
||||||
|
|
||||||
return response
|
return response
|
||||||
|
|
||||||
|
@ -81,11 +81,11 @@ def print_failures_checkers(results: Dict) -> str:
|
||||||
] # [('seo', 3), ..]
|
] # [('seo', 3), ..]
|
||||||
|
|
||||||
failures_checkers_messages = [
|
failures_checkers_messages = [
|
||||||
"{} ({})".format(fail_checker[0], fail_checker[1]) for fail_checker in failures_checkers
|
f"{fail_checker[0]} ({fail_checker[1]})" for fail_checker in failures_checkers
|
||||||
]
|
]
|
||||||
|
|
||||||
failures_checkers_message = ", ".join(failures_checkers_messages)
|
failures_checkers_message = ", ".join(failures_checkers_messages)
|
||||||
return "Failures from checkers: {}".format(failures_checkers_message)
|
return f"Failures from checkers: {failures_checkers_message}"
|
||||||
|
|
||||||
|
|
||||||
def get_enabled_checkers(results: Dict) -> List:
|
def get_enabled_checkers(results: Dict) -> List:
|
||||||
|
|
|
@ -57,17 +57,17 @@ def format_result(
|
||||||
output += "**[{}]({}{})**\n".format(record["Name"], login_url, record["Id"])
|
output += "**[{}]({}{})**\n".format(record["Name"], login_url, record["Id"])
|
||||||
for key, value in record.items():
|
for key, value in record.items():
|
||||||
if key not in exclude_keys:
|
if key not in exclude_keys:
|
||||||
output += ">**{}**: {}\n".format(key, value)
|
output += f">**{key}**: {value}\n"
|
||||||
else:
|
else:
|
||||||
for i, record in enumerate(result["records"]):
|
for i, record in enumerate(result["records"]):
|
||||||
if rank_output:
|
if rank_output:
|
||||||
output += "{}) ".format(i + 1)
|
output += f"{i + 1}) "
|
||||||
output += "**[{}]({}{})**\n".format(record["Name"], login_url, record["Id"])
|
output += "**[{}]({}{})**\n".format(record["Name"], login_url, record["Id"])
|
||||||
added_keys = False
|
added_keys = False
|
||||||
for key, value in record.items():
|
for key, value in record.items():
|
||||||
if key in force_keys or (show_all_keys and key not in exclude_keys):
|
if key in force_keys or (show_all_keys and key not in exclude_keys):
|
||||||
added_keys = True
|
added_keys = True
|
||||||
output += ">**{}**: {}\n".format(key, value)
|
output += f">**{key}**: {value}\n"
|
||||||
if added_keys:
|
if added_keys:
|
||||||
output += "\n"
|
output += "\n"
|
||||||
return output
|
return output
|
||||||
|
@ -88,7 +88,7 @@ def query_salesforce(
|
||||||
limit = re_limit.search(raw_arg)
|
limit = re_limit.search(raw_arg)
|
||||||
if limit:
|
if limit:
|
||||||
limit_num = int(limit.group().rsplit(" ", 1)[1])
|
limit_num = int(limit.group().rsplit(" ", 1)[1])
|
||||||
logging.info("Searching with limit {}".format(limit_num))
|
logging.info(f"Searching with limit {limit_num}")
|
||||||
query = default_query
|
query = default_query
|
||||||
if "query" in command.keys():
|
if "query" in command.keys():
|
||||||
query = command["query"]
|
query = command["query"]
|
||||||
|
@ -170,14 +170,14 @@ class SalesforceHandler:
|
||||||
security_token=self.config_info["security_token"],
|
security_token=self.config_info["security_token"],
|
||||||
)
|
)
|
||||||
except simple_salesforce.exceptions.SalesforceAuthenticationFailed as err:
|
except simple_salesforce.exceptions.SalesforceAuthenticationFailed as err:
|
||||||
bot_handler.quit("Failed to log in to Salesforce. {} {}".format(err.code, err.message))
|
bot_handler.quit(f"Failed to log in to Salesforce. {err.code} {err.message}")
|
||||||
|
|
||||||
def handle_message(self, message: Dict[str, Any], bot_handler: BotHandler) -> None:
|
def handle_message(self, message: Dict[str, Any], bot_handler: BotHandler) -> None:
|
||||||
try:
|
try:
|
||||||
bot_response = self.get_salesforce_response(message["content"])
|
bot_response = self.get_salesforce_response(message["content"])
|
||||||
bot_handler.send_reply(message, bot_response)
|
bot_handler.send_reply(message, bot_response)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
bot_handler.send_reply(message, "Error. {}.".format(e), bot_response)
|
bot_handler.send_reply(message, f"Error. {e}.", bot_response)
|
||||||
|
|
||||||
|
|
||||||
handler_class = SalesforceHandler
|
handler_class = SalesforceHandler
|
||||||
|
|
|
@ -151,8 +151,8 @@ class TestTicTacToeBot(BotTestCase, DefaultTests):
|
||||||
if bot is None:
|
if bot is None:
|
||||||
bot, bot_handler = self._get_handlers()
|
bot, bot_handler = self._get_handlers()
|
||||||
message = {
|
message = {
|
||||||
"sender_email": "{}@example.com".format(name),
|
"sender_email": f"{name}@example.com",
|
||||||
"sender_full_name": "{}".format(name),
|
"sender_full_name": f"{name}",
|
||||||
}
|
}
|
||||||
bot.add_user_to_cache(message)
|
bot.add_user_to_cache(message)
|
||||||
return bot
|
return bot
|
||||||
|
|
|
@ -241,14 +241,14 @@ class TicTacToeMessageHandler:
|
||||||
|
|
||||||
def parse_board(self, board: Any) -> str:
|
def parse_board(self, board: Any) -> str:
|
||||||
"""Takes the board as a nested list and returns a nice version for the user."""
|
"""Takes the board as a nested list and returns a nice version for the user."""
|
||||||
return "".join([self.parse_row(r, r_num) for r_num, r in enumerate(board)])
|
return "".join(self.parse_row(r, r_num) for r_num, r in enumerate(board))
|
||||||
|
|
||||||
def get_player_color(self, turn: int) -> str:
|
def get_player_color(self, turn: int) -> str:
|
||||||
return self.tokens[turn]
|
return self.tokens[turn]
|
||||||
|
|
||||||
def alert_move_message(self, original_player: str, move_info: str) -> str:
|
def alert_move_message(self, original_player: str, move_info: str) -> str:
|
||||||
move_info = move_info.replace("move ", "")
|
move_info = move_info.replace("move ", "")
|
||||||
return "{} put a token at {}".format(original_player, move_info)
|
return f"{original_player} put a token at {move_info}"
|
||||||
|
|
||||||
def game_start_message(self) -> str:
|
def game_start_message(self) -> str:
|
||||||
return (
|
return (
|
||||||
|
@ -299,7 +299,7 @@ def coords_from_command(cmd: str) -> str:
|
||||||
"""As there are various ways to input a coordinate (with/without parentheses, with/without spaces, etc.) the
|
"""As there are various ways to input a coordinate (with/without parentheses, with/without spaces, etc.) the
|
||||||
input is stripped to just the numbers before being used in the program."""
|
input is stripped to just the numbers before being used in the program."""
|
||||||
cmd_num = int(cmd.replace("move ", "")) - 1
|
cmd_num = int(cmd.replace("move ", "")) - 1
|
||||||
cmd = "{},{}".format((cmd_num % 3) + 1, (cmd_num // 3) + 1)
|
cmd = f"{(cmd_num % 3) + 1},{(cmd_num // 3) + 1}"
|
||||||
return cmd
|
return cmd
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -30,7 +30,7 @@ class TrelloHandler:
|
||||||
|
|
||||||
def check_access_token(self, bot_handler: BotHandler) -> None:
|
def check_access_token(self, bot_handler: BotHandler) -> None:
|
||||||
test_query_response = requests.get(
|
test_query_response = requests.get(
|
||||||
"https://api.trello.com/1/members/{}/".format(self.user_name), params=self.auth_params
|
f"https://api.trello.com/1/members/{self.user_name}/", params=self.auth_params
|
||||||
)
|
)
|
||||||
|
|
||||||
if test_query_response.text == "invalid key":
|
if test_query_response.text == "invalid key":
|
||||||
|
@ -75,12 +75,12 @@ class TrelloHandler:
|
||||||
def get_all_supported_commands(self) -> str:
|
def get_all_supported_commands(self) -> str:
|
||||||
bot_response = "**Commands:** \n"
|
bot_response = "**Commands:** \n"
|
||||||
for index, (command, desc) in enumerate(supported_commands):
|
for index, (command, desc) in enumerate(supported_commands):
|
||||||
bot_response += "{}. **{}**: {}\n".format(index + 1, command, desc)
|
bot_response += f"{index + 1}. **{command}**: {desc}\n"
|
||||||
|
|
||||||
return bot_response
|
return bot_response
|
||||||
|
|
||||||
def get_all_boards(self) -> str:
|
def get_all_boards(self) -> str:
|
||||||
get_board_ids_url = "https://api.trello.com/1/members/{}/".format(self.user_name)
|
get_board_ids_url = f"https://api.trello.com/1/members/{self.user_name}/"
|
||||||
board_ids_response = requests.get(get_board_ids_url, params=self.auth_params)
|
board_ids_response = requests.get(get_board_ids_url, params=self.auth_params)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -112,7 +112,7 @@ class TrelloHandler:
|
||||||
return INVALID_ARGUMENTS_ERROR_MESSAGE
|
return INVALID_ARGUMENTS_ERROR_MESSAGE
|
||||||
|
|
||||||
board_id = content[1]
|
board_id = content[1]
|
||||||
get_cards_url = "https://api.trello.com/1/boards/{}/cards".format(board_id)
|
get_cards_url = f"https://api.trello.com/1/boards/{board_id}/cards"
|
||||||
cards_response = requests.get(get_cards_url, params=self.auth_params)
|
cards_response = requests.get(get_cards_url, params=self.auth_params)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -133,7 +133,7 @@ class TrelloHandler:
|
||||||
return INVALID_ARGUMENTS_ERROR_MESSAGE
|
return INVALID_ARGUMENTS_ERROR_MESSAGE
|
||||||
|
|
||||||
card_id = content[1]
|
card_id = content[1]
|
||||||
get_checklists_url = "https://api.trello.com/1/cards/{}/checklists/".format(card_id)
|
get_checklists_url = f"https://api.trello.com/1/cards/{card_id}/checklists/"
|
||||||
checklists_response = requests.get(get_checklists_url, params=self.auth_params)
|
checklists_response = requests.get(get_checklists_url, params=self.auth_params)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
@ -160,7 +160,7 @@ class TrelloHandler:
|
||||||
return INVALID_ARGUMENTS_ERROR_MESSAGE
|
return INVALID_ARGUMENTS_ERROR_MESSAGE
|
||||||
|
|
||||||
board_id = content[1]
|
board_id = content[1]
|
||||||
get_lists_url = "https://api.trello.com/1/boards/{}/lists".format(board_id)
|
get_lists_url = f"https://api.trello.com/1/boards/{board_id}/lists"
|
||||||
lists_response = requests.get(get_lists_url, params=self.auth_params)
|
lists_response = requests.get(get_lists_url, params=self.auth_params)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -191,15 +191,13 @@ def format_quiz_for_markdown(quiz_id: str, quiz: Dict[str, Any]) -> str:
|
||||||
question = quiz["question"]
|
question = quiz["question"]
|
||||||
answers = quiz["answers"]
|
answers = quiz["answers"]
|
||||||
answer_list = "\n".join(
|
answer_list = "\n".join(
|
||||||
[
|
"* **{letter}** {answer}".format(
|
||||||
"* **{letter}** {answer}".format(
|
letter=letter,
|
||||||
letter=letter,
|
answer=answers[letter],
|
||||||
answer=answers[letter],
|
)
|
||||||
)
|
for letter in "ABCD"
|
||||||
for letter in "ABCD"
|
|
||||||
]
|
|
||||||
)
|
)
|
||||||
how_to_respond = """**reply**: answer {quiz_id} <letter>""".format(quiz_id=quiz_id)
|
how_to_respond = f"""**reply**: answer {quiz_id} <letter>"""
|
||||||
|
|
||||||
content = """
|
content = """
|
||||||
Q: {question}
|
Q: {question}
|
||||||
|
|
|
@ -48,7 +48,7 @@ class TwitpostBot:
|
||||||
status = self.post(" ".join(content[1:]))
|
status = self.post(" ".join(content[1:]))
|
||||||
screen_name = status["user"]["screen_name"]
|
screen_name = status["user"]["screen_name"]
|
||||||
id_str = status["id_str"]
|
id_str = status["id_str"]
|
||||||
bot_reply = "https://twitter.com/{}/status/{}".format(screen_name, id_str)
|
bot_reply = f"https://twitter.com/{screen_name}/status/{id_str}"
|
||||||
bot_reply = "Tweet Posted\n" + bot_reply
|
bot_reply = "Tweet Posted\n" + bot_reply
|
||||||
bot_handler.send_reply(message, bot_reply)
|
bot_handler.send_reply(message, bot_reply)
|
||||||
|
|
||||||
|
|
|
@ -25,7 +25,7 @@ class VirtualFsHandler:
|
||||||
|
|
||||||
recipient = message["display_recipient"]
|
recipient = message["display_recipient"]
|
||||||
if isinstance(recipient, list): # If not a stream, then hash on list of emails
|
if isinstance(recipient, list): # If not a stream, then hash on list of emails
|
||||||
recipient = " ".join([x["email"] for x in recipient])
|
recipient = " ".join(x["email"] for x in recipient)
|
||||||
|
|
||||||
storage = bot_handler.storage
|
storage = bot_handler.storage
|
||||||
if not storage.contains(recipient):
|
if not storage.contains(recipient):
|
||||||
|
@ -34,7 +34,7 @@ class VirtualFsHandler:
|
||||||
if sender not in fs["user_paths"]:
|
if sender not in fs["user_paths"]:
|
||||||
fs["user_paths"][sender] = "/"
|
fs["user_paths"][sender] = "/"
|
||||||
fs, msg = fs_command(fs, sender, command)
|
fs, msg = fs_command(fs, sender, command)
|
||||||
prependix = "{}:\n".format(sender)
|
prependix = f"{sender}:\n"
|
||||||
msg = prependix + msg
|
msg = prependix + msg
|
||||||
storage.put(recipient, fs)
|
storage.put(recipient, fs)
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ def syntax_help(cmd_name: str) -> str:
|
||||||
cmd = cmd_name + " " + arg_syntax
|
cmd = cmd_name + " " + arg_syntax
|
||||||
else:
|
else:
|
||||||
cmd = cmd_name
|
cmd = cmd_name
|
||||||
return "syntax: {}".format(cmd)
|
return f"syntax: {cmd}"
|
||||||
|
|
||||||
|
|
||||||
def fs_new() -> Dict[str, Any]:
|
def fs_new() -> Dict[str, Any]:
|
||||||
|
@ -190,7 +190,7 @@ def fs_mkdir(fs: Dict[str, Any], user: str, fn: str) -> Tuple[Dict[str, Any], An
|
||||||
return fs, "ERROR: file already exists"
|
return fs, "ERROR: file already exists"
|
||||||
dir_path = os.path.dirname(path)
|
dir_path = os.path.dirname(path)
|
||||||
if not is_directory(fs, dir_path):
|
if not is_directory(fs, dir_path):
|
||||||
msg = "ERROR: {} is not a directory".format(dir_path)
|
msg = f"ERROR: {dir_path} is not a directory"
|
||||||
return fs, msg
|
return fs, msg
|
||||||
new_fs = fs.copy()
|
new_fs = fs.copy()
|
||||||
new_dir = directory({path}.union(fs[dir_path]["fns"]))
|
new_dir = directory({path}.union(fs[dir_path]["fns"]))
|
||||||
|
@ -211,7 +211,7 @@ def fs_ls(fs: Dict[str, Any], user: str, fn: str) -> Tuple[Dict[str, Any], Any]:
|
||||||
msg = "ERROR: file does not exist"
|
msg = "ERROR: file does not exist"
|
||||||
return fs, msg
|
return fs, msg
|
||||||
if not is_directory(fs, path):
|
if not is_directory(fs, path):
|
||||||
return fs, "ERROR: {} is not a directory".format(path)
|
return fs, f"ERROR: {path} is not a directory"
|
||||||
fns = fs[path]["fns"]
|
fns = fs[path]["fns"]
|
||||||
if not fns:
|
if not fns:
|
||||||
return fs, "WARNING: directory is empty"
|
return fs, "WARNING: directory is empty"
|
||||||
|
@ -233,7 +233,7 @@ def fs_rm(fs: Dict[str, Any], user: str, fn: str) -> Tuple[Dict[str, Any], Any]:
|
||||||
msg = "ERROR: file does not exist"
|
msg = "ERROR: file does not exist"
|
||||||
return fs, msg
|
return fs, msg
|
||||||
if fs[path]["kind"] == "dir":
|
if fs[path]["kind"] == "dir":
|
||||||
msg = "ERROR: {} is a directory, file required".format(nice_path(fs, path))
|
msg = f"ERROR: {nice_path(fs, path)} is a directory, file required"
|
||||||
return fs, msg
|
return fs, msg
|
||||||
new_fs = fs.copy()
|
new_fs = fs.copy()
|
||||||
new_fs.pop(path)
|
new_fs.pop(path)
|
||||||
|
@ -251,7 +251,7 @@ def fs_rmdir(fs: Dict[str, Any], user: str, fn: str) -> Tuple[Dict[str, Any], An
|
||||||
msg = "ERROR: directory does not exist"
|
msg = "ERROR: directory does not exist"
|
||||||
return fs, msg
|
return fs, msg
|
||||||
if fs[path]["kind"] == "text":
|
if fs[path]["kind"] == "text":
|
||||||
msg = "ERROR: {} is a file, directory required".format(nice_path(fs, path))
|
msg = f"ERROR: {nice_path(fs, path)} is a file, directory required"
|
||||||
return fs, msg
|
return fs, msg
|
||||||
new_fs = fs.copy()
|
new_fs = fs.copy()
|
||||||
new_fs.pop(path)
|
new_fs.pop(path)
|
||||||
|
@ -273,7 +273,7 @@ def fs_write(fs: Dict[str, Any], user: str, fn: str, content: str) -> Tuple[Dict
|
||||||
return fs, msg
|
return fs, msg
|
||||||
dir_path = os.path.dirname(path)
|
dir_path = os.path.dirname(path)
|
||||||
if not is_directory(fs, dir_path):
|
if not is_directory(fs, dir_path):
|
||||||
msg = "ERROR: {} is not a directory".format(dir_path)
|
msg = f"ERROR: {dir_path} is not a directory"
|
||||||
return fs, msg
|
return fs, msg
|
||||||
new_fs = fs.copy()
|
new_fs = fs.copy()
|
||||||
new_dir = directory({path}.union(fs[dir_path]["fns"]))
|
new_dir = directory({path}.union(fs[dir_path]["fns"]))
|
||||||
|
@ -291,7 +291,7 @@ def fs_read(fs: Dict[str, Any], user: str, fn: str) -> Tuple[Dict[str, Any], Any
|
||||||
msg = "ERROR: file does not exist"
|
msg = "ERROR: file does not exist"
|
||||||
return fs, msg
|
return fs, msg
|
||||||
if fs[path]["kind"] == "dir":
|
if fs[path]["kind"] == "dir":
|
||||||
msg = "ERROR: {} is a directory, file required".format(nice_path(fs, path))
|
msg = f"ERROR: {nice_path(fs, path)} is a directory, file required"
|
||||||
return fs, msg
|
return fs, msg
|
||||||
val = fs[path]["content"]
|
val = fs[path]["content"]
|
||||||
return fs, val
|
return fs, val
|
||||||
|
@ -305,17 +305,17 @@ def fs_cd(fs: Dict[str, Any], user: str, fn: str) -> Tuple[Dict[str, Any], Any]:
|
||||||
msg = "ERROR: invalid path"
|
msg = "ERROR: invalid path"
|
||||||
return fs, msg
|
return fs, msg
|
||||||
if fs[path]["kind"] == "text":
|
if fs[path]["kind"] == "text":
|
||||||
msg = "ERROR: {} is a file, directory required".format(nice_path(fs, path))
|
msg = f"ERROR: {nice_path(fs, path)} is a file, directory required"
|
||||||
return fs, msg
|
return fs, msg
|
||||||
fs["user_paths"][user] = path
|
fs["user_paths"][user] = path
|
||||||
return fs, "Current path: {}".format(nice_path(fs, path))
|
return fs, f"Current path: {nice_path(fs, path)}"
|
||||||
|
|
||||||
|
|
||||||
def make_path(fs: Dict[str, Any], user: str, leaf: str) -> List[str]:
|
def make_path(fs: Dict[str, Any], user: str, leaf: str) -> List[str]:
|
||||||
if leaf == "/":
|
if leaf == "/":
|
||||||
return ["/", ""]
|
return ["/", ""]
|
||||||
if leaf.endswith("/"):
|
if leaf.endswith("/"):
|
||||||
return ["", "ERROR: {} is not a valid name".format(leaf)]
|
return ["", f"ERROR: {leaf} is not a valid name"]
|
||||||
if leaf.startswith("/"):
|
if leaf.startswith("/"):
|
||||||
return [leaf, ""]
|
return [leaf, ""]
|
||||||
path = fs["user_paths"][user]
|
path = fs["user_paths"][user]
|
||||||
|
@ -331,9 +331,9 @@ def nice_path(fs: Dict[str, Any], path: str) -> str:
|
||||||
if path not in fs:
|
if path not in fs:
|
||||||
return "ERROR: the current directory does not exist"
|
return "ERROR: the current directory does not exist"
|
||||||
if fs[path]["kind"] == "text":
|
if fs[path]["kind"] == "text":
|
||||||
path_nice = "{}*{}*".format(path[: slash + 1], path[slash + 1 :])
|
path_nice = f"{path[: slash + 1]}*{path[slash + 1 :]}*"
|
||||||
elif path != "/":
|
elif path != "/":
|
||||||
path_nice = "{}/".format(path)
|
path_nice = f"{path}/"
|
||||||
return path_nice
|
return path_nice
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -79,19 +79,15 @@ def get_xkcd_bot_response(message: Dict[str, str], quoted_name: str) -> str:
|
||||||
elif command.isdigit():
|
elif command.isdigit():
|
||||||
fetched = fetch_xkcd_query(XkcdBotCommand.COMIC_ID, command)
|
fetched = fetch_xkcd_query(XkcdBotCommand.COMIC_ID, command)
|
||||||
else:
|
else:
|
||||||
return commands_help % (
|
return commands_help % (f"xkcd bot only supports these commands, not `{command}`:",)
|
||||||
"xkcd bot only supports these commands, not `%s`:" % (command,),
|
|
||||||
)
|
|
||||||
except (requests.exceptions.ConnectionError, XkcdServerError):
|
except (requests.exceptions.ConnectionError, XkcdServerError):
|
||||||
logging.exception("Connection error occurred when trying to connect to xkcd server")
|
logging.exception("Connection error occurred when trying to connect to xkcd server")
|
||||||
return "Sorry, I cannot process your request right now, please try again later!"
|
return "Sorry, I cannot process your request right now, please try again later!"
|
||||||
except XkcdNotFoundError:
|
except XkcdNotFoundError:
|
||||||
logging.exception(
|
logging.exception(f"XKCD server responded 404 when trying to fetch comic with id {command}")
|
||||||
"XKCD server responded 404 when trying to fetch comic with id %s" % (command,)
|
return f"Sorry, there is likely no xkcd comic strip with id: #{command}"
|
||||||
)
|
|
||||||
return "Sorry, there is likely no xkcd comic strip with id: #%s" % (command,)
|
|
||||||
else:
|
else:
|
||||||
return "#%s: **%s**\n[%s](%s)" % (
|
return "#{}: **{}**\n[{}]({})".format(
|
||||||
fetched["num"],
|
fetched["num"],
|
||||||
fetched["title"],
|
fetched["title"],
|
||||||
fetched["alt"],
|
fetched["alt"],
|
||||||
|
|
|
@ -130,7 +130,7 @@ def get_bot_response(
|
||||||
).strip()
|
).strip()
|
||||||
|
|
||||||
for title, id in video_list:
|
for title, id in video_list:
|
||||||
reply = reply + "\n * %s - [Watch now](https://www.youtube.com/watch/%s)" % (title, id)
|
reply = reply + f"\n * {title} - [Watch now](https://www.youtube.com/watch/{id})"
|
||||||
# Using link https://www.youtube.com/watch/<id> to
|
# Using link https://www.youtube.com/watch/<id> to
|
||||||
# prevent showing multiple previews
|
# prevent showing multiple previews
|
||||||
return reply
|
return reply
|
||||||
|
|
|
@ -229,7 +229,7 @@ class GameAdapter:
|
||||||
|
|
||||||
if sender not in self.user_cache.keys():
|
if sender not in self.user_cache.keys():
|
||||||
self.add_user_to_cache(message)
|
self.add_user_to_cache(message)
|
||||||
logging.info("Added {} to user cache".format(sender))
|
logging.info(f"Added {sender} to user cache")
|
||||||
|
|
||||||
if self.is_single_player:
|
if self.is_single_player:
|
||||||
if content.lower().startswith("start game with") or content.lower().startswith(
|
if content.lower().startswith("start game with") or content.lower().startswith(
|
||||||
|
@ -300,7 +300,7 @@ class GameAdapter:
|
||||||
self.send_reply(message, self.help_message())
|
self.send_reply(message, self.help_message())
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logging.exception(str(e))
|
logging.exception(str(e))
|
||||||
self.bot_handler.send_reply(message, "Error {}.".format(e))
|
self.bot_handler.send_reply(message, f"Error {e}.")
|
||||||
|
|
||||||
def is_user_in_game(self, user_email: str) -> str:
|
def is_user_in_game(self, user_email: str) -> str:
|
||||||
for instance in self.instances.values():
|
for instance in self.instances.values():
|
||||||
|
@ -344,7 +344,7 @@ class GameAdapter:
|
||||||
self.send_reply(message, self.confirm_invitation_accepted(game_id))
|
self.send_reply(message, self.confirm_invitation_accepted(game_id))
|
||||||
self.broadcast(
|
self.broadcast(
|
||||||
game_id,
|
game_id,
|
||||||
"@**{}** has accepted the invitation.".format(self.get_username_by_email(sender)),
|
f"@**{self.get_username_by_email(sender)}** has accepted the invitation.",
|
||||||
)
|
)
|
||||||
self.start_game_if_ready(game_id)
|
self.start_game_if_ready(game_id)
|
||||||
|
|
||||||
|
@ -365,7 +365,7 @@ class GameAdapter:
|
||||||
if len(users) + 1 > self.max_players:
|
if len(users) + 1 > self.max_players:
|
||||||
self.send_reply(
|
self.send_reply(
|
||||||
message,
|
message,
|
||||||
"The maximum number of players for this game is {}.".format(self.max_players),
|
f"The maximum number of players for this game is {self.max_players}.",
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
game_id = self.generate_game_id()
|
game_id = self.generate_game_id()
|
||||||
|
@ -411,7 +411,7 @@ class GameAdapter:
|
||||||
self.broadcast(game_id, "Wait... That's me!", include_private=True)
|
self.broadcast(game_id, "Wait... That's me!", include_private=True)
|
||||||
if message["type"] == "stream":
|
if message["type"] == "stream":
|
||||||
self.broadcast(
|
self.broadcast(
|
||||||
game_id, "@**{}** accept".format(self.get_bot_username()), include_private=False
|
game_id, f"@**{self.get_bot_username()}** accept", include_private=False
|
||||||
)
|
)
|
||||||
game_id = self.set_invite_by_user(self.email, True, {"type": "stream"})
|
game_id = self.set_invite_by_user(self.email, True, {"type": "stream"})
|
||||||
self.start_game_if_ready(game_id)
|
self.start_game_if_ready(game_id)
|
||||||
|
@ -427,7 +427,7 @@ class GameAdapter:
|
||||||
self.send_reply(message, self.confirm_invitation_declined(game_id))
|
self.send_reply(message, self.confirm_invitation_declined(game_id))
|
||||||
self.broadcast(
|
self.broadcast(
|
||||||
game_id,
|
game_id,
|
||||||
"@**{}** has declined the invitation.".format(self.get_username_by_email(sender)),
|
f"@**{self.get_username_by_email(sender)}** has declined the invitation.",
|
||||||
)
|
)
|
||||||
if len(self.get_players(game_id, parameter="")) < self.min_players:
|
if len(self.get_players(game_id, parameter="")) < self.min_players:
|
||||||
self.cancel_game(game_id)
|
self.cancel_game(game_id)
|
||||||
|
@ -440,7 +440,7 @@ class GameAdapter:
|
||||||
if game_id == "":
|
if game_id == "":
|
||||||
self.send_reply(message, "You are not in a game. Type `help` for all commands.")
|
self.send_reply(message, "You are not in a game. Type `help` for all commands.")
|
||||||
sender_name = self.get_username_by_email(sender)
|
sender_name = self.get_username_by_email(sender)
|
||||||
self.cancel_game(game_id, reason="**{}** quit.".format(sender_name))
|
self.cancel_game(game_id, reason=f"**{sender_name}** quit.")
|
||||||
|
|
||||||
def command_join(self, message: Dict[str, Any], sender: str, content: str) -> None:
|
def command_join(self, message: Dict[str, Any], sender: str, content: str) -> None:
|
||||||
if not self.is_user_not_player(sender, message):
|
if not self.is_user_not_player(sender, message):
|
||||||
|
@ -472,7 +472,7 @@ class GameAdapter:
|
||||||
else:
|
else:
|
||||||
self.send_reply(
|
self.send_reply(
|
||||||
message,
|
message,
|
||||||
"Join {} more players to start the game".format(self.max_players - num_players),
|
f"Join {self.max_players - num_players} more players to start the game",
|
||||||
)
|
)
|
||||||
|
|
||||||
def command_leaderboard(self, message: Dict[str, Any], sender: str, content: str) -> None:
|
def command_leaderboard(self, message: Dict[str, Any], sender: str, content: str) -> None:
|
||||||
|
@ -483,9 +483,9 @@ class GameAdapter:
|
||||||
raw_headers = ["games_won", "games_drawn", "games_lost", "total_games"]
|
raw_headers = ["games_won", "games_drawn", "games_lost", "total_games"]
|
||||||
headers = ["Player"] + [key.replace("_", " ").title() for key in raw_headers]
|
headers = ["Player"] + [key.replace("_", " ").title() for key in raw_headers]
|
||||||
response += " | ".join(headers)
|
response += " | ".join(headers)
|
||||||
response += "\n" + " | ".join([" --- " for header in headers])
|
response += "\n" + " | ".join(" --- " for header in headers)
|
||||||
for player, stat in top_stats:
|
for player, stat in top_stats:
|
||||||
response += "\n **{}** | ".format(self.get_username_by_email(player))
|
response += f"\n **{self.get_username_by_email(player)}** | "
|
||||||
values = [str(stat[key]) for key in raw_headers]
|
values = [str(stat[key]) for key in raw_headers]
|
||||||
response += " | ".join(values)
|
response += " | ".join(values)
|
||||||
self.send_reply(message, response)
|
self.send_reply(message, response)
|
||||||
|
@ -536,7 +536,7 @@ class GameAdapter:
|
||||||
self.instances[game_id] = GameInstance(self, False, subject, game_id, players, stream)
|
self.instances[game_id] = GameInstance(self, False, subject, game_id, players, stream)
|
||||||
self.broadcast(
|
self.broadcast(
|
||||||
game_id,
|
game_id,
|
||||||
"The game has started in #{} {}".format(stream, self.instances[game_id].subject)
|
f"The game has started in #{stream} {self.instances[game_id].subject}"
|
||||||
+ "\n"
|
+ "\n"
|
||||||
+ self.get_formatted_game_object(game_id),
|
+ self.get_formatted_game_object(game_id),
|
||||||
)
|
)
|
||||||
|
@ -564,7 +564,7 @@ class GameAdapter:
|
||||||
return
|
return
|
||||||
self.invites[game_id].update({user_email: "a"})
|
self.invites[game_id].update({user_email: "a"})
|
||||||
self.broadcast(
|
self.broadcast(
|
||||||
game_id, "@**{}** has joined the game".format(self.get_username_by_email(user_email))
|
game_id, f"@**{self.get_username_by_email(user_email)}** has joined the game"
|
||||||
)
|
)
|
||||||
self.start_game_if_ready(game_id)
|
self.start_game_if_ready(game_id)
|
||||||
|
|
||||||
|
@ -873,7 +873,7 @@ class GameInstance:
|
||||||
def get_player_text(self) -> str:
|
def get_player_text(self) -> str:
|
||||||
player_text = ""
|
player_text = ""
|
||||||
for player in self.players:
|
for player in self.players:
|
||||||
player_text += " @**{}**".format(self.gameAdapter.get_username_by_email(player))
|
player_text += f" @**{self.gameAdapter.get_username_by_email(player)}**"
|
||||||
return player_text
|
return player_text
|
||||||
|
|
||||||
def get_start_message(self) -> str:
|
def get_start_message(self) -> str:
|
||||||
|
@ -890,7 +890,7 @@ class GameInstance:
|
||||||
def handle_message(self, content: str, player_email: str) -> None:
|
def handle_message(self, content: str, player_email: str) -> None:
|
||||||
if content == "forfeit":
|
if content == "forfeit":
|
||||||
player_name = self.gameAdapter.get_username_by_email(player_email)
|
player_name = self.gameAdapter.get_username_by_email(player_email)
|
||||||
self.broadcast("**{}** forfeited!".format(player_name))
|
self.broadcast(f"**{player_name}** forfeited!")
|
||||||
self.end_game("except:" + player_email)
|
self.end_game("except:" + player_email)
|
||||||
return
|
return
|
||||||
if content == "draw":
|
if content == "draw":
|
||||||
|
@ -1032,7 +1032,7 @@ class GameInstance:
|
||||||
loser = winner.lstrip("except:")
|
loser = winner.lstrip("except:")
|
||||||
else:
|
else:
|
||||||
winner_name = self.gameAdapter.get_username_by_email(winner)
|
winner_name = self.gameAdapter.get_username_by_email(winner)
|
||||||
self.broadcast("**{}** won! :tada:".format(winner_name))
|
self.broadcast(f"**{winner_name}** won! :tada:")
|
||||||
for u in self.players:
|
for u in self.players:
|
||||||
values = {"total_games": 1, "games_won": 0, "games_lost": 0, "games_drawn": 0}
|
values = {"total_games": 1, "games_won": 0, "games_lost": 0, "games_drawn": 0}
|
||||||
if loser == "":
|
if loser == "":
|
||||||
|
|
|
@ -139,13 +139,13 @@ class StateHandler:
|
||||||
self._client = client
|
self._client = client
|
||||||
self.marshal = lambda obj: json.dumps(obj)
|
self.marshal = lambda obj: json.dumps(obj)
|
||||||
self.demarshal = lambda obj: json.loads(obj)
|
self.demarshal = lambda obj: json.loads(obj)
|
||||||
self.state_ = dict()
|
self.state_: Dict[str, Any] = dict()
|
||||||
|
|
||||||
def put(self, key: str, value: Any) -> None:
|
def put(self, key: str, value: Any) -> None:
|
||||||
self.state_[key] = self.marshal(value)
|
self.state_[key] = self.marshal(value)
|
||||||
response = self._client.update_storage({"storage": {key: self.state_[key]}})
|
response = self._client.update_storage({"storage": {key: self.state_[key]}})
|
||||||
if response["result"] != "success":
|
if response["result"] != "success":
|
||||||
raise StateHandlerError("Error updating state: {}".format(str(response)))
|
raise StateHandlerError(f"Error updating state: {str(response)}")
|
||||||
|
|
||||||
def get(self, key: str) -> Any:
|
def get(self, key: str) -> Any:
|
||||||
if key in self.state_:
|
if key in self.state_:
|
||||||
|
@ -423,8 +423,8 @@ def is_private_message_but_not_group_pm(
|
||||||
|
|
||||||
def display_config_file_errors(error_msg: str, config_file: str) -> None:
|
def display_config_file_errors(error_msg: str, config_file: str) -> None:
|
||||||
file_contents = open(config_file).read()
|
file_contents = open(config_file).read()
|
||||||
print("\nERROR: {} seems to be broken:\n\n{}".format(config_file, file_contents))
|
print(f"\nERROR: {config_file} seems to be broken:\n\n{file_contents}")
|
||||||
print("\nMore details here:\n\n{}\n".format(error_msg))
|
print(f"\nMore details here:\n\n{error_msg}\n")
|
||||||
|
|
||||||
|
|
||||||
def prepare_message_handler(bot: str, bot_handler: BotHandler, bot_lib_module: Any) -> Any:
|
def prepare_message_handler(bot: str, bot_handler: BotHandler, bot_lib_module: Any) -> Any:
|
||||||
|
@ -459,7 +459,7 @@ def run_message_handler_for_bot(
|
||||||
bot_details.update(getattr(lib_module.handler_class, "META", {}))
|
bot_details.update(getattr(lib_module.handler_class, "META", {}))
|
||||||
# Make sure you set up your ~/.zuliprc
|
# Make sure you set up your ~/.zuliprc
|
||||||
|
|
||||||
client_name = "Zulip{}Bot".format(bot_name.capitalize())
|
client_name = f"Zulip{bot_name.capitalize()}Bot"
|
||||||
|
|
||||||
try:
|
try:
|
||||||
client = Client(config_file=config_file, client=client_name)
|
client = Client(config_file=config_file, client=client_name)
|
||||||
|
@ -479,9 +479,7 @@ def run_message_handler_for_bot(
|
||||||
if hasattr(message_handler, "usage"):
|
if hasattr(message_handler, "usage"):
|
||||||
print(message_handler.usage())
|
print(message_handler.usage())
|
||||||
else:
|
else:
|
||||||
print(
|
print(f"WARNING: {bot_name} is missing usage handler, please add one eventually")
|
||||||
"WARNING: {} is missing usage handler, please add one eventually".format(bot_name)
|
|
||||||
)
|
|
||||||
|
|
||||||
def handle_message(message: Dict[str, Any], flags: List[str]) -> None:
|
def handle_message(message: Dict[str, Any], flags: List[str]) -> None:
|
||||||
logging.info("waiting for next message")
|
logging.info("waiting for next message")
|
||||||
|
|
|
@ -21,7 +21,7 @@ def provision_bot(path_to_bot: str, force: bool) -> None:
|
||||||
req_path = os.path.join(path_to_bot, "requirements.txt")
|
req_path = os.path.join(path_to_bot, "requirements.txt")
|
||||||
if os.path.isfile(req_path):
|
if os.path.isfile(req_path):
|
||||||
bot_name = os.path.basename(path_to_bot)
|
bot_name = os.path.basename(path_to_bot)
|
||||||
logging.info("Installing dependencies for {}...".format(bot_name))
|
logging.info(f"Installing dependencies for {bot_name}...")
|
||||||
|
|
||||||
# pip install -r $BASEDIR/requirements.txt -t $BASEDIR/bot_dependencies --quiet
|
# pip install -r $BASEDIR/requirements.txt -t $BASEDIR/bot_dependencies --quiet
|
||||||
rcode = subprocess.call(["pip", "install", "-r", req_path])
|
rcode = subprocess.call(["pip", "install", "-r", req_path])
|
||||||
|
|
|
@ -63,7 +63,7 @@ def exit_gracefully_if_zulip_config_is_missing(config_file: Optional[str]) -> No
|
||||||
# but we'll catch those later.
|
# but we'll catch those later.
|
||||||
return
|
return
|
||||||
else:
|
else:
|
||||||
error_msg = "ERROR: %s does not exist." % (config_file,)
|
error_msg = f"ERROR: {config_file} does not exist."
|
||||||
|
|
||||||
else:
|
else:
|
||||||
if zulip_env_vars_are_present():
|
if zulip_env_vars_are_present():
|
||||||
|
|
|
@ -33,15 +33,13 @@ class MockMessageServer:
|
||||||
return message
|
return message
|
||||||
|
|
||||||
def add_reaction(self, reaction_data):
|
def add_reaction(self, reaction_data):
|
||||||
return dict(
|
return dict(result="success", msg="", uri=f"https://server/messages/{uuid4()}/reactions")
|
||||||
result="success", msg="", uri="https://server/messages/{}/reactions".format(uuid4())
|
|
||||||
)
|
|
||||||
|
|
||||||
def update(self, message):
|
def update(self, message):
|
||||||
self.messages[message["message_id"]] = message
|
self.messages[message["message_id"]] = message
|
||||||
|
|
||||||
def upload_file(self, file):
|
def upload_file(self, file):
|
||||||
return dict(result="success", msg="", uri="https://server/user_uploads/{}".format(uuid4()))
|
return dict(result="success", msg="", uri=f"https://server/user_uploads/{uuid4()}")
|
||||||
|
|
||||||
|
|
||||||
class TerminalBotHandler:
|
class TerminalBotHandler:
|
||||||
|
|
|
@ -44,7 +44,7 @@ def main():
|
||||||
if lib_module is None:
|
if lib_module is None:
|
||||||
raise OSError
|
raise OSError
|
||||||
except OSError:
|
except OSError:
|
||||||
print("Could not find and import bot '{}'".format(bot_name))
|
print(f"Could not find and import bot '{bot_name}'")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -27,7 +27,7 @@ def read_bot_fixture_data(bot_name: str, test_name: str) -> Dict[str, Any]:
|
||||||
base_path = os.path.realpath(
|
base_path = os.path.realpath(
|
||||||
os.path.join(os.path.dirname(os.path.abspath(__file__)), "bots", bot_name, "fixtures")
|
os.path.join(os.path.dirname(os.path.abspath(__file__)), "bots", bot_name, "fixtures")
|
||||||
)
|
)
|
||||||
http_data_path = os.path.join(base_path, "{}.json".format(test_name))
|
http_data_path = os.path.join(base_path, f"{test_name}.json")
|
||||||
with open(http_data_path, encoding="utf-8") as f:
|
with open(http_data_path, encoding="utf-8") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
http_data = json.loads(content)
|
http_data = json.loads(content)
|
||||||
|
|
|
@ -72,11 +72,11 @@ except ImportError:
|
||||||
except (ImportError, AssertionError):
|
except (ImportError, AssertionError):
|
||||||
if version is not None:
|
if version is not None:
|
||||||
print(
|
print(
|
||||||
"{name}>={version} is not installed.".format(name=module_name, version=version),
|
f"{module_name}>={version} is not installed.",
|
||||||
file=sys.stderr,
|
file=sys.stderr,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
print("{name} is not installed.".format(name=module_name), file=sys.stderr)
|
print(f"{module_name} is not installed.", file=sys.stderr)
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
check_dependency_manually("zulip")
|
check_dependency_manually("zulip")
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
import configparser
|
import configparser
|
||||||
import json
|
import json
|
||||||
from typing import Any, Dict, List, Optional
|
from typing import Any, Dict, List, Optional
|
||||||
from unittest import TestCase
|
from unittest import TestCase, mock
|
||||||
|
|
||||||
import mock
|
|
||||||
|
|
||||||
from zulip_botserver import server
|
from zulip_botserver import server
|
||||||
|
|
||||||
|
|
|
@ -6,8 +6,7 @@ from importlib import import_module
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from types import ModuleType
|
from types import ModuleType
|
||||||
from typing import Any, Dict
|
from typing import Any, Dict
|
||||||
|
from unittest import mock
|
||||||
import mock
|
|
||||||
|
|
||||||
from zulip_bots.lib import BotHandler
|
from zulip_bots.lib import BotHandler
|
||||||
from zulip_botserver import server
|
from zulip_botserver import server
|
||||||
|
|
|
@ -98,7 +98,7 @@ def read_config_file(
|
||||||
ignored_sections = parser.sections()[1:]
|
ignored_sections = parser.sections()[1:]
|
||||||
|
|
||||||
if len(ignored_sections) > 0:
|
if len(ignored_sections) > 0:
|
||||||
logging.warning("Sections except the '{}' will be ignored".format(bot_section))
|
logging.warning(f"Sections except the '{bot_section}' will be ignored")
|
||||||
|
|
||||||
return bots_config
|
return bots_config
|
||||||
|
|
||||||
|
@ -106,7 +106,7 @@ def read_config_file(
|
||||||
def parse_config_file(config_file_path: str) -> configparser.ConfigParser:
|
def parse_config_file(config_file_path: str) -> configparser.ConfigParser:
|
||||||
config_file_path = os.path.abspath(os.path.expanduser(config_file_path))
|
config_file_path = os.path.abspath(os.path.expanduser(config_file_path))
|
||||||
if not os.path.isfile(config_file_path):
|
if not os.path.isfile(config_file_path):
|
||||||
raise OSError("Could not read config file {}: File not found.".format(config_file_path))
|
raise OSError(f"Could not read config file {config_file_path}: File not found.")
|
||||||
parser = configparser.ConfigParser()
|
parser = configparser.ConfigParser()
|
||||||
parser.read(config_file_path)
|
parser.read(config_file_path)
|
||||||
return parser
|
return parser
|
||||||
|
|
Loading…
Reference in a new issue