0
0
Fork 0
This commit is contained in:
ston1th 2025-04-12 22:09:56 +02:00
commit 6196fe338f
2 changed files with 162 additions and 161 deletions

View file

@ -1 +1 @@
v0.1
0.1

467
main.go
View file

@ -18,6 +18,192 @@ import (
"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 (
X = 82
maxX = 574
@ -98,33 +284,6 @@ func getUV(img image.Image) (vals []float64) {
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 {
if v < 1.0 {
return "006300"
@ -152,195 +311,6 @@ func getColor(v float64) string {
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 {
col = color.RGBAModel.Convert(col)
c, ok := col.(color.RGBA)
@ -419,23 +389,7 @@ func fatal(err error) {
}
}
func generateLocPage() {
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
}
const DefaultCity = "24"
var locations = map[string]string{
"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>`