cleanup
This commit is contained in:
parent
10510c5322
commit
6196fe338f
2 changed files with 162 additions and 161 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
||||||
v0.1
|
0.1
|
||||||
|
|
|
||||||
467
main.go
467
main.go
|
|
@ -18,6 +18,192 @@ import (
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
version string
|
||||||
|
listen string
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
flag.StringVar(&listen, "l", ":7878", "listen addr")
|
||||||
|
flag.Parse()
|
||||||
|
|
||||||
|
ic := &ImageCrawler{
|
||||||
|
url: "https://uvi.bfs.de/Tagesgrafiken",
|
||||||
|
c: &http.Client{},
|
||||||
|
m: make(map[string]*Image),
|
||||||
|
}
|
||||||
|
http.HandleFunc("/set", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Method == "POST" {
|
||||||
|
r.ParseForm()
|
||||||
|
loc := r.PostForm.Get("loc")
|
||||||
|
if _, ok := locations[loc]; ok {
|
||||||
|
c := &http.Cookie{
|
||||||
|
Name: "loc",
|
||||||
|
Path: "/",
|
||||||
|
Value: loc,
|
||||||
|
Secure: true,
|
||||||
|
HttpOnly: true,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
}
|
||||||
|
http.SetCookie(w, c)
|
||||||
|
http.Redirect(w, r, "/", http.StatusMovedPermanently)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
w.Write([]byte(locPage))
|
||||||
|
})
|
||||||
|
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
cc := r.CookiesNamed("loc")
|
||||||
|
var c *http.Cookie
|
||||||
|
if len(cc) == 0 {
|
||||||
|
c = &http.Cookie{
|
||||||
|
Name: "loc",
|
||||||
|
Path: "/",
|
||||||
|
Value: DefaultCity,
|
||||||
|
Secure: true,
|
||||||
|
HttpOnly: true,
|
||||||
|
SameSite: http.SameSiteStrictMode,
|
||||||
|
}
|
||||||
|
http.SetCookie(w, c)
|
||||||
|
} else {
|
||||||
|
c = cc[0]
|
||||||
|
}
|
||||||
|
loc := c.Value
|
||||||
|
img, mod, err := ic.getImage(loc)
|
||||||
|
mod = mod.Local()
|
||||||
|
location := selectListReverse[loc]
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(w, indexPage,
|
||||||
|
"000000",
|
||||||
|
"Err",
|
||||||
|
mod.Format(time.RFC1123),
|
||||||
|
time.Now().Format(time.RFC1123),
|
||||||
|
location,
|
||||||
|
version,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
vals := getUV(img)
|
||||||
|
if len(vals) == 0 {
|
||||||
|
fmt.Fprintf(w, indexPage,
|
||||||
|
"000000",
|
||||||
|
"Err",
|
||||||
|
mod.Format(time.RFC1123),
|
||||||
|
time.Now().Format(time.RFC1123),
|
||||||
|
location,
|
||||||
|
version,
|
||||||
|
)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
last := vals[len(vals)-1]
|
||||||
|
fmt.Fprintf(w, indexPage,
|
||||||
|
getColor(last),
|
||||||
|
fmt.Sprintf("%.2f", last),
|
||||||
|
mod.Format(time.RFC1123),
|
||||||
|
time.Now().Format(time.RFC1123),
|
||||||
|
location,
|
||||||
|
version,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
err := http.ListenAndServe(listen, nil)
|
||||||
|
if err != nil {
|
||||||
|
fatal(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ImageCrawler struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
url string
|
||||||
|
c *http.Client
|
||||||
|
m map[string]*Image
|
||||||
|
}
|
||||||
|
|
||||||
|
type Image struct {
|
||||||
|
mu sync.RWMutex
|
||||||
|
ic *ImageCrawler
|
||||||
|
file string
|
||||||
|
img image.Image
|
||||||
|
etag string
|
||||||
|
modified time.Time
|
||||||
|
lastGet time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ic *ImageCrawler) getImage(loc string) (img image.Image, mod time.Time, err error) {
|
||||||
|
file, ok := locations[loc]
|
||||||
|
if !ok {
|
||||||
|
return nil, time.Time{}, errors.New("invalid location")
|
||||||
|
}
|
||||||
|
ic.mu.RLock()
|
||||||
|
if i, ok := ic.m[loc]; ok {
|
||||||
|
ic.mu.RUnlock()
|
||||||
|
return i.getImage()
|
||||||
|
}
|
||||||
|
ic.mu.RUnlock()
|
||||||
|
ic.mu.Lock()
|
||||||
|
i := &Image{
|
||||||
|
ic: ic,
|
||||||
|
file: file,
|
||||||
|
}
|
||||||
|
ic.m[loc] = i
|
||||||
|
ic.mu.Unlock()
|
||||||
|
return i.getImage()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Image) getImage() (img image.Image, mod time.Time, err error) {
|
||||||
|
i.mu.RLock()
|
||||||
|
if !i.lastGet.IsZero() &&
|
||||||
|
time.Now().Sub(i.lastGet) < time.Minute*3 {
|
||||||
|
defer i.mu.RUnlock()
|
||||||
|
return i.img, i.modified, nil
|
||||||
|
}
|
||||||
|
i.mu.RUnlock()
|
||||||
|
req, err := http.NewRequest("GET", i.ic.url+"/"+i.file, nil)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
req.Header.Set("user-agent", "uvgo/"+version)
|
||||||
|
i.mu.RLock()
|
||||||
|
if !i.modified.IsZero() {
|
||||||
|
req.Header.Set("if-modified-since", i.modified.Format(time.RFC1123))
|
||||||
|
}
|
||||||
|
if i.etag != "" {
|
||||||
|
req.Header.Set("if-none-match", i.etag)
|
||||||
|
}
|
||||||
|
i.mu.RUnlock()
|
||||||
|
resp, err := i.ic.c.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
if resp.StatusCode == http.StatusNotModified {
|
||||||
|
i.mu.RLock()
|
||||||
|
defer i.mu.RUnlock()
|
||||||
|
return i.img, i.modified, nil
|
||||||
|
}
|
||||||
|
if resp.StatusCode == http.StatusOK {
|
||||||
|
img, err = png.Decode(resp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
i.mu.Lock()
|
||||||
|
i.img = img
|
||||||
|
modTime, err := time.Parse(time.RFC1123, resp.Header.Get("last-modified"))
|
||||||
|
if err == nil {
|
||||||
|
i.modified = modTime
|
||||||
|
}
|
||||||
|
i.etag = resp.Header.Get("etag")
|
||||||
|
i.lastGet = time.Now()
|
||||||
|
defer i.mu.Unlock()
|
||||||
|
return i.img, i.modified, nil
|
||||||
|
}
|
||||||
|
i.mu.RLock()
|
||||||
|
defer i.mu.RUnlock()
|
||||||
|
if i.img != nil {
|
||||||
|
return i.img, i.modified, nil
|
||||||
|
}
|
||||||
|
return nil, time.Time{}, errors.New("error getting image")
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
X = 82
|
X = 82
|
||||||
maxX = 574
|
maxX = 574
|
||||||
|
|
@ -98,33 +284,6 @@ func getUV(img image.Image) (vals []float64) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var tmp = `<!doctype html>
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>UV Index</title>
|
|
||||||
<style>
|
|
||||||
a, a:link, a:visited {
|
|
||||||
color: #007d9c;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
<meta name="theme-color" content="#d6d6d6">
|
|
||||||
</head>
|
|
||||||
<body style="background-color:#d6d6d6;">
|
|
||||||
<center>
|
|
||||||
<h5 style="font-size:4em;">UV Index</h5>
|
|
||||||
<h1 style="font-size:15em;color:#%s;">%s</h1>
|
|
||||||
<h5 style="font-size:2em;">Updated: %s</h5>
|
|
||||||
<h5 style="font-size:2em;">Server: %s</h5>
|
|
||||||
<h5 style="font-size:3em;"><a href="/set">Location</a>: %s</h5>
|
|
||||||
<footer>UV data copyright belongs to <a href="https://www.bfs.de/DE/themen/opt/uv/uv-index/aktuelle-tagesverlaeufe/aktuell_node.html" rel="nofollow noreferrer noopener" target="_blank">Bundesamt für Strahlenschutz</a><br>
|
|
||||||
Use this service at your own risk.<br>
|
|
||||||
I take no responsibility for missing or incorrect data.<br>
|
|
||||||
© <a href="https://git.giftfish.de/ston1th/uvgo" rel="nofollow noreferrer noopener" target="_blank">uvgo</a> %s<br><br>
|
|
||||||
</footer>
|
|
||||||
</center>
|
|
||||||
</body>
|
|
||||||
</html>`
|
|
||||||
|
|
||||||
func getColor(v float64) string {
|
func getColor(v float64) string {
|
||||||
if v < 1.0 {
|
if v < 1.0 {
|
||||||
return "006300"
|
return "006300"
|
||||||
|
|
@ -152,195 +311,6 @@ func getColor(v float64) string {
|
||||||
return "000000"
|
return "000000"
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
|
||||||
locPage string
|
|
||||||
version string
|
|
||||||
listen string
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
flag.StringVar(&listen, "l", ":7878", "listen addr")
|
|
||||||
flag.Parse()
|
|
||||||
|
|
||||||
url := "https://uvi.bfs.de/Tagesgrafiken"
|
|
||||||
generateLocPage()
|
|
||||||
|
|
||||||
ic := &ImageCrawler{
|
|
||||||
url: url,
|
|
||||||
c: &http.Client{},
|
|
||||||
m: make(map[string]*Image),
|
|
||||||
}
|
|
||||||
http.HandleFunc("/set", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
if r.Method == "POST" {
|
|
||||||
r.ParseForm()
|
|
||||||
loc := r.PostForm.Get("loc")
|
|
||||||
if _, ok := locations[loc]; ok {
|
|
||||||
c := &http.Cookie{
|
|
||||||
Name: "loc",
|
|
||||||
Path: "/",
|
|
||||||
Value: loc,
|
|
||||||
Secure: true,
|
|
||||||
HttpOnly: true,
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
}
|
|
||||||
http.SetCookie(w, c)
|
|
||||||
http.Redirect(w, r, "/", http.StatusMovedPermanently)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
w.Write([]byte(locPage))
|
|
||||||
})
|
|
||||||
http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
|
|
||||||
cc := r.CookiesNamed("loc")
|
|
||||||
var c *http.Cookie
|
|
||||||
if len(cc) == 0 {
|
|
||||||
c = &http.Cookie{
|
|
||||||
Name: "loc",
|
|
||||||
Path: "/",
|
|
||||||
Value: "24",
|
|
||||||
Secure: true,
|
|
||||||
HttpOnly: true,
|
|
||||||
SameSite: http.SameSiteStrictMode,
|
|
||||||
}
|
|
||||||
http.SetCookie(w, c)
|
|
||||||
} else {
|
|
||||||
c = cc[0]
|
|
||||||
}
|
|
||||||
loc := c.Value
|
|
||||||
img, mod, err := ic.getImage(loc)
|
|
||||||
mod = mod.Local()
|
|
||||||
location := selectListReverse[loc]
|
|
||||||
if err != nil {
|
|
||||||
fmt.Fprintf(w, tmp,
|
|
||||||
"000000",
|
|
||||||
"Err",
|
|
||||||
mod.Format(time.RFC1123),
|
|
||||||
time.Now().Format(time.RFC1123),
|
|
||||||
location,
|
|
||||||
version,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
vals := getUV(img)
|
|
||||||
if len(vals) == 0 {
|
|
||||||
fmt.Fprintf(w, tmp,
|
|
||||||
"000000",
|
|
||||||
"Err",
|
|
||||||
mod.Format(time.RFC1123),
|
|
||||||
time.Now().Format(time.RFC1123),
|
|
||||||
location,
|
|
||||||
version,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
last := vals[len(vals)-1]
|
|
||||||
fmt.Fprintf(w, tmp,
|
|
||||||
getColor(last),
|
|
||||||
fmt.Sprintf("%.2f", last),
|
|
||||||
mod.Format(time.RFC1123),
|
|
||||||
time.Now().Format(time.RFC1123),
|
|
||||||
location,
|
|
||||||
version,
|
|
||||||
)
|
|
||||||
})
|
|
||||||
err := http.ListenAndServe(listen, nil)
|
|
||||||
if err != nil {
|
|
||||||
fatal(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ImageCrawler struct {
|
|
||||||
mu sync.RWMutex
|
|
||||||
url string
|
|
||||||
c *http.Client
|
|
||||||
m map[string]*Image
|
|
||||||
}
|
|
||||||
|
|
||||||
type Image struct {
|
|
||||||
mu sync.RWMutex
|
|
||||||
ic *ImageCrawler
|
|
||||||
file string
|
|
||||||
img image.Image
|
|
||||||
etag string
|
|
||||||
modified time.Time
|
|
||||||
lastGet time.Time
|
|
||||||
}
|
|
||||||
|
|
||||||
func (ic *ImageCrawler) getImage(loc string) (img image.Image, mod time.Time, err error) {
|
|
||||||
file, ok := locations[loc]
|
|
||||||
if !ok {
|
|
||||||
return nil, time.Time{}, errors.New("invalid location")
|
|
||||||
}
|
|
||||||
ic.mu.RLock()
|
|
||||||
if i, ok := ic.m[loc]; ok {
|
|
||||||
ic.mu.RUnlock()
|
|
||||||
return i.getImage()
|
|
||||||
}
|
|
||||||
ic.mu.RUnlock()
|
|
||||||
ic.mu.Lock()
|
|
||||||
i := &Image{
|
|
||||||
ic: ic,
|
|
||||||
file: file,
|
|
||||||
}
|
|
||||||
ic.m[loc] = i
|
|
||||||
ic.mu.Unlock()
|
|
||||||
return i.getImage()
|
|
||||||
}
|
|
||||||
|
|
||||||
func (i *Image) getImage() (img image.Image, mod time.Time, err error) {
|
|
||||||
i.mu.RLock()
|
|
||||||
if !i.lastGet.IsZero() &&
|
|
||||||
time.Now().Sub(i.lastGet) < time.Minute*3 {
|
|
||||||
defer i.mu.RUnlock()
|
|
||||||
return i.img, i.modified, nil
|
|
||||||
}
|
|
||||||
i.mu.RUnlock()
|
|
||||||
req, err := http.NewRequest("GET", i.ic.url+"/"+i.file, nil)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
i.mu.RLock()
|
|
||||||
if !i.modified.IsZero() {
|
|
||||||
req.Header.Set("if-modified-since", i.modified.Format(time.RFC1123))
|
|
||||||
}
|
|
||||||
if i.etag != "" {
|
|
||||||
req.Header.Set("if-none-match", i.etag)
|
|
||||||
}
|
|
||||||
i.mu.RUnlock()
|
|
||||||
resp, err := i.ic.c.Do(req)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer resp.Body.Close()
|
|
||||||
if resp.StatusCode == http.StatusNotModified {
|
|
||||||
i.mu.RLock()
|
|
||||||
defer i.mu.RUnlock()
|
|
||||||
return i.img, i.modified, nil
|
|
||||||
}
|
|
||||||
if resp.StatusCode == http.StatusOK {
|
|
||||||
img, err = png.Decode(resp.Body)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
i.mu.Lock()
|
|
||||||
i.img = img
|
|
||||||
modTime, err := time.Parse(time.RFC1123, resp.Header.Get("last-modified"))
|
|
||||||
if err == nil {
|
|
||||||
i.modified = modTime
|
|
||||||
}
|
|
||||||
i.etag = resp.Header.Get("etag")
|
|
||||||
i.lastGet = time.Now()
|
|
||||||
defer i.mu.Unlock()
|
|
||||||
return i.img, i.modified, nil
|
|
||||||
}
|
|
||||||
i.mu.RLock()
|
|
||||||
defer i.mu.RUnlock()
|
|
||||||
if i.img != nil {
|
|
||||||
return i.img, i.modified, nil
|
|
||||||
}
|
|
||||||
return nil, time.Time{}, errors.New("error getting image")
|
|
||||||
}
|
|
||||||
|
|
||||||
func isWhite(col color.Color) bool {
|
func isWhite(col color.Color) bool {
|
||||||
col = color.RGBAModel.Convert(col)
|
col = color.RGBAModel.Convert(col)
|
||||||
c, ok := col.(color.RGBA)
|
c, ok := col.(color.RGBA)
|
||||||
|
|
@ -419,23 +389,7 @@ func fatal(err error) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func generateLocPage() {
|
const DefaultCity = "24"
|
||||||
loc := `<!doctype html><html>
|
|
||||||
<head>
|
|
||||||
<title>UV Index Change Location</title>
|
|
||||||
<meta name="theme-color" content="#d6d6d6">
|
|
||||||
</head>
|
|
||||||
<body style="background-color:#d6d6d6;">
|
|
||||||
<center>
|
|
||||||
<h5 style="font-size:4em;">Change Location</h5>
|
|
||||||
<form action="/set" method="post">
|
|
||||||
<select name="loc" style="font-size:5em;margin-bottom:50px;">`
|
|
||||||
for k, v := range sortedMap(selectList) {
|
|
||||||
loc += `<option value="` + v + `">` + k + `</option>`
|
|
||||||
}
|
|
||||||
loc += `</select><br><input type="submit" style="font-size:5em;" value="Change"></input></form></center></body></html>`
|
|
||||||
locPage = loc
|
|
||||||
}
|
|
||||||
|
|
||||||
var locations = map[string]string{
|
var locations = map[string]string{
|
||||||
"0": "EEr_Andernach_today.png",
|
"0": "EEr_Andernach_today.png",
|
||||||
|
|
@ -556,3 +510,50 @@ func sortedMap[K cmp.Ordered, V any](m map[K]V) iter.Seq2[K, V] {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func generateLocPage() string {
|
||||||
|
loc := `<!doctype html><html>
|
||||||
|
<head>
|
||||||
|
<title>UV Index Change Location</title>
|
||||||
|
<meta name="theme-color" content="#d6d6d6">
|
||||||
|
</head>
|
||||||
|
<body style="background-color:#d6d6d6;">
|
||||||
|
<center>
|
||||||
|
<h5 style="font-size:4em;">Change Location</h5>
|
||||||
|
<form action="/set" method="post">
|
||||||
|
<select name="loc" style="font-size:5em;margin-bottom:50px;">`
|
||||||
|
for k, v := range sortedMap(selectList) {
|
||||||
|
loc += `<option value="` + v + `">` + k + `</option>`
|
||||||
|
}
|
||||||
|
loc += `</select><br><input type="submit" style="font-size:5em;" value="Change"></input></form></center></body></html>`
|
||||||
|
return loc
|
||||||
|
}
|
||||||
|
|
||||||
|
var locPage = generateLocPage()
|
||||||
|
|
||||||
|
var indexPage = `<!doctype html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>UV Index</title>
|
||||||
|
<style>
|
||||||
|
a, a:link, a:visited {
|
||||||
|
color: #007d9c;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<meta name="theme-color" content="#d6d6d6">
|
||||||
|
</head>
|
||||||
|
<body style="background-color:#d6d6d6;">
|
||||||
|
<center>
|
||||||
|
<h5 style="font-size:3.5em;">UV Index</h5>
|
||||||
|
<h1 style="font-size:15em;color:#%s;">%s</h1>
|
||||||
|
<h5 style="font-size:2em;">Updated: %s</h5>
|
||||||
|
<h5 style="font-size:2em;">Server: %s</h5>
|
||||||
|
<h5 style="font-size:2em;"><a href="/set">Location</a>: %s</h5>
|
||||||
|
<footer>UV data copyright belongs to <a href="https://www.bfs.de/DE/themen/opt/uv/uv-index/aktuelle-tagesverlaeufe/aktuell_node.html" rel="nofollow noreferrer noopener" target="_blank">Bundesamt für Strahlenschutz</a><br>
|
||||||
|
Use this service at your own risk.<br>
|
||||||
|
I take no responsibility for missing or incorrect data.<br>
|
||||||
|
© <a href="https://git.giftfish.de/ston1th/uvgo" rel="nofollow noreferrer noopener" target="_blank">uvgo</a> v%s<br><br>
|
||||||
|
</footer>
|
||||||
|
</center>
|
||||||
|
</body>
|
||||||
|
</html>`
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue