#!/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))