ack + repos
This commit is contained in:
2
lumbung-calendar-prototype/.gitignore
vendored
Normal file
2
lumbung-calendar-prototype/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
||||
event_feed_config.py
|
||||
__pycache__
|
9
lumbung-calendar-prototype/README.md
Normal file
9
lumbung-calendar-prototype/README.md
Normal file
@ -0,0 +1,9 @@
|
||||
# Calendar Feed
|
||||
Generate HUGO posts based on a publicly accessible ICS calendar.
|
||||
|
||||
## Use
|
||||
Fill in your details in `calendar_feed_config.py`
|
||||
|
||||
## TODO / FIXME
|
||||
|
||||
* Multiple calendars to multiple hugo categories
|
194
lumbung-calendar-prototype/event_feed.py
Normal file
194
lumbung-calendar-prototype/event_feed.py
Normal file
@ -0,0 +1,194 @@
|
||||
#!/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
|
||||
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):
|
||||
"""
|
||||
Create HUGO post based on calendar event metadata
|
||||
Searches for image URLS in description and downloads them
|
||||
Function is also called when post is in need of updating
|
||||
In that case it will also delete images no longer in metadata
|
||||
TODO: split this up into more functions for legibility
|
||||
"""
|
||||
|
||||
if not os.path.exists(post_dir):
|
||||
os.mkdir(post_dir)
|
||||
|
||||
event_metadata = create_metadata(event)
|
||||
|
||||
#list already existing images
|
||||
#so we can later delete them if we dont find them in the event metadata anymore
|
||||
existing_images = os.listdir(post_dir)
|
||||
try:
|
||||
existing_images.remove('index.md')
|
||||
existing_images.remove('.timestamp')
|
||||
except:
|
||||
pass
|
||||
|
||||
for img in event_metadata['images']:
|
||||
|
||||
#parse img url to safe local image name
|
||||
img_name = img.split('/')[-1]
|
||||
fn, ext = img_name.split('.')
|
||||
img_name = slugify(fn) + '.' + ext
|
||||
|
||||
local_image = os.path.join(post_dir, img_name)
|
||||
|
||||
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 image for event "{}"'.format(event.name))
|
||||
event_metadata['description'] = event_metadata['description'].replace(img, ''.format(img_name))
|
||||
if img_name in existing_images:
|
||||
existing_images.remove(img_name)
|
||||
|
||||
for left_over_image in existing_images:
|
||||
#remove images we found, but which are no longer in remote event
|
||||
os.remove(os.path.join(post_dir,left_over_image))
|
||||
print('deleted image', left_over_image)
|
||||
|
||||
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))
|
||||
|
||||
|
21
lumbung-calendar-prototype/event_template.md
Normal file
21
lumbung-calendar-prototype/event_template.md
Normal file
@ -0,0 +1,21 @@
|
||||
---
|
||||
title: "{{ event.name }}"
|
||||
date: "{{ event.begin }}" #2021-06-10T10:46:33+02:00
|
||||
draft: false
|
||||
categories: "calendar"
|
||||
event_begin: "{{ event.begin }}"
|
||||
event_end: "{{ event.end }}"
|
||||
duration: "{{ event.duration }}"
|
||||
localized_begin: "{{ event.localized_begin }}"
|
||||
uid: "{{ event.uid }}"
|
||||
{% if event.location %}
|
||||
location: "{{ event.location }}"
|
||||
{% endif %}
|
||||
|
||||
|
||||
---
|
||||
{% if event.description %}
|
||||
|
||||
{{ event.description }}
|
||||
|
||||
{% endif %}
|
16
lumbung-calendar-prototype/requirements.txt
Normal file
16
lumbung-calendar-prototype/requirements.txt
Normal file
@ -0,0 +1,16 @@
|
||||
# Automatically generated by https://github.com/damnever/pigar.
|
||||
|
||||
# calendar-feed/event_feed.py: 3
|
||||
Jinja2 == 2.10
|
||||
|
||||
# calendar-feed/event_feed.py: 1
|
||||
ics == 0.7
|
||||
|
||||
# calendar-feed/event_feed.py: 6
|
||||
natural == 0.2.0
|
||||
|
||||
# calendar-feed/event_feed.py: 5
|
||||
python_slugify == 5.0.2
|
||||
|
||||
# calendar-feed/event_feed.py: 2
|
||||
requests == 2.21.0
|
Reference in New Issue
Block a user