mirror of https://gitlab.com/bashrc2/epicyon
Merge branch 'main' of gitlab.com:bashrc2/epicyon
commit
e26c1b2b56
|
@ -372,3 +372,19 @@ To remove a shared item:
|
|||
``` bash
|
||||
python3 epicyon.py --undoItemName "spanner" --nickname [yournick] --domain [yourdomain] --password [c2s password]
|
||||
```
|
||||
|
||||
## Calendar
|
||||
|
||||
The calendar for each account can be accessed via CalDav (RFC4791). This makes it easy to integrate the social calendar into other applications. For example, to obtain events for a month:
|
||||
|
||||
```bash
|
||||
python3 epicyon.py --dav --nickname [yournick] --domain [yourdomain] --year [year] --month [month number]
|
||||
```
|
||||
|
||||
You will be prompted for your login password, or you can use the **--password** option. You can also use the **--day** option to obtain events for a particular day.
|
||||
|
||||
The CalDav endpoint for an account is:
|
||||
|
||||
```bash
|
||||
yourdomain/calendars/yournick
|
||||
```
|
||||
|
|
29
auth.py
29
auth.py
|
@ -94,16 +94,25 @@ def authorize_basic(base_dir: str, path: str, auth_header: str,
|
|||
'contain a space character')
|
||||
return False
|
||||
if not has_users_path(path):
|
||||
if debug:
|
||||
print('DEBUG: basic auth - ' +
|
||||
'path for Authorization does not contain a user')
|
||||
return False
|
||||
path_users_section = path.split('/users/')[1]
|
||||
if '/' not in path_users_section:
|
||||
if debug:
|
||||
print('DEBUG: basic auth - this is not a users endpoint')
|
||||
return False
|
||||
nickname_from_path = path_users_section.split('/')[0]
|
||||
if not path.startswith('/calendars/'):
|
||||
if debug:
|
||||
print('DEBUG: basic auth - ' +
|
||||
'path for Authorization does not contain a user')
|
||||
return False
|
||||
if path.startswith('/calendars/'):
|
||||
path_users_section = path.split('/calendars/')[1]
|
||||
nickname_from_path = path_users_section
|
||||
if '/' in nickname_from_path:
|
||||
nickname_from_path = nickname_from_path.split('/')[0]
|
||||
if '?' in nickname_from_path:
|
||||
nickname_from_path = nickname_from_path.split('?')[0]
|
||||
else:
|
||||
path_users_section = path.split('/users/')[1]
|
||||
if '/' not in path_users_section:
|
||||
if debug:
|
||||
print('DEBUG: basic auth - this is not a users endpoint')
|
||||
return False
|
||||
nickname_from_path = path_users_section.split('/')[0]
|
||||
if is_system_account(nickname_from_path):
|
||||
print('basic auth - attempted login using system account ' +
|
||||
nickname_from_path + ' in path')
|
||||
|
|
89
daemon.py
89
daemon.py
|
@ -949,7 +949,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.end_headers()
|
||||
|
||||
def _http_return_code(self, http_code: int, http_description: str,
|
||||
long_description: str) -> None:
|
||||
long_description: str, etag: str) -> None:
|
||||
msg = \
|
||||
'<html><head><title>' + str(http_code) + '</title></head>' \
|
||||
'<body bgcolor="linen" text="black">' \
|
||||
|
@ -965,6 +965,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.send_header('Content-Type', 'text/html; charset=utf-8')
|
||||
msg_len_str = str(len(msg))
|
||||
self.send_header('Content-Length', msg_len_str)
|
||||
if etag:
|
||||
self.send_header('ETag', etag)
|
||||
self.end_headers()
|
||||
if not self._write(msg):
|
||||
print('Error when showing ' + str(http_code))
|
||||
|
@ -973,71 +975,77 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
if self.server.translate:
|
||||
ok_str = self.server.translate['This is nothing ' +
|
||||
'less than an utter triumph']
|
||||
self._http_return_code(200, self.server.translate['Ok'], ok_str)
|
||||
self._http_return_code(200, self.server.translate['Ok'],
|
||||
ok_str, None)
|
||||
else:
|
||||
self._http_return_code(200, 'Ok',
|
||||
'This is nothing less ' +
|
||||
'than an utter triumph')
|
||||
'than an utter triumph', None)
|
||||
|
||||
def _201(self) -> None:
|
||||
def _201(self, etag: str) -> None:
|
||||
if self.server.translate:
|
||||
ok_str = self.server.translate['Done']
|
||||
done_str = self.server.translate['It is done']
|
||||
self._http_return_code(201,
|
||||
self.server.translate['Created'], ok_str)
|
||||
self.server.translate['Created'], done_str,
|
||||
etag)
|
||||
else:
|
||||
self._http_return_code(201, 'Created',
|
||||
'Done')
|
||||
self._http_return_code(201, 'Created', 'It is done', etag)
|
||||
|
||||
def _207(self) -> None:
|
||||
if self.server.translate:
|
||||
multi_str = self.server.translate['Lots of things']
|
||||
self._http_return_code(207,
|
||||
self.server.translate['Multi Status'],
|
||||
multi_str)
|
||||
multi_str, None)
|
||||
else:
|
||||
self._http_return_code(207, 'Multi Status',
|
||||
'Lots of things')
|
||||
'Lots of things', None)
|
||||
|
||||
def _403(self) -> None:
|
||||
if self.server.translate:
|
||||
self._http_return_code(403, self.server.translate['Forbidden'],
|
||||
self.server.translate["You're not allowed"])
|
||||
self.server.translate["You're not allowed"],
|
||||
None)
|
||||
else:
|
||||
self._http_return_code(403, 'Forbidden',
|
||||
"You're not allowed")
|
||||
"You're not allowed", None)
|
||||
|
||||
def _404(self) -> None:
|
||||
if self.server.translate:
|
||||
self._http_return_code(404, self.server.translate['Not Found'],
|
||||
self.server.translate['These are not the ' +
|
||||
'droids you are ' +
|
||||
'looking for'])
|
||||
'looking for'],
|
||||
None)
|
||||
else:
|
||||
self._http_return_code(404, 'Not Found',
|
||||
'These are not the ' +
|
||||
'droids you are ' +
|
||||
'looking for')
|
||||
'looking for', None)
|
||||
|
||||
def _304(self) -> None:
|
||||
if self.server.translate:
|
||||
self._http_return_code(304, self.server.translate['Not changed'],
|
||||
self.server.translate['The contents of ' +
|
||||
'your local cache ' +
|
||||
'are up to date'])
|
||||
'are up to date'],
|
||||
None)
|
||||
else:
|
||||
self._http_return_code(304, 'Not changed',
|
||||
'The contents of ' +
|
||||
'your local cache ' +
|
||||
'are up to date')
|
||||
'are up to date',
|
||||
None)
|
||||
|
||||
def _400(self) -> None:
|
||||
if self.server.translate:
|
||||
self._http_return_code(400, self.server.translate['Bad Request'],
|
||||
self.server.translate['Better luck ' +
|
||||
'next time'])
|
||||
'next time'],
|
||||
None)
|
||||
else:
|
||||
self._http_return_code(400, 'Bad Request',
|
||||
'Better luck next time')
|
||||
'Better luck next time', None)
|
||||
|
||||
def _503(self) -> None:
|
||||
if self.server.translate:
|
||||
|
@ -1045,11 +1053,11 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.translate['The server is busy. ' +
|
||||
'Please try again later']
|
||||
self._http_return_code(503, self.server.translate['Unavailable'],
|
||||
busy_str)
|
||||
busy_str, None)
|
||||
else:
|
||||
self._http_return_code(503, 'Unavailable',
|
||||
'The server is busy. Please try again ' +
|
||||
'later')
|
||||
'later', None)
|
||||
|
||||
def _write(self, msg) -> bool:
|
||||
tries = 0
|
||||
|
@ -16824,13 +16832,15 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
'_GET', 'end benchmarks',
|
||||
self.server.debug)
|
||||
|
||||
def _dav_handler(self, endpoint_type: str):
|
||||
def _dav_handler(self, endpoint_type: str, debug: bool):
|
||||
calling_domain = self.server.domain_full
|
||||
if not self._has_accept(calling_domain):
|
||||
self._400()
|
||||
return
|
||||
accept_str = self.headers['Accept']
|
||||
if 'application/xml' not in accept_str:
|
||||
if debug:
|
||||
print(endpoint_type.upper() + ' is not of xml type')
|
||||
self._400()
|
||||
return
|
||||
if not self.headers.get('Content-length'):
|
||||
|
@ -16847,8 +16857,10 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
print(endpoint_type.upper() + ' without /calendars ' + self.path)
|
||||
self._404()
|
||||
return
|
||||
if debug:
|
||||
print(endpoint_type.upper() + ' checking authorization')
|
||||
if not self._is_authorized():
|
||||
print('PROPFIND Not authorized')
|
||||
print(endpoint_type.upper() + ' not authorized')
|
||||
self._403()
|
||||
return
|
||||
nickname = self.path.split('/calendars/')[1]
|
||||
|
@ -16869,9 +16881,10 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
propfind_bytes = self.rfile.read(length)
|
||||
except SocketError as ex:
|
||||
if ex.errno == errno.ECONNRESET:
|
||||
print('EX: PROPFIND connection reset by peer')
|
||||
print('EX: ' + endpoint_type.upper() +
|
||||
' connection reset by peer')
|
||||
else:
|
||||
print('EX: PROPFIND socket error')
|
||||
print('EX: ' + endpoint_type.upper() + ' socket error')
|
||||
self._400()
|
||||
return
|
||||
except ValueError as ex:
|
||||
|
@ -16898,7 +16911,8 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
nickname, self.server.domain,
|
||||
depth, propfind_xml,
|
||||
self.server.http_prefix,
|
||||
self.server.system_language)
|
||||
self.server.system_language,
|
||||
self.server.recent_dav_etags)
|
||||
elif endpoint_type == 'report':
|
||||
curr_etag = None
|
||||
if self.headers.get('ETag'):
|
||||
|
@ -16912,6 +16926,7 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self.server.person_cache,
|
||||
self.server.http_prefix,
|
||||
curr_etag,
|
||||
self.server.recent_dav_etags,
|
||||
self.server.domain_full)
|
||||
elif endpoint_type == 'delete':
|
||||
response_str = \
|
||||
|
@ -16925,7 +16940,13 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._404()
|
||||
return
|
||||
if response_str == 'Not modified':
|
||||
return self._304()
|
||||
if endpoint_type == 'put':
|
||||
return self._200()
|
||||
else:
|
||||
return self._304()
|
||||
elif response_str.startswith('ETag:') and endpoint_type == 'put':
|
||||
response_etag = response_str.split('ETag:', 1)[1]
|
||||
self._201(response_etag)
|
||||
elif response_str != 'Ok':
|
||||
message_xml = response_str.encode('utf-8')
|
||||
message_xml_len = len(message_xml)
|
||||
|
@ -16935,22 +16956,19 @@ class PubServer(BaseHTTPRequestHandler):
|
|||
self._write(message_xml)
|
||||
if 'multistatus' in response_str:
|
||||
return self._207()
|
||||
if endpoint_type == 'put':
|
||||
self._201()
|
||||
else:
|
||||
self._200()
|
||||
self._200()
|
||||
|
||||
def do_PROPFIND(self):
|
||||
self._dav_handler('propfind')
|
||||
self._dav_handler('propfind', self.server.debug)
|
||||
|
||||
def do_PUT(self):
|
||||
self._dav_handler('put')
|
||||
self._dav_handler('put', self.server.debug)
|
||||
|
||||
def do_REPORT(self):
|
||||
self._dav_handler('report')
|
||||
self._dav_handler('report', self.server.debug)
|
||||
|
||||
def do_DELETE(self):
|
||||
self._dav_handler('delete')
|
||||
self._dav_handler('delete', self.server.debug)
|
||||
|
||||
def do_HEAD(self):
|
||||
calling_domain = self.server.domain_full
|
||||
|
@ -19274,6 +19292,9 @@ def run_daemon(dyslexic_font: bool,
|
|||
default_reply_interval_hrs = 9999999
|
||||
httpd.default_reply_interval_hrs = default_reply_interval_hrs
|
||||
|
||||
# recent caldav etags for each account
|
||||
httpd.recent_dav_etags = {}
|
||||
|
||||
httpd.key_shortcuts = {}
|
||||
load_access_keys_for_accounts(base_dir, httpd.key_shortcuts,
|
||||
httpd.access_keys)
|
||||
|
|
|
@ -466,6 +466,9 @@ def _desktop_reply_to_post(session, post_id: str,
|
|||
comments_enabled = True
|
||||
city = 'London, England'
|
||||
say_str = 'Sending reply'
|
||||
event_date = None
|
||||
event_time = None
|
||||
location = None
|
||||
_say_command(say_str, say_str, screenreader, system_language, espeak)
|
||||
if send_post_via_server(signing_priv_key_pem, __version__,
|
||||
base_dir, session, nickname, password,
|
||||
|
@ -477,6 +480,7 @@ def _desktop_reply_to_post(session, post_id: str,
|
|||
cached_webfingers, person_cache, is_article,
|
||||
system_language, languages_understood,
|
||||
low_bandwidth, content_license_url,
|
||||
event_date, event_time, location,
|
||||
debug, post_id, post_id,
|
||||
conversation_id, subject) == 0:
|
||||
say_str = 'Reply sent'
|
||||
|
@ -535,6 +539,9 @@ def _desktop_new_post(session,
|
|||
comments_enabled = True
|
||||
subject = None
|
||||
say_str = 'Sending'
|
||||
event_date = None
|
||||
event_time = None
|
||||
location = None
|
||||
_say_command(say_str, say_str, screenreader, system_language, espeak)
|
||||
if send_post_via_server(signing_priv_key_pem, __version__,
|
||||
base_dir, session, nickname, password,
|
||||
|
@ -546,6 +553,7 @@ def _desktop_new_post(session,
|
|||
cached_webfingers, person_cache, is_article,
|
||||
system_language, languages_understood,
|
||||
low_bandwidth, content_license_url,
|
||||
event_date, event_time, location,
|
||||
debug, None, None,
|
||||
conversation_id, subject) == 0:
|
||||
say_str = 'Post sent'
|
||||
|
@ -1260,6 +1268,10 @@ def _desktop_new_dm_base(session, to_handle: str,
|
|||
_say_command(say_str, say_str, screenreader, system_language, espeak)
|
||||
return
|
||||
|
||||
event_date = None
|
||||
event_time = None
|
||||
location = None
|
||||
|
||||
say_str = 'Sending'
|
||||
_say_command(say_str, say_str, screenreader, system_language, espeak)
|
||||
if send_post_via_server(signing_priv_key_pem, __version__,
|
||||
|
@ -1272,6 +1284,7 @@ def _desktop_new_dm_base(session, to_handle: str,
|
|||
cached_webfingers, person_cache, is_article,
|
||||
system_language, languages_understood,
|
||||
low_bandwidth, content_license_url,
|
||||
event_date, event_time, location,
|
||||
debug, None, None,
|
||||
conversation_id, subject) == 0:
|
||||
say_str = 'Direct message sent'
|
||||
|
|
94
epicyon.py
94
epicyon.py
|
@ -13,6 +13,7 @@ import sys
|
|||
import time
|
||||
import argparse
|
||||
import getpass
|
||||
import datetime
|
||||
from person import get_actor_json
|
||||
from person import create_person
|
||||
from person import create_group
|
||||
|
@ -101,6 +102,8 @@ from announce import send_announce_via_server
|
|||
from socnet import instances_graph
|
||||
from migrate import migrate_accounts
|
||||
from desktop_client import run_desktop_client
|
||||
from happening import dav_month_via_server
|
||||
from happening import dav_day_via_server
|
||||
|
||||
|
||||
def str2bool(value_str) -> bool:
|
||||
|
@ -115,7 +118,19 @@ def str2bool(value_str) -> bool:
|
|||
raise argparse.ArgumentTypeError('Boolean value expected.')
|
||||
|
||||
|
||||
search_date = datetime.datetime.now()
|
||||
parser = argparse.ArgumentParser(description='ActivityPub Server')
|
||||
parser.add_argument('--eventDate', type=str,
|
||||
default=None,
|
||||
help='Date for an event when sending a c2s post' +
|
||||
' YYYY-MM-DD')
|
||||
parser.add_argument('--eventTime', type=str,
|
||||
default=None,
|
||||
help='Time for an event when sending a c2s post' +
|
||||
' HH:MM')
|
||||
parser.add_argument('--eventLocation', type=str,
|
||||
default=None,
|
||||
help='Location for an event when sending a c2s post')
|
||||
parser.add_argument('--content_license_url', type=str,
|
||||
default='https://creativecommons.org/licenses/by/4.0',
|
||||
help='Url of the license used for the instance content')
|
||||
|
@ -172,6 +187,15 @@ parser.add_argument('--i2p_domain', dest='i2p_domain', type=str,
|
|||
parser.add_argument('-p', '--port', dest='port', type=int,
|
||||
default=None,
|
||||
help='Port number to run on')
|
||||
parser.add_argument('--year', dest='year', type=int,
|
||||
default=search_date.year,
|
||||
help='Year for calendar query')
|
||||
parser.add_argument('--month', dest='month', type=int,
|
||||
default=search_date.month,
|
||||
help='Month for calendar query')
|
||||
parser.add_argument('--day', dest='day', type=int,
|
||||
default=None,
|
||||
help='Day for calendar query')
|
||||
parser.add_argument('--postsPerSource',
|
||||
dest='max_newswire_postsPerSource', type=int,
|
||||
default=4,
|
||||
|
@ -329,6 +353,11 @@ parser.add_argument("--repliesEnabled", "--commentsEnabled",
|
|||
type=str2bool, nargs='?',
|
||||
const=True, default=True,
|
||||
help="Enable replies to a post")
|
||||
parser.add_argument("--dav",
|
||||
dest='dav',
|
||||
type=str2bool, nargs='?',
|
||||
const=True, default=False,
|
||||
help="Caldav")
|
||||
parser.add_argument("--show_publish_as_icon",
|
||||
dest='show_publish_as_icon',
|
||||
type=str2bool, nargs='?',
|
||||
|
@ -1296,6 +1325,16 @@ if args.message:
|
|||
print('Specify a nickname with the --nickname option')
|
||||
sys.exit()
|
||||
|
||||
if args.eventDate:
|
||||
if '-' not in args.eventDate or len(args.eventDate) != 10:
|
||||
print('Event date format should be YYYY-MM-DD')
|
||||
sys.exit()
|
||||
|
||||
if args.eventTime:
|
||||
if ':' not in args.eventTime or len(args.eventTime) != 5:
|
||||
print('Event time format should be HH:MM')
|
||||
sys.exit()
|
||||
|
||||
if not args.password:
|
||||
args.password = getpass.getpass('Password: ')
|
||||
if not args.password:
|
||||
|
@ -1356,8 +1395,8 @@ if args.message:
|
|||
if args.secure_mode:
|
||||
signing_priv_key_pem = get_instance_actor_key(base_dir, domain)
|
||||
languages_understood = [args.language]
|
||||
print('Sending post to ' + args.sendto)
|
||||
|
||||
print('Sending post to ' + args.sendto)
|
||||
send_post_via_server(signing_priv_key_pem, __version__,
|
||||
base_dir, session, args.nickname, args.password,
|
||||
domain, port,
|
||||
|
@ -1368,13 +1407,64 @@ if args.message:
|
|||
cached_webfingers, person_cache, is_article,
|
||||
args.language, languages_understood,
|
||||
args.low_bandwidth,
|
||||
args.content_license_url, args.debug,
|
||||
args.content_license_url,
|
||||
args.eventDate, args.eventTime, args.eventLocation,
|
||||
args.debug,
|
||||
reply_to, reply_to, args.conversationId, subject)
|
||||
for i in range(10):
|
||||
# TODO detect send success/fail
|
||||
time.sleep(1)
|
||||
sys.exit()
|
||||
|
||||
if args.dav:
|
||||
if not args.nickname:
|
||||
print('Please specify a nickname with --nickname')
|
||||
sys.exit()
|
||||
if not args.domain:
|
||||
print('Please specify a domain with --domain')
|
||||
sys.exit()
|
||||
if not args.year:
|
||||
print('Please specify a year with --year')
|
||||
sys.exit()
|
||||
if not args.month:
|
||||
print('Please specify a month with --month')
|
||||
sys.exit()
|
||||
if not args.password:
|
||||
args.password = getpass.getpass('Password: ')
|
||||
if not args.password:
|
||||
print('Specify a password with the --password option')
|
||||
sys.exit()
|
||||
args.password = args.password.replace('\n', '')
|
||||
proxy_type = None
|
||||
if args.tor or domain.endswith('.onion'):
|
||||
proxy_type = 'tor'
|
||||
if domain.endswith('.onion'):
|
||||
args.port = 80
|
||||
elif args.i2p or domain.endswith('.i2p'):
|
||||
proxy_type = 'i2p'
|
||||
if domain.endswith('.i2p'):
|
||||
args.port = 80
|
||||
elif args.gnunet:
|
||||
proxy_type = 'gnunet'
|
||||
session = create_session(proxy_type)
|
||||
if args.day:
|
||||
result = \
|
||||
dav_day_via_server(session, http_prefix,
|
||||
args.nickname, args.domain, args.port,
|
||||
args.debug,
|
||||
args.year, args.month, args.day,
|
||||
args.password)
|
||||
else:
|
||||
result = \
|
||||
dav_month_via_server(session, http_prefix,
|
||||
args.nickname, args.domain, args.port,
|
||||
args.debug,
|
||||
args.year, args.month,
|
||||
args.password)
|
||||
if result:
|
||||
print(str(result))
|
||||
sys.exit()
|
||||
|
||||
if args.announce:
|
||||
if not args.nickname:
|
||||
print('Specify a nickname with the --nickname option')
|
||||
|
|
489
happening.py
489
happening.py
|
@ -23,8 +23,36 @@ from utils import remove_html
|
|||
from utils import get_display_name
|
||||
from utils import delete_post
|
||||
from utils import get_status_number
|
||||
from utils import get_full_domain
|
||||
from filters import is_filtered
|
||||
from context import get_individual_post_context
|
||||
from session import get_method
|
||||
from auth import create_basic_auth_header
|
||||
|
||||
|
||||
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]
|
||||
|
||||
if not timestamp_year.isdigit() or \
|
||||
not timestamp_month.isdigit() or \
|
||||
not timestamp_day.isdigit() or \
|
||||
not timestamp_hour.isdigit() or \
|
||||
not timestamp_min.isdigit() or \
|
||||
not timestamp_sec.isdigit():
|
||||
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
|
||||
|
||||
|
||||
def _valid_uuid(test_uuid: str, version: int):
|
||||
|
@ -181,9 +209,26 @@ def _is_happening_post(post_json_object: {}) -> bool:
|
|||
return True
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def get_todays_events(base_dir: str, nickname: str, domain: str,
|
||||
curr_year: int, curr_month_number: int,
|
||||
curr_day_of_month: int) -> {}:
|
||||
curr_day_of_month: int,
|
||||
text_match: str) -> {}:
|
||||
"""Retrieves calendar events for today
|
||||
Returns a dictionary of lists containing Event and Place activities
|
||||
"""
|
||||
|
@ -222,6 +267,12 @@ def get_todays_events(base_dir: str, nickname: str, domain: str,
|
|||
if not _is_happening_post(post_json_object):
|
||||
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
|
||||
|
||||
public_event = is_public_post(post_json_object)
|
||||
|
||||
post_event = []
|
||||
|
@ -413,13 +464,15 @@ def _icalendar_day(base_dir: str, nickname: str, domain: str,
|
|||
def get_todays_events_icalendar(base_dir: str, nickname: str, domain: str,
|
||||
year: int, month_number: int,
|
||||
day_number: int, person_cache: {},
|
||||
http_prefix: str) -> str:
|
||||
http_prefix: str,
|
||||
text_match: str) -> str:
|
||||
"""Returns today's events in icalendar format
|
||||
"""
|
||||
day_events = None
|
||||
events = \
|
||||
get_todays_events(base_dir, nickname, domain,
|
||||
year, month_number, day_number)
|
||||
year, month_number, day_number,
|
||||
text_match)
|
||||
if events:
|
||||
if events.get(str(day_number)):
|
||||
day_events = events[str(day_number)]
|
||||
|
@ -447,12 +500,13 @@ def get_month_events_icalendar(base_dir: str, nickname: str, domain: str,
|
|||
year: int,
|
||||
month_number: int,
|
||||
person_cache: {},
|
||||
http_prefix: str) -> str:
|
||||
http_prefix: str,
|
||||
text_match: str) -> str:
|
||||
"""Returns today's events in icalendar format
|
||||
"""
|
||||
month_events = \
|
||||
get_calendar_events(base_dir, nickname, domain, year,
|
||||
month_number)
|
||||
month_number, text_match)
|
||||
|
||||
ical_str = \
|
||||
'BEGIN:VCALENDAR\n' + \
|
||||
|
@ -597,7 +651,8 @@ def get_this_weeks_events(base_dir: str, nickname: str, domain: str) -> {}:
|
|||
|
||||
|
||||
def get_calendar_events(base_dir: str, nickname: str, domain: str,
|
||||
year: int, month_number: int) -> {}:
|
||||
year: int, month_number: int,
|
||||
text_match: str) -> {}:
|
||||
"""Retrieves calendar events
|
||||
Returns a dictionary indexed by day number of lists containing
|
||||
Event and Place activities
|
||||
|
@ -621,9 +676,17 @@ def get_calendar_events(base_dir: str, nickname: str, domain: str,
|
|||
continue
|
||||
|
||||
post_json_object = load_json(post_filename)
|
||||
if not post_json_object:
|
||||
continue
|
||||
if not _is_happening_post(post_json_object):
|
||||
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']:
|
||||
|
@ -784,66 +847,15 @@ def _dav_store_event(base_dir: str, nickname: str, domain: str,
|
|||
return False
|
||||
|
||||
# convert to the expected time format
|
||||
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]
|
||||
|
||||
if not timestamp_year.isdigit() or \
|
||||
not timestamp_month.isdigit() or \
|
||||
not timestamp_day.isdigit() or \
|
||||
not timestamp_hour.isdigit() or \
|
||||
not timestamp_min.isdigit() or \
|
||||
not timestamp_sec.isdigit():
|
||||
published = _dav_date_from_string(timestamp)
|
||||
if not published:
|
||||
return False
|
||||
if int(timestamp_year) < 2020 or int(timestamp_year) > 2100:
|
||||
start_time = _dav_date_from_string(start_time)
|
||||
if not start_time:
|
||||
return False
|
||||
published = \
|
||||
timestamp_year + '-' + timestamp_month + '-' + timestamp_day + 'T' + \
|
||||
timestamp_hour + ':' + timestamp_min + ':' + timestamp_sec + 'Z'
|
||||
|
||||
start_time_year = start_time[:4]
|
||||
start_time_month = start_time[4:][:2]
|
||||
start_time_day = start_time[6:][:2]
|
||||
start_time_hour = start_time[9:][:2]
|
||||
start_time_min = start_time[11:][:2]
|
||||
start_time_sec = start_time[13:][:2]
|
||||
|
||||
if not start_time_year.isdigit() or \
|
||||
not start_time_month.isdigit() or \
|
||||
not start_time_day.isdigit() or \
|
||||
not start_time_hour.isdigit() or \
|
||||
not start_time_min.isdigit() or \
|
||||
not start_time_sec.isdigit():
|
||||
end_time = _dav_date_from_string(end_time)
|
||||
if not end_time:
|
||||
return False
|
||||
if int(start_time_year) < 2020 or int(start_time_year) > 2100:
|
||||
return False
|
||||
start_time = \
|
||||
start_time_year + '-' + start_time_month + '-' + \
|
||||
start_time_day + 'T' + \
|
||||
start_time_hour + ':' + start_time_min + ':' + start_time_sec + 'Z'
|
||||
|
||||
end_time_year = end_time[:4]
|
||||
end_time_month = end_time[4:][:2]
|
||||
end_time_day = end_time[6:][:2]
|
||||
end_time_hour = end_time[9:][:2]
|
||||
end_time_min = end_time[11:][:2]
|
||||
end_time_sec = end_time[13:][:2]
|
||||
|
||||
if not end_time_year.isdigit() or \
|
||||
not end_time_month.isdigit() or \
|
||||
not end_time_day.isdigit() or \
|
||||
not end_time_hour.isdigit() or \
|
||||
not end_time_min.isdigit() or \
|
||||
not end_time_sec.isdigit():
|
||||
return False
|
||||
if int(end_time_year) < 2020 or int(end_time_year) > 2100:
|
||||
return False
|
||||
end_time = \
|
||||
end_time_year + '-' + end_time_month + '-' + end_time_day + 'T' + \
|
||||
end_time_hour + ':' + end_time_min + ':' + end_time_sec + 'Z'
|
||||
|
||||
post_id = ''
|
||||
post_context = get_individual_post_context()
|
||||
|
@ -932,9 +944,26 @@ def _dav_store_event(base_dir: str, nickname: str, domain: str,
|
|||
return True
|
||||
|
||||
|
||||
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)
|
||||
|
||||
|
||||
def dav_put_response(base_dir: str, nickname: str, domain: str,
|
||||
depth: int, xml_str: str, http_prefix: str,
|
||||
system_language: str) -> str:
|
||||
system_language: str,
|
||||
recent_dav_etags: {}) -> str:
|
||||
"""Returns the response to caldav PUT
|
||||
"""
|
||||
if '\n' not in xml_str:
|
||||
|
@ -946,6 +975,11 @@ def dav_put_response(base_dir: str, nickname: str, domain: str,
|
|||
'END:VEVENT' not in xml_str:
|
||||
return None
|
||||
|
||||
etag = md5(xml_str.encode('utf-8')).hexdigest()
|
||||
if recent_dav_etags.get(nickname):
|
||||
if etag in recent_dav_etags[nickname]:
|
||||
return 'Not modified'
|
||||
|
||||
stored_count = 0
|
||||
reading_event = False
|
||||
lines_list = xml_str.split('\n')
|
||||
|
@ -968,13 +1002,14 @@ def dav_put_response(base_dir: str, nickname: str, domain: str,
|
|||
event_list.append(line)
|
||||
if stored_count == 0:
|
||||
return None
|
||||
return 'Ok'
|
||||
_dav_update_recent_etags(etag, nickname, recent_dav_etags)
|
||||
return 'ETag:' + etag
|
||||
|
||||
|
||||
def dav_report_response(base_dir: str, nickname: str, domain: str,
|
||||
depth: int, xml_str: str,
|
||||
person_cache: {}, http_prefix: str,
|
||||
curr_etag: str,
|
||||
curr_etag: str, recent_dav_etags: {},
|
||||
domain_full: str) -> str:
|
||||
"""Returns the response to caldav REPORT
|
||||
"""
|
||||
|
@ -983,42 +1018,227 @@ def dav_report_response(base_dir: str, nickname: str, domain: str,
|
|||
if '<c:calendar-multiget' not in xml_str or \
|
||||
'</c:calendar-multiget>' not in xml_str:
|
||||
return None
|
||||
|
||||
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)
|
||||
|
||||
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 = ''
|
||||
|
||||
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 = \
|
||||
datetime(year=query_start_year,
|
||||
month=query_start_month,
|
||||
day=query_start_day)
|
||||
ical_events = \
|
||||
get_todays_events_icalendar(base_dir, nickname, domain,
|
||||
search_date.year,
|
||||
search_date.month,
|
||||
search_date.day, person_cache,
|
||||
http_prefix, text_match)
|
||||
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:
|
||||
if 'VEVENT' in ical_events:
|
||||
ical_events_encoded = ical_events.encode('utf-8')
|
||||
etag = md5(ical_events_encoded).hexdigest()
|
||||
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'
|
||||
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,
|
||||
http_prefix,
|
||||
text_match)
|
||||
events_href = \
|
||||
http_prefix + '://' + domain_full + '/users/' + \
|
||||
nickname + '/calendar?year=' + \
|
||||
str(query_start_year) + '?month=' + \
|
||||
str(query_start_month)
|
||||
if ical_events:
|
||||
if 'VEVENT' in ical_events:
|
||||
ical_events_encoded = ical_events.encode('utf-8')
|
||||
etag = md5(ical_events_encoded).hexdigest()
|
||||
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'
|
||||
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,
|
||||
http_prefix,
|
||||
text_match)
|
||||
events_href = \
|
||||
http_prefix + '://' + domain_full + '/users/' + \
|
||||
nickname + '/calendar?year=' + \
|
||||
str(year) + '?month=' + \
|
||||
str(month)
|
||||
if ical_events:
|
||||
if 'VEVENT' in ical_events:
|
||||
all_events += ical_events
|
||||
ical_events_encoded = ical_events.encode('utf-8')
|
||||
local_etag = md5(ical_events_encoded).hexdigest()
|
||||
responses += \
|
||||
' <d:response>\n' + \
|
||||
' <d:href>' + events_href + \
|
||||
'</d:href>\n' + \
|
||||
' <d:propstat>\n' + \
|
||||
' <d:prop>\n' + \
|
||||
' <d:getetag>"' + \
|
||||
local_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'
|
||||
ical_events_encoded = all_events.encode('utf-8')
|
||||
etag = md5(ical_events_encoded).hexdigest()
|
||||
|
||||
# today's calendar events
|
||||
now = datetime.now()
|
||||
ical_events = \
|
||||
get_todays_events_icalendar(base_dir, nickname, domain,
|
||||
now.year, now.month,
|
||||
now.day, person_cache,
|
||||
http_prefix)
|
||||
if not ical_events:
|
||||
ical_events = \
|
||||
get_todays_events_icalendar(base_dir, nickname, domain,
|
||||
search_date.year, search_date.month,
|
||||
search_date.day, person_cache,
|
||||
http_prefix, text_match)
|
||||
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:
|
||||
if 'VEVENT' in ical_events:
|
||||
ical_events_encoded = ical_events.encode('utf-8')
|
||||
etag = md5(ical_events_encoded).hexdigest()
|
||||
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'
|
||||
|
||||
if not ical_events or not etag:
|
||||
return None
|
||||
if 'VEVENT' not in ical_events:
|
||||
return None
|
||||
|
||||
etag = md5(ical_events).hexdigest()
|
||||
if etag == curr_etag:
|
||||
return "Not modified"
|
||||
events_href = \
|
||||
http_prefix + '://' + domain_full + '/users/' + \
|
||||
nickname + '/calendar?year=' + \
|
||||
str(now.year) + '?month=' + str(now.month) + '?day=' + str(now.day)
|
||||
response_str = \
|
||||
'<?xml version="1.0" encoding="utf-8" ?>\n' + \
|
||||
'<d:multistatus xmlns:d="DAV:" ' + \
|
||||
'xmlns:cs="http://calendarserver.org/ns/">\n' + \
|
||||
' <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' + \
|
||||
' <d:response>\n' + \
|
||||
'</d:multistatus>'
|
||||
responses + '</d:multistatus>'
|
||||
|
||||
_dav_update_recent_etags(etag, nickname, recent_dav_etags)
|
||||
return response_str
|
||||
|
||||
|
||||
|
@ -1048,3 +1268,92 @@ def dav_delete_response(base_dir: str, nickname: str, domain: str,
|
|||
nickname, domain, post_filename,
|
||||
debug, recent_posts_cache)
|
||||
return 'Ok'
|
||||
|
||||
|
||||
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 + \
|
||||
'31T235959Z"/>\n' + \
|
||||
' </c:comp-filter>\n' + \
|
||||
' </c:comp-filter>\n' + \
|
||||
' </c:filter>\n' + \
|
||||
'</c:calendar-query>'
|
||||
result = \
|
||||
get_method("REPORT", xml_str, session, url, params, headers, debug)
|
||||
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' + \
|
||||
' </c:comp-filter>\n' + \
|
||||
' </c:comp-filter>\n' + \
|
||||
' </c:filter>\n' + \
|
||||
'</c:calendar-query>'
|
||||
result = \
|
||||
get_method("REPORT", xml_str, session, url, params, headers, debug)
|
||||
return result
|
||||
|
|
7
posts.py
7
posts.py
|
@ -2490,6 +2490,8 @@ def send_post_via_server(signing_priv_key_pem: str, project_version: str,
|
|||
languages_understood: [],
|
||||
low_bandwidth: bool,
|
||||
content_license_url: str,
|
||||
event_date: str, event_time: str,
|
||||
location: str,
|
||||
debug: bool = False,
|
||||
in_reply_to: str = None,
|
||||
in_reply_to_atom_uri: str = None,
|
||||
|
@ -2574,8 +2576,9 @@ def send_post_via_server(signing_priv_key_pem: str, project_version: str,
|
|||
image_description, city,
|
||||
False, is_article, in_reply_to,
|
||||
in_reply_to_atom_uri, subject,
|
||||
False, None, None, None, None, None,
|
||||
None, None, None,
|
||||
False,
|
||||
event_date, event_time, location,
|
||||
None, None, None, None, None,
|
||||
None, None, None, None, None, system_language,
|
||||
conversation_id, low_bandwidth,
|
||||
content_license_url, languages_understood)
|
||||
|
|
100
session.py
100
session.py
|
@ -102,10 +102,13 @@ def _get_json_request(session, url: str, domain_full: str, session_headers: {},
|
|||
elif result.status_code == 410:
|
||||
print('WARN: get_json no longer available url: ' + url)
|
||||
else:
|
||||
session_headers2 = session_headers.copy()
|
||||
if session_headers2.get('Authorization'):
|
||||
session_headers2['Authorization'] = 'REDACTED'
|
||||
print('WARN: get_json url: ' + url +
|
||||
' failed with error code ' +
|
||||
str(result.status_code) +
|
||||
' headers: ' + str(session_headers))
|
||||
' headers: ' + str(session_headers2))
|
||||
if return_json:
|
||||
return result.json()
|
||||
return result.content
|
||||
|
@ -210,7 +213,7 @@ def _get_json_signed(session, url: str, domain_full: str, session_headers: {},
|
|||
|
||||
def get_json(signing_priv_key_pem: str,
|
||||
session, url: str, headers: {}, params: {}, debug: bool,
|
||||
version: str = '1.3.0', http_prefix: str = 'https',
|
||||
version: str = __version__, http_prefix: str = 'https',
|
||||
domain: str = 'testdomain',
|
||||
timeout_sec: int = 20, quiet: bool = False) -> {}:
|
||||
if not isinstance(url, str):
|
||||
|
@ -248,7 +251,7 @@ def get_json(signing_priv_key_pem: str,
|
|||
|
||||
def get_vcard(xml_format: bool,
|
||||
session, url: str, params: {}, debug: bool,
|
||||
version: str = '1.3.0', http_prefix: str = 'https',
|
||||
version: str = __version__, http_prefix: str = 'https',
|
||||
domain: str = 'testdomain',
|
||||
timeout_sec: int = 20, quiet: bool = False) -> {}:
|
||||
if not isinstance(url, str):
|
||||
|
@ -292,10 +295,13 @@ def get_vcard(xml_format: bool,
|
|||
elif result.status_code == 410:
|
||||
print('WARN: get_vcard no longer available url: ' + url)
|
||||
else:
|
||||
session_headers2 = session_headers.copy()
|
||||
if session_headers2.get('Authorization'):
|
||||
session_headers2['Authorization'] = 'REDACTED'
|
||||
print('WARN: get_vcard url: ' + url +
|
||||
' failed with error code ' +
|
||||
str(result.status_code) +
|
||||
' headers: ' + str(session_headers))
|
||||
' headers: ' + str(session_headers2))
|
||||
return result.content.decode('utf-8')
|
||||
except requests.exceptions.RequestException as ex:
|
||||
session_headers2 = session_headers.copy()
|
||||
|
@ -323,7 +329,7 @@ def get_vcard(xml_format: bool,
|
|||
|
||||
def download_html(signing_priv_key_pem: str,
|
||||
session, url: str, headers: {}, params: {}, debug: bool,
|
||||
version: str = '1.3.0', http_prefix: str = 'https',
|
||||
version: str = __version__, http_prefix: str = 'https',
|
||||
domain: str = 'testdomain',
|
||||
timeout_sec: int = 20, quiet: bool = False) -> {}:
|
||||
if not isinstance(url, str):
|
||||
|
@ -664,3 +670,87 @@ def download_image_any_mime_type(session, url: str,
|
|||
if 'image/' + m_type in content_type:
|
||||
mime_type = 'image/' + m_type
|
||||
return result.content, mime_type
|
||||
|
||||
|
||||
def get_method(method_name: str, xml_str: str,
|
||||
session, url: str, params: {}, headers: {}, debug: bool,
|
||||
version: str = __version__, http_prefix: str = 'https',
|
||||
domain: str = 'testdomain',
|
||||
timeout_sec: int = 20, quiet: bool = False) -> {}:
|
||||
if method_name not in ("REPORT", "PUT", "PROPFIND"):
|
||||
print("Unrecognized method: " + method_name)
|
||||
return None
|
||||
if not isinstance(url, str):
|
||||
if debug and not quiet:
|
||||
print('url: ' + str(url))
|
||||
print('ERROR: get_method failed, url should be a string')
|
||||
return None
|
||||
if not headers:
|
||||
headers = {
|
||||
'Accept': 'application/xml'
|
||||
}
|
||||
else:
|
||||
headers['Accept'] = 'application/xml'
|
||||
session_params = {}
|
||||
session_headers = {}
|
||||
if headers:
|
||||
session_headers = headers
|
||||
if params:
|
||||
session_params = params
|
||||
session_headers['User-Agent'] = 'Epicyon/' + version
|
||||
if domain:
|
||||
session_headers['User-Agent'] += \
|
||||
'; +' + http_prefix + '://' + domain + '/'
|
||||
if not session:
|
||||
if not quiet:
|
||||
print('WARN: get_method failed, ' +
|
||||
'no session specified for get_vcard')
|
||||
return None
|
||||
|
||||
if debug:
|
||||
HTTPConnection.debuglevel = 1
|
||||
|
||||
try:
|
||||
result = session.request(method_name, url, headers=session_headers,
|
||||
data=xml_str,
|
||||
params=session_params, timeout=timeout_sec)
|
||||
if result.status_code != 200 and result.status_code != 207:
|
||||
if result.status_code == 401:
|
||||
print("WARN: get_method " + url + ' rejected by secure mode')
|
||||
elif result.status_code == 403:
|
||||
print('WARN: get_method Forbidden url: ' + url)
|
||||
elif result.status_code == 404:
|
||||
print('WARN: get_method Not Found url: ' + url)
|
||||
elif result.status_code == 410:
|
||||
print('WARN: get_method no longer available url: ' + url)
|
||||
else:
|
||||
session_headers2 = session_headers.copy()
|
||||
if session_headers2.get('Authorization'):
|
||||
session_headers2['Authorization'] = 'REDACTED'
|
||||
print('WARN: get_method url: ' + url +
|
||||
' failed with error code ' +
|
||||
str(result.status_code) +
|
||||
' headers: ' + str(session_headers2))
|
||||
return result.content.decode('utf-8')
|
||||
except requests.exceptions.RequestException as ex:
|
||||
session_headers2 = session_headers.copy()
|
||||
if session_headers2.get('Authorization'):
|
||||
session_headers2['Authorization'] = 'REDACTED'
|
||||
if debug and not quiet:
|
||||
print('EX: get_method failed, url: ' + str(url) + ', ' +
|
||||
'headers: ' + str(session_headers2) + ', ' +
|
||||
'params: ' + str(session_params) + ', ' + str(ex))
|
||||
except ValueError as ex:
|
||||
session_headers2 = session_headers.copy()
|
||||
if session_headers2.get('Authorization'):
|
||||
session_headers2['Authorization'] = 'REDACTED'
|
||||
if debug and not quiet:
|
||||
print('EX: get_method failed, url: ' + str(url) + ', ' +
|
||||
'headers: ' + str(session_headers2) + ', ' +
|
||||
'params: ' + str(session_params) + ', ' + str(ex))
|
||||
except SocketError as ex:
|
||||
if not quiet:
|
||||
if ex.errno == errno.ECONNRESET:
|
||||
print('EX: get_method failed, ' +
|
||||
'connection was reset during get_vcard ' + str(ex))
|
||||
return None
|
||||
|
|
57
tests.py
57
tests.py
|
@ -176,6 +176,9 @@ from shares import send_share_via_server
|
|||
from shares import get_shared_items_catalog_via_server
|
||||
from blocking import load_cw_lists
|
||||
from blocking import add_cw_from_lists
|
||||
from happening import dav_month_via_server
|
||||
from happening import dav_day_via_server
|
||||
|
||||
|
||||
TEST_SERVER_GROUP_RUNNING = False
|
||||
TEST_SERVER_ALICE_RUNNING = False
|
||||
|
@ -2946,6 +2949,13 @@ def test_client_to_server(base_dir: str):
|
|||
|
||||
time.sleep(1)
|
||||
|
||||
# set bob to be following the calendar of alice
|
||||
print('Bob follows the calendar of Alice')
|
||||
following_cal_path = \
|
||||
bob_dir + '/accounts/bob@' + bob_domain + '/followingCalendar.txt'
|
||||
with open(following_cal_path, 'w+') as fp:
|
||||
fp.write('alice@' + alice_domain + '\n')
|
||||
|
||||
print('\n\n*******************************************************')
|
||||
print('EVENT: Alice sends to Bob via c2s')
|
||||
|
||||
|
@ -2981,6 +2991,12 @@ def test_client_to_server(base_dir: str):
|
|||
if os.path.isfile(os.path.join(bob_outbox_path, name))]) == 0
|
||||
print('EVENT: all inboxes and outboxes are empty')
|
||||
signing_priv_key_pem = None
|
||||
test_date = datetime.datetime.now()
|
||||
event_date = \
|
||||
str(test_date.year) + '-' + str(test_date.month) + '-' + \
|
||||
str(test_date.day)
|
||||
event_time = '11:45'
|
||||
location = "Kinshasa"
|
||||
send_result = \
|
||||
send_post_via_server(signing_priv_key_pem, __version__,
|
||||
alice_dir, session_alice, 'alice', password,
|
||||
|
@ -2993,6 +3009,7 @@ def test_client_to_server(base_dir: str):
|
|||
cached_webfingers, person_cache, is_article,
|
||||
system_language, languages_understood,
|
||||
low_bandwidth, content_license_url,
|
||||
event_date, event_time, location,
|
||||
True, None, None,
|
||||
conversation_id, None)
|
||||
print('send_result: ' + str(send_result))
|
||||
|
@ -3028,6 +3045,17 @@ def test_client_to_server(base_dir: str):
|
|||
if os.path.isfile(os.path.join(bob_outbox_path, name))]) == 0
|
||||
|
||||
print(">>> s2s post arrived in Bob's inbox")
|
||||
|
||||
calendar_path = bob_dir + '/accounts/bob@' + bob_domain + '/calendar'
|
||||
if not os.path.isdir(calendar_path):
|
||||
print('Missing calendar path: ' + calendar_path)
|
||||
assert os.path.isdir(calendar_path)
|
||||
assert os.path.isdir(calendar_path + '/' + str(test_date.year))
|
||||
assert os.path.isfile(calendar_path + '/' + str(test_date.year) + '/' +
|
||||
str(test_date.month) + '.txt')
|
||||
print(">>> calendar entry created for s2s post which arrived at " +
|
||||
"Bob's inbox")
|
||||
|
||||
print("c2s send success\n\n\n")
|
||||
|
||||
print('\n\nEVENT: Getting message id for the post')
|
||||
|
@ -3147,6 +3175,35 @@ def test_client_to_server(base_dir: str):
|
|||
show_test_boxes('bob', bob_inbox_path, bob_outbox_path)
|
||||
assert len([name for name in os.listdir(alice_inbox_path)
|
||||
if os.path.isfile(os.path.join(alice_inbox_path, name))]) == 0
|
||||
|
||||
print('\n\nEVENT: Bob checks his calendar via caldav')
|
||||
# test caldav result for a month
|
||||
result = \
|
||||
dav_month_via_server(session_bob, http_prefix,
|
||||
'bob', bob_domain, bob_port, True,
|
||||
test_date.year, test_date.month,
|
||||
'bobpass')
|
||||
print('response: ' + str(result))
|
||||
assert 'VCALENDAR' in str(result)
|
||||
assert 'VEVENT' in str(result)
|
||||
# test caldav result for a day
|
||||
result = \
|
||||
dav_day_via_server(session_bob, http_prefix,
|
||||
'bob', bob_domain, bob_port, True,
|
||||
test_date.year, test_date.month,
|
||||
test_date.day, 'bobpass')
|
||||
print('response: ' + str(result))
|
||||
assert 'VCALENDAR' in str(result)
|
||||
assert 'VEVENT' in str(result)
|
||||
# test for incorrect caldav login
|
||||
result = \
|
||||
dav_day_via_server(session_bob, http_prefix,
|
||||
'bob', bob_domain, bob_port, True,
|
||||
test_date.year, test_date.month,
|
||||
test_date.day, 'wrongpass')
|
||||
assert 'VCALENDAR' not in str(result)
|
||||
assert 'VEVENT' not in str(result)
|
||||
|
||||
print('\n\nEVENT: Bob likes the post')
|
||||
send_like_via_server(bob_dir, session_bob,
|
||||
'bob', 'bobpass',
|
||||
|
|
|
@ -267,6 +267,7 @@ def html_calendar(person_cache: {}, css_cache: {}, translate: {},
|
|||
"""
|
||||
domain = remove_domain_port(domain_full)
|
||||
|
||||
text_match = ''
|
||||
default_year = 1970
|
||||
default_month = 0
|
||||
month_number = default_month
|
||||
|
@ -320,11 +321,13 @@ def html_calendar(person_cache: {}, css_cache: {}, translate: {},
|
|||
year, month_number,
|
||||
day_number,
|
||||
person_cache,
|
||||
http_prefix)
|
||||
http_prefix,
|
||||
text_match)
|
||||
day_events = None
|
||||
events = \
|
||||
get_todays_events(base_dir, nickname, domain,
|
||||
year, month_number, day_number)
|
||||
year, month_number, day_number,
|
||||
text_match)
|
||||
if events:
|
||||
if events.get(str(day_number)):
|
||||
day_events = events[str(day_number)]
|
||||
|
@ -337,10 +340,11 @@ def html_calendar(person_cache: {}, css_cache: {}, translate: {},
|
|||
if icalendar:
|
||||
return get_month_events_icalendar(base_dir, nickname, domain,
|
||||
year, month_number, person_cache,
|
||||
http_prefix)
|
||||
http_prefix, text_match)
|
||||
|
||||
events = \
|
||||
get_calendar_events(base_dir, nickname, domain, year, month_number)
|
||||
get_calendar_events(base_dir, nickname, domain, year, month_number,
|
||||
text_match)
|
||||
|
||||
prev_year = year
|
||||
prev_month_number = month_number - 1
|
||||
|
|
|
@ -54,36 +54,40 @@ def _add_embedded_video_from_sites(translate: {}, content: str,
|
|||
if '"' + video_site in content:
|
||||
url = content.split('"' + video_site)[1]
|
||||
if '"' in url:
|
||||
url = url.split('"')[0].replace('/watch?v=', '/embed/')
|
||||
if '&' in url:
|
||||
url = url.split('&')[0]
|
||||
if '?utm_' in url:
|
||||
url = url.split('?utm_')[0]
|
||||
content += \
|
||||
"<center>\n<iframe loading=\"lazy\" src=\"" + \
|
||||
video_site + url + "\" width=\"" + str(width) + \
|
||||
"\" height=\"" + str(height) + \
|
||||
"\" frameborder=\"0\" allow=\"autoplay; fullscreen\" " + \
|
||||
"allowfullscreen></iframe>\n</center>\n"
|
||||
return content
|
||||
url = url.split('"')[0]
|
||||
if '/channel/' not in url:
|
||||
url = url.replace('/watch?v=', '/embed/')
|
||||
if '&' in url:
|
||||
url = url.split('&')[0]
|
||||
if '?utm_' in url:
|
||||
url = url.split('?utm_')[0]
|
||||
content += \
|
||||
"<center>\n<iframe loading=\"lazy\" src=\"" + \
|
||||
video_site + url + "\" width=\"" + str(width) + \
|
||||
"\" height=\"" + str(height) + \
|
||||
"\" frameborder=\"0\" allow=\"autoplay; fullscreen\" " + \
|
||||
"allowfullscreen></iframe>\n</center>\n"
|
||||
return content
|
||||
|
||||
video_site = 'https://youtu.be/'
|
||||
if '"' + video_site in content:
|
||||
url = content.split('"' + video_site)[1]
|
||||
if '"' in url:
|
||||
url = 'embed/' + url.split('"')[0]
|
||||
if '&' in url:
|
||||
url = url.split('&')[0]
|
||||
if '?utm_' in url:
|
||||
url = url.split('?utm_')[0]
|
||||
video_site = 'https://www.youtube.com/'
|
||||
content += \
|
||||
"<center>\n<iframe loading=\"lazy\" src=\"" + \
|
||||
video_site + url + "\" width=\"" + str(width) + \
|
||||
"\" height=\"" + str(height) + \
|
||||
"\" frameborder=\"0\" allow=\"autoplay; fullscreen\" " + \
|
||||
"allowfullscreen></iframe>\n</center>\n"
|
||||
return content
|
||||
url = url.split('"')[0]
|
||||
if '/channel/' not in url:
|
||||
url = 'embed/' + url
|
||||
if '&' in url:
|
||||
url = url.split('&')[0]
|
||||
if '?utm_' in url:
|
||||
url = url.split('?utm_')[0]
|
||||
video_site = 'https://www.youtube.com/'
|
||||
content += \
|
||||
"<center>\n<iframe loading=\"lazy\" src=\"" + \
|
||||
video_site + url + "\" width=\"" + str(width) + \
|
||||
"\" height=\"" + str(height) + \
|
||||
"\" frameborder=\"0\" allow=\"autoplay; fullscreen\" " + \
|
||||
"allowfullscreen></iframe>\n</center>\n"
|
||||
return content
|
||||
|
||||
invidious_sites = (
|
||||
'https://invidious.snopyta.org',
|
||||
|
|
|
@ -316,8 +316,9 @@ def _webfinger_update_vcard(wf_json: {}, actor_json: {}) -> bool:
|
|||
"""Updates the vcard link
|
||||
"""
|
||||
for link in wf_json['links']:
|
||||
if link['type'] == 'text/vcard':
|
||||
return False
|
||||
if link.get('type'):
|
||||
if link['type'] == 'text/vcard':
|
||||
return False
|
||||
wf_json['links'].append({
|
||||
"href": actor_json['url'],
|
||||
"rel": "http://webfinger.net/rel/profile-page",
|
||||
|
|
Loading…
Reference in New Issue