diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8709bac --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +invite.conf diff --git a/README.md b/README.md index df08554..1e28a2d 100644 --- a/README.md +++ b/README.md @@ -1,26 +1,18 @@ # Einladung zum Plenum des Hackspace Jena e.V. -Das Repository enthält Dateien zum automatischen Versenden von Einladungsmails -für das Plenum. +Das Repository enthält Dateien zum automatischen Versenden von Einladungsmails für das Plenum. -Das Shellskript ermittelt den kommenden Donnerstag, prüft, ob es ein erster -Donnerstag des Monats ist, und bricht sonst ab, ersetzt im Einladungstext (Datei -`email_text`) Platzhalter mit dem Datum des kommenden Donnerstag und verschickt -den Text als E-Mail. - -Die Service- und Timer-Dateien lassen systemd das Skript einmal wöchentlich -ausführen. +Die Service- und Timer-Dateien lassen systemd das Skript einmal monatlich ausführen. # Installation -Dieses Repository muss nach `/opt` in das Verzeichnis `plenumsinvite` kopiert -werden: +Dieses Repository soll nach `/opt` in das Verzeichnis `plenumsinvite` kopiert werden: ``` mkdir -p /opt cd /opt git clone https://github.com/HackspaceJena/plenums_invite.git -cd /plenums_invite +cd plenums_invite ``` Danach müssen die beiden systemd-Unit-Dateien nach `/etc/systemd/system` diff --git a/email_text b/email_text deleted file mode 100644 index 8d10aa5..0000000 --- a/email_text +++ /dev/null @@ -1,22 +0,0 @@ -Hallo zusammen, - -am PROSEDATE findet wieder unser monatliches Plenum statt. -Wir treffen uns um 20 Uhr im virtuellen Raum unter: - -https://talk.kabi.tk/krautspace - -Für Vorbereitung und Protokoll gibt es wieder ein Pad, bitte -tragt schon mal Themen ein, die Ihr gern besprechen würdet: - -https://vereinte.verwirrung.institute/p/ksplenum-URLDATE - -Ggf. muss das Pad erst noch vorbereitet werden, die Vorlage -mit Anleitung findet Ihr hier: - -https://vereinte.verwirrung.institute/p/ksplenum-template - -Wir freuen uns auf zahlreiche Teilnehmer:innnen. - -Mit den besten Grüßen, - -das Einladeskript diff --git a/invite.ics b/invite.ics deleted file mode 100644 index eb17fc0..0000000 --- a/invite.ics +++ /dev/null @@ -1,22 +0,0 @@ -BEGIN:VCALENDAR -PRODID:-//KrautSpace Jena//KrautSpace Einladescript//DE -VERSION:2.0 -CALSCALE:GREGORIAN -METHOD:PUBLISH -BEGIN:VEVENT -DTSTART:#DTSTART# -DTEND:#DTEND# -DTSTAMP:#DTSTAMP# -UID:#DTSTAMP#@kraut.space -SUMMARY:#SUMMARY# -DESCRIPTION:Wir treffen uns um 20 Uhr im virtuellen Raum unter:\n#LOCATION#\n\nFür Vorbereitung und Protokoll gibt es wieder ein Pad, bitte tragt schon mal Themen ein, die Ihr gern besprechen würdet:\nhttps://vereinte.verwirrung.institute/p/ksplenum-#URLDATE#\n\nGgf. muss das Pad erst noch vorbereitet werden, die Vorlage mit Anleitung findet Ihr hier:\nhttps://vereinte.verwirrung.institute/p/ksplenum-template -LOCATION:#LOCATION# -ORGANIZER;ROLE=CHAIR;PARTSTAT=ACCEPTED;RSVP=FALSE:mailto:#FROM# -CONFERENCE;VALUE=URI:#LOCATION# -SEQUENCE:0 -STATUS:CONFIRMED -CLASS:PUBLIC -CATEGORIES:Meeting -TRANSP:OPAQUE -END:VEVENT -END:VCALENDAR diff --git a/invite.py b/invite.py new file mode 100755 index 0000000..7a5faf7 --- /dev/null +++ b/invite.py @@ -0,0 +1,112 @@ +#!/usr/bin/env python3 + +import configparser +import datetime +import logging +import optparse +import requests + +class JsonObject(object): + '''An object constructed from a JSON response''' + + def __init__(self, json): + self.update_attributes(json) + + def __str__(self): + return '{} with ID: {}'.format(self.__class__.__name__, self.id) + + def __repr__(self): + vars_string = str(self.__dict__) + + replace = {': ': '=', '{': '', '}': ''} + for key in replace: + vars_string = vars_string.replace(key, replace[key]) + + return '{}({})'.format(self.__class__.__name__, vars_string) + + def update_attributes(self, json): + self.__dict__.update(json) + +class Topic(JsonObject): + + def __init__(self, client, **kwargs): + self.client = client + super().__init__(**kwargs) + +class Client(object): + + def __init__(self, host, api_username='', api_key=''): + self.host = host + + self.session = requests.Session() + self.session.headers.update({ + 'Api-Username': api_username, + 'Api-Key': api_key, + }) + + # Class Methods + def _request(self, method, path, params=None, data=None, json=None): + response = self.session.request( + method=method.upper(), + url=requests.compat.urljoin(self.host, path), + params=params, + data=data, + json=json, + ) + response.raise_for_status() + + return response.json() + + def create_topic(self, title, raw, category=None): + response = self._request('POST', 'posts.json', json={ + 'title': title, + 'raw': raw, + 'category': category, + }) + + return Topic(client=self, json=response) + +def main(): + op = optparse.OptionParser() + op.add_option("-c", "--config", default="invite.conf") + op.add_option("-v", "--verbose", action="store_true") + options, args = op.parse_args() + + config = configparser.ConfigParser() + configfile = options.config + if not config.read(configfile): + op.error(f"Configuration file '{configfile}' not found or not readable.") + + if options.verbose: + logging.basicConfig() + logging.getLogger().setLevel(logging.DEBUG) + + client = Client( + host = config['discourse']['uri'], + api_username = config['discourse']['api_username'], + api_key = config['discourse']['api_key'] + ) + + now = datetime.date.today() + date = now + datetime.timedelta(3 - now.weekday()) + date_str = date.strftime('%d.%m.%Y') + + subject = f'Einladung zum Plenum am {date_str}' + msg_body = '''Hallo zusammen, + +am Donnerstag findet wieder unser monatliches Plenum statt. +Wir treffen uns um 20 Uhr im virtuellen Raum unter: + +https://talk.kabi.tk/krautspace + +Antwortet einfach hier auf diesen Post, wenn ihr etwas thematisieren möchtet. +Wir freuen uns auf zahlreiche Teilnehmer:innnen. + +Mit den besten Grüßen, + +das Einladeskript''' + client.create_topic(title=subject, raw=msg_body, category=7) + + +if __name__ == '__main__': + main() diff --git a/invite.sh b/invite.sh deleted file mode 100755 index fc12c33..0000000 --- a/invite.sh +++ /dev/null @@ -1,47 +0,0 @@ -#!/usr/bin/env bash - -## Configuration - -from="office@krautspace.de" -to=("hackspace-jena@lstsrv.org" "krautspace-announce@lstsrv.org") -#replyto="hackspace-jena@lstsrv.org" -subject="Einladung zum Plenum" -location="https://talk.kabi.tk/krautspace" -body_file=email_text # Use PROSEDATE for dd.mm.YYYY and URLDATE for YYYmmdd - -## Find next Thursday -date_fmt () { date -u -d "$2 next Thu" "$1"; } - -## Check if next Thursday is the first of a month - -if (( $(date_fmt +%_d) > 7 )); then - echo "Invitation should be sent at most a week in advance." - echo "Exiting without sending an inviation..." - exit -fi - -## Send invitation - -prose_date=$(date_fmt +%d.%m.%Y) -url_date=$(date_fmt +%Y%m%d) - -script_path=$(readlink -f "$0") -source_directory=$(dirname "$script_path") - -sed -E \ - -e "s/#DTSTART#/$(date_fmt +%Y%m%dT%H%M%SZ "TZ=\"Europe/Berlin\" 20:00")/" \ - -e "s/#DTEND#/$(date_fmt +%Y%m%dT%H%M%SZ "TZ=\"Europe/Berlin\" 21:00")/" \ - -e "s/#DTSTAMP#/$(date -u +%Y%m%dT%H%M%SZ)/" \ - -e "s/#SUMMARY#/KrautSpace Plenum/" \ - -e "s%#LOCATION#%${location}%" \ - -e "s/#FROM#/${from}/g" \ - -e "s/#URLDATE#/${url_date}/g" \ - -e 's/(^[^ ].{73}|.{73})/\1\r\n /g' \ - "$source_directory/invite.ics" > /tmp/invite.ics - -sed -e "s/PROSEDATE/$prose_date/g" \ - -e "s/URLDATE/$url_date/g" \ - "$source_directory/$body_file" | EMAIL="$from" mutt -s "$subject" -a /tmp/invite.ics -- "${to[@]}" -ret=$? -rm -f /tmp/invite.ics -exit $ret diff --git a/plenums_invite.service b/plenums_invite.service index 9815de1..d2d42ad 100644 --- a/plenums_invite.service +++ b/plenums_invite.service @@ -1,8 +1,37 @@ [Unit] -Description=Send invitation to Hackspace's Plenum +Description=Send invitation to Hackspace's Announce Discourse [Service] Type=oneshot -ExecStart=/opt/plenums_invite/invite.sh +ExecStart=/opt/plenums_invite/invite.py +WorkingDirectory=/opt/plenums_invite + +UMask=077 +#DynamicUser=yes + +PrivateDevices=yes +PrivateUsers=yes PrivateTmp=yes + +ProtectSystem=strict +ProtectHome=yes +ProtectClock=yes +ProtectKernelModules=yes +ProtectKernelTunables=yes +ProtectControlGroups=yes +ProtectKernelLogs=yes +ProtectProc=invisible +ProcSubset=pid +ProtectHostname=yes + +ReadOnlyDirectories=/ + +NoNewPrivileges=true +CapabilityBoundingSet= +MemoryDenyWriteExecute=true +RestrictRealtime=true +RestrictNamespaces=true +SystemCallArchitectures=native +LockPersonality=yes +RestrictAddressFamilies=AF_INET AF_INET6