2023-12-04 07:43:09 +01:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
|
|
|
import json
|
|
|
|
import urllib.request
|
|
|
|
|
|
|
|
from base64 import b64decode
|
|
|
|
from subprocess import run
|
|
|
|
|
|
|
|
import cherrypy
|
|
|
|
|
2023-12-09 08:03:09 +01:00
|
|
|
from defusedxml.ElementTree import fromstring
|
2023-12-04 07:43:09 +01:00
|
|
|
from icalendar import Calendar, Todo
|
|
|
|
|
|
|
|
GITEA_REPO_URL="https://git.bitmessage.org/api/v1"
|
|
|
|
|
|
|
|
combined = {}
|
|
|
|
|
|
|
|
def get_combined(token):
|
|
|
|
for q in ("assigned", "review_requested"):
|
|
|
|
req = urllib.request.Request(GITEA_REPO_URL
|
|
|
|
+ f"/repos/issues/search?{q}=true")
|
|
|
|
req.add_header("Accept", "application/json")
|
|
|
|
req.add_header("Content-Type", "application/json")
|
|
|
|
req.add_header("Authorization", "token " + token)
|
|
|
|
|
|
|
|
with urllib.request.urlopen(req) \
|
|
|
|
as response:
|
|
|
|
issues = json.load(response)
|
|
|
|
for issue in issues:
|
|
|
|
_id = issue['id']
|
|
|
|
if _id in combined:
|
|
|
|
combined[_id]['categories'].append(q)
|
|
|
|
else:
|
|
|
|
combined[_id] = issue
|
|
|
|
combined[_id]['categories'] = [q]
|
|
|
|
return combined
|
|
|
|
|
|
|
|
def process_combined(combined):
|
|
|
|
cal = Calendar()
|
|
|
|
for _id, issue in combined.items():
|
|
|
|
todo = Todo()
|
|
|
|
todo['uid'] = _id
|
|
|
|
todo['dtstamp'] = issue['created_at']
|
|
|
|
if issue['due_date']:
|
|
|
|
todo['due'] = issue['due_date']
|
|
|
|
todo['summary'] = issue['title']
|
|
|
|
todo['description'] = issue['body']
|
|
|
|
todo['categories'] = issue['categories']
|
|
|
|
cal.add_component(todo)
|
|
|
|
return cal.to_ical()
|
|
|
|
|
|
|
|
def get_token(input_token):
|
2023-12-04 09:56:04 +01:00
|
|
|
token = input_token.removeprefix("Basic ")
|
2023-12-04 07:43:09 +01:00
|
|
|
token = b64decode(token).decode('utf8', 'ignore')
|
2023-12-04 09:18:55 +01:00
|
|
|
with cherrypy.HTTPError.handle(ValueError, 401):
|
2023-12-04 09:09:08 +01:00
|
|
|
_, token = token.split(":", 2)
|
2023-12-04 07:43:09 +01:00
|
|
|
return token
|
|
|
|
|
|
|
|
class Root:
|
|
|
|
@cherrypy.expose
|
2023-12-09 08:03:09 +01:00
|
|
|
def index(self):
|
2023-12-04 09:56:04 +01:00
|
|
|
cherrypy.response.headers['WWW-Authenticate'] = \
|
|
|
|
'Basic realm="ICS access"'
|
2023-12-04 07:43:09 +01:00
|
|
|
authorization = cherrypy.request.headers.get('Authorization', ':')
|
2023-12-04 09:06:22 +01:00
|
|
|
if not authorization:
|
|
|
|
raise cherrypy.HTTPError(401, 'Unauthorized')
|
2023-12-04 07:43:09 +01:00
|
|
|
token = get_token(authorization)
|
2023-12-04 09:06:22 +01:00
|
|
|
if not token:
|
|
|
|
raise cherrypy.HTTPError(401, 'Unauthorized')
|
2023-12-04 07:43:09 +01:00
|
|
|
combined = get_combined(token)
|
|
|
|
if not combined:
|
|
|
|
raise cherrypy.HTTPError(401, 'Unauthorized')
|
|
|
|
return(process_combined(combined))
|
|
|
|
|
2023-12-09 08:03:09 +01:00
|
|
|
class Dav:
|
|
|
|
exposed = True
|
|
|
|
#@cherrypy.expose
|
|
|
|
@cherrypy.tools.accept(media='application/xml')
|
|
|
|
def PROPFIND(self):
|
|
|
|
cl = cherrypy.request.headers['Content-Length']
|
|
|
|
rawbody = cherrypy.request.body.read(size=int(cl))
|
|
|
|
et = fromstring(rawbody)
|
|
|
|
for child in et:
|
|
|
|
if child.tag == '{DAV:}prop':
|
|
|
|
for x in child:
|
|
|
|
print("T:", x.tag, "; A:", x.attrib)
|
|
|
|
#print(et)
|
|
|
|
print(f"BODY {rawbody}")
|
|
|
|
|
|
|
|
|
2023-12-04 07:43:09 +01:00
|
|
|
if __name__ == '__main__':
|
2023-12-09 08:03:09 +01:00
|
|
|
conf = {
|
|
|
|
'/dav': {
|
|
|
|
'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
|
|
|
|
'request.methods_with_bodies': ('POST',
|
|
|
|
'PUT',
|
|
|
|
'PROPFIND'),
|
|
|
|
}
|
|
|
|
}
|
2023-12-04 08:58:33 +01:00
|
|
|
cherrypy.config.update({'server.socket_host': '0.0.0.0',
|
|
|
|
'server.socket_port': 8080,
|
|
|
|
})
|
2023-12-09 08:03:09 +01:00
|
|
|
webapp = Root()
|
|
|
|
webapp.dav = Dav()
|
|
|
|
cherrypy.quickstart(webapp, '/', conf)
|