summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.gitignore1
-rw-r--r--LICENSE5
-rw-r--r--README1
-rw-r--r--earthporn.go74
-rw-r--r--links.go34
-rw-r--r--main.go115
-rw-r--r--template.html62
-rw-r--r--yr_no.go81
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
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..d15d509
--- /dev/null
+++ b/LICENSE
@@ -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.
+
diff --git a/README b/README
new file mode 100644
index 0000000..9a5bbc1
--- /dev/null
+++ b/README
@@ -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
+}
diff --git a/main.go b/main.go
new file mode 100644
index 0000000..952802a
--- /dev/null
+++ b/main.go
@@ -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
+}