diff --git a/.gitignore b/.gitignore index da0a2c4..6ed89e9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ /ics *.html *.ics +/tmp/ diff --git a/go.mod b/go.mod index 9763513..3d89765 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,6 @@ module github.com/cbix/ics go 1.21.6 require ( - github.com/arran4/golang-ical v0.2.3 // indirect - github.com/coreos/go-systemd/v22 v22.5.0 // indirect + github.com/arran4/golang-ical v0.2.3 + github.com/coreos/go-systemd/v22 v22.5.0 ) diff --git a/go.sum b/go.sum index 0dd9286..de3e61e 100644 --- a/go.sum +++ b/go.sum @@ -4,16 +4,20 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8 github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= 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/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= 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/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= 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 h1:hjy8E9ON/egN1tAYqKb61G10WtihqetD4sz2H+8nIeA= gopkg.in/yaml.v3 v3.0.0/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/theateraachen.go b/theateraachen.go new file mode 100644 index 0000000..cd8370a --- /dev/null +++ b/theateraachen.go @@ -0,0 +1,119 @@ +package main + +import ( + "bufio" + "encoding/json" + "errors" + "fmt" + "io" + "net/http" + "strings" + "time" + + "github.com/arran4/golang-ical" +) + +func init() { + RegisterAdapter("theateraachen", theateraachenUrl, "Theatre in Aachen (beta)", theateraachenAdapter) +} + +type theateraachenData struct { + Ecommerce theateraachenEcommerce `json:"ecommerce"` +} + +type theateraachenEcommerce struct { + Impressions []theateraachenImpression `json:"impressions"` +} + +type theateraachenImpression struct { + Id string `json:"id"` + Name string `json:"name"` + Variant theateraachenVariant `json:"variant"` + Position int `json:"position"` +} + +type theateraachenVariant struct { + DateTime string `json:"dateTime"` + VenueName string `json:"venueName"` + GenreName string `json:"genreName"` +} + +func (i theateraachenImpression) StartTime() time.Time { + t, _ := time.ParseInLocation(theateraachenLocalFormat, i.Variant.DateTime, theateraachenTZ) + return t +} + +func (i theateraachenImpression) EndTime() time.Time { + return i.StartTime().Add(2 * time.Hour) +} + +const ( + theateraachenUrl = "https://www.theateraachen.de/" + theateraachenSrc = "https://theateraachen.reservix.de/events/%d" + theateraachenJsonPrefix = "var impressionData = " + theateraachenLocalFormat = "2006-01-02 15:04:05" +) + +var theateraachenTZ, _ = time.LoadLocation("Europe/Berlin") + +func theateraachenAdapter(w io.Writer) error { + cal := ics.NewCalendarFor("github.com/cbix/ics") + cal.SetName("Theater Aachen") + cal.SetColor("#f0ff3c") + cal.SetUrl(theateraachenUrl) + cal.SetTimezoneId("Europe/Berlin") + now := time.Now() + pos := 0 +page: + for i := 1; i < 10; i++ { + req, err := http.NewRequest("GET", fmt.Sprintf(theateraachenSrc, i), nil) + if err != nil { + return err + } + req.Header.Set("User-Agent", "Mozilla/5.0 Gecko/20100101 Firefox/122.0") + res, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + if res.StatusCode != http.StatusOK { + return errors.New(fmt.Sprintf("received status %s", res.Status)) + } + scanner := bufio.NewScanner(res.Body) + var jsonData []byte + found := false + for scanner.Scan() { + l := strings.Trim(scanner.Text(), " ;") + if strings.HasPrefix(l, theateraachenJsonPrefix) { + jsonData = []byte(strings.TrimPrefix(l, theateraachenJsonPrefix)) + found = true + break + } + } + if !found { + return errors.New("Expected JSON data not found") + } + var data theateraachenData + err = json.Unmarshal(jsonData, &data) + if err != nil { + return err + } + if len(data.Ecommerce.Impressions) == 0 { + break + } + for _, ev := range data.Ecommerce.Impressions { + p := ev.Position + if p <= pos { + break page + } + pos = p + event := cal.AddEvent(fmt.Sprintf("%s@theateraachen.reservix.de", ev.Id)) + event.SetDtStampTime(now) + event.SetSummary(ev.Name) + event.SetDescription(ev.Variant.GenreName) + event.SetLocation(ev.Variant.VenueName) + event.SetStartAt(ev.StartTime()) + event.SetEndAt(ev.EndTime()) + } + } + return cal.SerializeTo(w) +}