package main import ( "encoding/json" "fmt" "io/ioutil" "net/http" "net/url" "os" "path/filepath" "regexp" "strconv" "strings" "time" "github.com/spf13/pflag" "gopkg.in/yaml.v2" ) var Version = "development" type Config struct { ChatID string `yaml:"CHATID"` Token string `yaml:"TOKEN"` Timeout int `yaml:"TIMEOUT"` } var ( configFile string chatID string token string parse_mode string timeout int silent bool versionFlag bool helpFlag bool ) func init() { pflag.StringVarP(&configFile, "config", "f", "", "Configuration file path") pflag.StringVarP(&chatID, "chatid", "c", "", "Chat ID value") pflag.StringVarP(&token, "token", "t", "", "Bot token value") pflag.StringVarP(&parse_mode, "parse", "p", "MarkdownV2", "Telegram message parse mode") pflag.IntVarP(&timeout, "timeout", "", 10, "Timeout value") pflag.BoolVarP(&silent, "silent", "s", false, "Disable notification sound") pflag.BoolVarP(&versionFlag, "version", "v", false, "Print version information and exit") pflag.BoolVarP(&helpFlag, "help", "h", false, "Prints this message") } func main() { //Set version message versionMsg := fmt.Sprintf("\033[0;36mtbotsend\033[0m - Version: \033[0;33m%s\033[0m", Version) pflag.Usage = func() { fmt.Fprintf(os.Stderr, "%s\n", versionMsg) fmt.Fprintf(os.Stderr, "%s\n", strings.Repeat("-", len(stripANSI(versionMsg)))) fmt.Fprintf(os.Stderr, "Usage: %s [OPTIONS] {MESSAGE}\n\n", os.Args[0]) fmt.Fprintf(os.Stderr, "Options:\n") pflag.PrintDefaults() fmt.Fprintf(os.Stderr, "\nExamples:\n") fmt.Fprintf(os.Stderr, " %s -f config.yaml -c 123456 -t botToken123 --silent Hello world!\n", os.Args[0]) fmt.Fprintf(os.Stderr, " %s --version\n", os.Args[0]) } pflag.Parse() if versionFlag { //fmt.Printf("tbotsend version %s\n", Version) fmt.Printf("%s\n", versionMsg) os.Exit(0) } if helpFlag { pflag.Usage() os.Exit(0) } args := pflag.Args() message := strings.Join(args, " ") // Print the date and time for logging purposes fmt.Println(" -- " + time.Now().Format("2006-01-02 15:04:05")) var config Config configUsed := "" // Check configuration if (chatID != "" || token != "") && configFile != "" { printError("Cannot use both configuration file and configuration flags") os.Exit(2) } else if chatID != "" && token != "" { // Use configuration flags config.ChatID = chatID config.Token = token config.Timeout = timeout configUsed = "MANUAL" } else { // Use configuration file if configFile == "" { // Use default config file at ${HOME}/.config/tbotsend.yaml homeDir, err := os.UserHomeDir() if err != nil { printError(fmt.Sprintf("Failed to get user home directory: %v", err)) os.Exit(2) } configFile = filepath.Join(homeDir, ".config", "tbotsend.yaml") } // Read configuration file data, err := ioutil.ReadFile(configFile) if err != nil { // Try secondary configuration file at ${HOME}/.tbotsend.yaml if configFile == filepath.Join(os.Getenv("HOME"), ".config", "tbotsend.yaml") { secondaryConfigFile := filepath.Join(os.Getenv("HOME"), ".tbotsend.yaml") data, err = ioutil.ReadFile(secondaryConfigFile) if err != nil { printError(fmt.Sprintf("Failed to read configuration file '%s' or '%s': %v", configFile, secondaryConfigFile, err)) os.Exit(2) } configFile = secondaryConfigFile } else { printError(fmt.Sprintf("Failed to read configuration file '%s': %v", configFile, err)) os.Exit(2) } } // Parse YAML err = yaml.Unmarshal(data, &config) if err != nil { printError(fmt.Sprintf("Failed to parse configuration file '%s': %v", configFile, err)) os.Exit(2) } // Set default timeout if not set if config.Timeout == 0 { config.Timeout = 10 } configUsed = configFile } // Validate configuration if config.ChatID == "" || config.Token == "" { printError("Configuration is missing ChatID or Token") os.Exit(2) } // Ensure message is provided if message == "" { printError("No message provided") os.Exit(2) } // Prepare to send the message urlStr := fmt.Sprintf("https://api.telegram.org/bot%s/sendMessage", config.Token) data := url.Values{} data.Set("chat_id", config.ChatID) data.Set("disable_web_page_preview", "1") if silent { data.Set("disable_notification", "true") } data.Set("text", message) data.Set("parse_mode", parse_mode) client := &http.Client{ Timeout: time.Duration(config.Timeout) * time.Second, } // Send the POST request resp, err := client.PostForm(urlStr, data) if err != nil { printFailure(err.Error()) os.Exit(13) } defer resp.Body.Close() // Read the response body body, err := ioutil.ReadAll(resp.Body) if err != nil { printFailure(err.Error()) os.Exit(13) } // Parse the response to check for success type TelegramResponse struct { Ok bool `json:"ok"` Description string `json:"description"` } var telegramResp TelegramResponse err = json.Unmarshal(body, &telegramResp) if err != nil { printFailure(err.Error()) os.Exit(13) } if !telegramResp.Ok { printFailure(telegramResp.Description) os.Exit(13) } // Print the required information fmt.Printf("\033[1mConfig Used:\033[0m %s\n", configUsed) fmt.Printf("\033[1mChat ID:\033[0m %s\n", config.ChatID) fmt.Printf("\033[1mSilent:\033[0m %s\n", strconv.FormatBool(silent)) fmt.Printf("\033[1mMessage:\033[0m %s\n", message) // Print SUCCESS in bold green text printSuccess() } func printError(msg string) { fmt.Fprintf(os.Stderr, "[\033[1;31mERROR\033[0m] %s\n", msg) } func printFailure(msg string) { fmt.Fprintf(os.Stderr, "\033[1mResult:\033[0m [\033[1;31mFAILURE\033[0m]: %s\n\n", msg) } func printSuccess() { fmt.Fprintf(os.Stdout, "\033[1mResult:\033[0m [\033[1;32mSUCCESS\033[0m]\n\n") } // Strip ANSI escape sequences for accurate string length assessment func stripANSI(input string) string { ansiPattern := `\033\[[0-9;]*[a-zA-Z]` re := regexp.MustCompile(ansiPattern) return re.ReplaceAllString(input, "") }