Skip to content

Commit d6005a7

Browse files
committed
refactor: 重构温度解析及telegram bot
1 parent ea4a0f3 commit d6005a7

File tree

6 files changed

+257
-238
lines changed

6 files changed

+257
-238
lines changed

app.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package main
2+
3+
import (
4+
"context"
5+
"github.com/go-telegram/bot"
6+
"github.com/go-telegram/bot/models"
7+
"log"
8+
"time"
9+
)
10+
11+
const (
12+
highTempThreshold = 50.0
13+
sleepDuration = 10 * time.Minute
14+
)
15+
16+
type Application struct {
17+
conf *Config
18+
bot *bot.Bot
19+
}
20+
21+
func NewApplication(conf *Config) (*Application, error) {
22+
b, err := bot.New(conf.Token, bot.WithSkipGetMe())
23+
if err != nil {
24+
return nil, err
25+
}
26+
return &Application{
27+
conf: conf,
28+
bot: b,
29+
}, nil
30+
}
31+
32+
func (a *Application) startMonitoring(ctx context.Context) {
33+
for {
34+
a.sendTemperatureToTelegram(ctx)
35+
time.Sleep(sleepDuration)
36+
}
37+
}
38+
39+
func (a *Application) sendTemperatureToTelegram(ctx context.Context) {
40+
temp, err := LoadSensorsTemperature()
41+
if err != nil {
42+
log.Printf("error getting system temp: %v", err)
43+
return
44+
}
45+
_, err = a.bot.SendMessage(ctx, &bot.SendMessageParams{
46+
ChatID: a.conf.TargetId,
47+
Text: temp.RenderMessage(),
48+
DisableNotification: temp.IsHigherThanThreshold(highTempThreshold),
49+
})
50+
if err != nil {
51+
log.Printf("error sending message: %v", err)
52+
}
53+
}
54+
55+
func (a *Application) startPolling(ctx context.Context) {
56+
a.bot.RegisterHandler(bot.HandlerTypeMessageText, "/temp", bot.MatchTypePrefix, func(ctx context.Context, bot *bot.Bot, update *models.Update) {
57+
a.sendTemperatureToTelegram(ctx)
58+
})
59+
a.bot.Start(ctx)
60+
}

config.go

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package main
2+
3+
import (
4+
"encoding/json"
5+
"log"
6+
"net/http"
7+
"os"
8+
"strings"
9+
)
10+
11+
type Config struct {
12+
Token string `json:"token"`
13+
TargetId int64 `json:"target_id"`
14+
}
15+
16+
func loadConfig(path string) (*Config, error) {
17+
if strings.HasPrefix(path, "http") {
18+
resp, err := http.Get(path)
19+
if err != nil {
20+
return nil, err
21+
}
22+
defer resp.Body.Close()
23+
config := &Config{}
24+
err = json.NewDecoder(resp.Body).Decode(config)
25+
if err != nil {
26+
return nil, err
27+
}
28+
return config, nil
29+
} else {
30+
file, err := os.ReadFile(path)
31+
if err != nil {
32+
log.Fatal(err)
33+
}
34+
config := &Config{}
35+
err = json.Unmarshal(file, config)
36+
if err != nil {
37+
return nil, err
38+
}
39+
return config, nil
40+
}
41+
}

go.mod

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,11 @@
11
module pve-status
2+
3+
go 1.23.2
4+
5+
require github.com/go-telegram/bot v1.10.1
6+
7+
require (
8+
github.com/tidwall/gjson v1.18.0 // indirect
9+
github.com/tidwall/match v1.1.1 // indirect
10+
github.com/tidwall/pretty v1.2.1 // indirect
11+
)

go.sum

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
github.com/go-telegram/bot v1.10.0 h1:/Y9ZupcZhYQiAOFE1ETCF8P4836XE42nDkLcWHYTg3g=
2+
github.com/go-telegram/bot v1.10.0/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM=
3+
github.com/go-telegram/bot v1.10.1 h1:zwbEjz6ZlqBsyT5EqNqZDYX1ogHIiln1lwYpcK3E8XA=
4+
github.com/go-telegram/bot v1.10.1/go.mod h1:i2TRs7fXWIeaceF3z7KzsMt/he0TwkVC680mvdTFYeM=
5+
github.com/tidwall/gjson v1.18.0 h1:FIDeeyB800efLX89e5a8Y0BNH+LOngJyGrIWxG2FKQY=
6+
github.com/tidwall/gjson v1.18.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk=
7+
github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA=
8+
github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM=
9+
github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=
10+
github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4=
11+
github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU=

