Commit 0c88ed04 authored by Henrik Hüttemann's avatar Henrik Hüttemann

INIT

Signed-off-by: Henrik Hüttemann's avatarHerHde <mail@herh.de>
parents
# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class
# C extensions
*.so
# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# PyInstaller
# Usually these files are written by a python script from a template
# before PyInstaller builds the exe, so as to inject date/other infos into it.
*.manifest
*.spec
# Installer logs
pip-log.txt
pip-delete-this-directory.txt
# Unit test / coverage reports
htmlcov/
.tox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
.hypothesis/
# Translations
*.mo
*.pot
# Django stuff:
*.log
local_settings.py
# Flask stuff:
instance/
.webassets-cache
# Scrapy stuff:
.scrapy
# Sphinx documentation
docs/_build/
# PyBuilder
target/
# Jupyter Notebook
.ipynb_checkpoints
# pyenv
.python-version
# celery beat schedule file
celerybeat-schedule
# SageMath parsed files
*.sage.py
# Environments
.env
.venv
env/
venv/
ENV/
# Spyder project settings
.spyderproject
.spyproject
# Rope project settings
.ropeproject
# mkdocs documentation
/site
# mypy
.mypy_cache/
MAIL_FROM = "events@example.com"
MAIL_TO = "info@example.com"
MAIL_SUBJECT = "[Events] Termine vom {{ date_min.date }} bis {{ date_max.date }}" # This is parsed as a Jinja2-template.
ICAL_TZ = "Europe/Berlin" # Timezone, like "Europe/Dublin", "Asia/Seoul", "Japan" or "America/Los_Angeles".
ICAL_URLS = [
"https://example.com/calendar.ics",
"https://example.com/events.ics"
]
DAYS_PREV = 0 # How many days from the past should be included?
DAYS_NEXT = 3 # And how many future days?
SMTP_HOST = "mail.example.com"
SMTP_PORT = 587
SMTP_USER = "username"
SMTP_PASS = "password"
# These formats are parsed with strftime().
FORMAT_DATE = "%Y-%m-%d"
FORMAT_TIME = "%H:%M"
FORMAT_DATETIME = FORMAT_DATE + " " + FORMAT_TIME
TEMPLATE_FILE = "plain.jinja" # Template file in templates/
\ No newline at end of file
#!/usr/bin/env python3
import urllib.request
from email.message import Message
import smtplib
from datetime import datetime, timedelta, date
from pytz import timezone
from icalendar import Calendar, vDDDTypes
from dateutil import rrule
import jinja2
import config
TZ = timezone(config.ICAL_TZ)
TODAY = datetime.now(TZ).replace(hour=0, minute=0, second=0, microsecond=0)
DATE_MIN = TODAY - timedelta(days=config.DAYS_PREV)
DATE_MAX = TODAY + timedelta(days=config.DAYS_NEXT) - timedelta(microseconds=1)
EVENT_PROPERTIES = {
"unique": [
"class", "created", "description", "dtstart", "geo", "last-mod",
"location", "organizer", "priority", "dtstamp", "seq", "status",
"summary", "transp", "uid", "url", "recurid"
],
"xor": [
"dtend", "duration"
],
"many": [
"attach", "attendee", "categories", "comment", "contact", "exdate",
"exrule", "rstatus", "related", "resources", "rdate", "rrule", "x-prop"
]
}
def to_tz_datetime(adate, dtend=False):
"""Return a timezoned datetime from a given date or datetime.
Args:
adate: A date or datetime instance.
dtend: A boolean defining whether a date is a dtend and therefore should
be set to 23:59.
Returns:
An offset-aware timezoned datetime.
"""
if type(adate) is date and dtend:
adate = datetime(adate.year, adate.month, adate.day, 23, 59, 59, 0, TZ)
elif type(adate) is date and not dtend:
adate = datetime(adate.year, adate.month, adate.day, 0, 0, 0, 0, TZ)
else:
adate = adate.astimezone(TZ)
return adate
def format_date(adate):
"""Return a dictionary containing formatted versions of adate."""
return {
"dt": to_tz_datetime(adate),
"datetime": to_tz_datetime(adate).strftime(config.FORMAT_DATETIME),
"date": to_tz_datetime(adate).strftime(config.FORMAT_DATE),
"time": to_tz_datetime(adate).strftime(config.FORMAT_TIME)
}
def parse_ics(ics_url):
"""Parse an ics-file and return the vevents as a list of tuples.
Returns:
A list of tuples containing
1. the event as an icalendar event
1. the starttime as a datetime
1. the endtime as a datetime
1. the duration as a timedelta
of an event.
"""
ics = urllib.request.urlopen(ics_url).read()
cal = Calendar.from_ical(ics)
event_list = []
for event in cal.walk('vevent'):
dtstart = event.get('dtstart').dt
duration = event.get('dtend').dt - dtstart
dtstart = to_tz_datetime(dtstart)
# Generate recurrences
if "rrule" in event:
rule = rrule.rrulestr(
event.get('rrule').to_ical().decode('utf8'),
dtstart=to_tz_datetime(event.get('dtstart').dt)
)
for dtstart_rec in rule.between(DATE_MIN - timedelta(microseconds=1), DATE_MAX):
event_list.append(
(
event,
dtstart_rec,
dtstart_rec + duration,
duration
)
)
elif dtstart >= DATE_MIN and dtstart < DATE_MAX:
event_list.append(
(
event,
dtstart,
to_tz_datetime(event.get('dtend').dt, True),
duration
)
)
return remove_modified_recurrence(event_list)
def debug_events(event_list):
"""Print the events in event_list fpr debugging purposes."""
for item in event_list:
for prop in ["summary", "uid", "dtstart", "sequence", "recurrence-id", "rrule"]:
if prop in item[0]:
print(prop.rjust(14), end=': ')
if isinstance(item[0][prop], vDDDTypes):
print(item[0][prop].dt)
else:
print(item[0][prop])
print("start".rjust(14), end=': ')
print(item[1])
print("ende".rjust(14), end=': ')
print(item[2])
print()
def remove_modified_recurrence(event_list):
"""Remove events which have a modified recurrence."""
recurrences = []
for item in event_list:
if "recurrence-id" in item[0]:
recurrences.append((item[0].get("uid"), item[0].get("recurrence-id").dt))
for item in event_list:
for rec in recurrences:
if item[1] == rec[1] and item[0].get("uid") == rec[0]:
event_list.remove(item)
return event_list
def simplify_events(event_list):
"""Prepare events for an easy usage in Templates."""
simple_events = []
for item in event_list:
new_event = {}
for prop in EVENT_PROPERTIES["unique"]:
if prop in item[0]:
if isinstance(item[0].get(prop), vDDDTypes):
new_event[prop] = to_tz_datetime(item[0].get(prop).dt)
else:
new_event[prop] = item[0].get(prop).to_ical().decode()
# else:
# new_event[prop] = ""
# Duration ignored, only parsing dtend
new_event["dtend"] = to_tz_datetime(item[0].get("dtend").dt)
new_event["duration"] = item[3]
# TODO Not properly parsed but pasted
for prop in EVENT_PROPERTIES["many"]:
if prop in item[0]:
new_event[prop] = item[0].get(prop)
# else:
# new_event[prop] = []
# Now the simple properties
new_event["start"] = format_date(item[1])
new_event["end"] = format_date(item[2])
simple_events.append(new_event)
return simple_events
def generate_output(event_list):
"""Parse events with the template and return the output.
Return:
A tupel containing
1. the content and
1. the title
for usage in emails.
"""
template_loader = jinja2.FileSystemLoader(searchpath="templates/")
template_env = jinja2.Environment(
loader=template_loader,
trim_blocks=True,
lstrip_blocks=True
)
header_title_template = template_env.from_string(config.MAIL_SUBJECT)
template = template_env.get_template(config.TEMPLATE_FILE)
template_vars = {
"today": format_date(TODAY),
"date_min": format_date(DATE_MIN),
"date_max": format_date(DATE_MAX),
"days_prev": config.DAYS_PREV,
"days_next": config.DAYS_NEXT,
"format_datetime": config.FORMAT_DATETIME,
"format_date": config.FORMAT_DATE,
"format_time": config.FORMAT_TIME,
"calendars": config.ICAL_URLS,
"timezone": config.ICAL_TZ,
"mail_from": config.MAIL_FROM,
"mail_to": config.MAIL_TO,
"mail_title": config.MAIL_SUBJECT,
"events": simplify_events(event_list)
}
return [
template.render(template_vars),
header_title_template.render(template_vars)
]
def send_mail(content):
"""Send out an email."""
msg = Message()
msg.set_payload(content[0], "utf-8")
msg["Subject"] = content[1]
msg["From"] = config.MAIL_FROM
msg["To"] = config.MAIL_TO
server = smtplib.SMTP(config.SMTP_HOST, config.SMTP_PORT)
server.ehlo()
server.starttls()
server.ehlo()
server.login(config.SMTP_USER, config.SMTP_PASS)
server.sendmail(config.MAIL_FROM, config.MAIL_TO, msg.as_string())
server.quit()
def main():
events = []
for url in config.ICAL_URLS:
events += parse_ics(url)
events = sorted(events, key=lambda event: event[1])
output_text = generate_output(events)
send_mail(output_text)
# print("TITLE: " + output_text[1])
# print("----------------------------------------")
# print(output_text[0].replace(' ', ' '))
if __name__ == "__main__":
main()
astroid==1.5.3
icalendar==3.11.6
isort==4.2.15
Jinja2==2.9.6
lazy-object-proxy==1.3.1
MarkupSafe==1.0
mccabe==0.6.1
pylint==1.7.2
python-dateutil==2.6.1
pytz==2017.2
six==1.10.0
wrapt==1.10.11
Termine vom {{ date_min.date }} bis {{ date_max.date }}:
{% for day, list in events | groupby("start.date") %}
{{ day }}
{% for event in list %}
{{ event.start.time }}: {{ event.summary }}
{% if day == event.end.date %}
Ende: {{ event.end.time }}
{% else %}
Ende: {{ event.end.datetime }}
{% endif %}
{% if event.location %}
Ort: {{ event.location }}
{% endif %}
{% if event.url %}
Link: {{ event.url }}
{% endif %}
{% if event.organizer %}
Organisator: {{ event.organizer }}
{% endif %}
{% if event.categories %}
{% if event.categories is string %}
Kategorie: {{ event.categories }}
{% else %}
Kategorien: {{ event.categories|join(', ')|wordwrap(width=80-29, wrapstring="\n ") }}
{% endif %}
{% endif %}
{% if event.description %}
Beschreibung: {{ event.description|wordwrap(width=80-29, wrapstring="\n ") }}
{% endif %}
{% endfor %}
{% endfor %}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment