Turns calendar events in to HUGO posts. Lumbung-interlokal members can use it to schedule events from a shared calendar and have them appear as event announcements on lumbung.space
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

168 lines
5.3 KiB

#!/bin/python3
#lumbung.space calendar feed generator
#© 2021 roel roscam abbing gplv3 etc
from ics import Calendar
import requests
import jinja2
import os
import shutil
from slugify import slugify
from natural import date
from event_feed_config import calendar_url, output_dir
from urllib.parse import urlparse
import arrow
import re
cal = Calendar(requests.get(calendar_url).text)
env = jinja2.Environment(
loader=jinja2.FileSystemLoader(os.path.curdir)
)
if not os.path.exists(output_dir):
os.mkdir(output_dir)
template = env.get_template('event_template.md')
existing_posts = os.listdir(output_dir)
def findURLs(string):
"""
return all URLs in a given string
"""
regex = r"(?i)\b((?:https?://|www\d{0,3}[.]|[a-z0-9.\-]+[.][a-z]{2,4}/)(?:[^\s()<>]+|\(([^\s()<>]+|(\([^\s()<>]+\)))*\))+(?:\(([^\s()<>]+|(\([^\s()<>]+\)))*\)|[^\s`!()\[\]{};:'\".,<>?«»“”‘’]))"
url = re.findall(regex,string)
return [x[0] for x in url]
def find_imageURLS(string):
"""
return all image URLS in a given string
"""
regex = r"(?:http\:|https\:)?\/\/.*?\.(?:png|jpg|jpeg|gif|svg)"
img_urls = re.findall(regex, string, flags=re.IGNORECASE)
return img_urls
def create_metadata(event):
"""
construct a formatted dict of event metadata for use as frontmatter for HUGO post
"""
if event.location:
location_urls = findURLs(event.location)
if location_urls:
location_url = location_urls[0]
event.location = '[{}]({})'.format(urlparse(location_url).netloc, location_url)
event_metadata = {
'name':event.name,
'created':event.created.format(),
'description': event.description,
'localized_begin': '           '.join(localize_time(event.begin)), #non-breaking space characters to defeat markdown
'begin': event.begin.format(),
'end': event.end.format(),
'duration': date.compress(event.duration),
'location': event.location,
'uid': event.uid,
'images' : find_imageURLS(event.description) # currently not used in template
}
return event_metadata
def localize_time(date):
"""
Turn a given date into various timezones
"""
# Dates need to be displayed for the various TZs
# takes arrow objects
# 3 PM Kassel, Germany, 4 PM Ramallah/Jerusalem, Palestina (QoF),
# 8 AM Bogota, Colombia (MaMa), 8 PM Jakarta, Indonesia (Gudskul),
# 1 PM (+1day) Wellington, New Zealand (Fafswag), 9 AM Havana, Cuba (Instar).
tzs = [
('Kassel','Europe/Berlin'),
('Bamako', 'Europe/London'),
('Palestine','Asia/Jerusalem'),
('Bogota','America/Bogota'),
('Jakarta','Asia/Jakarta'),
('Makassar','Asia/Makassar'),
('Wellington', 'Pacific/Auckland')
]
localized_begins =[]
for location, tz in tzs:
localized_begins.append( #javascript formatting because of string creation from hell
'__{}__ {}'.format(
str(location),
str(date.to(tz).format("YYYY-MM-DD __HH:mm__"))
)
)
return localized_begins
def create_event_post(post_dir, event):
if not os.path.exists(post_dir):
os.mkdir(post_dir)
event_metadata = create_metadata(event)
for img in event_metadata['images']:
img_name = img.split('/')[-1]
local_image = os.path.join(post_dir, img_name)
#TODO: handle the fact that someone might update a post
#and delete / replace an image
if not os.path.exists(local_image):
#download preview image
response = requests.get(img, stream=True)
with open(local_image, 'wb') as img_file:
shutil.copyfileobj(response.raw, img_file)
print('Downloaded event image for event "{}"'.format(event.name))
event_metadata['description'] = event_metadata['description'].replace(img, '![]({})'.format(img_name))
with open(os.path.join(post_dir,'index.md'),'w') as f:
post = template.render(event = event_metadata)
f.write(post)
print('created post for', event.name, '({})'.format(event.uid))
with open(os.path.join(post_dir,'.timestamp'),'w') as f:
f.write(event_metadata['created'])
def update_event_post(post_dir, event):
"""
Update a post based on the VCARD event 'created' field which changes when updated
"""
if os.path.exists(post_dir):
old_timestamp = open(os.path.join(post_dir,'.timestamp')).read()
if event.created > arrow.get(old_timestamp):
print('Updating', event.name, '({})'.format(event.uid))
create_event_post(post_dir, event)
else:
print('Event current: ', event.name, '({})'.format(event.uid))
for event in list(cal.events):
post_dir = os.path.join(output_dir, event.uid)
if event.uid not in existing_posts:
#if there is an event we dont already have, make it
create_event_post(post_dir, event)
elif event.uid in existing_posts:
#if we already have it, update
update_event_post(post_dir, event)
existing_posts.remove(event.uid) # create list of posts which have not been returned by the calendar
for post in existing_posts:
#remove events not returned by the calendar (deletion)
print('deleted', post)
shutil.rmtree(os.path.join(output_dir,post))