main.go

Lines changed: 8 additions & 238 deletions
Original file line numberDiff line numberDiff line change
@@ -1,240 +1,16 @@
11
package main
22

33
import (
4-
"bytes"
5-
"encoding/json"
4+
"context"
65
"flag"
76
"fmt"
87
"log"
9-
"net/http"
10-
"os"
11-
"os/exec"
12-
"strconv"
13-
"strings"
14-
"time"
158
)
169

1710
var BuildVersion = "dev"
1811

19-
const (
20-
highTempThreshold = 50.0
21-
sleepDuration = 10 * time.Minute
22-
)
23-
24-
func parseJsonNumber(n json.Number) (float64, error) {
25-
if n == "" {
26-
return 0, nil
27-
}
28-
return strconv.ParseFloat(string(n), 64)
29-
}
30-
31-
func renderMessage() (string, float64, error) {
32-
output, err := exec.Command("sensors", "-j").Output()
33-
if err != nil {
34-
return "", 0, err
35-
}
36-
37-
var sensorData map[string]interface{}
38-
if e := json.Unmarshal(output, &sensorData); e != nil {
39-
return "", 0, e
40-
}
41-
42-
cpuData, ok := sensorData["coretemp-isa-0000"].(map[string]interface{})
43-
if !ok {
44-
return "", 0, fmt.Errorf("failed to find CPU data")
45-
}
46-
47-
packageData, ok := cpuData["Package id 0"].(map[string]interface{})
48-
if !ok {
49-
return "", 0, fmt.Errorf("failed to find Package id 0 data")
50-
}
51-
52-
mainTemp, err := parseJsonNumber(json.Number(fmt.Sprintf("%v", packageData["temp1_input"])))
53-
if err != nil {
54-
return "", 0, err
55-
}
56-
57-
warning := ""
58-
if mainTemp > highTempThreshold {
59-
warning = "⚠️ High Temperature Warning"
60-
}
61-
62-
var coreTemps []string
63-
coreTemps = append(coreTemps, fmt.Sprintf("%.1f", mainTemp))
64-
65-
for key, value := range cpuData {
66-
if strings.HasPrefix(key, "Core") {
67-
coreData, exist := value.(map[string]interface{})
68-
if !exist {
69-
continue
70-
}
71-
for k, v := range coreData {
72-
if strings.HasSuffix(k, "_input") {
73-
coreTemp, e := parseJsonNumber(json.Number(fmt.Sprintf("%v", v)))
74-
if e == nil {
75-
coreTemps = append(coreTemps, fmt.Sprintf("%.1f", coreTemp))
76-
}
77-
break
78-
}
79-
}
80-
}
81-
}
82-
83-
text := fmt.Sprintf("%s\nCPU: %s°C\n", warning, strings.Join(coreTemps, "°C | "))
84-
85-
if acpiData, exist := sensorData["acpitz-acpi-0"].(map[string]interface{}); exist {
86-
if temp1, tExist := acpiData["temp1"].(map[string]interface{}); tExist {
87-
if acpiTemp, aExist := temp1["temp1_input"]; aExist {
88-
acpiTempFloat, _ := parseJsonNumber(json.Number(fmt.Sprintf("%v", acpiTemp)))
89-
text += fmt.Sprintf("\nACPI: %.1f°C", acpiTempFloat)
90-
}
91-
}
92-
}
93-
94-
text += "\n"
95-
96-
if nvmeData, exist := sensorData["nvme-pci-0400"].(map[string]interface{}); exist {
97-
if composite, cExist := nvmeData["Composite"].(map[string]interface{}); cExist {
98-
if nvmeTemp, nExist := composite["temp1_input"]; nExist {
99-
nvmeTempFloat, _ := parseJsonNumber(json.Number(fmt.Sprintf("%v", nvmeTemp)))
100-
text += fmt.Sprintf("\nNVME: %.1f°C", nvmeTempFloat)
101-
}
102-
}
103-
}
104-
105-
return text, mainTemp, nil
106-
}
107-
108-
func sendPVEStatusToTelegram(text string, temp float64, conf *Config) error {
109-
data := map[string]interface{}{
110-
"chat_id": conf.TargetId,
111-
"text": text,
112-
"disable_notification": temp <= 50,
113-
}
114-
jsonData, err := json.Marshal(data)
115-
if err != nil {
116-
return err
117-
}
118-
119-
req, err := http.NewRequest("POST", fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", conf.Token), bytes.NewBuffer(jsonData))
120-
if err != nil {
121-
return err
122-
}
123-
req.Header.Set("Content-Type", "application/json")
124-
125-
resp, err := http.DefaultClient.Do(req)
126-
if err != nil {
127-
return err
128-
}
129-
defer resp.Body.Close()
130-
131-
if resp.StatusCode != http.StatusOK {
132-
return fmt.Errorf("request failed with status code: %s", resp.Status)
133-
}
134-
135-
defer log.Printf("%.1f°C", temp)
136-
137-
if !conf.PinLatest {
138-
return nil
139-
}
140-
141-
var respData struct {
142-
Result struct {
143-
MessageId int64 `json:"message_id"`
144-
}
145-
}
146-
err = json.NewDecoder(resp.Body).Decode(&respData)
147-
if err != nil {
148-
return err
149-
}
150-
151-
err = pinMessageToTelegram(conf, respData.Result.MessageId)
152-
if err != nil {
153-
return err
154-
}
155-
156-
return nil
157-
}
158-
159-
func pinMessageToTelegram(conf *Config, messageId int64) error {
160-
// unpin all messages
161-
data := map[string]interface{}{
162-
"chat_id": conf.TargetId,
163-
}
164-
jsonData, err := json.Marshal(data)
165-
if err != nil {
166-
return err
167-
}
168-
req, err := http.NewRequest("POST", fmt.Sprintf("https://api.telegram.org/bot%s/unpinAllChatMessages", conf.Token), bytes.NewBuffer(jsonData))
169-
if err != nil {
170-
return err
171-
}
172-
req.Header.Set("Content-Type", "application/json")
173-
resp, err := http.DefaultClient.Do(req)
174-
if err != nil {
175-
return err
176-
}
177-
defer resp.Body.Close()
178-
179-
// pin the message
180-
data = map[string]interface{}{
181-
"chat_id": conf.TargetId,
182-
"message_id": messageId,
183-
"disable_notification": true,
184-
}
185-
jsonData, err = json.Marshal(data)
186-
if err != nil {
187-
return err
188-
}
189-
req, err = http.NewRequest("POST", fmt.Sprintf("https://api.telegram.org/bot%s/pinChatMessage", conf.Token), bytes.NewBuffer(jsonData))
190-
if err != nil {
191-
return err
192-
}
193-
req.Header.Set("Content-Type", "application/json")
194-
resp, err = http.DefaultClient.Do(req)
195-
if err != nil {
196-
return err
197-
}
198-
defer resp.Body.Close()
199-
200-
return nil
201-
}
202-
203-
func loadConfig(path string) (*Config, error) {
204-
if strings.HasPrefix(path, "http") {
205-
resp, err := http.Get(path)
206-
if err != nil {
207-
return nil, err
208-
}
209-
defer resp.Body.Close()
210-
config := &Config{}
211-
err = json.NewDecoder(resp.Body).Decode(config)
212-
if err != nil {
213-
return nil, err
214-
}
215-
return config, nil
216-
} else {
217-
file, err := os.ReadFile(path)
218-
if err != nil {
219-
log.Fatal(err)
220-
}
221-
config := &Config{}
222-
err = json.Unmarshal(file, config)
223-
if err != nil {
224-
return nil, err
225-
}
226-
return config, nil
227-
}
228-
}
229-
230-
type Config struct {
231-
Token string `json:"token"`
232-
TargetId int64 `json:"target_id"`
233-
PinLatest bool `json:"pin_latest"`
234-
}
235-
23612
func main() {
237-
conf := flag.String("config", "config.json", "config file path")
13+
config := flag.String("config", "config.json", "config file path")
23814
help := flag.Bool("help", false, "show help")
23915
flag.Parse()
24016

@@ -243,20 +19,14 @@ func main() {
24319
flag.Usage()
24420
return
24521
}
246-
247-
config, err := loadConfig(*conf)
22+
conf, err := loadConfig(*config)
24823
if err != nil {
24924
log.Fatal(err)
25025
}
251-
for {
252-
text, temp, e := renderMessage()
253-
if e != nil {
254-
log.Printf("Error rendering message: %v", e)
255-
continue
256-
}
257-
if se := sendPVEStatusToTelegram(text, temp, config); se != nil {
258-
log.Printf("Error sending message to Telegram: %v", se)
259-
}
260-
time.Sleep(sleepDuration)
26+
app, err := NewApplication(conf)
27+
if err != nil {
28+
log.Fatal(err)
26129
}
30+
go app.startPolling(context.Background())
31+
app.startMonitoring(context.Background())
26232
}

0 commit comments

Comments
 (0)