gitea-to-ics/gitea-caldav.py

106 lines
3.3 KiB
Python
Raw Normal View History

2023-12-04 06:43:09 +00:00
#!/usr/bin/env python3
import json
import urllib.request
from base64 import b64decode
from subprocess import run
import cherrypy
2023-12-09 07:03:09 +00:00
from defusedxml.ElementTree import fromstring
2023-12-04 06:43:09 +00: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 08:56:04 +00:00
token = input_token.removeprefix("Basic ")
2023-12-04 06:43:09 +00:00
token = b64decode(token).decode('utf8', 'ignore')
2023-12-04 08:18:55 +00:00
with cherrypy.HTTPError.handle(ValueError, 401):
2023-12-04 08:09:08 +00:00
_, token = token.split(":", 2)
2023-12-04 06:43:09 +00:00
return token
class Root:
@cherrypy.expose
2023-12-09 07:03:09 +00:00
def index(self):
2023-12-04 08:56:04 +00:00
cherrypy.response.headers['WWW-Authenticate'] = \
'Basic realm="ICS access"'
2023-12-04 06:43:09 +00:00
authorization = cherrypy.request.headers.get('Authorization', ':')
if not authorization:
raise cherrypy.HTTPError(401, 'Unauthorized')
2023-12-04 06:43:09 +00:00
token = get_token(authorization)
if not token:
raise cherrypy.HTTPError(401, 'Unauthorized')
2023-12-04 06:43:09 +00:00
combined = get_combined(token)
if not combined:
raise cherrypy.HTTPError(401, 'Unauthorized')
return(process_combined(combined))
2023-12-09 07:03:09 +00: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 06:43:09 +00:00
if __name__ == '__main__':
2023-12-09 07:03:09 +00:00
conf = {
'/dav': {
'request.dispatch': cherrypy.dispatch.MethodDispatcher(),
'request.methods_with_bodies': ('POST',
'PUT',
'PROPFIND'),
}
}
2023-12-04 07:58:33 +00:00
cherrypy.config.update({'server.socket_host': '0.0.0.0',
'server.socket_port': 8080,
})
2023-12-09 07:03:09 +00:00
webapp = Root()
webapp.dav = Dav()
cherrypy.quickstart(webapp, '/', conf)