diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | LICENSE | 5 | ||||
-rw-r--r-- | README | 1 | ||||
-rw-r--r-- | earthporn.go | 74 | ||||
-rw-r--r-- | links.go | 34 | ||||
-rw-r--r-- | main.go | 115 | ||||
-rw-r--r-- | template.html | 62 | ||||
-rw-r--r-- | yr_no.go | 81 |
8 files changed, 373 insertions, 0 deletions
diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e55425f --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +startpage @@ -0,0 +1,5 @@ + DO WHATEVER THE FUCK YOU WANT, PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. You just DO WHATEVER THE FUCK YOU WANT. + @@ -0,0 +1 @@ +A simple start page with an Background image from /r/EarthPorn, weather from yr.no and customizable links.
\ No newline at end of file diff --git a/earthporn.go b/earthporn.go new file mode 100644 index 0000000..ad1864a --- /dev/null +++ b/earthporn.go @@ -0,0 +1,74 @@ +package main + +import ( + "encoding/json" + "errors" + "mime" + "net/http" + "strings" +) + +type redditList struct { + Data struct { + Children []struct { + Data EarthPorn `json:"data"` + } `json:"children"` + } `json:"data"` +} + +type EarthPorn struct { + Title string `json:"title"` + URL string `json:"url,omitempty"` + Permalink string `json:"permalink"` +} + +const earthPornURL = "http://www.reddit.com/r/EarthPorn.json" + +func GetEarthPorn() (EarthPorn, error) { + resp, err := http.Get(earthPornURL) + if err != nil { + return EarthPorn{}, err + } + defer resp.Body.Close() + + var list redditList + dec := json.NewDecoder(resp.Body) + if err := dec.Decode(&list); err != nil { + return EarthPorn{}, err + } + + for _, el := range list.Data.Children { + p := el.Data + if p.URL == "" { + continue + } + + if (&p).getImageURL() { + return p, nil + } + } + + return EarthPorn{}, errors.New("Could not get EarthPorn: No image could be extracted") +} + +func (p *EarthPorn) getImageURL() bool { + // TODO: We can do further processing here (e.g. if we get a link to flickr, extract the image). + // For now, we will simply test, if the URL points to an image. + + resp, err := http.Head(p.URL) + if err != nil { + return false + } + defer resp.Body.Close() + + t := resp.Header.Get("Content-Type") + if t == "" { + return false + } + mt, _, err := mime.ParseMediaType(t) + if err != nil { + return false + } + + return (strings.Split(mt, "/")[0] == "image") +} diff --git a/links.go b/links.go new file mode 100644 index 0000000..f7bda76 --- /dev/null +++ b/links.go @@ -0,0 +1,34 @@ +package main + +import ( + "bufio" + "html/template" + "log" + "os" + "strings" +) + +type Link struct { + Title string + URL template.URL +} + +func GetLinks() (links []Link) { + fh, err := os.Open(os.ExpandEnv("$HOME/.startpage-urls")) + if err != nil { + log.Printf("Couldn't read links: %s", err) + return + } + defer fh.Close() + + scanner := bufio.NewScanner(fh) + for scanner.Scan() { + parts := strings.SplitN(scanner.Text(), "->", 2) + links = append(links, Link{ + strings.TrimSpace(parts[0]), + template.URL(strings.TrimSpace(parts[1])), + }) + } + + return +} @@ -0,0 +1,115 @@ +package main + +import ( + "flag" + "html/template" + "log" + "net/http" + "time" +) + +var porn EarthPorn +var weather Weather +var sun Sun + +func trylater(ch chan<- bool) { + log.Println("Will try again later...") + time.Sleep(1 * time.Minute) + ch <- true +} + +func earthPornUpdater(ch chan bool) { + for _ = range ch { + newporn, err := GetEarthPorn() + if err != nil { + log.Printf("Failed getting fap material: %s", err) + go trylater(ch) + } + + porn = newporn + log.Println("New fap material!") + } +} + +func weatherUpdater(ch chan bool) { + for _ = range ch { + newW, newS, err := CurrentWeather() + if err != nil { + log.Printf("Failed getting latest weather data: %s", err) + go trylater(ch) + } + + weather = newW + sun = newS + log.Println("New weather data") + } +} + +func intervalUpdates(d time.Duration, stopch <-chan bool, chans ...chan<- bool) { + send := func(chans ...chan<- bool) { + for _, ch := range chans { + go func(ch chan<- bool) { + ch <- true + }(ch) + } + } + + send(chans...) + + tick := time.NewTicker(d) + for { + select { + case <-tick.C: + send(chans...) + case <-stopch: + tick.Stop() + for _, ch := range chans { + close(ch) + } + return + } + } +} + +func main() { + laddr := flag.String("laddr", ":25145", "Listen on this port") + flag.Parse() + + pornch := make(chan bool) + weatherch := make(chan bool) + stopch := make(chan bool) + + go intervalUpdates(30*time.Minute, stopch, pornch, weatherch) + go weatherUpdater(weatherch) + go earthPornUpdater(pornch) + + defer func(stopch chan<- bool) { + stopch <- true + }(stopch) + + http.HandleFunc("/", startpage) + log.Fatal(http.ListenAndServe(*laddr, nil)) +} + +var tpl = template.Must(template.ParseFiles("template.html")) + +type TplData struct { + Porn *EarthPorn + Weather *Weather + Links []Link + LCols int +} + +func startpage(rw http.ResponseWriter, req *http.Request) { + links := GetLinks() + lcols := len(links) / 3 + if lcols < 1 { + lcols = 1 + } else if lcols > 4 { + lcols = 4 + } + + if err := tpl.Execute(rw, &TplData{&porn, &weather, links, lcols}); err != nil { + log.Printf("Failed executing template: %s\n", err) + } +} diff --git a/template.html b/template.html new file mode 100644 index 0000000..288c933 --- /dev/null +++ b/template.html @@ -0,0 +1,62 @@ +<!DOCTYPE html> +<html> + <head> + <title>Startpage</title> + <style type="text/css"> + body { + background: black url({{ .Porn.URL }}) no-repeat center center fixed; + -moz-background-size: cover; + -o-background-size: cover; + background-size: cover; + } + a { + color: #eee; + text-decoration: none; + } + a:hover { + color: white; + text-decoration: underline; + } + * { font-family: "DejaVu Sans Light", sans-serif; } + #earthporninfo { position: fixed; bottom: 2mm; right: 10mm; font-size: 8pt; } + #earthporninfo a { background: black; } + #weather { position: fixed; top: 10mm; left: 10mm; } + #temp { position: absolute; top: 30px; left: 60px; font-size:70px; text-shadow: black 2px 2px; color: white;} + #links { + position: absolute; + top: 40%; + left: 20%; + columns: {{ .LCols }}; + -moz-columns: {{ .LCols }}; + padding: 5mm; + background: rgba(0,0,0,0.6); + } + #links ul { + padding: 0px; + margin: 0px; + list-style: none; + } + #links ul li { + padding: 0px; + margin: 0px; + font-size: 10pt; + } + </style> + </head> + <body> + <div id="weather"> + <a href="{{ .Weather.URL }}"><img src="{{ .Weather.Icon }}" alt="" /></a> + <span id="temp">{{ .Weather.Temp.Value }}°</span> + </div> + <div id="links"> + <ul> + {{ range .Links }} + <li><a href="{{ .URL }}">{{ .Title }}</a></li> + {{ end }} + </ul> + </div> + <div id="earthporninfo"> + <a href="http://reddit.com{{ .Porn.Permalink }}">{{ .Porn.Title }}</a> + </div> + </body> +</html>
\ No newline at end of file diff --git a/yr_no.go b/yr_no.go new file mode 100644 index 0000000..269ff62 --- /dev/null +++ b/yr_no.go @@ -0,0 +1,81 @@ +package main + +import ( + "encoding/xml" + "fmt" + "net/http" + "time" +) + +func toTime(s string) time.Time { + t, _ := time.Parse("2006-01-02T15:04:05", s) + return t +} + +type Weather struct { + Temp Temperature `xml:"temperature"` + Symbol struct { + Number int `xml:"number,attr"` + } `xml:"symbol"` + From string `xml:"from,attr"` + URL string + Icon string +} + +func (w *Weather) prepIcon(sun Sun) { + rise := toTime(sun.Rise) + set := toTime(sun.Set) + t := toTime(w.From) + + night := t.Before(rise) || t.After(set) + format := "http://symbol.yr.no/grafikk/sym/b100/%02d" + switch w.Symbol.Number { + case 1, 2, 3, 5, 6, 7, 8, 20, 21: + if night { + format += "n.50" + } else { + format += "d" + } + } + format += ".png" + + w.Icon = fmt.Sprintf(format, w.Symbol.Number) +} + +type Temperature struct { + Value int `xml:"value,attr"` + Unit string `xml:"unit,attr"` +} + +type Sun struct { + Rise string `xml:"rise,attr"` + Set string `xml:"set,attr"` +} + +type weatherdata struct { + Sun Sun `xml:"sun"` + Forecast []*Weather `xml:"forecast>tabular>time"` +} + +const place = "Germany/Schleswig-Holstein/Lübeck" + +func CurrentWeather() (Weather, Sun, error) { + url := "http://www.yr.no/place/" + place + "/forecast_hour_by_hour.xml" + resp, err := http.Get(url) + if err != nil { + return Weather{}, Sun{}, err + } + defer resp.Body.Close() + + var wd weatherdata + dec := xml.NewDecoder(resp.Body) + if err := dec.Decode(&wd); err != nil { + return Weather{}, Sun{}, err + } + + w := wd.Forecast[0] + w.URL = "http://www.yr.no/place/" + place + w.prepIcon(wd.Sun) + + return *w, wd.Sun, nil +} |