commit 3942e957656a7262977f2c237a6089b2aaacc14b Author: Hugh Rundle Date: Sun Sep 15 11:55:45 2024 +1000 first commit diff --git a/README.md b/README.md new file mode 100644 index 0000000..1fcea7b --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Discord cardiParty webhook + +This is a simple webhook to push new cardiParties into the cardiParty channel. + +## install + +1. Create and activate [virtual env](https://docs.python.org/3/library/venv.html) +2. Install requirements: `pip install -r requirements.txt` +3. Set up a cronjob + 1. set the env values (see below) + 2. set a time to run e.g. every 30 mins + 3. set the command listed below under "Cron job command" +4. Test cronjob works +5. Relax + +## How it works + +We run this on the newcardigan wordpress server. + +1. With a cron job we call the civiCRM API use `cv` to grab the details of the latest cardiParty +2. We pipe that string into a python script +3. The script checks a custom RSS feed for new parties +4. If no new party, end +5. If new party, + 1. save the guid to `latest_post.txt` for the next round of checking + 2. use the image and title from the RSS feed and the details from the cv API call + 3. publish via a webhook to Discord + +## env values + +We need to include some secrets via environment values: + +**channel** is the channel ID (`DISCORD_CHANNEL`) +**token** is the secret token for the webhook (`DISCORD_TOKEN`) +**cardiparty_ping** is the ID of the `@cardiParty ping` role (`DISCORD_CARDIPARTY_PING`) + +## Cron job command + +```sh +cv api4 Event.get '{"select":["title","address.city","start_date","summary"],"join":[["Address AS address","LEFT",["loc_block_id.address_id","=","address.id"]]],"orderBy":{"start_date":"DESC"},"limit":1}' | ./webhook.py +``` +This should pipe a string like this into the script: + +``` +[{"id":108,"title":"Melbourne Art Library","address.city":"Melbourne","start_date":"2024-04-06 16:30:00","summary":"Melbourne Art Library (MAL) is a not-for-profit lending library that collects specialised art and design texts. They are proudly independent and are curious about what being a 'library' means."}] +``` + +## RSS feed + +The rss feed is `https://newcardigan.org/category/cardiparties/?feed=cardipartyfeed`. + +This special `cardipartyfeed` is a slightly modified RSS2 feed. The only thing it does differently is include the featured image in an `enclosure` so that we can pick it up directly from the feed. See the newcardigan WordPress theme for how this works. \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..04cb338 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,3 @@ +# lazy request list ;) +feedparser +requests \ No newline at end of file diff --git a/webhook.py b/webhook.py new file mode 100755 index 0000000..e4fd063 --- /dev/null +++ b/webhook.py @@ -0,0 +1,60 @@ +#!/usr/bin/env python3 + +from datetime import datetime +import json +import os +import sys + +import feedparser +import requests + +channel = os.getenv("DISCORD_CHANNEL") +token = os.getenv("DISCORD_TOKEN") +cardiparty_ping = os.getenv("DISCORD_CARDIPARTY_PING") + +# get the latest cardiparty with image as enclosure +f = feedparser.parse("https://newcardigan.org/category/cardiparties/?feed=cardipartyfeed") +first = f.entries[0] +# check whether we have already seen this entry +with open("latest_post.txt", "r+") as f: + guid = f.read().strip() + f.close() + +if guid != first.guid: + # update the guid + with open("latest_post.txt", "w+") as f: + f.write(first.guid) + f.close() + + api_response = sys.stdin.read() # read the API call output that was piped in + api_data = json.loads(api_response)[0] + + title = api_data["title"] + city = api_data["address.city"] + start_date = api_data["start_date"] + summary = api_data["summary"] + start_date = datetime.fromisoformat(start_date).astimezone().strftime( + "%I:%M%p %a %d %b %Y" + ).strip("0") # remove time zero padding if any + + content = f"[<:newCardigan:1280097925149626419> **{title}**]({first.link})\n_{city}_\n_{start_date}_\n\n{summary}\n\n<@&{cardiparty_ping}>" + embeds = [{ + "title": "Find out more and register!", + "url": first.link, + "color": 16741516, + "image": { + "url": first.enclosures[0].href + } + }] + + headers = {'user-agent': 'cardiParty-discord-bot/1.0.0'} + url = f"https://discord.com/api/webhooks/{channel}/{token}" + payload = { + "thread_name": f"{first.title} | {title}", + "content": content, + "embeds": embeds, + "allowed_mentions": { "roles": [cardiparty_ping] } + } + + r = requests.post(url, json=payload, headers=headers) + r.raise_for_status() # if we got a 4xx response this will log it out