diff --git a/README.md b/README.md new file mode 100644 index 0000000..0e00810 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# moca24-ics + +Smal tool that fetches the [MOCA 2024 calendar](https://moca.camp/calendar/index.html) via HackerTracker API and prints it in ICS format. diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2dc9f59 --- /dev/null +++ b/go.mod @@ -0,0 +1,7 @@ +module git.cicciogatto.lol/insomniac/moca-ics + +go 1.22.6 + +require github.com/sirupsen/logrus v1.9.3 + +require golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 // indirect diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..21f9bfb --- /dev/null +++ b/go.sum @@ -0,0 +1,15 @@ +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/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.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +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= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8 h1:0A+M6Uqn+Eje4kHMK80dtF3JCXC4ykBgQG4Fe06QRhQ= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..2f5f1b7 --- /dev/null +++ b/main.go @@ -0,0 +1,184 @@ +package main + +import ( + "bytes" + "encoding/json" + "fmt" + "io" + "net/http" + "net/url" + "strings" + "time" + + "github.com/sirupsen/logrus" +) + +var ( + hturl = url.URL{ + Scheme: "https", + Host: "firestore.googleapis.com", + Path: "/v1/projects/junctor-hackertracker/databases/(default)/documents/conferences/MOCA2024:runQuery", + } +) + +type StringField struct { + StringValue string `json:"stringValue,omitempty"` +} + +type IntField struct { + IntValue string `json:"intValue,omitempty"` +} + +type Value struct { + MapValue struct { + Fields struct { + ConferenceID interface{} `json:"conference_id,omitempty"` + EventIDs ArrayField `json:"event_i_ds,omitempty"` + Name StringField `json:"name,omitempty"` + Links ArrayField `json:"links,omitempty"` + Affiliations ArrayField `json:"affiliations,omitempty"` + Media ArrayField `json:"media,omitempty"` + ID IntField `json:"id,omitempty"` + ShortName StringField `json:"short_name,omitempty"` + } `json:"fields,omitempty"` + } `json:"mapValue,omitempty"` +} + +type ArrayField struct { + ArrayValue struct { + Values []Value `json:"values,omitempty"` + } `json:"arrayValue,omitempty"` +} + +type Item struct { + Document struct { + Name string `json:"name"` + Fields struct { + Conference StringField `json:"conference,omitempty"` + Timezone StringField `json:"timezone,omitempty"` + Link StringField `json:"link,omitempty"` + Title StringField `json:"title,omitempty"` + Description StringField `json:"description,omitempty"` + Media ArrayField `json:"media,omitempty"` + Type interface{} `json:"type,omitempty"` + BeginTsz StringField `json:"begin_tsz,omitempty"` + EndTsz StringField `json:"end_tsz,omitempty"` + BeginTimestamp StringField `json:"begin_timestamp,omitempty"` + EndTimestamp StringField `json:"end_timestamp,omitempty"` + UpdatedTsz StringField `json:"updated_tsz,omitempty"` + UpdatedTimestamp StringField `json:"updated_timestamp,omitempty"` + Location Value `json:"location,omitempty"` + Speakers ArrayField `json:"speakers,omitempty"` + Links ArrayField `json:"links,omitempty"` + } `json:"fields,omitempty"` + } `json:"document"` +} + +func main() { + reqData := []byte(`{ + "structuredQuery": { + "from": [ + { + "collectionId": "events" + } + ], + "orderBy": [ + { + "field": { + "fieldPath": "begin_timestamp" + }, + "direction": "DESCENDING" + }, + { + "field": { + "fieldPath": "__name__" + }, + "direction": "DESCENDING" + } + ] + } +}`) + req, err := http.NewRequest(http.MethodPost, hturl.String(), bytes.NewBuffer(reqData)) + if err != nil { + logrus.Fatalf("Failed to create new request: %v", err) + } + client := http.Client{} + resp, err := client.Do(req) + if err != nil { + logrus.Fatalf("HTTP POST failed: %v", err) + } + defer func() { + _ = resp.Body.Close() + }() + if resp.StatusCode != 200 { + logrus.Fatalf("Status code is not 200 OK but %s", resp.Status) + } + body, err := io.ReadAll(resp.Body) + if err != nil { + logrus.Fatalf("Failed to read HTTP body: %v", err) + } + var items []Item + if err := json.Unmarshal(body, &items); err != nil { + logrus.Fatalf("Failed to unmarshal response: %v", err) + } + ics := fmt.Sprintf(`BEGIN:VCALENDAR +VERSION:2.0 +PRODID:-//moca/camp/schedule v1.0//EN +VTIMEZONE: Europe/Rome +`) + + for _, item := range items { + eventUID := item.Document.Name + location := item.Document.Fields.Location.MapValue.Fields.Name.StringValue + speakerNames := make([]string, 0, len(item.Document.Fields.Speakers.ArrayValue.Values)) + for _, v := range item.Document.Fields.Speakers.ArrayValue.Values { + name := v.MapValue.Fields.Name.StringValue + if name != "" { + name = strings.Replace(name, "\n", "\\n", -1) + name = strings.Replace(name, "\"", "'", -1) + speakerNames = append(speakerNames, name) + } + } + speakers := "unknown" + if len(speakerNames) > 0 { + speakers = strings.Join(speakerNames, ", ") + } + speakerEmail := "unknown" + start, err := time.Parse("2006-01-02T15:04:05Z", item.Document.Fields.BeginTsz.StringValue) + if err != nil { + logrus.Fatalf("Failed to parse start time %q: %v", item.Document.Fields.BeginTsz.StringValue, err) + } + end, err := time.Parse("2006-01-02T15:04:05Z", item.Document.Fields.EndTsz.StringValue) + if err != nil { + logrus.Fatalf("Failed to parse end time %q: %v", item.Document.Fields.EndTsz.StringValue, err) + } + summary := item.Document.Fields.Title.StringValue + description := item.Document.Fields.Description.StringValue + description = strings.Replace(description, "\n", "\\n", -1) + description = strings.Replace(description, "\r", "", -1) + ics += fmt.Sprintf(`BEGIN:VEVENT +UID:%s +ORGANIZER;CN=%s:MAILTO:%s +DTSTAMP:20240912T140000Z +DTSTART:%s +DTEND:%s +SUMMARY:%s +DESCRIPTION: %s +GEO:42.5816338;14.0901461 +LOCATION: %s +END:VEVENT +`, + eventUID, + speakers, + speakerEmail, + start.Format("20060102T150405Z"), + end.Format("20060102T150405Z"), + summary, + description, + location, + ) + } + ics += fmt.Sprintln("END:VCALENDAR") + ics = strings.Replace(ics, "\n", "\r\n", -1) + fmt.Println(ics) +}