Skip to content

Commit

Permalink
Stable Version v0.4.0-jle
Browse files Browse the repository at this point in the history
  • Loading branch information
JM-Lemmi authored Feb 24, 2022
2 parents e5d9b96 + 0e1f0b5 commit 8c85af2
Show file tree
Hide file tree
Showing 9 changed files with 150 additions and 40 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
ical-relay
/dist
rice-box.go
.devcontainer
addical.ics
config.toml
10 changes: 10 additions & 0 deletions Dockerfile-build
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
FROM golang

RUN apt-get update && apt-get install golang-rice git -y

WORKDIR /app
COPY . /app

RUN rice embed-go && go build .

CMD ./ical-relay
39 changes: 35 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,48 @@ Relay ical event url and exclude events based on a regex.
* Run from source: `go run .`
* Build and run: `rice embed-go && go build . && ./ical-relay`

Access filtered ical file on `server:8080/profiles/profilename`

Add `config.toml` to executing directory for configuration options.

All events in `addical.ics` will be added to the filtered ical.

# Config
```toml
url = "https://example.com/events.ical"

[server]
addr = ":8080"
loglevel = "info"

[profiles]
[profiles.p1]
[profiles.profilename]
url = "https://example.com/events.ical"
regex = ["pattern1", "pattern2"]
public = true
from = "1970-01-01T00:00:00Z"
until = "2100-01-01T00:00:00Z"
passid = true
```
Access filtered stream on `/profiles/p1`

### URL

The URL of the original ical.

If pointed to localhost:8080/profiles/otherprofile it can be used to combine multiple profiles.

### Regex

The Regex Patterns are matched against both the Summary as well as the ID. This can be used to exclude one specific entry.

### From & Until

The From and Until value allow for excluding the Pattern only in the selected Timeframe.

Time has to be provided in compliance with RFC3339.

### PassID

Bool Value to allow passing the original EventIDs to the new calendar.

### AddURL

A list of URLs pointing to other icals, whose entries should be added to the final calendar.
8 changes: 7 additions & 1 deletion config.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package main
import (
"regexp"

"time"

"github.com/BurntSushi/toml"
log "github.com/sirupsen/logrus"
)
Expand All @@ -12,8 +14,13 @@ type regex struct {
}

type profile struct {
URL string
RegEx []regex
Public bool
From time.Time
Until time.Time
PassID bool
AddURL []string
}

