Skip to content
GitLab
Explore
Sign in
Primary navigation
Search or go to…
Project
H
hub
Manage
Activity
Members
Labels
Plan
Issues
Issue boards
Milestones
Wiki
Code
Merge requests
Repository
Branches
Commits
Tags
Repository graph
Compare revisions
Deploy
Package registry
Operate
Terraform modules
Analyze
Contributor analytics
Help
Help
Support
GitLab documentation
Compare GitLab plans
Community forum
Contribute to GitLab
Provide feedback
Terms and privacy
Keyboard shortcuts
?
Snippets
Groups
Projects
Show more breadcrumbs
thomasDOTwtf
hub
Commits
1d24046c
Commit
1d24046c
authored
5 months ago
by
Andreas Hubel
Browse files
Options
Downloads
Patches
Plain Diff
draft: new ScheduleICal class to import events from an ical feed
parent
0037fb9f
Branches
720-schedule_source
No related tags found
No related merge requests found
Changes
1
Show whitespace changes
Inline
Side-by-side
Showing
1 changed file
src/core/schedules/scheduleical.py
+235
-0
235 additions, 0 deletions
src/core/schedules/scheduleical.py
with
235 additions
and
0 deletions
src/core/schedules/scheduleical.py
0 → 100644
+
235
−
0
View file @
1d24046c
import
re
from
urllib.parse
import
urlparse
import
icalendar
import
requests
from
requests_file
import
FileAdapter
from
.base
import
BaseScheduleSupport
,
filter_additional_data
,
schedule_time_to_timedelta
s
=
requests
.
Session
()
s
.
mount
(
'
file://
'
,
FileAdapter
())
class
ScheduleICalSupport
(
BaseScheduleSupport
):
identifier
=
'
schedule-ical
'
readonly
=
True
configuration_fields
=
{
# 'key': (type, default value, mandatory, translation text)
'
kind
'
:
(
'
string
'
,
'
assembly
'
,
False
,
'
kind of events, either
"
assembly
"
or
"
official
"
or
"
sos
"
or
"
lightning
"'
),
'
headers
'
:
(
'
dict
'
,
{},
False
,
'
HTTP headers to send with the request e.g. Authorization
'
),
}
def
ready
(
self
):
r
=
s
.
head
(
self
.
remote_url
)
return
r
.
ok
def
fetch
(
self
):
"""
This method is the workhorse of the schedule support module:
its job is to query upstream for the current set of data.
It shall return a dictionary with keys
'
rooms
'
and
'
events
'
,
each containing a dictionary with entries mapping a source id
to a dictionary which can be understood by Room.from_dict()
and Event.from_dict() respectively.
The hub will update events it already knows by the source id:
all events need to have an unique but stable identifier, i.e.
if the name of the event changes the identifier must not change!
"""
headers
=
{}
if
self
.
conf_value
(
'
headers
'
):
headers
=
self
.
conf_value
(
'
headers
'
)
schedule
=
ScheduleICal
.
from_url
(
self
.
remote_url
,
headers
=
headers
)
instance
=
urlparse
(
schedule
.
get
(
'
base_url
'
,
self
.
remote_url
))
host
=
f
'
//
{
instance
.
netloc
}
'
kind
=
self
.
conf_value
(
'
kind
'
)
def
ensure_full_url
(
uri
):
if
not
uri
:
return
None
if
not
uri
.
startswith
(
'
http
'
)
and
not
uri
.
startswith
(
'
//
'
):
return
f
'
{
host
}{
uri
}
'
return
uri
return
{
'
version
'
:
schedule
.
version
(),
'
rooms
'
:
{
r
[
'
name
'
]:
r
for
r
in
schedule
.
rooms
()},
'
events
'
:
{
e
.
get
(
'
id
'
):
{
'
guid
'
:
e
.
get
(
'
guid
'
),
'
slug
'
:
e
.
get
(
'
slug
'
).
split
(
f
"
{
e
.
get
(
'
id
'
)
}
-
"
)[
1
][
0
:
150
].
strip
(
'
-
'
)
or
e
.
get
(
'
slug
'
)[
0
:
150
].
strip
(
'
-
'
),
'
name
'
:
e
.
get
(
'
title
'
),
'
language
'
:
e
.
get
(
'
language
'
),
'
abstract
'
:
e
.
get
(
'
abstract
'
)
or
''
,
'
description
'
:
e
.
get
(
'
description
'
)
or
''
,
'
track
'
:
e
.
get
(
'
track
'
),
'
room
'
:
e
.
get
(
'
room
'
),
'
schedule_start
'
:
e
.
get
(
'
date
'
),
'
schedule_duration
'
:
str
(
schedule_time_to_timedelta
(
e
.
get
(
'
duration
'
))),
'
is_public
'
:
True
,
'
kind
'
:
kind
,
'
speakers
'
:
e
.
get
(
'
persons
'
,
[]),
'
banner_image_url
'
:
ensure_full_url
(
e
.
get
(
'
logo
'
)),
'
additional_data
'
:
filter_additional_data
(
e
,
self
.
computed_data
(
e
)),
}
for
e
in
schedule
.
events
()
},
}
def
computed_data
(
self
,
event
:
dict
):
# TODO only add feedback_url if feedback is enabled via configuraiton_fields in ScheduleSource config
if
self
.
conf_value
(
'
feedback
'
):
return
{
'
feedback_url
'
:
f
"
{
event
[
'
url
'
]
}
feedback/
"
}
return
{}
class
ScheduleICal
:
"""
Schedule from iCal feed
"""
_cal
=
None
_events
=
None
def
__init__
(
self
,
cal
,
event_map
=
False
):
self
.
_cal
=
cal
# if event_map:
# self._events = {e.get('guid'): e for e in self.events()}
@classmethod
def
from_url
(
cls
,
url
,
client
=
None
,
headers
=
None
,
event_map
=
False
):
r
=
(
client
if
client
else
s
).
get
(
url
=
re
.
sub
(
r
'
^webcal
'
,
'
http
'
,
url
),
headers
=
headers
)
if
r
.
ok
is
False
:
raise
Exception
(
f
'
Request failed, HTTP
{
r
.
status_code
}
.
'
)
cal
=
icalendar
.
Calendar
.
from_ical
(
r
.
text
)
# Close the raw file handle if it's still open
if
hasattr
(
r
,
'
raw
'
)
and
r
.
raw
.
closed
is
False
:
r
.
raw
.
close
()
return
ScheduleICal
(
cal
,
event_map
=
event_map
)
@classmethod
def
event_to_dict
(
e
:
icalendar
.
Event
,
context
:
ScheduleICal
)
->
dict
:
# title, subtitle, event_type = re.match(r"^(.+?)(?:( ?[:–] .+?))?(?: \((.+?)\))?$", e.name).groups()
(
track
,)
=
[
str
(
c
)
for
c
in
e
.
get
(
'
categories
'
).
cats
]
or
[
None
]
begin
=
e
[
'
dtstart
'
].
dt
end
=
e
[
'
dtend
'
].
dt
duration
=
end
-
begin
return
{
k
:
(
v
if
isinstance
(
v
,
list
)
or
v
is
None
else
str
(
v
))
for
k
,
v
in
{
'
guid
'
:
gen_uuid
(
e
[
'
uid
'
]),
'
id
'
:
e
[
'
event-id
'
],
'
title
'
:
e
.
get
(
'
summary
'
),
'
subtitle
'
:
''
,
'
abstract
'
:
e
[
'
description
'
],
'
description
'
:
''
,
# empty description for pretalx importer (temporary workaround)
'
date
'
:
begin
.
isoformat
(),
'
start
'
:
begin
.
strftime
(
'
%H:%M
'
),
'
duration
'
:
format_duration
(
duration
),
'
room
'
:
track
,
# context['name'],
'
persons
'
:
[{
**
p
,
'
id
'
:
0
}
for
p
in
extract_persons
(
e
)],
'
track
'
:
track
,
'
language
'
:
'
de
'
,
'
type
'
:
'
Session
'
,
'
url
'
:
e
.
get
(
'
url
'
,
None
),
}.
items
()
}
def
__getitem__
(
self
,
key
):
return
self
.
_cal
[
key
]
def
get
(
self
,
key
,
default
=
None
):
return
self
.
_cal
.
get
(
key
,
default
)
def
schedule
(
self
):
raise
'
Not implemented
'
def
version
(
self
):
raise
'
Not implemented
'
def
days
(
self
):
raise
'
Not implemented
'
def
rooms
(
self
):
# try to access the room dict from schedule.json gen 2021
rooms
=
self
.
_schedule
.
get
(
'
conference
'
,
{}).
get
(
'
rooms
'
,
[])
if
rooms
:
return
list
(
rooms
)
# looks like we have an older schudule.json (gen 2020), without a dedicated room list
# so we have use a fallback and iterate all days adding the rooms to a set, creating uniqueness
rooms
=
set
()
for
day
in
self
.
days
():
for
roomname
in
day
.
get
(
'
rooms
'
):
rooms
.
add
(
roomname
)
return
[{
'
name
'
:
name
}
for
name
in
rooms
]
def
events
(
self
):
for
event
in
self
.
_cal
.
walk
(
'
vevent
'
):
yield
self
.
event
(
event
)
def
event
(
self
,
guid
):
if
guid
in
self
.
_events
:
return
self
.
_events
[
guid
]
return
None
def
__str__
(
self
):
return
json
.
dumps
(
self
.
_cal
,
indent
=
2
)
def
extract_persons
(
e
:
icalendar
.
Event
)
->
list
:
person_str
=
str
(
e
.
get
(
'
location
'
,
''
)).
replace
(
'
und
'
,
'
;
'
).
strip
()
# persons = re.split(r'\s*[,;/]\s*', person_str)
persons
=
re
.
split
(
r
'
[,;/](?![^()]*\))
'
,
person_str
)
if
len
(
persons
)
==
0
:
return
[]
pattern
=
r
'
([^()]+)(?:\((\w{2,3}\s+)?([^)]*)\))
'
result
=
[]
for
p
in
persons
:
# p is either "name (org)" or or "name (org role)" or "name (name@org.tld)"
match
=
re
.
match
(
pattern
,
p
)
if
match
:
name
,
org
,
role
=
match
.
groups
()
if
role
and
'
@
'
in
role
:
match
=
re
.
search
(
r
'
@(.+)(\.de)?$
'
,
role
)
org
=
match
.
group
(
1
)
result
.
append
({
'
name
'
:
name
.
strip
(),
'
org
'
:
org
.
strip
(),
'
email
'
:
role
.
strip
(),
'
guid
'
:
gen_person_uuid
(
role
)})
else
:
if
not
org
:
if
len
(
role
)
<=
3
:
org
=
role
role
=
None
else
:
# try catch `Distribution Cordinator, ZER` and split org
m
=
re
.
match
(
r
'
^(.+?), (\w{2,3})$
'
,
role
)
if
m
:
org
=
m
.
group
(
2
)
role
=
m
.
group
(
1
)
if
name
:
result
.
append
(
{
'
name
'
:
name
.
strip
(),
'
org
'
:
org
.
strip
()
if
org
else
None
,
'
role
'
:
role
.
strip
()
if
role
else
None
,
}
)
elif
p
:
result
.
append
(
{
'
name
'
:
p
.
strip
(),
}
)
return
result
This diff is collapsed.
Click to expand it.
Preview
0%
Loading
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Save comment
Cancel
Please
register
or
sign in
to comment