Code upload
7
README.md
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
# trayweather
|
||||||
|
|
||||||
|
Простой индикатор погоды в трее.
|
||||||
|
|
||||||
|
Доступные источники погоды:
|
||||||
|
|
||||||
|
* [Yandex](https://yandex.ru/pogoda)
|
8
api.go
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
type WeatherData interface {
|
||||||
|
CurrentTemperature() float64
|
||||||
|
FeelsLikeTemperature() float64
|
||||||
|
Description() string
|
||||||
|
IconName() string
|
||||||
|
}
|
38
config.go
Normal file
|
@ -0,0 +1,38 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/ilyakaznacheev/cleanenv"
|
||||||
|
"github.com/ncruces/zenity"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Config struct {
|
||||||
|
CityName string `toml:"CityName" env:"CITY_NAME"`
|
||||||
|
UpdatePeriod time.Duration `toml:"UpdatePeriod", env:"UPDATE_PERIOD"`
|
||||||
|
}
|
||||||
|
|
||||||
|
var config Config
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.SetFlags(0)
|
||||||
|
|
||||||
|
err := cleanenv.ReadConfig("config.toml", &config)
|
||||||
|
if err != nil {
|
||||||
|
zenity.Notify("Ошибка при чтении настроек из файла config.toml:\n" + err.Error())
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if config.CityName == "" {
|
||||||
|
zenity.Notify("Город (поле CityName) не может быть пустым.")
|
||||||
|
log.Fatalln("Город (поле CityName) не может быть пустым.")
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Println(config.UpdatePeriod)
|
||||||
|
|
||||||
|
if config.UpdatePeriod < time.Minute {
|
||||||
|
log.Printf("Частота обновлений слишком низкая (%s), будет установлено значение в одну минуту.")
|
||||||
|
config.UpdatePeriod = time.Minute
|
||||||
|
}
|
||||||
|
}
|
2
config.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
CityName = "Moscow"
|
||||||
|
UpdatePeriod = 300_000_000_000 # Кол-во минут * 1_000_000_000
|
BIN
icons/01d.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/02d.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/03d.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/04d.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/09d.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/10d.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/11d.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/13d.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/50d.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/exit.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
icons/unknown.ico
Normal file
After Width: | Height: | Size: 1.1 KiB |
69
main.go
Normal file
|
@ -0,0 +1,69 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"embed"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/getlantern/systray"
|
||||||
|
"github.com/nxshock/trayweather/yandex"
|
||||||
|
)
|
||||||
|
|
||||||
|
//go:embed icons/*.ico
|
||||||
|
var icons embed.FS
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
log.SetFlags(0)
|
||||||
|
|
||||||
|
http.DefaultClient.Timeout = 10 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
systray.Run(onReady, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func onReady() {
|
||||||
|
setTrayIcon("unknown.ico")
|
||||||
|
go update()
|
||||||
|
|
||||||
|
mQuit := systray.AddMenuItem("Выход", "Выйти из приложения")
|
||||||
|
exitIcon, err := icons.ReadFile("icons/exit.ico")
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
}
|
||||||
|
mQuit.SetIcon(exitIcon)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
<-mQuit.ClickedCh
|
||||||
|
os.Exit(0)
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
func update() {
|
||||||
|
for {
|
||||||
|
c, err := yandex.Get(config.CityName)
|
||||||
|
if err != nil {
|
||||||
|
systray.SetTooltip(err.Error())
|
||||||
|
setTrayIcon("unknown")
|
||||||
|
time.Sleep(time.Minute)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
systray.SetTooltip(fmt.Sprintf("%s\n%.1f °C (%.1f °C)", c.Description(), c.CurrentTemperature(), c.FeelsLikeTemperature()))
|
||||||
|
setTrayIcon(c.IconName())
|
||||||
|
time.Sleep(config.UpdatePeriod)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func setTrayIcon(name string) error {
|
||||||
|
b, err := icons.ReadFile("icons/" + name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
systray.SetIcon(b)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
1
make.bat
Normal file
|
@ -0,0 +1 @@
|
||||||
|
go build -ldflags "-H=windowsgui -linkmode=external -s -w" -buildmode=pie -trimpath
|
90
yandex/api.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package yandex
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
)
|
||||||
|
|
||||||
|
type WeatherData struct {
|
||||||
|
currentTemperature float64
|
||||||
|
feelsLikeTemperature float64
|
||||||
|
description string
|
||||||
|
iconURL string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WeatherData) CurrentTemperature() float64 {
|
||||||
|
return w.currentTemperature
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *WeatherData) FeelsLikeTemperature() float64 {
|
||||||
|
return w.feelsLikeTemperature
|
||||||
|
}
|
||||||
|
func (w *WeatherData) Description() string {
|
||||||
|
return w.description
|
||||||
|
}
|
||||||
|
func (w *WeatherData) IconName() string {
|
||||||
|
switch w.description {
|
||||||
|
case "Ясно":
|
||||||
|
return "01d.ico"
|
||||||
|
case "Облачно с прояснениями":
|
||||||
|
return "02d.ico"
|
||||||
|
case "Пасмурно":
|
||||||
|
return "03d.ico"
|
||||||
|
case "Небольшой снег":
|
||||||
|
return "09d.ico"
|
||||||
|
case "Снег":
|
||||||
|
return "13d.ico"
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func Get(cityName string) (*WeatherData, error) {
|
||||||
|
url := fmt.Sprintf("https://yandex.ru/pogoda/%s", strings.ToLower(cityName))
|
||||||
|
|
||||||
|
resp, err := http.Get(url)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
doc, err := goquery.NewDocumentFromResponse(resp)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
currentTemp, err := parseFloat(doc.Find("div.fact__temp > span.temp__value").Text())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse current temperature error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
feelsLikeTemp, err := parseFloat(doc.Find("div.fact__feels-like > div.term__value span.temp__value").Text())
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("parse feels like temperature error: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
wd := &WeatherData{
|
||||||
|
currentTemperature: currentTemp,
|
||||||
|
feelsLikeTemperature: feelsLikeTemp,
|
||||||
|
description: doc.Find("div.link__condition").Text(),
|
||||||
|
iconURL: doc.Find("div.fact__temp-wrap img.fact__icon").AttrOr("src", "")}
|
||||||
|
|
||||||
|
return wd, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseFloat(s string) (float64, error) {
|
||||||
|
var b []rune
|
||||||
|
|
||||||
|
s = strings.ReplaceAll(s, ",", ".")
|
||||||
|
s = strings.ReplaceAll(s, "−", "-")
|
||||||
|
|
||||||
|
for _, r := range []rune(s) {
|
||||||
|
if (r >= '0' && r <= '9') || (r == '+' || r == '-') || (r == '.') {
|
||||||
|
b = append(b, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return strconv.ParseFloat(string(b), 32)
|
||||||
|
}
|