~andreafeletto/gmnimetr

ref: 1ca3822c6952e234fb579783fb8607b61d5568b5 gmnimetr/main.go -rw-r--r-- 3.7 KiB
1ca3822cAndrea Feletto refactor api url to external file 3 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
package main

import (
	"bytes"
	"embed"
	"encoding/json"
	"errors"
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"
	"os"
	"strings"
	"text/template"
)

//go:embed *.gmi
var templateFiles embed.FS

//go:embed apiurl
var apiUrl string

type ApiResult struct {
	Description      string     `json:"evoluzione"`
	ShortDescription string     `json:"evoluzioneBreve"`
	Forecasts        []Forecast `json:"previsione"`
}

type Forecast struct {
	Location string        `json:"localita"`
	Altitude int           `json:"quota"`
	Days     []ForecastDay `json:"giorni"`
}

type ForecastDay struct {
	Date        string     `json:"giorno"`
	Description string     `json:"descIcona"`
	Text        string     `json:"testoGiorno"`
	Slots       []TimeSlot `json:"fasce"`
}

type TimeSlot struct {
	SlotName      string `json:"fasciaPer"`
	SlotHours     string `json:"fasciaOre"`
	Description   string `json:"descIcona"`
	HighWind      string `json:"descVentoIntQuota"`
	ValleyWind    string `json:"descVentoIntValle"`
	FreezingLevel int    `json:"zeroTermico"`
}

// Print a gemini header and exit with status `0`.
func renderFatal(code int, err error) {
	fmt.Printf("%d %s\r\n", code, err)
	os.Exit(0)
}

// Render the tamplate `templateName` and execute it with `data`.
// In case of errors, an error is returned with the appropriate <META> string.
func renderTemplate(templateName string, data interface{}) error {
	renderError := errors.New("Could not render the template")

	templ, err := template.ParseFS(templateFiles, templateName)
	if err != nil {
		return renderError
	}

	var buf bytes.Buffer
	err = templ.Execute(&buf, data)
	if err != nil {
		return renderError
	}

	fmt.Printf("20 text/gemini\r\n")
	buf.WriteTo(os.Stdout)
	return nil
}

// Render the tamplate "forecast.gmi" and execute it with `data`.
// In case of errors, an error is returned with the appropriate <META> string.
func renderForecast(data ApiResult) error {
	return renderTemplate("forecast.gmi", data)
}

// Render the tamplate "missing.gmi" and execute it with a payload containing
// the requested location `loc`.
// In case of errors, an error is returned with the appropriate <META> string.
func renderMissing(loc string) error {
	type Payload struct {
		Location string
	}
	data := Payload{
		Location: loc,
	}
	return renderTemplate("missing.gmi", data)
}

// Get the requested location from the `QUERY_STRING` environment variable.
// If the query is of type `<value>` then `value` is returned in uppercase.
// Otherwise the function returns an error with the appropriate <META> string.
func getLocation() (string, error) {
	query := os.Getenv("QUERY_STRING")
	params, err := url.ParseQuery(query)
	if err != nil || len(params) != 1 {
		return "", errors.New("Wrong query format")
	}

	for key := range params {
		return strings.ToUpper(key), nil
	}
	return "", nil // unreachable
}

// Fetch data from `url` and unmarshal it into `out`.
// In case of errors, an error is returned with the appropriate <META> string.
func fetchData(url string, out interface{}) error {
	fetchError := errors.New("Could not connect to meteotrentino.it api")

	resp, err := http.Get(url)
	if err != nil {
		return fetchError
	}
	defer resp.Body.Close()

	body, err := ioutil.ReadAll(resp.Body)
	if err != nil {
		return fetchError
	}

	err = json.Unmarshal(body, &out)
	if err != nil {
		return fetchError
	}
	return nil
}

func main() {
	if os.Getenv("QUERY_STRING") == "" {
		fmt.Printf("10 Inserisci la località:\r\n")
		os.Exit(0)
	}

	location, err := getLocation()
	if err != nil {
		renderFatal(59, err)
	}

	var data ApiResult
	err = fetchData(apiUrl + location, &data)
	if err != nil {
		renderFatal(40, err)
	}

	if len(data.Forecasts) == 1 {
		err = renderForecast(data)
	} else {
		err = renderMissing(location)
	}
	if err != nil {
		renderFatal(40, err)
	}
}