epicyon/happening.py

1492 lines
56 KiB
Python
Raw Normal View History

2020-04-03 11:53:31 +00:00
__filename__ = "happening.py"
__author__ = "Bob Mottram"
__license__ = "AGPL3+"
2024-01-21 19:01:20 +00:00
__version__ = "1.5.0"
2020-04-03 11:53:31 +00:00
__maintainer__ = "Bob Mottram"
2021-09-10 16:14:50 +00:00
__email__ = "bob@libreserver.org"
2020-04-03 11:53:31 +00:00
__status__ = "Production"
2021-06-25 16:10:09 +00:00
__module_group__ = "Core"
2020-04-03 11:53:31 +00:00
import os
2020-08-13 13:40:38 +00:00
from uuid import UUID
2022-02-23 12:19:27 +00:00
from hashlib import md5
from datetime import datetime
2021-05-31 13:00:17 +00:00
from datetime import timedelta
2024-08-08 17:23:33 +00:00
from utils import replace_strings
from utils import date_from_numbers
2023-11-20 22:27:58 +00:00
from utils import date_from_string_format
2022-12-18 15:29:54 +00:00
from utils import acct_handle_dir
2021-12-28 14:41:10 +00:00
from utils import is_public_post
2021-12-26 15:13:34 +00:00
from utils import load_json
2021-12-26 14:47:21 +00:00
from utils import save_json
2021-12-26 20:36:08 +00:00
from utils import locate_post
2021-12-26 10:57:03 +00:00
from utils import has_object_dict
2021-12-26 12:02:29 +00:00
from utils import acct_dir
2022-02-21 11:08:48 +00:00
from utils import remove_html
from utils import get_display_name
2022-02-23 09:51:00 +00:00
from utils import delete_post
from utils import get_status_number
2022-02-24 10:18:54 +00:00
from utils import get_full_domain
2022-06-10 11:43:33 +00:00
from utils import text_in_file
2022-06-21 11:58:50 +00:00
from utils import remove_eol
from utils import is_reminder
2022-02-23 09:51:00 +00:00
from filters import is_filtered
from context import get_individual_post_context
2022-02-24 10:18:54 +00:00
from session import get_method
from auth import create_basic_auth_header
2020-04-03 11:53:31 +00:00
2024-07-15 10:37:15 +00:00
def _strings_are_digits(strings_list: []) -> bool:
"""Are the given list of strings digits?
"""
for text in strings_list:
if not text.isdigit():
return False
return True
2022-02-23 18:04:34 +00:00
def _dav_date_from_string(timestamp: str) -> str:
"""Returns a datetime from a caldav date
"""
timestamp_year = timestamp[:4]
timestamp_month = timestamp[4:][:2]
timestamp_day = timestamp[6:][:2]
timestamp_hour = timestamp[9:][:2]
timestamp_min = timestamp[11:][:2]
timestamp_sec = timestamp[13:][:2]
2024-07-15 10:37:15 +00:00
if not _strings_are_digits([timestamp_year, timestamp_month,
timestamp_day, timestamp_hour,
timestamp_min, timestamp_sec]):
2022-02-23 18:04:34 +00:00
return None
if int(timestamp_year) < 2020 or int(timestamp_year) > 2100:
return None
published = \
timestamp_year + '-' + timestamp_month + '-' + timestamp_day + 'T' + \
timestamp_hour + ':' + timestamp_min + ':' + timestamp_sec + 'Z'
return published
2022-01-02 15:16:47 +00:00
def _valid_uuid(test_uuid: str, version: int):
2020-08-13 13:40:38 +00:00
"""Check if uuid_to_test is a valid UUID
"""
try:
2022-01-02 15:16:47 +00:00
uuid_obj = UUID(test_uuid, version=version)
2020-08-13 13:40:38 +00:00
except ValueError:
return False
2022-01-02 15:16:47 +00:00
return str(uuid_obj) == test_uuid
2020-08-13 13:40:38 +00:00
2022-01-02 15:16:47 +00:00
def _remove_event_from_timeline(event_id: str,
tl_events_filename: str) -> None:
2020-08-13 13:40:38 +00:00
"""Removes the given event Id from the timeline
"""
2022-06-10 11:43:33 +00:00
if not text_in_file(event_id + '\n', tl_events_filename):
2020-08-13 13:40:38 +00:00
return
events_timeline = ''
2022-06-09 14:46:30 +00:00
with open(tl_events_filename, 'r',
encoding='utf-8') as fp_tl:
2022-01-02 15:16:47 +00:00
events_timeline = fp_tl.read().replace(event_id + '\n', '')
if events_timeline:
try:
2022-06-09 14:46:30 +00:00
with open(tl_events_filename, 'w+',
encoding='utf-8') as fp2:
2022-01-02 15:16:47 +00:00
fp2.write(events_timeline)
2021-11-25 21:18:53 +00:00
except OSError:
2021-10-29 18:48:15 +00:00
print('EX: ERROR: unable to save events timeline')
elif os.path.isfile(tl_events_filename):
try:
os.remove(tl_events_filename)
except OSError:
print('EX: ERROR: unable to remove events timeline')
2020-08-13 13:40:38 +00:00
2021-12-29 21:55:09 +00:00
def save_event_post(base_dir: str, handle: str, post_id: str,
2022-01-02 15:16:47 +00:00
event_json: {}) -> bool:
2020-08-13 13:40:38 +00:00
"""Saves an event to the calendar and/or the events timeline
If an event has extra fields, as per Mobilizon,
Then it is saved as a separate entity and added to the
events timeline
2020-08-20 17:08:25 +00:00
See https://framagit.org/framasoft/mobilizon/-/blob/
master/lib/federation/activity_stream/converter/event.ex
2020-08-13 11:58:05 +00:00
"""
2022-12-18 15:29:54 +00:00
handle_dir = acct_handle_dir(base_dir, handle)
if not os.path.isdir(handle_dir):
print('WARN: Account does not exist at ' + handle_dir)
calendar_path = handle_dir + '/calendar'
2022-01-02 15:16:47 +00:00
if not os.path.isdir(calendar_path):
os.mkdir(calendar_path)
2020-08-13 11:58:05 +00:00
# get the year, month and day from the event
2023-11-20 22:27:58 +00:00
event_time = date_from_string_format(event_json['startTime'],
["%Y-%m-%dT%H:%M:%S%z"])
2022-01-02 15:16:47 +00:00
event_year = int(event_time.strftime("%Y"))
if event_year < 2020 or event_year >= 2100:
2020-08-13 13:40:38 +00:00
return False
2022-01-02 15:16:47 +00:00
event_month_number = int(event_time.strftime("%m"))
if event_month_number < 1 or event_month_number > 12:
2020-08-13 13:40:38 +00:00
return False
2022-01-02 15:16:47 +00:00
event_day_of_month = int(event_time.strftime("%d"))
if event_day_of_month < 1 or event_day_of_month > 31:
2020-08-13 13:40:38 +00:00
return False
2022-01-02 15:16:47 +00:00
if event_json.get('name') and event_json.get('actor') and \
event_json.get('uuid') and event_json.get('content'):
if not _valid_uuid(event_json['uuid'], 4):
2020-08-13 13:40:38 +00:00
return False
2020-08-20 16:51:48 +00:00
print('Mobilizon type event')
2020-08-13 13:40:38 +00:00
# if this is a full description of an event then save it
# as a separate json file
2022-12-18 15:29:54 +00:00
events_path = handle_dir + '/events'
2022-01-02 15:16:47 +00:00
if not os.path.isdir(events_path):
os.mkdir(events_path)
events_year_path = \
2022-12-18 15:29:54 +00:00
handle_dir + '/events/' + str(event_year)
2022-01-02 15:16:47 +00:00
if not os.path.isdir(events_year_path):
os.mkdir(events_year_path)
event_id = str(event_year) + '-' + event_time.strftime("%m") + '-' + \
event_time.strftime("%d") + '_' + event_json['uuid']
event_filename = events_year_path + '/' + event_id + '.json'
save_json(event_json, event_filename)
2020-08-13 13:40:38 +00:00
# save to the events timeline
2022-12-18 15:29:54 +00:00
tl_events_filename = handle_dir + '/events.txt'
2020-08-13 13:40:38 +00:00
2022-01-02 15:16:47 +00:00
if os.path.isfile(tl_events_filename):
_remove_event_from_timeline(event_id, tl_events_filename)
2020-08-13 13:40:38 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(tl_events_filename, 'r+',
2024-07-14 13:01:46 +00:00
encoding='utf-8') as fp_tl_events:
content = fp_tl_events.read()
2022-01-02 15:16:47 +00:00
if event_id + '\n' not in content:
2024-07-14 13:01:46 +00:00
fp_tl_events.seek(0, 0)
fp_tl_events.write(event_id + '\n' + content)
2021-12-25 15:28:52 +00:00
except OSError as ex:
2021-11-25 22:22:54 +00:00
print('EX: Failed to write entry to events file ' +
2022-01-02 15:16:47 +00:00
tl_events_filename + ' ' + str(ex))
2020-08-13 13:40:38 +00:00
return False
else:
2021-11-25 21:18:53 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(tl_events_filename, 'w+',
2024-07-14 13:01:46 +00:00
encoding='utf-8') as fp_tl_events:
fp_tl_events.write(event_id + '\n')
2021-11-25 21:18:53 +00:00
except OSError:
2024-07-02 22:16:13 +00:00
print('EX: save_event_post unable to write ' +
tl_events_filename)
2020-08-13 11:58:05 +00:00
# create a directory for the calendar year
2022-01-02 15:16:47 +00:00
if not os.path.isdir(calendar_path + '/' + str(event_year)):
os.mkdir(calendar_path + '/' + str(event_year))
2020-08-13 11:58:05 +00:00
# calendar month file containing event post Ids
2022-01-02 15:16:47 +00:00
calendar_filename = calendar_path + '/' + str(event_year) + \
'/' + str(event_month_number) + '.txt'
2020-08-13 11:58:05 +00:00
# Does this event post already exist within the calendar month?
2022-01-02 15:16:47 +00:00
if os.path.isfile(calendar_filename):
2022-06-10 13:01:39 +00:00
if text_in_file(post_id, calendar_filename):
2020-08-13 11:58:05 +00:00
# Event post already exists
return False
# append the post Id to the file for the calendar month
2021-11-25 21:18:53 +00:00
try:
2024-07-14 13:01:46 +00:00
with open(calendar_filename, 'a+', encoding='utf-8') as fp_calendar:
fp_calendar.write(post_id + '\n')
2021-11-25 21:18:53 +00:00
except OSError:
2022-12-09 18:50:24 +00:00
print('EX: unable to append to calendar ' + calendar_filename)
2020-08-13 11:58:05 +00:00
# create a file which will trigger a notification that
# a new event has been added
2022-12-18 15:29:54 +00:00
cal_notify_filename = handle_dir + '/.newCalendar'
2022-01-02 15:16:47 +00:00
notify_str = \
'/calendar?year=' + str(event_year) + '?month=' + \
str(event_month_number) + '?day=' + str(event_day_of_month)
2021-11-25 21:18:53 +00:00
try:
2024-07-14 13:01:46 +00:00
with open(cal_notify_filename, 'w+', encoding='utf-8') as fp_cal:
fp_cal.write(notify_str)
2021-11-25 21:18:53 +00:00
except OSError:
2024-07-02 22:16:13 +00:00
print('EX: save_event_post unable to write ' + cal_notify_filename)
2021-11-25 21:18:53 +00:00
return False
2020-08-13 11:58:05 +00:00
return True
2021-12-29 21:55:09 +00:00
def _is_happening_event(tag: {}) -> bool:
2020-02-24 11:10:48 +00:00
"""Is this tag an Event or Place ActivityStreams type?
"""
if not tag.get('type'):
return False
2020-04-03 11:53:31 +00:00
if tag['type'] != 'Event' and tag['type'] != 'Place':
2020-02-24 11:10:48 +00:00
return False
return True
2020-04-03 11:53:31 +00:00
2021-12-29 21:55:09 +00:00
def _is_happening_post(post_json_object: {}) -> bool:
2020-02-24 11:10:48 +00:00
"""Is this a post with tags?
"""
2021-12-25 22:09:19 +00:00
if not post_json_object:
2020-02-24 11:10:48 +00:00
return False
2021-12-26 10:57:03 +00:00
if not has_object_dict(post_json_object):
2020-02-24 11:10:48 +00:00
return False
2021-12-25 22:09:19 +00:00
if not post_json_object['object'].get('tag'):
2020-02-24 11:10:48 +00:00
return False
return True
2020-04-03 11:53:31 +00:00
2022-02-23 18:46:20 +00:00
def _event_text_match(content: str, text_match: str) -> bool:
"""Returns true of the content matches the search text
"""
if not text_match:
return True
if '+' not in text_match:
if text_match.strip().lower() in content.lower():
return True
else:
match_list = text_match.split('+')
for possible_match in match_list:
if possible_match.strip().lower() in content.lower():
return True
return False
2023-09-18 12:25:49 +00:00
def _sort_todays_events(post_events_list: []) -> []:
2023-09-18 12:04:30 +00:00
"""Returns a list of events sorted in chronological order
"""
2023-09-18 12:32:45 +00:00
post_events_dict = {}
2023-09-18 12:04:30 +00:00
# convert the list to a dict indexed on time
2023-09-18 12:25:49 +00:00
for post_event in post_events_list:
for tag in post_event:
# only check events (not places)
2024-07-15 10:38:49 +00:00
if tag['type'] != 'Event':
continue
event_time = \
date_from_string_format(tag['startTime'],
["%Y-%m-%dT%H:%M:%S%z"])
post_events_dict[event_time] = post_event
break
2023-09-18 12:04:30 +00:00
# sort the dict
2023-09-18 12:32:45 +00:00
new_post_events_list = []
sorted_events_dict = dict(sorted(post_events_dict.items()))
for _, post_event in sorted_events_dict.items():
new_post_events_list.append(post_event)
return new_post_events_list
2023-09-18 12:04:30 +00:00
2021-12-29 21:55:09 +00:00
def get_todays_events(base_dir: str, nickname: str, domain: str,
2022-02-21 11:08:48 +00:00
curr_year: int, curr_month_number: int,
2022-02-23 18:46:20 +00:00
curr_day_of_month: int,
2022-04-13 15:06:03 +00:00
text_match: str, system_language: str) -> {}:
"""Retrieves calendar events for today
Returns a dictionary of lists containing Event and Place activities
"""
2020-04-03 11:53:31 +00:00
now = datetime.now()
2022-02-21 11:08:48 +00:00
if not curr_year:
2020-04-03 11:53:31 +00:00
year = now.year
else:
2022-02-21 11:08:48 +00:00
year = curr_year
if not curr_month_number:
2022-01-02 15:16:47 +00:00
month_number = now.month
else:
2022-02-21 11:08:48 +00:00
month_number = curr_month_number
2022-02-21 16:58:37 +00:00
if not curr_day_of_month:
2022-01-02 15:16:47 +00:00
day_number = now.day
else:
2022-02-21 16:58:37 +00:00
day_number = curr_day_of_month
2020-02-23 09:45:04 +00:00
2022-01-02 15:16:47 +00:00
calendar_filename = \
2021-12-26 12:02:29 +00:00
acct_dir(base_dir, nickname, domain) + \
2022-01-02 15:16:47 +00:00
'/calendar/' + str(year) + '/' + str(month_number) + '.txt'
2020-04-03 11:53:31 +00:00
events = {}
2022-01-02 15:16:47 +00:00
if not os.path.isfile(calendar_filename):
return events
2020-02-23 09:45:04 +00:00
2022-01-02 15:16:47 +00:00
calendar_post_ids = []
recreate_events_file = False
try:
2024-07-14 13:01:46 +00:00
with open(calendar_filename, 'r', encoding='utf-8') as fp_events:
for post_id in fp_events:
post_id = remove_eol(post_id)
post_filename = \
locate_post(base_dir, nickname, domain, post_id)
if not post_filename:
recreate_events_file = True
continue
post_json_object = load_json(post_filename)
if not _is_happening_post(post_json_object):
continue
content_language = system_language
if post_json_object.get('object'):
content = None
if post_json_object['object'].get('contentMap'):
sys_lang = system_language
content_map = post_json_object['object']['contentMap']
if content_map.get(sys_lang):
content = content_map[sys_lang]
content_language = sys_lang
if not content:
if post_json_object['object'].get('content'):
content = post_json_object['object']['content']
if content:
if not _event_text_match(content, text_match):
continue
public_event = is_public_post(post_json_object)
post_event = []
day_of_month = None
for tag in post_json_object['object']['tag']:
if not _is_happening_event(tag):
continue
2024-07-15 10:48:31 +00:00
# this tag is an event or a place
2024-07-15 10:48:31 +00:00
if tag['type'] != 'Event':
# tag is a place
2022-01-02 15:16:47 +00:00
post_event.append(tag)
2024-07-15 10:48:31 +00:00
continue
# tag is an event
if not tag.get('startTime'):
continue
# is the tag for this day?
event_time = \
date_from_string_format(tag['startTime'],
["%Y-%m-%dT%H:%M:%S%z"])
event_year = int(event_time.strftime("%Y"))
event_month = int(event_time.strftime("%m"))
event_day = int(event_time.strftime("%d"))
if not (event_year == year and
event_month == month_number and
event_day == day_number):
continue
day_of_month = str(event_day)
if '#statuses#' in post_id:
# link to the id so that the event can be
# easily deleted
tag['post_id'] = post_id.split('#statuses#')[1]
tag['id'] = post_id.replace('#', '/')
tag['sender'] = post_id.split('#statuses#')[0]
tag['sender'] = tag['sender'].replace('#', '/')
tag['public'] = public_event
tag['language'] = content_language
post_event.append(tag)
2024-07-15 10:50:22 +00:00
if not (post_event and day_of_month):
continue
calendar_post_ids.append(post_id)
if not events.get(day_of_month):
events[day_of_month] = []
events[day_of_month].append(post_event)
events[day_of_month] = \
_sort_todays_events(events[day_of_month])
2024-07-15 10:39:55 +00:00
except OSError as exc:
print('EX: get_todays_events failed to read ' +
calendar_filename + ' ' + str(exc))
# if some posts have been deleted then regenerate the calendar file
2022-01-02 15:16:47 +00:00
if recreate_events_file:
2021-11-25 21:18:53 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(calendar_filename, 'w+',
2024-07-14 13:01:46 +00:00
encoding='utf-8') as fp_calendar:
2022-01-02 15:16:47 +00:00
for post_id in calendar_post_ids:
2024-07-14 13:01:46 +00:00
fp_calendar.write(post_id + '\n')
2021-11-25 21:18:53 +00:00
except OSError:
2022-12-09 14:13:40 +00:00
print('EX: unable to recreate events file 1 ' +
calendar_filename)
return events
2020-04-03 11:53:31 +00:00
2022-02-21 18:20:59 +00:00
def _ical_date_string(date_str: str) -> str:
"""Returns an icalendar formatted date
"""
2024-08-08 17:23:33 +00:00
replacements = {
'-': '',
':': '',
' ': ''
}
date_str = replace_strings(date_str, replacements)
return date_str
2022-02-21 18:20:59 +00:00
2022-02-23 09:51:00 +00:00
def _dav_encode_token(year: int, month_number: int,
message_id: str) -> str:
"""Returns a token corresponding to a calendar event
"""
return str(year) + '_' + str(month_number) + '_' + \
message_id.replace('/', '--').replace('#', '--')
2022-02-21 11:08:48 +00:00
def _icalendar_day(base_dir: str, nickname: str, domain: str,
2022-06-14 11:52:43 +00:00
day_events: [], person_cache: {}) -> str:
2022-02-21 11:08:48 +00:00
"""Returns a day's events in icalendar format
"""
ical_str = ''
2022-02-21 14:49:19 +00:00
print('icalendar: ' + str(day_events))
2022-02-21 11:08:48 +00:00
for event_post in day_events:
event_description = None
event_place = None
post_id = None
sender_name = ''
sender_actor = None
event_is_public = False
event_start = None
event_end = None
for evnt in event_post:
if evnt['type'] == 'Event':
2022-02-21 19:13:19 +00:00
if evnt.get('id'):
post_id = evnt['id']
2022-02-21 11:08:48 +00:00
if evnt.get('startTime'):
event_start = \
2023-11-20 22:27:58 +00:00
date_from_string_format(evnt['startTime'],
["%Y-%m-%dT%H:%M:%S%z"])
2022-02-21 19:01:19 +00:00
if evnt.get('endTime'):
event_end = \
2023-11-20 22:27:58 +00:00
date_from_string_format(evnt['startTime'],
["%Y-%m-%dT%H:%M:%S%z"])
2022-02-21 11:08:48 +00:00
if 'public' in evnt:
if evnt['public'] is True:
event_is_public = True
if evnt.get('sender'):
# get display name from sending actor
if evnt.get('sender'):
sender_actor = evnt['sender']
disp_name = \
get_display_name(base_dir, sender_actor,
person_cache)
if disp_name:
sender_name = \
'<a href="' + sender_actor + '">' + \
2022-02-21 19:09:43 +00:00
disp_name + '</a>'
2022-02-21 11:08:48 +00:00
if evnt.get('name'):
event_description = evnt['name'].strip()
elif evnt['type'] == 'Place':
if evnt.get('name'):
2023-07-12 11:46:25 +00:00
event_place = remove_html(evnt['name'])
2022-02-21 11:08:48 +00:00
2022-02-21 14:46:46 +00:00
print('icalendar: ' + str(post_id) + ' ' +
str(event_start) + ' ' + str(event_description) + ' ' +
str(sender_actor))
2022-02-21 19:01:19 +00:00
if not post_id or not event_start or not event_end or \
2022-02-21 11:08:48 +00:00
not event_description or not sender_actor:
continue
# find the corresponding post
post_filename = locate_post(base_dir, nickname, domain, post_id)
if not post_filename:
continue
post_json_object = load_json(post_filename)
if not post_json_object:
continue
# get the published date from the post
if not post_json_object.get('object'):
continue
if not isinstance(post_json_object['object'], dict):
continue
if not post_json_object['object'].get('published'):
continue
if not isinstance(post_json_object['object']['published'], str):
continue
2022-02-21 18:20:59 +00:00
published = \
_ical_date_string(post_json_object['object']['published'])
event_start = \
_ical_date_string(event_start.strftime("%Y-%m-%dT%H:%M:%SZ"))
2022-02-21 19:01:19 +00:00
event_end = \
_ical_date_string(event_end.strftime("%Y-%m-%dT%H:%M:%SZ"))
2022-02-21 11:08:48 +00:00
2022-02-23 09:58:54 +00:00
token_year = int(event_start[:4])
2022-02-23 10:00:12 +00:00
token_month_number = int(event_start[4:][:2])
2022-02-23 09:51:00 +00:00
uid = _dav_encode_token(token_year, token_month_number, post_id)
2022-02-21 11:08:48 +00:00
ical_str += \
'BEGIN:VEVENT\n' + \
'DTSTAMP:' + published + '\n' + \
2022-02-23 09:51:00 +00:00
'UID:' + uid + '\n' + \
2022-02-21 11:08:48 +00:00
'DTSTART:' + event_start + '\n' + \
'DTEND:' + event_end + '\n' + \
'STATUS:CONFIRMED\n'
descr = remove_html(event_description)
if len(descr) < 255:
ical_str += \
'SUMMARY:' + descr + '\n'
else:
ical_str += \
'SUMMARY:' + descr[255:] + '\n'
ical_str += \
'DESCRIPTION:' + descr + '\n'
if event_is_public:
ical_str += \
'CATEGORIES:APPOINTMENT,PUBLIC\n'
else:
ical_str += \
'CATEGORIES:APPOINTMENT\n'
if sender_name:
ical_str += \
'ORGANIZER;CN=' + remove_html(sender_name) + ':' + \
sender_actor + '\n'
else:
ical_str += \
'ORGANIZER:' + sender_actor + '\n'
if event_place:
ical_str += \
'LOCATION:' + remove_html(event_place) + '\n'
ical_str += 'END:VEVENT\n'
return ical_str
def get_todays_events_icalendar(base_dir: str, nickname: str, domain: str,
year: int, month_number: int,
2022-02-21 19:01:19 +00:00
day_number: int, person_cache: {},
2022-04-13 15:06:03 +00:00
text_match: str, system_language: str) -> str:
2022-02-21 11:08:48 +00:00
"""Returns today's events in icalendar format
"""
2022-02-21 17:27:37 +00:00
day_events = None
2022-02-21 11:08:48 +00:00
events = \
get_todays_events(base_dir, nickname, domain,
2022-02-23 18:46:20 +00:00
year, month_number, day_number,
2022-04-13 15:06:03 +00:00
text_match, system_language)
2022-02-21 17:27:37 +00:00
if events:
if events.get(str(day_number)):
day_events = events[str(day_number)]
2022-02-21 11:08:48 +00:00
ical_str = \
'BEGIN:VCALENDAR\n' + \
2022-02-21 11:45:29 +00:00
'PRODID:-//Fediverse//NONSGML Epicyon//EN\n' + \
'VERSION:2.0\n'
2022-02-21 17:27:37 +00:00
if not day_events:
2022-02-21 17:38:07 +00:00
print('icalendar daily: ' + nickname + '@' + domain + ' ' +
str(year) + '-' + str(month_number) +
2022-02-21 17:27:37 +00:00
'-' + str(day_number) + ' ' + str(day_events))
2022-02-21 11:08:48 +00:00
ical_str += 'END:VCALENDAR\n'
return ical_str
ical_str += \
2022-06-14 11:52:43 +00:00
_icalendar_day(base_dir, nickname, domain, day_events, person_cache)
2022-02-21 11:08:48 +00:00
ical_str += 'END:VCALENDAR\n'
return ical_str
def get_month_events_icalendar(base_dir: str, nickname: str, domain: str,
2022-02-21 14:46:46 +00:00
year: int,
month_number: int,
2022-02-21 19:01:19 +00:00
person_cache: {},
2022-02-23 18:46:20 +00:00
text_match: str) -> str:
2022-02-21 11:08:48 +00:00
"""Returns today's events in icalendar format
"""
only_show_reminders = False
2022-02-21 18:20:59 +00:00
month_events = \
2022-02-21 14:46:46 +00:00
get_calendar_events(base_dir, nickname, domain, year,
month_number, text_match,
only_show_reminders)
2022-02-21 17:34:14 +00:00
2022-02-21 11:08:48 +00:00
ical_str = \
'BEGIN:VCALENDAR\n' + \
2022-02-21 11:51:43 +00:00
'PRODID:-//Fediverse//NONSGML Epicyon//EN\n' + \
'VERSION:2.0\n'
2022-02-21 17:34:14 +00:00
if not month_events:
2022-02-21 11:08:48 +00:00
ical_str += 'END:VCALENDAR\n'
return ical_str
2022-02-21 17:34:14 +00:00
print('icalendar month: ' + str(month_events))
2022-02-21 14:46:46 +00:00
for day_of_month in range(1, 32):
2022-02-21 17:34:14 +00:00
if not month_events.get(str(day_of_month)):
2022-02-21 11:08:48 +00:00
continue
2022-02-21 17:34:14 +00:00
day_events = month_events[str(day_of_month)]
2022-02-21 11:08:48 +00:00
ical_str += \
2022-02-21 14:46:46 +00:00
_icalendar_day(base_dir, nickname, domain,
2022-06-14 11:52:43 +00:00
day_events, person_cache)
2022-02-21 11:08:48 +00:00
ical_str += 'END:VCALENDAR\n'
return ical_str
2021-12-29 21:55:09 +00:00
def day_events_check(base_dir: str, nickname: str, domain: str,
2022-02-21 11:08:48 +00:00
curr_date) -> bool:
2021-05-31 11:57:36 +00:00
"""Are there calendar events for the given date?
"""
2022-02-21 11:08:48 +00:00
year = curr_date.year
month_number = curr_date.month
day_number = curr_date.day
2020-04-03 11:53:31 +00:00
2022-01-02 15:16:47 +00:00
calendar_filename = \
2021-12-26 12:02:29 +00:00
acct_dir(base_dir, nickname, domain) + \
2022-01-02 15:16:47 +00:00
'/calendar/' + str(year) + '/' + str(month_number) + '.txt'
if not os.path.isfile(calendar_filename):
return False
2020-02-23 09:45:04 +00:00
2022-01-02 15:16:47 +00:00
events_exist = False
try:
2024-07-14 13:01:46 +00:00
with open(calendar_filename, 'r', encoding='utf-8') as fp_events:
for post_id in fp_events:
post_id = remove_eol(post_id)
post_filename = \
locate_post(base_dir, nickname, domain, post_id)
if not post_filename:
2021-05-31 10:24:48 +00:00
continue
post_json_object = load_json(post_filename)
if not _is_happening_post(post_json_object):
2021-05-31 10:24:48 +00:00
continue
for tag in post_json_object['object']['tag']:
if not _is_happening_event(tag):
continue
# this tag is an event or a place
if tag['type'] != 'Event':
continue
# tag is an event
if not tag.get('startTime'):
continue
event_time = \
date_from_string_format(tag['startTime'],
["%Y-%m-%dT%H:%M:%S%z"])
if int(event_time.strftime("%d")) != day_number:
continue
if int(event_time.strftime("%m")) != month_number:
continue
if int(event_time.strftime("%Y")) != year:
continue
events_exist = True
break
except OSError:
print('EX: day_events_check failed to read ' + calendar_filename)
2022-01-02 15:16:47 +00:00
return events_exist
2020-04-03 11:53:31 +00:00
2021-12-29 21:55:09 +00:00
def get_this_weeks_events(base_dir: str, nickname: str, domain: str) -> {}:
"""Retrieves calendar events for this week
2020-02-24 10:55:49 +00:00
Returns a dictionary indexed by day number of lists containing
Event and Place activities
2020-02-23 09:42:09 +00:00
Note: currently not used but could be with a weekly calendar screen
"""
2020-04-03 11:53:31 +00:00
now = datetime.now()
2022-01-02 15:16:47 +00:00
end_of_week = now + timedelta(7)
2020-04-03 11:53:31 +00:00
year = now.year
2022-01-02 15:16:47 +00:00
month_number = now.month
2020-02-23 09:45:04 +00:00
2022-01-02 15:16:47 +00:00
calendar_filename = \
2021-12-26 12:02:29 +00:00
acct_dir(base_dir, nickname, domain) + \
2022-01-02 15:16:47 +00:00
'/calendar/' + str(year) + '/' + str(month_number) + '.txt'
2020-02-23 09:45:04 +00:00
2020-04-03 11:53:31 +00:00
events = {}
2022-01-02 15:16:47 +00:00
if not os.path.isfile(calendar_filename):
return events
2020-02-23 09:45:04 +00:00
2022-01-02 15:16:47 +00:00
calendar_post_ids = []
recreate_events_file = False
try:
2024-07-14 13:01:46 +00:00
with open(calendar_filename, 'r', encoding='utf-8') as fp_events:
for post_id in fp_events:
post_id = remove_eol(post_id)
post_filename = \
locate_post(base_dir, nickname, domain, post_id)
if not post_filename:
recreate_events_file = True
continue
post_json_object = load_json(post_filename)
if not _is_happening_post(post_json_object):
continue
post_event = []
week_day_index = None
for tag in post_json_object['object']['tag']:
if not _is_happening_event(tag):
continue
2024-07-15 10:53:05 +00:00
# this tag is an event or a place
2024-07-15 10:53:05 +00:00
if tag['type'] != 'Event':
# tag is a place
2022-01-02 15:16:47 +00:00
post_event.append(tag)
2024-07-15 10:53:05 +00:00
continue
# tag is an event
if not tag.get('startTime'):
continue
event_time = \
date_from_string_format(tag['startTime'],
["%Y-%m-%dT%H:%M:%S%z"])
if now <= event_time <= end_of_week:
week_day_index = (event_time - now).days()
post_event.append(tag)
2024-07-15 10:54:10 +00:00
if not (post_event and week_day_index):
continue
calendar_post_ids.append(post_id)
if not events.get(week_day_index):
events[week_day_index] = []
events[week_day_index].append(post_event)
except OSError:
print('EX: get_this_weeks_events failed to read ' + calendar_filename)
# if some posts have been deleted then regenerate the calendar file
2022-01-02 15:16:47 +00:00
if recreate_events_file:
2021-11-25 21:18:53 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(calendar_filename, 'w+',
2024-07-14 13:01:46 +00:00
encoding='utf-8') as fp_calendar:
2022-01-02 15:16:47 +00:00
for post_id in calendar_post_ids:
2024-07-14 13:01:46 +00:00
fp_calendar.write(post_id + '\n')
2021-11-25 21:18:53 +00:00
except OSError:
2022-12-09 14:13:40 +00:00
print('EX: unable to recreate events file 2 ' +
calendar_filename)
return events
2020-04-03 11:53:31 +00:00
2021-12-29 21:55:09 +00:00
def get_calendar_events(base_dir: str, nickname: str, domain: str,
2022-02-23 18:46:20 +00:00
year: int, month_number: int,
text_match: str,
only_show_reminders: bool) -> {}:
"""Retrieves calendar events
2020-02-24 10:55:49 +00:00
Returns a dictionary indexed by day number of lists containing
Event and Place activities
"""
2022-01-02 15:16:47 +00:00
calendar_filename = \
2021-12-26 12:02:29 +00:00
acct_dir(base_dir, nickname, domain) + \
2022-01-02 15:16:47 +00:00
'/calendar/' + str(year) + '/' + str(month_number) + '.txt'
2020-02-23 09:45:04 +00:00
2020-04-03 11:53:31 +00:00
events = {}
2022-01-02 15:16:47 +00:00
if not os.path.isfile(calendar_filename):
return events
2020-02-23 09:45:04 +00:00
2022-01-02 15:16:47 +00:00
calendar_post_ids = []
recreate_events_file = False
try:
2024-07-14 13:01:46 +00:00
with open(calendar_filename, 'r', encoding='utf-8') as fp_events:
for post_id in fp_events:
post_id = remove_eol(post_id)
post_filename = \
locate_post(base_dir, nickname, domain, post_id)
if not post_filename:
recreate_events_file = True
continue
2020-02-23 09:42:09 +00:00
post_json_object = load_json(post_filename)
if not post_json_object:
continue
if not _is_happening_post(post_json_object):
continue
if only_show_reminders:
if not is_reminder(post_json_object):
2022-02-23 18:46:20 +00:00
continue
if post_json_object.get('object'):
if post_json_object['object'].get('content'):
content = post_json_object['object']['content']
if not _event_text_match(content, text_match):
continue
post_event = []
day_of_month = None
for tag in post_json_object['object']['tag']:
if not _is_happening_event(tag):
2020-02-23 09:42:09 +00:00
continue
2024-07-15 10:58:16 +00:00
# this tag is an event or a place
2024-07-15 10:58:16 +00:00
if tag['type'] != 'Event':
# tag is a place
2022-01-02 15:16:47 +00:00
post_event.append(tag)
2024-07-15 10:58:16 +00:00
continue
# tag is an event
if not tag.get('startTime'):
continue
# is the tag for this month?
event_time = \
date_from_string_format(tag['startTime'],
["%Y-%m-%dT%H:%M:%S%z"])
event_year = int(event_time.strftime("%Y"))
event_month = int(event_time.strftime("%m"))
if not (event_year == year and
event_month == month_number):
continue
2020-02-23 09:42:09 +00:00
2024-07-15 10:58:16 +00:00
event_day = int(event_time.strftime("%d"))
day_of_month = str(event_day)
if '#statuses#' in post_id:
tag['post_id'] = post_id.split('#statuses#')[1]
tag['id'] = post_id.replace('#', '/')
tag['sender'] = post_id.split('#statuses#')[0]
tag['sender'] = tag['sender'].replace('#', '/')
post_event.append(tag)
if not (post_event and day_of_month):
continue
calendar_post_ids.append(post_id)
if not events.get(day_of_month):
events[day_of_month] = []
events[day_of_month].append(post_event)
except OSError:
print('EX: get_calendar_events failed to read ' + calendar_filename)
# if some posts have been deleted then regenerate the calendar file
2022-01-02 15:16:47 +00:00
if recreate_events_file:
2021-11-25 21:18:53 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(calendar_filename, 'w+',
2024-07-14 13:01:46 +00:00
encoding='utf-8') as fp_calendar:
2022-01-02 15:16:47 +00:00
for post_id in calendar_post_ids:
2024-07-14 13:01:46 +00:00
fp_calendar.write(post_id + '\n')
2021-11-25 21:18:53 +00:00
except OSError:
2022-12-09 14:13:40 +00:00
print('EX: unable to recreate events file 3 ' +
calendar_filename)
2020-03-22 21:16:02 +00:00
return events
2020-02-23 13:28:27 +00:00
2020-04-03 11:53:31 +00:00
2021-12-29 21:55:09 +00:00
def remove_calendar_event(base_dir: str, nickname: str, domain: str,
2022-01-02 15:16:47 +00:00
year: int, month_number: int,
message_id: str) -> None:
2020-02-23 13:28:27 +00:00
"""Removes a calendar event
"""
2022-01-02 15:16:47 +00:00
calendar_filename = \
2021-12-26 12:02:29 +00:00
acct_dir(base_dir, nickname, domain) + \
2022-01-02 15:16:47 +00:00
'/calendar/' + str(year) + '/' + str(month_number) + '.txt'
if not os.path.isfile(calendar_filename):
2020-02-23 13:28:27 +00:00
return
2022-01-02 15:16:47 +00:00
if '/' in message_id:
message_id = message_id.replace('/', '#')
2022-06-10 11:43:33 +00:00
if not text_in_file(message_id, calendar_filename):
2022-12-17 17:49:54 +00:00
message_id = message_id.replace('#', '/')
if not text_in_file(message_id, calendar_filename):
return
lines_str = ''
try:
with open(calendar_filename, 'r', encoding='utf-8') as fp_cal:
lines_str = fp_cal.read()
except OSError:
print('EX: remove_calendar_event unable to read calendar file ' +
2022-12-17 17:49:54 +00:00
calendar_filename)
if not lines_str:
2020-02-23 13:28:27 +00:00
return
2022-12-17 17:49:54 +00:00
lines = lines_str.split('\n')
print('Removing calendar event: ' + message_id)
2021-11-25 21:18:53 +00:00
try:
2022-06-09 14:46:30 +00:00
with open(calendar_filename, 'w+', encoding='utf-8') as fp_cal:
2021-11-25 21:18:53 +00:00
for line in lines:
2022-12-17 17:49:54 +00:00
if message_id in line:
continue
fp_cal.write(line + '\n')
2021-11-25 21:18:53 +00:00
except OSError:
2022-12-09 14:13:40 +00:00
print('EX: unable to remove calendar event ' +
calendar_filename)
2022-02-23 09:51:00 +00:00
def _dav_decode_token(token: str) -> (int, int, str):
"""Decodes a token corresponding to a calendar event
"""
if '_' not in token or '--' not in token:
return None, None, None
token_sections = token.split('_')
if len(token_sections) != 3:
return None, None, None
if not token_sections[0].isdigit():
return None, None, None
if not token_sections[1].isdigit():
return None, None, None
token_year = int(token_sections[0])
token_month_number = int(token_sections[1])
token_post_id = token_sections[2].replace('--', '/')
return token_year, token_month_number, token_post_id
2022-06-12 20:03:03 +00:00
def dav_propfind_response(nickname: str, xml_str: str) -> str:
2022-02-23 09:51:00 +00:00
"""Returns the response to caldav PROPFIND
"""
if '<d:propfind' not in xml_str or \
'</d:propfind>' not in xml_str:
return None
response_str = \
'<d:multistatus xmlns:d="DAV:" ' + \
'xmlns:cs="http://calendarserver.org/ns/">\n' + \
' <d:response>\n' + \
' <d:href>/calendars/' + nickname + '/</d:href>\n' + \
' <d:propstat>\n' + \
' <d:prop>\n' + \
' <d:displayname />\n' + \
' <cs:getctag />\n' + \
' </d:prop>\n' + \
' <d:status>HTTP/1.1 200 OK</d:status>\n' + \
' </d:propstat>\n' + \
' </d:response>\n' + \
'</d:multistatus>'
return response_str
def _dav_store_event(base_dir: str, nickname: str, domain: str,
event_list: [], http_prefix: str,
system_language: str) -> bool:
"""Stores a calendar event obtained via caldav PUT
"""
event_str = str(event_list)
if 'DTSTAMP:' not in event_str or \
'DTSTART:' not in event_str or \
'DTEND:' not in event_str:
return False
if 'STATUS:' not in event_str and 'DESCRIPTION:' not in event_str:
return False
timestamp = None
start_time = None
end_time = None
description = None
for line in event_list:
if line.startswith('DTSTAMP:'):
timestamp = line.split(':', 1)[1]
elif line.startswith('DTSTART:'):
start_time = line.split(':', 1)[1]
elif line.startswith('DTEND:'):
end_time = line.split(':', 1)[1]
elif line.startswith('SUMMARY:') or line.startswith('DESCRIPTION:'):
description = line.split(':', 1)[1]
elif line.startswith('LOCATION:'):
location = line.split(':', 1)[1]
if not timestamp or \
not start_time or \
not end_time or \
not description:
return False
if len(timestamp) < 15:
return False
if len(start_time) < 15:
return False
if len(end_time) < 15:
return False
# check that the description is valid
2022-09-25 17:26:11 +00:00
if is_filtered(base_dir, nickname, domain, description,
system_language):
2022-02-23 09:51:00 +00:00
return False
# convert to the expected time format
2022-02-23 18:04:34 +00:00
published = _dav_date_from_string(timestamp)
if not published:
2022-02-23 09:51:00 +00:00
return False
2022-02-23 18:04:34 +00:00
start_time = _dav_date_from_string(start_time)
if not start_time:
2022-02-23 09:51:00 +00:00
return False
2022-02-23 18:04:34 +00:00
end_time = _dav_date_from_string(end_time)
if not end_time:
2022-02-23 09:51:00 +00:00
return False
post_id = ''
post_context = get_individual_post_context()
# create the status number from DTSTAMP
status_number, published = get_status_number(published)
# get the post id
actor = http_prefix + "://" + domain + "/users/" + nickname
actor2 = http_prefix + "://" + domain + "/@" + nickname
post_id = actor + "/statuses/" + status_number
next_str = post_id + "/replies?only_other_accounts=true&page=true"
content = \
'<p><span class=\"h-card\"><a href=\"' + actor2 + \
2022-05-25 13:20:49 +00:00
'\" class=\"u-url mention\" tabindex="10">@<span>' + nickname + \
2022-02-23 09:51:00 +00:00
'</span></a></span>' + remove_html(description) + '</p>'
event_json = {
"@context": post_context,
"id": post_id + "/activity",
"type": "Create",
"actor": actor,
"published": published,
"to": [actor],
"cc": [],
"object": {
"id": post_id,
"conversation": post_id,
"context": post_id,
2022-02-23 09:51:00 +00:00
"type": "Note",
"summary": None,
"inReplyTo": None,
"published": published,
"url": actor + "/" + status_number,
"attributedTo": actor,
"to": [actor],
"cc": [],
"sensitive": False,
"atomUri": post_id,
"inReplyToAtomUri": None,
"commentsEnabled": False,
"rejectReplies": True,
"mediaType": "text/html",
"content": content,
"contentMap": {
system_language: content
},
"attachment": [],
"tag": [
{
"href": actor2,
"name": "@" + nickname + "@" + domain,
"type": "Mention"
},
{
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Event",
"name": content,
"startTime": start_time,
"endTime": end_time
}
],
"replies": {
"id": post_id + "/replies",
"type": "Collection",
"first": {
"type": "CollectionPage",
"next": next_str,
"partOf": post_id + "/replies",
"items": []
}
}
}
}
if location:
event_json['object']['tag'].append({
"@context": "https://www.w3.org/ns/activitystreams",
"type": "Place",
"name": location
})
event_json['object']['location'] = {
'type': 'Place',
'name': location
}
2022-02-23 09:51:00 +00:00
handle = nickname + '@' + domain
2022-12-18 15:29:54 +00:00
handle_dir = acct_handle_dir(base_dir, handle)
outbox_dir = handle_dir + '/outbox'
2022-02-23 09:51:00 +00:00
if not os.path.isdir(outbox_dir):
return False
filename = outbox_dir + '/' + post_id.replace('/', '#') + '.json'
save_json(event_json, filename)
save_event_post(base_dir, handle, post_id, event_json)
return True
2022-02-23 18:04:34 +00:00
def _dav_update_recent_etags(etag: str, nickname: str,
recent_dav_etags: {}) -> None:
"""Updates the recent etags for each account
"""
# update the recent caldav etags for each account
if not recent_dav_etags.get(nickname):
recent_dav_etags[nickname] = [etag]
else:
# only keep a limited number of recent etags
while len(recent_dav_etags[nickname]) > 32:
recent_dav_etags[nickname].pop(0)
# append the etag to the recent list
if etag not in recent_dav_etags[nickname]:
recent_dav_etags[nickname].append(etag)
2022-02-23 09:51:00 +00:00
def dav_put_response(base_dir: str, nickname: str, domain: str,
2022-06-12 20:03:03 +00:00
xml_str: str, http_prefix: str,
2022-02-23 18:04:34 +00:00
system_language: str,
recent_dav_etags: {}) -> str:
2022-02-23 09:51:00 +00:00
"""Returns the response to caldav PUT
"""
if '\n' not in xml_str:
return None
if 'BEGIN:VCALENDAR' not in xml_str or \
'END:VCALENDAR' not in xml_str:
return None
if 'BEGIN:VEVENT' not in xml_str or \
'END:VEVENT' not in xml_str:
return None
2022-02-24 16:55:37 +00:00
etag = md5(xml_str.encode('utf-8')).hexdigest()
2022-02-23 18:04:34 +00:00
if recent_dav_etags.get(nickname):
if etag in recent_dav_etags[nickname]:
return 'Not modified'
2022-02-23 09:51:00 +00:00
stored_count = 0
reading_event = False
lines_list = xml_str.split('\n')
event_list = []
for line in lines_list:
line = line.strip()
if not reading_event:
if line == 'BEGIN:VEVENT':
reading_event = True
event_list = []
else:
if line == 'END:VEVENT':
if event_list:
_dav_store_event(base_dir, nickname, domain,
event_list, http_prefix,
system_language)
stored_count += 1
reading_event = False
else:
event_list.append(line)
if stored_count == 0:
return None
2022-02-23 18:04:34 +00:00
_dav_update_recent_etags(etag, nickname, recent_dav_etags)
return 'ETag:' + etag
2022-02-23 09:51:00 +00:00
def dav_report_response(base_dir: str, nickname: str, domain: str,
2022-06-12 20:03:03 +00:00
xml_str: str,
2022-02-23 12:19:27 +00:00
person_cache: {}, http_prefix: str,
2022-02-23 18:04:34 +00:00
curr_etag: str, recent_dav_etags: {},
2022-04-13 15:06:03 +00:00
domain_full: str, system_language: str) -> str:
2022-02-23 09:51:00 +00:00
"""Returns the response to caldav REPORT
"""
if '<c:calendar-query' not in xml_str or \
'</c:calendar-query>' not in xml_str:
if '<c:calendar-multiget' not in xml_str or \
'</c:calendar-multiget>' not in xml_str:
return None
2022-02-23 18:04:34 +00:00
if curr_etag:
if recent_dav_etags.get(nickname):
if curr_etag in recent_dav_etags[nickname]:
return "Not modified"
xml_str_lower = xml_str.lower()
query_start_time = None
query_end_time = None
if ':time-range' in xml_str_lower:
time_range_str = xml_str_lower.split(':time-range')[1]
if 'start=' in time_range_str and 'end=' in time_range_str:
start_time_str = time_range_str.split('start=')[1]
if start_time_str.startswith("'"):
query_start_time_str = start_time_str.split("'")[1]
query_start_time = _dav_date_from_string(query_start_time_str)
elif start_time_str.startswith('"'):
query_start_time_str = start_time_str.split('"')[1]
query_start_time = _dav_date_from_string(query_start_time_str)
end_time_str = time_range_str.split('end=')[1]
if end_time_str.startswith("'"):
query_end_time_str = end_time_str.split("'")[1]
query_end_time = _dav_date_from_string(query_end_time_str)
elif end_time_str.startswith('"'):
query_end_time_str = end_time_str.split('"')[1]
query_end_time = _dav_date_from_string(query_end_time_str)
2022-02-23 18:46:20 +00:00
text_match = ''
if ':text-match' in xml_str_lower:
match_str = xml_str_lower.split(':text-match')[1]
if '>' in match_str and '<' in match_str:
text_match = match_str.split('>')[1]
if '<' in text_match:
text_match = text_match.split('<')[0]
else:
text_match = ''
2022-02-23 18:04:34 +00:00
ical_events = None
etag = None
events_href = ''
responses = ''
search_date = datetime.now()
if query_start_time and query_end_time:
query_start_year = int(query_start_time.split('-')[0])
query_start_month = int(query_start_time.split('-')[1])
query_start_day = query_start_time.split('-')[2]
query_start_day = int(query_start_day.split('T')[0])
query_end_year = int(query_end_time.split('-')[0])
query_end_month = int(query_end_time.split('-')[1])
query_end_day = query_end_time.split('-')[2]
query_end_day = int(query_end_day.split('T')[0])
if query_start_year == query_end_year and \
query_start_month == query_end_month:
if query_start_day == query_end_day:
# calendar for one day
search_date = \
date_from_numbers(query_start_year, query_start_month,
query_start_day, 0, 0)
2022-02-23 18:04:34 +00:00
ical_events = \
get_todays_events_icalendar(base_dir, nickname, domain,
search_date.year,
search_date.month,
search_date.day, person_cache,
2022-06-14 11:52:43 +00:00
text_match,
2022-04-13 15:06:03 +00:00
system_language)
2022-02-23 18:04:34 +00:00
events_href = \
http_prefix + '://' + domain_full + '/users/' + \
nickname + '/calendar?year=' + \
str(search_date.year) + '?month=' + \
str(search_date.month) + '?day=' + str(search_date.day)
if ical_events:
2022-02-23 18:46:20 +00:00
if 'VEVENT' in ical_events:
2022-02-24 16:55:37 +00:00
ical_events_encoded = ical_events.encode('utf-8')
etag = md5(ical_events_encoded).hexdigest()
2022-02-23 18:46:20 +00:00
responses = \
' <d:response>\n' + \
' <d:href>' + events_href + \
'</d:href>\n' + \
' <d:propstat>\n' + \
' <d:prop>\n' + \
' <d:getetag>"' + \
etag + '"</d:getetag>\n' + \
' <c:calendar-data>' + \
ical_events + \
' </c:calendar-data>\n' + \
' </d:prop>\n' + \
' <d:status>HTTP/1.1 200 OK' + \
'</d:status>\n' + \
' </d:propstat>\n' + \
' </d:response>\n'
2022-02-23 18:04:34 +00:00
elif query_start_day == 1 and query_start_day >= 28:
# calendar for a month
ical_events = \
get_month_events_icalendar(base_dir, nickname, domain,
query_start_year,
query_start_month,
person_cache,
2022-02-23 18:46:20 +00:00
text_match)
2022-02-23 18:04:34 +00:00
events_href = \
http_prefix + '://' + domain_full + '/users/' + \
nickname + '/calendar?year=' + \
str(query_start_year) + '?month=' + \
str(query_start_month)
if ical_events:
2022-02-23 18:46:20 +00:00
if 'VEVENT' in ical_events:
2022-02-24 16:55:37 +00:00
ical_events_encoded = ical_events.encode('utf-8')
etag = md5(ical_events_encoded).hexdigest()
2022-02-23 18:46:20 +00:00
responses = \
' <d:response>\n' + \
' <d:href>' + events_href + \
'</d:href>\n' + \
' <d:propstat>\n' + \
' <d:prop>\n' + \
' <d:getetag>"' + \
etag + '"</d:getetag>\n' + \
' <c:calendar-data>' + \
ical_events + \
' </c:calendar-data>\n' + \
' </d:prop>\n' + \
' <d:status>HTTP/1.1 200 OK' + \
'</d:status>\n' + \
' </d:propstat>\n' + \
' </d:response>\n'
2022-02-23 18:04:34 +00:00
if not responses:
all_events = ''
for year in range(query_start_year, query_end_year+1):
if query_start_year == query_end_year:
start_month_number = query_start_month
end_month_number = query_end_month
elif year == query_end_year:
start_month_number = 1
end_month_number = query_end_month
elif year == query_start_year:
start_month_number = query_start_month
end_month_number = 12
else:
start_month_number = 1
end_month_number = 12
for month in range(start_month_number, end_month_number+1):
ical_events = \
get_month_events_icalendar(base_dir,
nickname, domain,
year, month,
person_cache,
2022-02-23 18:46:20 +00:00
text_match)
2022-02-23 18:04:34 +00:00
events_href = \
http_prefix + '://' + domain_full + '/users/' + \
nickname + '/calendar?year=' + \
str(year) + '?month=' + \
str(month)
if ical_events:
2022-02-23 18:46:20 +00:00
if 'VEVENT' in ical_events:
all_events += ical_events
2022-02-24 16:55:37 +00:00
ical_events_encoded = ical_events.encode('utf-8')
local_etag = md5(ical_events_encoded).hexdigest()
2022-02-23 18:46:20 +00:00
responses += \
' <d:response>\n' + \
' <d:href>' + events_href + \
'</d:href>\n' + \
' <d:propstat>\n' + \
' <d:prop>\n' + \
' <d:getetag>"' + \
2022-02-24 16:55:37 +00:00
local_etag + '"</d:getetag>\n' + \
2022-02-23 18:46:20 +00:00
' <c:calendar-data>' + \
ical_events + \
' </c:calendar-data>\n' + \
' </d:prop>\n' + \
' <d:status>HTTP/1.1 200 OK' + \
'</d:status>\n' + \
' </d:propstat>\n' + \
' </d:response>\n'
2022-02-24 16:55:37 +00:00
ical_events_encoded = all_events.encode('utf-8')
etag = md5(ical_events_encoded).hexdigest()
2022-02-23 18:04:34 +00:00
2022-02-23 12:19:27 +00:00
# today's calendar events
if not ical_events:
2022-02-23 18:04:34 +00:00
ical_events = \
get_todays_events_icalendar(base_dir, nickname, domain,
search_date.year, search_date.month,
search_date.day, person_cache,
2022-06-14 11:52:43 +00:00
text_match,
2022-04-13 15:06:03 +00:00
system_language)
2022-02-23 18:04:34 +00:00
events_href = \
http_prefix + '://' + domain_full + '/users/' + \
nickname + '/calendar?year=' + \
str(search_date.year) + '?month=' + \
str(search_date.month) + '?day=' + str(search_date.day)
if ical_events:
2022-02-23 18:46:20 +00:00
if 'VEVENT' in ical_events:
2022-02-24 16:55:37 +00:00
ical_events_encoded = ical_events.encode('utf-8')
etag = md5(ical_events_encoded).hexdigest()
2022-02-23 18:46:20 +00:00
responses = \
' <d:response>\n' + \
' <d:href>' + events_href + '</d:href>\n' + \
' <d:propstat>\n' + \
' <d:prop>\n' + \
' <d:getetag>"' + etag + \
'"</d:getetag>\n' + \
' <c:calendar-data>' + ical_events + \
' </c:calendar-data>\n' + \
' </d:prop>\n' + \
' <d:status>HTTP/1.1 200 OK</d:status>\n' + \
' </d:propstat>\n' + \
' </d:response>\n'
2022-02-23 18:04:34 +00:00
if not ical_events or not etag:
2022-02-23 12:19:27 +00:00
return None
if 'VEVENT' not in ical_events:
return None
if etag == curr_etag:
return "Not modified"
response_str = \
2022-02-23 18:04:34 +00:00
'<?xml version="1.0" encoding="utf-8" ?>\n' + \
2022-02-23 12:19:27 +00:00
'<d:multistatus xmlns:d="DAV:" ' + \
'xmlns:cs="http://calendarserver.org/ns/">\n' + \
2022-02-23 18:04:34 +00:00
responses + '</d:multistatus>'
2022-02-23 12:19:27 +00:00
2022-02-23 18:04:34 +00:00
_dav_update_recent_etags(etag, nickname, recent_dav_etags)
2022-02-23 12:19:27 +00:00
return response_str
2022-02-23 09:51:00 +00:00
def dav_delete_response(base_dir: str, nickname: str, domain: str,
2024-02-19 13:23:22 +00:00
path: str, http_prefix: str, debug: bool,
2022-02-23 09:51:00 +00:00
recent_posts_cache: {}) -> str:
"""Returns the response to caldav DELETE
"""
token = path.split('/calendars/' + nickname + '/')[1]
token_year, token_month_number, token_post_id = \
_dav_decode_token(token)
if not token_year:
return None
post_filename = locate_post(base_dir, nickname, domain, token_post_id)
if not post_filename:
print('Calendar post not found ' + token_post_id)
return None
post_json_object = load_json(post_filename)
if not _is_happening_post(post_json_object):
print(token_post_id + ' is not a calendar post')
return None
remove_calendar_event(base_dir, nickname, domain,
token_year, token_month_number,
token_post_id)
delete_post(base_dir, http_prefix,
nickname, domain, post_filename,
debug, recent_posts_cache, True)
2022-02-23 09:51:00 +00:00
return 'Ok'
2022-02-24 10:18:54 +00:00
def dav_month_via_server(session, http_prefix: str,
nickname: str, domain: str, port: int,
debug: bool,
year: int, month: int,
password: str) -> str:
"""Gets the icalendar for a month via caldav
"""
auth_header = create_basic_auth_header(nickname, password)
headers = {
'host': domain,
'Content-type': 'application/xml',
'Authorization': auth_header
}
domain_full = get_full_domain(domain, port)
params = {}
url = http_prefix + '://' + domain_full + '/calendars/' + nickname
month_str = str(month)
if month < 10:
month_str = '0' + month_str
xml_str = \
'<?xml version="1.0" encoding="utf-8" ?>\n' + \
'<c:calendar-query xmlns:d="DAV:"\n' + \
' xmlns:c="urn:ietf:params:xml:ns:caldav">\n' + \
' <d:prop>\n' + \
' <d:getetag/>\n' + \
' </d:prop>\n' + \
' <c:filter>\n' + \
' <c:comp-filter name="VCALENDAR">\n' + \
' <c:comp-filter name="VEVENT">\n' + \
' <c:time-range start="' + str(year) + month_str + \
'01T000000Z"\n' + \
' end="' + str(year) + month_str + \
2022-02-24 17:23:41 +00:00
'31T235959Z"/>\n' + \
' </c:comp-filter>\n' + \
' </c:comp-filter>\n' + \
' </c:filter>\n' + \
'</c:calendar-query>'
result = \
2022-05-26 21:58:22 +00:00
get_method("REPORT", xml_str, session, url, params, headers, debug,
__version__, http_prefix, domain)
2022-02-24 17:23:41 +00:00
return result
def dav_day_via_server(session, http_prefix: str,
nickname: str, domain: str, port: int,
debug: bool,
year: int, month: int, day: int,
password: str) -> str:
"""Gets the icalendar for a day via caldav
"""
auth_header = create_basic_auth_header(nickname, password)
headers = {
'host': domain,
'Content-type': 'application/xml',
'Authorization': auth_header
}
domain_full = get_full_domain(domain, port)
params = {}
url = http_prefix + '://' + domain_full + '/calendars/' + nickname
month_str = str(month)
if month < 10:
month_str = '0' + month_str
day_str = str(day)
if day < 10:
day_str = '0' + day_str
xml_str = \
'<?xml version="1.0" encoding="utf-8" ?>\n' + \
'<c:calendar-query xmlns:d="DAV:"\n' + \
' xmlns:c="urn:ietf:params:xml:ns:caldav">\n' + \
' <d:prop>\n' + \
' <d:getetag/>\n' + \
' </d:prop>\n' + \
' <c:filter>\n' + \
' <c:comp-filter name="VCALENDAR">\n' + \
' <c:comp-filter name="VEVENT">\n' + \
' <c:time-range start="' + str(year) + month_str + \
day_str + 'T000000Z"\n' + \
' end="' + str(year) + month_str + \
day_str + 'T235959Z"/>\n' + \
2022-02-24 10:18:54 +00:00
' </c:comp-filter>\n' + \
' </c:comp-filter>\n' + \
' </c:filter>\n' + \
'</c:calendar-query>'
2022-02-24 11:10:14 +00:00
result = \
2022-05-26 21:58:22 +00:00
get_method("REPORT", xml_str, session, url, params, headers, debug,
__version__, http_prefix, domain)
2022-02-24 10:18:54 +00:00
return result