Skip to content

Instantly share code, notes, and snippets.

@dewey
Last active December 2, 2015 23:39
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dewey/cb70e13f3618981f059f to your computer and use it in GitHub Desktop.
Save dewey/cb70e13f3618981f059f to your computer and use it in GitHub Desktop.
Parse multiple .ics feeds into one and create the file and serve it via http.
package main
import (
"bufio"
"crypto/md5"
"encoding/hex"
"fmt"
"log"
"net/http"
"os"
"time"
"github.com/PuloV/ics-golang"
)
// EventParsed ... i just want this warning gone
type EventParsed struct {
start time.Time
end time.Time
created time.Time
importedId string
description string
summary string
id string
wholeDayEvent bool
}
func GetMD5Hash(text string) string {
hasher := md5.New()
hasher.Write([]byte(text))
return hex.EncodeToString(hasher.Sum(nil))
}
func main() {
parser := ics.New()
// We are just using this for the timestamp on the served static site
var lastModified time.Time
m := make(map[string]EventParsed)
f, err := os.Create("tmp/output-calendar.ics")
lastModified = time.Now()
w := bufio.NewWriter(f)
// Write the header of the calendar file
_, err = w.WriteString("BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:icalendar-ruby\r\nCALSCALE:GREGORIAN\r\nBEGIN:VTIMEZONE\r\nTZID:Europe/Vienna\r\nEND:VTIMEZONE\r\n")
if err != nil {
log.Print("error while writing file")
}
// Parse all our RSS feeds we want to unify
parserChan := parser.GetInputChan()
parserChan <- "https://trakt.tv/calendars/premieres.ics?slurm=xxx&countries=au%2Cat%2Cbr%2Cde%2Cnz%2Cgb%2Cus&ratings=10-100"
parserChan <- "https://trakt.tv/calendars/new-shows.ics?slurm=xxx&countries=au%2Cat%2Cbr%2Cde%2Cnz%2Cgb%2Cus&ratings=10-100"
parserChan <- "https://trakt.tv/calendars/premieres.ics?slurm=xxx&networks=Netflix"
outputChan := parser.GetOutputChan()
// Add all our events to our map. Key is on summary because that way we can take advantage of the maps unique key constraint
go func() {
for event := range outputChan {
m[fmt.Sprintf("%x", md5.Sum([]byte(event.GetSummary())))] = EventParsed{event.GetStart(), event.GetEnd(), event.GetCreated(), fmt.Sprintf("%x", md5.Sum([]byte(event.GetSummary()))), event.GetDescription(), event.GetSummary(), event.GetID(), event.GetWholeDayEvent()}
}
}()
parser.Wait()
t := time.Now()
// iterate through all events in our map and add them to the file
for _, value := range m {
var datestringStart string
var dateStringEnd string
datestring := fmt.Sprintf("%d%02d%02dT%02d%02d%02d", value.start.Year(), value.start.Month(), value.start.Day(), t.Hour(), t.Minute(), t.Second())
// If there's something close to the end of the month we just move it a few days earlier. This is needed because some calender clients need to have the
// timestamp in a certain format for events to show up as all day events. Because I don't want to deal with timestamps overlapping month / years etc. it's done
// this way.
if value.start.Day() == 30 || value.start.Day() == 31 || value.start.Day() == 29 {
datestringStart = fmt.Sprintf("%d%02d%02d", value.start.Year(), value.start.Month(), value.start.Day()-1)
dateStringEnd = fmt.Sprintf("%d%02d%02d", value.start.Year(), value.start.Month(), value.start.Day())
} else {
datestringStart = fmt.Sprintf("%d%02d%02d", value.start.Year(), value.start.Month(), value.start.Day())
dateStringEnd = fmt.Sprintf("%d%02d%02d", value.start.Year(), value.start.Month(), value.start.Day()+1)
}
stringEvent := fmt.Sprintf("BEGIN:VEVENT\r\nDTSTAMP:%sZ\r\nUID:%s\r\nDTSTART;TZID=Europe/Vienna:%s\r\nDTEND;TZID=Europe/Vienna:%s\r\nDESCRIPTION:%s\r\nSUMMARY:%s\r\nURL:%s\r\nEND:VEVENT\r\n", datestring, value.importedId, datestringStart, dateStringEnd, value.description, value.summary, "https://example.com")
_, err = w.WriteString(stringEvent)
if err != nil {
log.Print("error")
}
}
// Write the last line of the calender
_, err = w.WriteString("END:VCALENDAR")
if err != nil {
log.Print("error while writing end calendar")
}
// Make sure all write operations are cleared
w.Flush()
// Serve file via http on localhost
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
handler(w, r, lastModified)
})
// Serve static file
http.HandleFunc("/tmp/", func(w http.ResponseWriter, r *http.Request) {
http.ServeFile(w, r, r.URL.Path[1:])
})
// Listen on localhost
err = http.ListenAndServe(":9001", nil)
if err != nil {
log.Fatal(err)
}
}
// Render the site
func handler(w http.ResponseWriter, r *http.Request, t time.Time) {
fmt.Fprintf(w, fmt.Sprintf("<body><h1>Unified Trakt.TV calendar</h1><p>Download .ics file <a href=/tmp/output-calendar.ics>here</a><br>Last updated: %s</body>", t.Format("Mon Jan _2 15:04:05 2006")))
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment