From b146c2bc76c63857593ca0ca52db303f69bf6d92 Mon Sep 17 00:00:00 2001 From: nxshock Date: Fri, 19 Mar 2021 22:27:25 +0500 Subject: [PATCH] Code upload --- README.md | 7 ++++ api.go | 8 +++++ config.go | 38 ++++++++++++++++++++ config.toml | 2 ++ icons/01d.ico | Bin 0 -> 1150 bytes icons/02d.ico | Bin 0 -> 1150 bytes icons/03d.ico | Bin 0 -> 1150 bytes icons/04d.ico | Bin 0 -> 1150 bytes icons/09d.ico | Bin 0 -> 1150 bytes icons/10d.ico | Bin 0 -> 1150 bytes icons/11d.ico | Bin 0 -> 1150 bytes icons/13d.ico | Bin 0 -> 1150 bytes icons/50d.ico | Bin 0 -> 1150 bytes icons/exit.ico | Bin 0 -> 1150 bytes icons/unknown.ico | Bin 0 -> 1150 bytes main.go | 69 +++++++++++++++++++++++++++++++++++ make.bat | 1 + yandex/api.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++ 18 files changed, 215 insertions(+) create mode 100644 README.md create mode 100644 api.go create mode 100644 config.go create mode 100644 config.toml create mode 100644 icons/01d.ico create mode 100644 icons/02d.ico create mode 100644 icons/03d.ico create mode 100644 icons/04d.ico create mode 100644 icons/09d.ico create mode 100644 icons/10d.ico create mode 100644 icons/11d.ico create mode 100644 icons/13d.ico create mode 100644 icons/50d.ico create mode 100644 icons/exit.ico create mode 100644 icons/unknown.ico create mode 100644 main.go create mode 100644 make.bat create mode 100644 yandex/api.go diff --git a/README.md b/README.md new file mode 100644 index 0000000..44d43bd --- /dev/null +++ b/README.md @@ -0,0 +1,7 @@ +# trayweather + +Простой индикатор погоды в трее. + +Доступные источники погоды: + +* [Yandex](https://yandex.ru/pogoda) diff --git a/api.go b/api.go new file mode 100644 index 0000000..962a34c --- /dev/null +++ b/api.go @@ -0,0 +1,8 @@ +package main + +type WeatherData interface { + CurrentTemperature() float64 + FeelsLikeTemperature() float64 + Description() string + IconName() string +} diff --git a/config.go b/config.go new file mode 100644 index 0000000..21d8b23 --- /dev/null +++ b/config.go @@ -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 + } +} diff --git a/config.toml b/config.toml new file mode 100644 index 0000000..48827a2 --- /dev/null +++ b/config.toml @@ -0,0 +1,2 @@ +CityName = "Moscow" +UpdatePeriod = 300_000_000_000 # Кол-во минут * 1_000_000_000 diff --git a/icons/01d.ico b/icons/01d.ico new file mode 100644 index 0000000000000000000000000000000000000000..c996aa0971f449a501b039eb59bc5d2f1a7350db GIT binary patch literal 1150 zcmZQzU}Ruq5D);-3Je)63=Con3=A3!3=9Gc3=9ek5OD?u1_lQf5J?~GoAX@JC-jD^#M&m8ZZm-;?=uU|p* z!1N;1uits+JU7E;MnLuxVf=bw=DhykUHC>C-3;%%H@&#+h3SRTxvys->-WujA?}^` z<|Xm^-+b}Td!-69!!z%VE75vkVVd*G6QMdhEP|{Z%=XHEqv@0P z`Y|puymQ~Y@XdRt2Ud)O@Xq`o=#~4r)jRj~Lzp?HqNI!$M;9e*N<4|Bd?}|F7Eh;lFe0oBtl!FLU3#dBgYd z4#@8<&|1aM1{=ZA=Yq*^-y&&2<_tgxTUKsu9(jUKYsl9>C>l0|NsAI{`m2u>c@{C|9<-P=|8FX!-o&)pFVx6Bvl`h`sz=g zK4l>BNpZvb4<9}#y?ptS=i|qZ_lPy)5MKIDG<`0*1Vy&(Co z-@g8LadP_a>g@dA+tUlIALNIZFJJO}`0ybcqy`)Q`Sa)hA3uKlKYr}^e`iMrus)F8 zAPmwE!tdX|cfh9i+STj-AP6>+9>8K7Rao z4sOQBkNC>m%-@ku5 z-qPIi&eh2YRiCS;r>C1|Kxk+vC$e5_Y*#0z7?2-f*wfv80XB8`#JpU+3_RUEi#**t KeekIps&WAO$m8Sy literal 0 HcmV?d00001 diff --git a/icons/09d.ico b/icons/09d.ico new file mode 100644 index 0000000000000000000000000000000000000000..16f8a06a32290c12b6eda238e622ef0c9c15b08e GIT binary patch literal 1150 zcmZQzU}Ruq5D);-3Je)63=Con3=A3!3=9Gc3=9ek5OD?u1_lQf5J`LN<>IC3=jX@k z;o;%t;o%`ltc`ANZbGiEu8QvN?tUQJ!_6bw+uhxdSpDA4-b!Aco_kzfUAa9yJPv{M zkzzkcy{@jVps}$rtEH)t4~XwzXIExrWyK3(U9#lL^l8)1 ze*gJ>)t4_{R)72UZQj?fUsrzo_;K}z4<8nM`0zm)-JFjfKd$)n>C=A@{`&RH|8L*E zf!QEFI{x_a<7q7VzoV%$9@kF4||wC82$0% zNA-^%KmG#g1!0i>;K0ED4t93`J=`FAL3Vq1cv!&npwk~eezf}Z>C?vV-@mWT$)6)c94=%p5v$LV6hsSkKcaL|T?w&=s)D2~E08<=IK>z>% literal 0 HcmV?d00001 diff --git a/icons/10d.ico b/icons/10d.ico new file mode 100644 index 0000000000000000000000000000000000000000..101b0e12114e2ac20268ac9d7d9ba2fb148a30b4 GIT binary patch literal 1150 zcmZQzU}Ruq5D);-3Je)63=Con3=A3!3=9Gc3=9ek5OD?u1_lQf5J`J%Y;4Tx>Efvy z8XC&!>E;{HS@p^c8u$!BktD2da>04S_ia9wrlo8VZ;ll^xj~_oy`|;z)yvXqI_rHJt zn)~(Z*SX)meVhIL=l2PpKYg0_>C>lqA3l7@K{xB;$B*hCKYslA>C>nGApHINcQ6fN zW5XXmevCslK`v3Cvr~k(;eE9F0 z_WF)}{Cy5&Gd_O&Sp4bJr<)%?e*BB955xxP{qgnV|Cy`b|F?;K{ognL&3~Wlmx;*w zVeC&30Qdig51;;Dy7lq@l{+8*2Y~eDz4`Bx_vXKM?wd-O9%TCC$B!F8c7pKd&!7IE zx%%r?be1ZLk*rvdK3YH$Dm literal 0 HcmV?d00001 diff --git a/icons/11d.ico b/icons/11d.ico new file mode 100644 index 0000000000000000000000000000000000000000..872f0ad60d79109eb7adf81839163155ac429b0a GIT binary patch literal 1150 zcmZQzU}Ruq5D);-3Je)63=Con3=A3!3=9Gc3=9ek5OD?u1_lQf5J@}ik@wQZJMTpx zB{q2Hy*}<+_)>)&Gc=r^2z%wcneCPH`nE;#BOzkV`1tW-@yCxJcfET3;euDzoBu(@ zZ~r$3`LPcmHRu`SAbSmycM@`1bYd&;S4bvxD@1 z{P^(^vVIWz$G4CFK^VkF#$UgF{r~gl&;M)Jto?0gWAoVC!*ln?j~`=^)!<@({`~p> zsZ*!_CnqNUcX4w1@9pXJ-^;`EKhzBtA3l7j{`m3ZZ*=`%zI^!)vKNFmZru3a+0hZK z52P1_LHa>>)v8s@*z{kzbou{^C1kF!?S;o;#%Q1JjL005W2wAugw literal 0 HcmV?d00001 diff --git a/icons/13d.ico b/icons/13d.ico new file mode 100644 index 0000000000000000000000000000000000000000..5c633af39220370c448d879c931a1dffe7b4e463 GIT binary patch literal 1150 zcmZQzU}Ruq5D);-3Je)63=Con3=A3!3=9Gc3=9ek5OD?u1_lQf5IJ{DEcWs75y54Kr;Deqm%GO$FL%$!?(Xj1-mcy{p6;G|KSuW5Yh~?na*O9^1V?(yE!!(#?W%+uZDtA~e2 z9X7onF_0fY;ROmecX#(lFL#fnZf>^_?C^AVUkwejT2D_;6Hj-K zgZB3J0w8@}9-d!3JUl`{YO!Gt4-aKech3w@caQTfE-rFj9v+>Z?jEl}?)LQXnCRi* zQSIg7QG!h`vY3aPN1d04N1>;O#|2M!j|ZSQ@pSX_ad&rjM^=ZA?dj>M>*eJo@9FNj O9ON#L-|(p)sd4}xm3s;R literal 0 HcmV?d00001 diff --git a/icons/50d.ico b/icons/50d.ico new file mode 100644 index 0000000000000000000000000000000000000000..913eb0bb959fc58935748df012611d682e5ab68d GIT binary patch literal 1150 zcmZQzU}Ruq5D);-3Je)63=Con3=A3!3=9Gc3=9ek5OD?u1_lQf5IOv@m%DqOr@LnZ zI`;7JFeT)BdwY8h7Z(>PZ)a~MbnN2d!Vi*XU|?XvW{$hNyMvdz$7bx5NPz1-bvaAACb0vU+s3 nmz$S|r@MO*PVDLFgsuk{-^0x#6BN#H?Cv=Ym)cQr%0d7D zr6>LO>D=|-uXWRZ-{y7yV|zCJPnvh}e_B{9LuP0UgFss@gK$&!e}>6!{|jT1{%0j6 z|4&az{-2ftqLcn-Bu4-D@7n!8pl!>4|CWvaGp6qRA3yu-e+C8y19%wpxc&FPyWu}W zuk-)xh`9f$aS8vE6BGU?CB*+vi4FfB*t_q4P}lbVf$dxV7tcTRKX&@b{|w#UpBTE_ z|1)&B{%2@${LgS}(f@>-oBuQPI{nWGiTa-u9rHgSChC7eM9}|`35WiN_U!ua*SPk7 z-Lez^qoy4B57L|b_QHRLD+~TJT$=Zv;mn->3}@&3&%d)5?3cul@c%L4VgI9pegB6~ zIr=}MZ_j`ClG*<|)}Q|$KH&gZf8xUn{}b+>`k#E~#D9h(v;Q+3oAbZv;fenYQ{4Z@ z1cdyL2oC-q?(g+KYTBv)(f#}WEA9OLf9lrD|3iBB{AcL(c@J^}Lx;`chdO)x5Ayc?@9*pP-{05gzptC)|F}7q{>Mx``ag2Q z!T-yS-2d;_w)H>51Rn;5iS7(iZFvmhZMpv$rn&zQvUmOO@9z2E*VF62kC)ef9}kcJ zUQV|E6BbG5>h#~)#_WIm!W;i%W}W#TG41649hYDK_iR}6-@kG-gHP3R2CwR63|{qX z{<~Nh{&%)E|L<&T0mn|(X8&!Bb^h^kLTL0grcIE$qIs5n4&vw-Of403EOoR9!agaPn9n1`vevsJ41j+xmmZbcDw6XaAgLV1; z@2$@Ie|JS1m1_n8h{@KAID~~qW{lBw3?f~bHYH{!8C{u z5(mkH)PdB4^nvuRix&HNbxy?pn+sz8U!NBR!E+=2U!58B|MFC?|5s-C|GzdT93nn1 z3ak#K9;6SXe|@y*|LgOk{$H6D`v1zz5D1^5XxmlrL z^&ov9{UE>GTAum;(o|nKzBt+Y|M`BG|L?Bv1)FhmN&5c_6W#w`n&Jai15yvt2i6}Y z{QutiivQ>O-2R{McmIF3*X942F8lvyI_>_SYBl@+<>?i$8D~0dz-mD1KvX{C-`+g<|6qyi|F<{xgZW2mwZZZrbs+U1eIWgybg(f_^8bs| z>;7L~nEwB8h06ci>+8XKU<8N{5(mkH)PdB4^uhFRh!u6)o}=^s$>9b6AMEV?f40x# z|A7+u|NHYq!8C{u5(mkH)Pd9^>j$wxVjJV6{-2qg@c+?)Isadt+wlM8+4W!=#0QCk uOmN0KZp&(Ab+ik6#Tw6Lg4?}2>$;d8pH>Q!_>fNZ2Dnx#L@tj05?(q literal 0 HcmV?d00001 diff --git a/main.go b/main.go new file mode 100644 index 0000000..cf3d813 --- /dev/null +++ b/main.go @@ -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 +} diff --git a/make.bat b/make.bat new file mode 100644 index 0000000..193f058 --- /dev/null +++ b/make.bat @@ -0,0 +1 @@ +go build -ldflags "-H=windowsgui -linkmode=external -s -w" -buildmode=pie -trimpath \ No newline at end of file diff --git a/yandex/api.go b/yandex/api.go new file mode 100644 index 0000000..c206563 --- /dev/null +++ b/yandex/api.go @@ -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) +}