From 2da2a1affc83b4d0d4d73a2c0801307452796ea6 Mon Sep 17 00:00:00 2001 From: Ryan Cavicchioni Date: Mon, 30 Dec 2019 19:53:35 -0600 Subject: [PATCH] Add simple Discord bot to notify of Minecraft events --- roles/minecraft/defaults/main.yaml | 2 + roles/minecraft/files/minecraft-discord.py | 118 ++++++++++++++++++ roles/minecraft/handlers/main.yaml | 5 + roles/minecraft/tasks/main.yaml | 37 ++++++ .../templates/rsyslog/minecraft.conf.j2 | 11 ++ 5 files changed, 173 insertions(+) create mode 100644 roles/minecraft/files/minecraft-discord.py create mode 100644 roles/minecraft/templates/rsyslog/minecraft.conf.j2 diff --git a/roles/minecraft/defaults/main.yaml b/roles/minecraft/defaults/main.yaml index 4e87410..6a4c90e 100644 --- a/roles/minecraft/defaults/main.yaml +++ b/roles/minecraft/defaults/main.yaml @@ -19,3 +19,5 @@ minecraft_var_path: "{{ minecraft_opt_path }}/var" minecraft_backup_path: "{{ minecraft_opt_path }}/backup" minecraft_syslog_facility: local5 + +minecraft_notifier_state: present diff --git a/roles/minecraft/files/minecraft-discord.py b/roles/minecraft/files/minecraft-discord.py new file mode 100644 index 0000000..2684309 --- /dev/null +++ b/roles/minecraft/files/minecraft-discord.py @@ -0,0 +1,118 @@ +#!/usr/bin/env python + +from __future__ import print_function + +import sys +import requests +import re +import argparse + +from urlparse import urljoin + +PATTERN = re.compile(r"(\S+) (joined|left) the game") + +PATTERNS = ( + (re.compile(r"(\S+) (joined|left) the game"), "{0} {1} the game"), + (re.compile(r"\[(\S+): Gave (\d+) \[(.+)\] to (\S+)\]"), "{0} gave {1} \"{2}\" to {3}"), + (re.compile(r"(\S+) was (\S+) by (\S+)"), ":skull: {0} was {1} by {2}"), +) + + +def print_err(s): + print(s, file=sys.stderr) + sys.stderr.flush() + + +def ok(): + print("OK") + sys.stdout.flush() + + +def cli_parse(args): + parser = argparse.ArgumentParser() + opt = parser.add_argument + opt("--config", "-c", dest="config", type=parse_kv_file) + opt("--confirm", action="store_const", dest="confirm", const=True, default=True) + opt("--no-confirm", action="store_const", dest="confirm", const=False) + opt("--verbose", "-v", action="store_true") + opt("--debug", "-d", action="store_true") + cli_args = parser.parse_args(args[1:]) + return cli_args, parser + + +def parse_kv_file(f, mode="r"): + if isinstance(f, str): + f = open(f, mode) + + kv = {} + with f: + for line in f: + k, v = line.partition("=")[::2] + kv[k.strip().lower()] = v.strip() + + return kv + + +class DiscordHook: + def __init__(self, hook_id, hook_token): + url_path = "/".join([hook_id, hook_token]) + url = urljoin("https://discordapp.com/api/webhooks/", url_path) + self.url = url + + def send(self, content): + data = {"content": content} + r = requests.post(self.url, data=data) + r.raise_for_status() + return r + + +def loop(handler, confirm=True): + if confirm: + ok() + + while 1: + try: + line = sys.stdin.readline() + except KeyboardInterrupt: + print_err("\nreceived sigint, exiting") + break + + if not line: + break + + for pattern, fmt in PATTERNS: + match = pattern.search(line.strip()) + + if match: + message = fmt.format(*match.groups()) + + try: + handler.send(message) + except Exception as e: + print_err(e) + continue + + if confirm: + ok() + + +def main(argv): + args, _ = cli_parse(argv) + + if args.debug: + print("started with args {0}".format(vars(args))) + + webhook_id = args.config.get("webhook_id") + webhook_token = args.config.get("webhook_token") + + if webhook_id is None: + raise SystemExit("webhook_id is unset") + if webhook_token is None: + raise SystemExit("webhook_token is unset") + + handler = DiscordHook(webhook_id, webhook_token) + + return loop(handler, confirm=args.confirm) + + +raise SystemExit(main(sys.argv)) diff --git a/roles/minecraft/handlers/main.yaml b/roles/minecraft/handlers/main.yaml index 38c2de9..9ff27b7 100644 --- a/roles/minecraft/handlers/main.yaml +++ b/roles/minecraft/handlers/main.yaml @@ -9,3 +9,8 @@ service: name: "{{ minecraft_service_name }}" state: restarted + +- name: restart rsyslog + service: + name: rsyslog + state: restarted diff --git a/roles/minecraft/tasks/main.yaml b/roles/minecraft/tasks/main.yaml index 544a4f6..6c09cf5 100644 --- a/roles/minecraft/tasks/main.yaml +++ b/roles/minecraft/tasks/main.yaml @@ -161,3 +161,40 @@ state: link when: "'nginx' in ansible_play_role_names" notify: reload nginx + +- name: install discord notifier + copy: + src: minecraft-discord.py + dest: "{{ minecraft_opt_path }}/bin/minecraft-discord" + owner: root + group: root + mode: 0755 + notify: restart rsyslog + +- name: configure discord notifier + copy: + dest: "{{ minecraft_opt_path }}/etc/discord.cfg" + owner: syslog + group: syslog + mode: 0600 + content: "{% for k, v in minecraft_discord_config.items() %}{{ k }}={{ v }}{{ \"\n\" }}{% endfor %}" + notify: restart rsyslog + +- name: configure rsyslog program + template: + src: rsyslog/minecraft.conf.j2 + dest: /etc/rsyslog.d/05-minecraft.conf + owner: root + group: root + mode: 0644 + notify: restart rsyslog + +- name: manage rsyslog configuration + file: + path: "{{ item }}" + state: "{{ (minecraft_notifier_state == 'present') | ternary('file', 'absent') }}" + loop: + - /etc/rsyslog.d/05-minecraft.conf + - "{{ minecraft_opt_path }}/etc/discord.cfg" + - "{{ minecraft_opt_path }}/bin/minecraft-discord" + notify: restart rsyslog diff --git a/roles/minecraft/templates/rsyslog/minecraft.conf.j2 b/roles/minecraft/templates/rsyslog/minecraft.conf.j2 new file mode 100644 index 0000000..9cf8ccd --- /dev/null +++ b/roles/minecraft/templates/rsyslog/minecraft.conf.j2 @@ -0,0 +1,11 @@ +# {{ ansible_managed }} + +module(load="omprog") + +if ( $programname == "minecraft" ) then { + action( + type="omprog" + binary="{{ minecraft_opt_path }}/bin/minecraft-discord --config {{ minecraft_opt_path }}/etc/discord.cfg" + confirmmessages="on" + ) +}