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)
|
||||
}
|