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