package main import ( "bufio" "bytes" "encoding/json" "flag" "fmt" "io" "log" "net" "net/http" "os" "regexp" "strings" "time" ) var DX = readDXExpeFile("dx.txt") type ClusterMessage struct { From string DX string Freq string Mode string Report string Time string } func ParseFlags() (string, error) { // String that contains the configured configuration path var configPath string // Set up a CLI flag called "-config" to allow users // to supply the configuration file flag.StringVar(&configPath, "config", "./config.yml", "path to config file") // Actually parse the flags flag.Parse() // Validate the path first if err := ValidateConfigPath(configPath); err != nil { return "", err } // Return the configuration path return configPath, nil } // Message structure for Gotify type GotifyMessage struct { Title string `json:"title"` Message string `json:"message"` Priority int `json:"priority"` } func readDXExpeFile(filename string) string { file, err := os.Open(filename) if err != nil { fmt.Println("Error while opening file", err) } defer file.Close() // Lire tout le contenu du fichier content, err := io.ReadAll(file) if err != nil { fmt.Println("Error while reading the file", err) } log.Printf("DX Expe file has been loaded properly with following calls: %s", content) return string(content) } // Function to send message to Gotify func sendToGotify(title string, sMess ClusterMessage, priority int, cfg Config) { message := fmt.Sprintf("DX: %s\nFrom: %s\nFreq: %s\nMode: %s\nReport: %s\nTime: %s", sMess.DX, sMess.From, sMess.Freq, sMess.Mode, sMess.Report, sMess.Time) gotifyMsg := GotifyMessage{ Title: title, Message: message, Priority: priority, } jsonData, err := json.Marshal(gotifyMsg) if err != nil { log.Println("Error marshaling JSON:", err) return } req, err := http.NewRequest("POST", cfg.Gotify.URL, bytes.NewBuffer(jsonData)) if err != nil { log.Println("Error creating request:", err) return } req.Header.Set("Content-Type", "application/json") req.Header.Add("Authorization", "Bearer "+cfg.Gotify.Token) client := &http.Client{} resp, err := client.Do(req) if err != nil { log.Println("Error sending request:", err) return } defer resp.Body.Close() if resp.StatusCode != http.StatusOK { log.Println("Gotify server returned non-OK status:", resp.Status) } else { log.Println("message successfully sent to Gotify") } } func SanitizeClusterMessage(message string) ClusterMessage { r := regexp.MustCompile(`DX\sde\s([A-Z0-9]+)[-#:]+[\s]+([0-9]+.[0-9])[\s]+([^\s]+)[\s]+([A-Z]+[0-9])\s+(.*dB).*(.{4})Z$`) matches := r.FindStringSubmatch(message) mes := ClusterMessage{} if len(matches) > 0 { timeLayout := "1504" formatedTime, _ := time.Parse(timeLayout, matches[6]) formatedTime = formatedTime.Add(time.Hour * time.Duration(7)) newTime := formatedTime.Format("15:04") mes := ClusterMessage{ From: matches[1], Freq: matches[2], DX: matches[3], Mode: matches[4], Report: matches[5], Time: newTime, } return mes } return mes } func sendTelnetMessage(conn net.Conn, message string) { if message != "XV9Q-5" { time.Sleep(2 * time.Second) } _, err := conn.Write([]byte(message + "\n")) if err != nil { conn.Close() time.Sleep(5 * time.Second) // Wait before retrying } } func sendFilters(conn net.Conn) { go sendTelnetMessage(conn, "set/ft8") time.Sleep(1 * time.Second) //go sendTelnetMessage(conn, "SET/FILTER DOC/PASS 3W") //time.Sleep(1 * time.Second) go sendTelnetMessage(conn, "set/skimmer") time.Sleep(1 * time.Second) go sendTelnetMessage(conn, "set/ft4") } func main() { // Generate our config based on the config supplied // by the user in the flags cfgPath, err := ParseFlags() if err != nil { log.Fatal(err) } cfg, err := NewConfig(cfgPath) if err != nil { log.Fatal(err) } login := false filters_sent := false fmt.Println("PushDXCluster v0.1") for { // Connect to the Telnet server conn, err := net.Dial("tcp", cfg.Cluster.Host) if err != nil { log.Printf("Failed to connect to Telnet server: %v", err) time.Sleep(5 * time.Second) // Wait before retrying continue } log.Printf("Connected to %s server", cfg.Cluster.Host) // Create a buffered reader to read from the Telnet server reader := bufio.NewReader(conn) // Loop to read from the Telnet server for { message, err := reader.ReadString('\n') if err != nil { log.Printf("Error reading from Telnet server: %v", err) conn.Close() break } // Trim and Sanitize spots messages message = strings.TrimSpace(message) sMess := SanitizeClusterMessage(message) if sMess.DX != "" { log.Printf("Sanitized message: Reporter: %s, DX: %s, Freq: %s, Report: %s, Time: %s", sMess.From, sMess.DX, sMess.Freq, sMess.Report, sMess.Time) } else { log.Printf("Received message: %s", message) } // Send Call to ID to the Server if message != "" && strings.Contains("Please enter your call:", message) { go sendTelnetMessage(conn, "XV9Q-5") login = true } // Set the FT8, CW, FT4 and only spots from Vietnam filters only once if login && !filters_sent { go sendFilters(conn) filters_sent = true } // If calls is in the DX List then send a Gotify notification if sMess.DX != "" && sMess.From == "XV9Q" && strings.Contains(DX, sMess.DX) { sendToGotify("Spot", sMess, 5, *cfg) } } log.Println("Disconnected from Telnet server, reconnecting...") time.Sleep(5 * time.Second) // Wait before reconnecting } }