type serverConfig struct {
Expand All @@ -23,7 +30,6 @@ type serverConfig struct {

// Config represents configuration for the application
type Config struct {
URL string
Profiles map[string]profile
Server serverConfig
}
Expand Down
10 changes: 0 additions & 10 deletions config.toml

This file was deleted.

13 changes: 13 additions & 0 deletions config.toml.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[server]
addr = ":8080"
loglevel = "debug"

[profiles]
[profiles.relay]
url = "https://example.com/calendar.ics"
regex = ["testentry"]
public = true
from = "2021-12-02T00:00:00Z"
until = "2021-12-31T00:00:00Z"
passid = true
addurl = ["https://othersource.com/othercalendar.ics"]
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.12
require (
github.com/BurntSushi/toml v0.3.1
github.com/GeertJohan/go.rice v1.0.0
github.com/arran4/golang-ical v0.0.0-20211212012649-32b67e209c4f
github.com/arran4/golang-ical v0.0.0-20220220103556-c519bf07e7e6
github.com/google/uuid v1.1.1
github.com/gorilla/mux v1.7.0
github.com/sirupsen/logrus v1.4.0
Expand Down
16 changes: 14 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,12 @@ github.com/GeertJohan/go.rice v1.0.0 h1:KkI6O9uMaQU3VEKaj01ulavtF7o1fWT7+pk/4voi
github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0=
github.com/akavel/rsrc v0.8.0 h1:zjWn7ukO9Kc5Q62DOJCcxGpXC18RawVtYAGdz2aLlfw=
github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c=
github.com/arran4/golang-ical v0.0.0-20190321110053-15f120a86efc h1:8q5Kca2k9P5QNJegdhWUfdREYVdemKe3FD4we/8r3Nc=
github.com/arran4/golang-ical v0.0.0-20190321110053-15f120a86efc/go.mod h1:iDFK01A1Yb5HiGn6YEaZ516fCow5FnEAjyMfOOIYMuo=
github.com/arran4/golang-ical v0.0.0-20220220103556-c519bf07e7e6 h1:ZGTgVX7P0syB2TZ/2P4N0pdOw4aTL8NOtniQbIwWGsg=
github.com/arran4/golang-ical v0.0.0-20220220103556-c519bf07e7e6/go.mod h1:BSTTrYHuM12oAL8jDdcmPdw02SBThKYWNFHQlvEG6b0=
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/daaku/go.zipexe v1.0.0 h1:VSOgZtH418pH9L16hC/JrgSNJbbAL26pj7lmD1+CGdY=
github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.1.1 h1:Gkbcsh/GbpXz7lPftLA3P6TYMwjCLYm83jiFQZF/3gY=
Expand All @@ -20,15 +22,21 @@ github.com/jessevdk/go-flags v1.4.0 h1:4IU2WS7AumrZ/40jfhf4QVDMsQwqA7VEHozFRrGAR
github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229 h1:E2B8qYyeSgv5MXpmzZXRNp8IAQ4vjxIjhpAf5hv/tAg=
github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.4.0 h1:yKenngtzGh+cUSSh6GWbxW2abRqhYUSR/t/6+2QqNvE=
github.com/sirupsen/logrus v1.4.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.0.1 h1:tY9CJiPnMXf1ERmG2EyK7gNUd+c6RKGD0IfU8WdUSz8=
Expand All @@ -37,3 +45,7 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793 h1:u+LnwYTOOW7Ukr/fppxEb1
golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
89 changes: 67 additions & 22 deletions handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"html/template"
"io/ioutil"
"net/http"
"os"

ics "github.com/arran4/golang-ical"
"github.com/google/uuid"
Expand All @@ -24,6 +25,7 @@ func indexHandler(w http.ResponseWriter, r *http.Request) {

func profileHandler(w http.ResponseWriter, r *http.Request) {
requestLogger := log.WithFields(log.Fields{"request": uuid.New().String()})
requestLogger.Infoln("Client-addr:", r.RemoteAddr)
// load profile
vars := mux.Vars(r)
profileName := vars["profile"]
Expand All @@ -35,7 +37,7 @@ func profileHandler(w http.ResponseWriter, r *http.Request) {
return
}
// request original ical
response, err := http.Get(conf.URL)
response, err := http.Get(profile.URL)
if err != nil {
requestLogger.Errorln(err)
http.Error(w, fmt.Sprintf("Error requesting original URL: %s", err.Error()), 500)
Expand Down Expand Up @@ -74,45 +76,88 @@ func profileHandler(w http.ResponseWriter, r *http.Request) {
}
excludedEvents := 0
for _, event := range calendar.Events() {
// extract summary from original event
// extract summary and time from original event
summary := event.GetProperty(ics.ComponentPropertySummary).Value
date, _ := event.GetStartAt()
id := event.Id()
// check if one of the profiles regex's matches summary
exclude := false
for _, excludeRe := range profile.RegEx {
if excludeRe.MatchString(summary) {
exclude = true
break
if date.After(profile.From) && profile.Until.After(date) {
if excludeRe.MatchString(summary) || excludeRe.MatchString(id) {
exclude = true
break
}
}
}
if !exclude {
// add event to new calendar
// overwrite uid to prevent conflicts with original ical stream
h := md5.New()
h.Write([]byte(event.Id()))
h.Write([]byte(conf.URL))
id := fmt.Sprintf("%x@%s", h.Sum(nil), "ical-relay")
newEvent := newCalendar.AddEvent(id)
// exclude organizer, uuid, attendee property due to broken escaping
for _, property := range event.Properties {
if (property.IANAToken != string(ics.ComponentPropertyOrganizer)) && (property.IANAToken != string(ics.ComponentPropertyUniqueId) && (property.IANAToken != string(ics.ComponentPropertyAttendee))) {
newEvent.Properties = append(newEvent.Properties, property)
}
if !profile.PassID {
// overwrite uid to prevent conflicts with original ical stream
h := md5.New()
h.Write([]byte(event.Id()))
h.Write([]byte(profile.URL))
id = fmt.Sprintf("%x@%s", h.Sum(nil), "ical-relay")
event.SetProperty(ics.ComponentPropertyUniqueId, id)
}
sequenceProperty := ics.IANAProperty{BaseProperty: ics.BaseProperty{IANAToken: "SEQUENCE", Value: "0"}}
newEvent.Properties = append(newEvent.Properties, sequenceProperty)
newCalendar.AddVEvent(event)
} else {
excludedEvents++
requestLogger.Debugf("Excluding event with summary '%s'\n", summary)
requestLogger.Debugf("Excluding event '%s' with id %s\n", summary, id)
}
}

// read additional ical files
addedEvents := 0
if _, err := os.Stat("addical.ics"); err == nil {
addicsfile, _ := os.Open("addical.ics")
addics, _ := ics.ParseCalendar(addicsfile)
for _, event := range addics.Events() {
newCalendar.AddVEvent(event)
addedEvents++
}
}

// read additional ical url
if len(profile.AddURL) != 0 {
for _, url := range profile.AddURL {
response, err := http.Get(url)
if err != nil {
requestLogger.Errorln(err)
http.Error(w, fmt.Sprintf("Error requesting additional URL: %s", err.Error()), 500)
return
}
if response.StatusCode != 200 {
requestLogger.Errorf("Unexpected status '%s' from additional URL\n", response.Status)
resp, err := ioutil.ReadAll(response.Body)
if err != nil {
requestLogger.Errorln(err)
}
requestLogger.Debugf("Full response body: %s\n", resp)
http.Error(w, fmt.Sprintf("Error response from additional URL: Status %s", response.Status), 500)
return
}
// parse aditional calendar
addcal, err := ics.ParseCalendar(response.Body)
if err != nil {
requestLogger.Errorln(err)
}
// add to new calendar
for _, event := range addcal.Events() {
newCalendar.AddVEvent(event)
addedEvents++
}
}
}
// make sure new calendar has all events but excluded
eventCountDiff := len(newCalendar.Events()) + excludedEvents - len(calendar.Events())

// make sure new calendar has all events but excluded and added
eventCountDiff := len(newCalendar.Events()) + excludedEvents - addedEvents - len(calendar.Events())
if eventCountDiff == 0 {
requestLogger.Debugf("Output validation successfull; event counts match")
} else {
requestLogger.Warnf("This shouldn't happen, event count diff: %d", eventCountDiff)
}
requestLogger.Debugf("Excluded %d events", excludedEvents)
requestLogger.Debugf("Excluded %d events and added %d events", excludedEvents, addedEvents)
// return new calendar
w.Header().Set("Content-Type", "text/calendar; charset=utf-8")
w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=%s.ics", profileName))
Expand Down

0 comments on commit 8c85af2

Please sign in to comment.