initial commit
This commit is contained in:
+75
@@ -0,0 +1,75 @@
|
|||||||
|
# Created by https://www.toptal.com/developers/gitignore/api/go,linux,windows
|
||||||
|
# Edit at https://www.toptal.com/developers/gitignore?templates=go,linux,windows
|
||||||
|
|
||||||
|
### Go ###
|
||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
|
||||||
|
### Linux ###
|
||||||
|
*~
|
||||||
|
|
||||||
|
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||||
|
.fuse_hidden*
|
||||||
|
|
||||||
|
# KDE directory preferences
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# Linux trash folder which might appear on any partition or disk
|
||||||
|
.Trash-*
|
||||||
|
|
||||||
|
# .nfs files are created when an open file is removed but is still being accessed
|
||||||
|
.nfs*
|
||||||
|
|
||||||
|
### Windows ###
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
Thumbs.db:encryptable
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
# End of https://www.toptal.com/developers/gitignore/api/go,linux,windows
|
||||||
|
|
||||||
|
# Env files
|
||||||
|
.env
|
||||||
|
|
||||||
|
# Run Files
|
||||||
|
sunset.json
|
||||||
|
sunset.txt
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResponseData struct {
|
||||||
|
SunsetTime time.Time `json:"sunset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSunsetData(lat string, lng string) (*ResponseData, error) {
|
||||||
|
u, _ := url.Parse("https://api.sunrise-sunset.org/v2")
|
||||||
|
|
||||||
|
q := u.Query()
|
||||||
|
q.Set("lat", lat)
|
||||||
|
q.Set("lng", lng)
|
||||||
|
q.Set("formatted", "0")
|
||||||
|
|
||||||
|
u.RawQuery = q.Encode()
|
||||||
|
|
||||||
|
URL := u.String()
|
||||||
|
|
||||||
|
r, err := http.Get(URL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer r.Body.Close()
|
||||||
|
|
||||||
|
if r.StatusCode != http.StatusOK {
|
||||||
|
return nil, fmt.Errorf("bad status: %d", r.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
var apiResponse struct {
|
||||||
|
SunsetTime string `json:"sunset"`
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := json.NewDecoder(r.Body).Decode(&apiResponse); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
parsedTime, err := time.Parse(time.RFC3339, apiResponse.SunsetTime)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ResponseData{SunsetTime: parsedTime}, nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,86 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Data struct {
|
||||||
|
SunsetTime time.Time `json:"sunset"`
|
||||||
|
TenMinuteWarning time.Time `json:"ten_minute_warning"`
|
||||||
|
LastUpdated time.Time `json:"last_updated"`
|
||||||
|
MorningNotifySent bool `json:"morning_notify_sent"`
|
||||||
|
TenMinuteWarningSent bool `json:"ten_minute_warning_sent"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewData() *Data {
|
||||||
|
return &Data{
|
||||||
|
MorningNotifySent: false,
|
||||||
|
TenMinuteWarningSent: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadData(path string) (*Data, error) {
|
||||||
|
state, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
return NewData(), nil
|
||||||
|
} else {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(state) == 0 {
|
||||||
|
return NewData(), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var d Data
|
||||||
|
err = json.Unmarshal(state, &d)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &d, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Data) Save(path string) error {
|
||||||
|
b, err := json.Marshal(d)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
tmpPath := path + ".path"
|
||||||
|
defer os.Remove(tmpPath)
|
||||||
|
|
||||||
|
if err := os.WriteFile(tmpPath, b, 0644); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := os.Rename(tmpPath, path); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *Data) HandleSunsetNotifications(path string) error {
|
||||||
|
if !d.MorningNotifySent {
|
||||||
|
t := d.SunsetTime.Format("15:04")
|
||||||
|
err := SendNotification(fmt.Sprintf("Sunset is at %s", t))
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to send notification | %s", err)
|
||||||
|
}
|
||||||
|
d.MorningNotifySent = true
|
||||||
|
d.Save(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
if time.Now().After(d.TenMinuteWarning) && !d.TenMinuteWarningSent {
|
||||||
|
err := SendNotification("Sunset is Approaching!")
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("Unable to send notification | %s", err)
|
||||||
|
}
|
||||||
|
d.TenMinuteWarningSent = true
|
||||||
|
d.Save(path)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var logger *slog.Logger
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
logger = slog.New(slog.NewJSONHandler(os.Stdout, nil))
|
||||||
|
slog.SetDefault(logger)
|
||||||
|
}
|
||||||
|
|
||||||
|
func isToday(t time.Time) bool {
|
||||||
|
now := time.Now()
|
||||||
|
return t.Year() == now.Year() && t.Month() == now.Month() && t.Day() == now.Day()
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
sunsetJSONPath := os.Getenv("SUNSET_JSON_PATH")
|
||||||
|
sunset, err := loadData(sunsetJSONPath)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if !isToday(sunset.LastUpdated) {
|
||||||
|
slog.Info("Sunset Data out of date. Refreshing.", "last_update", sunset.LastUpdated)
|
||||||
|
|
||||||
|
lat := os.Getenv("LAT")
|
||||||
|
lng := os.Getenv("LNG")
|
||||||
|
apiResponse, err := getSunsetData(lat, lng)
|
||||||
|
if err != nil {
|
||||||
|
slog.Error("Unable to get Sunset data from API", "err", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
sunset = &Data{
|
||||||
|
SunsetTime: apiResponse.SunsetTime,
|
||||||
|
TenMinuteWarning: apiResponse.SunsetTime.Add(-10 * time.Minute),
|
||||||
|
LastUpdated: time.Now(),
|
||||||
|
MorningNotifySent: false,
|
||||||
|
TenMinuteWarningSent: false,
|
||||||
|
}
|
||||||
|
sunset.Save(sunsetJSONPath)
|
||||||
|
slog.Info("Sunset Data Saved", "path", sunsetJSONPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
sunset.HandleSunsetNotifications(sunsetJSONPath)
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func SendNotification(m string) error {
|
||||||
|
req, err := http.NewRequest(http.MethodPost, "https://ntfy.delpilar.net/test", strings.NewReader(m))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
ntfyUser := os.Getenv("NTFY_USER")
|
||||||
|
ntfyPass := os.Getenv("NTFY_PASS")
|
||||||
|
|
||||||
|
if ntfyUser == "" || ntfyPass == "" {
|
||||||
|
return fmt.Errorf("ntfy username or password not set in environment variables. Please set them and try again")
|
||||||
|
}
|
||||||
|
|
||||||
|
req.SetBasicAuth(ntfyUser, ntfyPass)
|
||||||
|
|
||||||
|
client := &http.Client{}
|
||||||
|
resp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer resp.Body.Close()
|
||||||
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("unexpected status: %d", resp.StatusCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user