From 0e2b33755befbb4a724173ef0ce063672c1bd39f Mon Sep 17 00:00:00 2001 From: Injamul Mohammad Mollah Date: Sat, 23 Aug 2025 12:38:22 +0530 Subject: [PATCH 1/4] feat: add gotify notification support --- .gitignore | 1 + README.md | 1 + chatz.ini | 5 +++++ config/config.go | 2 ++ constants/common.go | 1 + docs/gotify.md | 45 ++++++++++++++++++++++++++++++++++++++++++++ providers/agent.go | 2 ++ providers/gotify.go | 46 +++++++++++++++++++++++++++++++++++++++++++++ utils/common.go | 9 +++++++++ 9 files changed, 112 insertions(+) create mode 100644 docs/gotify.md create mode 100644 providers/gotify.go diff --git a/.gitignore b/.gitignore index f2ada31..f47a2c3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ build chatz +*.exe coverage.out diff --git a/README.md b/README.md index 41ab7dd..9898798 100644 --- a/README.md +++ b/README.md @@ -28,6 +28,7 @@ With chatz, you can streamline your notification processes across multiple platf - Discord: [Read documentation](docs/discord.md) - Redis: [Read documentation](docs/redis.md) - SMTP: [Read documentation](docs/smtp.md) +- Gotify: [Read documentation](docs/gotify.md) ## Installation Download and install executable binary from GitHub releases page. diff --git a/chatz.ini b/chatz.ini index 85e3724..d52c86a 100644 --- a/chatz.ini +++ b/chatz.ini @@ -36,3 +36,8 @@ SMTP_PASSWORD= SMTP_SUBJECT= SMTP_FROM= SMTP_TO= + +[gotify] +PROVIDER=gotify +GOTIFY_URL= +GOTIFY_TOKEN= diff --git a/config/config.go b/config/config.go index 7cef296..1b53791 100644 --- a/config/config.go +++ b/config/config.go @@ -17,4 +17,6 @@ type Config struct { SMTPSubject string SMTPFrom string SMTPTo string + GotifyURL string + GotifyToken string } diff --git a/constants/common.go b/constants/common.go index fff8268..a1dcbe1 100644 --- a/constants/common.go +++ b/constants/common.go @@ -7,4 +7,5 @@ const ( PROVIDER_GOOGLE = "google" PROVIDER_REDIS = "redis" PROVIDER_SMTP = "smtp" + PROVIDER_GOTIFY = "gotify" ) diff --git a/docs/gotify.md b/docs/gotify.md new file mode 100644 index 0000000..ca7c270 --- /dev/null +++ b/docs/gotify.md @@ -0,0 +1,45 @@ +# Gotify Notification Setup + +This guide will walk you through setting up Gotify notifications. You will learn how to obtain the necessary credentials and configure them in your environment. + +## 1. Get Gotify URL and App Token + +First, you need to get the Gotify server URL and an app token. + +1. **Gotify Server URL**: This is the URL of your Gotify server. +2. **App Token**: You can create a new app in the Gotify web UI and get the token. + +## 2. Configure Chatz + +Next, you need to configure Chatz to use the Gotify provider. You can do this by editing the `~/.chatz.ini` file or by setting environment variables. + +### Using `~/.chatz.ini` + +Add the following section to your `~/.chatz.ini` file: + +```ini +[gotify] +PROVIDER=gotify +GOTIFY_URL= +GOTIFY_TOKEN= +``` + +### Using Environment Variables + +Set the following environment variables: + +```sh +export PROVIDER=gotify +export GOTIFY_URL= +export GOTIFY_TOKEN= +``` + +## 3. Send a Test Notification + +Now you can send a test notification to your Gotify server. + +```sh +chatz --profile=gotify "Hello from Chatz!" +``` + +You should receive a notification on your Gotify server. diff --git a/providers/agent.go b/providers/agent.go index b6b4ea5..c987a8a 100644 --- a/providers/agent.go +++ b/providers/agent.go @@ -26,6 +26,8 @@ func NewProvider(env *config.Config) (Provider, error) { return &RedisProvider{config: env}, nil case constants.PROVIDER_SMTP: return &SMTPProvider{config: env}, nil + case constants.PROVIDER_GOTIFY: + return &GotifyProvider{config: env}, nil default: return nil, errors.New("Invalid provider config in ~/.chatz.ini") } diff --git a/providers/gotify.go b/providers/gotify.go new file mode 100644 index 0000000..75d5bcb --- /dev/null +++ b/providers/gotify.go @@ -0,0 +1,46 @@ +package providers + +import ( + "errors" + "fmt" + "io" + "net/http" + "strings" + + "github.com/tech-thinker/chatz/config" +) + +type GotifyProvider struct { + config *config.Config +} + +func (agent *GotifyProvider) Post(message string) (interface{}, error) { + url := fmt.Sprintf("%s/message", agent.config.GotifyURL) + + payloadStr := fmt.Sprintf( + `{"message": "%s", "priority": 5, "title": "Chatz Notification"}`, + message, + ) + + payload := strings.NewReader(payloadStr) + + req, _ := http.NewRequest("POST", url, payload) + + req.Header.Add("Content-Type", "application/json") + req.Header.Add("User-Agent", "tech-thinker/chatz") + req.Header.Add("X-Gotify-Key", agent.config.GotifyToken) + + res, err := http.DefaultClient.Do(req) + if err != nil { + return nil, err + } + + defer res.Body.Close() + body, err := io.ReadAll(res.Body) + return string(body), err +} + +func (agent *GotifyProvider) Reply(threadId string, message string) (interface{}, error) { + fmt.Println("Reply to gotify not supported yet.") + return nil, errors.New("reply to gotify not supported yet") +} diff --git a/utils/common.go b/utils/common.go index 8dbfb5d..dd1c7be 100644 --- a/utils/common.go +++ b/utils/common.go @@ -37,6 +37,8 @@ func loadEnvFromSystemEnv() (*config.Config, error) { smtpSubject := v.GetString("SMTP_SUBJECT") smtpFrom := v.GetString("SMTP_FROM") smtpTo := v.GetString("SMTP_TO") + gotifyURL := v.GetString("GOTIFY_URL") + gotifyToken := v.GetString("GOTIFY_TOKEN") var env config.Config @@ -55,6 +57,8 @@ func loadEnvFromSystemEnv() (*config.Config, error) { env.SMTPSubject = smtpSubject env.SMTPFrom = smtpFrom env.SMTPTo = smtpTo + env.GotifyURL = gotifyURL + env.GotifyToken = gotifyToken return &env, nil } @@ -99,6 +103,9 @@ func loadEnvFromFile(profile string) (*config.Config, error) { smtpFrom := viper.GetString(fmt.Sprintf("%s.SMTP_FROM", profile)) smtpTo := viper.GetString(fmt.Sprintf("%s.SMTP_TO", profile)) + gotifyURL := viper.GetString(fmt.Sprintf("%s.GOTIFY_URL", profile)) + gotifyToken := viper.GetString(fmt.Sprintf("%s.GOTIFY_TOKEN", profile)) + var env config.Config env.Provider = provider env.WebHookURL = webHookURL @@ -115,6 +122,8 @@ func loadEnvFromFile(profile string) (*config.Config, error) { env.SMTPSubject = smtpSubject env.SMTPFrom = smtpFrom env.SMTPTo = smtpTo + env.GotifyURL = gotifyURL + env.GotifyToken = gotifyToken return &env, nil } From aa2e50885781b70098ee31940c15f3c69195c528 Mon Sep 17 00:00:00 2001 From: Injamul Mohammad Mollah Date: Sat, 23 Aug 2025 17:44:26 +0530 Subject: [PATCH 2/4] fix: #8 add title and priority for gotify notification --- main.go | 25 ++++++++++++++++++++++++- providers/gotify.go | 17 +++++++++++++++-- 2 files changed, 39 insertions(+), 3 deletions(-) diff --git a/main.go b/main.go index bc06edb..40b56d2 100644 --- a/main.go +++ b/main.go @@ -22,6 +22,8 @@ func main() { var threadId string var output bool var fromEnv bool + var title string + var priority int app := cli.NewApp() app.Name = "chatz" @@ -59,6 +61,18 @@ func main() { Usage: "To use config from environment variables", Destination: &fromEnv, }, + &cli.StringFlag{ + Name: "title", + Aliases: []string{"ti"}, + Usage: "Title for gotify notification", + Destination: &title, + }, + &cli.IntFlag{ + Name: "priority", + Aliases: []string{"pr"}, + Usage: "Priority for gotify notification", + Destination: &priority, + }, } app.Action = func(ctx *cli.Context) error { if version { @@ -99,6 +113,15 @@ func main() { } return nil } + + if gotifyProvider, ok := provider.(*providers.GotifyProvider); ok { + res, err := gotifyProvider.PostWithPriority(message, title, priority) + if output { + fmt.Println(res) + } + return err + } + res, err := provider.Post(message) if output { fmt.Println(res) @@ -110,4 +133,4 @@ func main() { panic(err) } -} +} \ No newline at end of file diff --git a/providers/gotify.go b/providers/gotify.go index 75d5bcb..b508031 100644 --- a/providers/gotify.go +++ b/providers/gotify.go @@ -15,11 +15,24 @@ type GotifyProvider struct { } func (agent *GotifyProvider) Post(message string) (interface{}, error) { + return agent.PostWithPriority(message, "", 0) +} + +func (agent *GotifyProvider) PostWithPriority(message string, title string, priority int) (interface{}, error) { url := fmt.Sprintf("%s/message", agent.config.GotifyURL) + if len(title) == 0 { + title = "Chatz Notification" + } + if priority == 0 { + priority = 5 + } + payloadStr := fmt.Sprintf( - `{"message": "%s", "priority": 5, "title": "Chatz Notification"}`, + `{"message": "%s", "priority": %d, "title": "%s"}`, message, + priority, + title, ) payload := strings.NewReader(payloadStr) @@ -43,4 +56,4 @@ func (agent *GotifyProvider) Post(message string) (interface{}, error) { func (agent *GotifyProvider) Reply(threadId string, message string) (interface{}, error) { fmt.Println("Reply to gotify not supported yet.") return nil, errors.New("reply to gotify not supported yet") -} +} \ No newline at end of file From d4fd3c2649003551908be3a5912c5fbda7e7e9ce Mon Sep 17 00:00:00 2001 From: Injamul Mohammad Mollah Date: Sat, 23 Aug 2025 19:26:20 +0530 Subject: [PATCH 3/4] fix: add subject for smtp too and load default title and priority from env or config for gotify --- chatz.ini | 2 ++ config/config.go | 36 +++++++++++++++--------------- docs/gotify.md | 14 ++++++++++++ docs/smtp.md | 53 +++++++++++++++++++++++++-------------------- main.go | 22 +++++++++++++------ man/chatz.1 | 3 +++ providers/gotify.go | 6 ++--- providers/smtp.go | 8 ++++++- utils/common.go | 8 +++++++ 9 files changed, 100 insertions(+), 52 deletions(-) diff --git a/chatz.ini b/chatz.ini index d52c86a..aaa91c5 100644 --- a/chatz.ini +++ b/chatz.ini @@ -41,3 +41,5 @@ SMTP_TO= PROVIDER=gotify GOTIFY_URL= GOTIFY_TOKEN= +GOTIFY_TITLE="Chatz" +GOTIFY_PRIORITY=5 diff --git a/config/config.go b/config/config.go index 1b53791..3d821fb 100644 --- a/config/config.go +++ b/config/config.go @@ -2,21 +2,23 @@ package config // Environment type Config struct { - Provider string - WebHookURL string - Token string - ChannelId string - ChatId string - ConnectionURL string - SMTPHost string - SMTPPort string - UseTLS bool - UseSTARTTLS bool - SMTPUser string - SMTPPassword string - SMTPSubject string - SMTPFrom string - SMTPTo string - GotifyURL string - GotifyToken string + Provider string + WebHookURL string + Token string + ChannelId string + ChatId string + ConnectionURL string + SMTPHost string + SMTPPort string + UseTLS bool + UseSTARTTLS bool + SMTPUser string + SMTPPassword string + SMTPSubject string + SMTPFrom string + SMTPTo string + GotifyURL string + GotifyToken string + GotifyTitle string + GotifyPriority int } diff --git a/docs/gotify.md b/docs/gotify.md index ca7c270..96bfa0e 100644 --- a/docs/gotify.md +++ b/docs/gotify.md @@ -22,6 +22,8 @@ Add the following section to your `~/.chatz.ini` file: PROVIDER=gotify GOTIFY_URL= GOTIFY_TOKEN= +GOTIFY_TITLE= +GOTIFY_PRIORITY= ``` ### Using Environment Variables @@ -32,6 +34,8 @@ Set the following environment variables: export PROVIDER=gotify export GOTIFY_URL= export GOTIFY_TOKEN= +export GOTIFY_TITLE= +export GOTIFY_PRIORITY= ``` ## 3. Send a Test Notification @@ -43,3 +47,13 @@ chatz --profile=gotify "Hello from Chatz!" ``` You should receive a notification on your Gotify server. + +### Using CLI Flags + +You can also override the title and priority using CLI flags: + +```sh +chatz --profile=gotify --subject "Custom Title" --priority 7 "Hello from Chatz!" +``` + +This will send a notification with "Custom Title" as the title and a priority of 7. diff --git a/docs/smtp.md b/docs/smtp.md index 8998533..ee614c8 100644 --- a/docs/smtp.md +++ b/docs/smtp.md @@ -2,41 +2,34 @@ This document explains how to configure SMTP with Gmail, including creating an App Password, and understanding TLS/STARTTLS and encryption options. - ## 1. Creating an App Password -Gmail requires an "App Password" for less secure apps accessing your account. This is a more secure alternative to using your regular Gmail password directly. +Gmail requires an "App Password" for less secure apps accessing your account. This is a more secure alternative to using your regular Gmail password directly. 1. Go to your [Google account security settings](https://myaccount.google.com/security). 2. Scroll down to "Signing in to Google" and click "App Passwords". -3. Select "Mail" as the app and "Other (Custom name)" as the device. Give it a descriptive name (e.g., "My SMTP App"). -4. Click "Generate". A new password will be displayed. **Copy this password immediately; you won't be able to see it again.** +3. Select "Mail" as the app and "Other (Custom name)" as the device. Give it a descriptive name (e.g., "My SMTP App"). +4. Click "Generate". A new password will be displayed. **Copy this password immediately; you won't be able to see it again.** ## 2. SMTP Server Settings -* **Server:** `smtp.gmail.com` -* **Port:** - * **STARTTLS:** 587 (Recommended) - * **TLS:** 465 - * **No Encryption (Insecure - Avoid):** 25 -* **Username:** Your full Gmail address (e.g., `yourname@gmail.com`) -* **Password:** The App Password you generated. - +- **Server:** `smtp.gmail.com` +- **Port:** + - **STARTTLS:** 587 (Recommended) + - **TLS:** 465 + - **No Encryption (Insecure - Avoid):** 25 +- **Username:** Your full Gmail address (e.g., `yourname@gmail.com`) +- **Password:** The App Password you generated. ## 3. TLS/STARTTLS and Encryption -* **TLS (Transport Layer Security) / STARTTLS:** These are encryption protocols that secure the connection between your email client and the SMTP server. They encrypt your email messages and prevent eavesdropping. **Using TLS/STARTTLS is strongly recommended.** Many email clients support STARTTLS, initiating encryption during the connection process. - - -* **No Encryption:** Sending emails without encryption is highly discouraged. Your email, including the subject, body, and any attachments, could be intercepted by malicious actors. Avoid this unless absolutely necessary, and only with trusted parties. - +- **TLS (Transport Layer Security) / STARTTLS:** These are encryption protocols that secure the connection between your email client and the SMTP server. They encrypt your email messages and prevent eavesdropping. **Using TLS/STARTTLS is strongly recommended.** Many email clients support STARTTLS, initiating encryption during the connection process. +- **No Encryption:** Sending emails without encryption is highly discouraged. Your email, including the subject, body, and any attachments, could be intercepted by malicious actors. Avoid this unless absolutely necessary, and only with trusted parties. ## 4. Example Configuration (Illustrative - adapt to your email client) - -This is a generic example; the exact settings will depend on your email client (e.g., Outlook, Thunderbird). Refer to your email client's documentation for specific instructions. - +This is a generic example; the exact settings will depend on your email client (e.g., Outlook, Thunderbird). Refer to your email client's documentation for specific instructions. ``` SMTP_HOST=smtp.gmail.com @@ -46,14 +39,26 @@ SMTP_USE_STARTTLS=true Remember to replace placeholders with your actual information. -## 5. Troubleshooting +### Using CLI Flags +You can also override the subject using the CLI flag: + +```sh +chatz --profile=smtp --subject "Custom Subject" "Hello from Chatz!" +``` + +This will send an email with "Custom Subject" as the subject. + +## 5. Troubleshooting If you encounter issues, check: -* **Correct App Password:** Verify you copied the correct App Password. -* **Firewall Settings:** Ensure your firewall isn't blocking outgoing connections on port 587 (or 465 if using no encryption). -* **Email Client Configuration:** Double-check all server settings in your email client. +- **Correct App Password:** Verify you copied the correct App Password. +- **Firewall Settings:** Ensure your firewall isn't blocking outgoing connections on port 587 (or 465 if using no encryption). +- **Email Client Configuration:** Double-check all server settings in your email client. Using an App Password is crucial for securing your Gmail account when using SMTP. Always prioritize using TLS/STARTTLS encryption for the security of your emails. + +``` + ``` diff --git a/main.go b/main.go index 40b56d2..088257c 100644 --- a/main.go +++ b/main.go @@ -22,7 +22,7 @@ func main() { var threadId string var output bool var fromEnv bool - var title string + var subject string var priority int app := cli.NewApp() @@ -62,10 +62,10 @@ func main() { Destination: &fromEnv, }, &cli.StringFlag{ - Name: "title", - Aliases: []string{"ti"}, - Usage: "Title for gotify notification", - Destination: &title, + Name: "subject", + Aliases: []string{"s"}, + Usage: "Subject for provider which supports subject or title", + Destination: &subject, }, &cli.IntFlag{ Name: "priority", @@ -115,7 +115,15 @@ func main() { } if gotifyProvider, ok := provider.(*providers.GotifyProvider); ok { - res, err := gotifyProvider.PostWithPriority(message, title, priority) + res, err := gotifyProvider.PostWithTitleAndPriority(message, subject, priority) + if output { + fmt.Println(res) + } + return err + } + + if smtpProvider, ok := provider.(*providers.SMTPProvider); ok { + res, err := smtpProvider.PostWithSubject(message, subject) if output { fmt.Println(res) } @@ -133,4 +141,4 @@ func main() { panic(err) } -} \ No newline at end of file +} diff --git a/man/chatz.1 b/man/chatz.1 index 4b0177b..9b1b41a 100644 --- a/man/chatz.1 +++ b/man/chatz.1 @@ -25,6 +25,9 @@ Print output to stdout. .B \-t, \-\-thread-id Thread ID for replies to a message. .TP +.B \-s, \-\-subject +Subject for provider which supports subject or title. +.TP .B \-e, \-\-from-env Use system environment variables. .TP diff --git a/providers/gotify.go b/providers/gotify.go index b508031..1b41504 100644 --- a/providers/gotify.go +++ b/providers/gotify.go @@ -15,10 +15,10 @@ type GotifyProvider struct { } func (agent *GotifyProvider) Post(message string) (interface{}, error) { - return agent.PostWithPriority(message, "", 0) + return agent.PostWithTitleAndPriority(message, agent.config.GotifyTitle, agent.config.GotifyPriority) } -func (agent *GotifyProvider) PostWithPriority(message string, title string, priority int) (interface{}, error) { +func (agent *GotifyProvider) PostWithTitleAndPriority(message string, title string, priority int) (interface{}, error) { url := fmt.Sprintf("%s/message", agent.config.GotifyURL) if len(title) == 0 { @@ -56,4 +56,4 @@ func (agent *GotifyProvider) PostWithPriority(message string, title string, prio func (agent *GotifyProvider) Reply(threadId string, message string) (interface{}, error) { fmt.Println("Reply to gotify not supported yet.") return nil, errors.New("reply to gotify not supported yet") -} \ No newline at end of file +} diff --git a/providers/smtp.go b/providers/smtp.go index d5bed09..824647c 100644 --- a/providers/smtp.go +++ b/providers/smtp.go @@ -16,13 +16,19 @@ type SMTPProvider struct { } func (agent *SMTPProvider) Post(message string) (interface{}, error) { + return agent.PostWithSubject(message, "") +} + +func (agent *SMTPProvider) PostWithSubject(message string, subject string) (interface{}, error) { host := agent.config.SMTPHost port := agent.config.SMTPPort smtpServer := fmt.Sprintf("%s:%s", host, port) user := agent.config.SMTPUser password := agent.config.SMTPPassword - subject := agent.config.SMTPSubject + if len(subject) == 0 { + subject = agent.config.SMTPSubject + } from := agent.config.SMTPFrom recipients := agent.config.SMTPTo diff --git a/utils/common.go b/utils/common.go index dd1c7be..e15c5ae 100644 --- a/utils/common.go +++ b/utils/common.go @@ -39,6 +39,8 @@ func loadEnvFromSystemEnv() (*config.Config, error) { smtpTo := v.GetString("SMTP_TO") gotifyURL := v.GetString("GOTIFY_URL") gotifyToken := v.GetString("GOTIFY_TOKEN") + gotifyTitle := v.GetString("GOTIFY_TITLE") + gotifyPriority := v.GetInt("GOTIFY_PRIORITY") var env config.Config @@ -59,6 +61,8 @@ func loadEnvFromSystemEnv() (*config.Config, error) { env.SMTPTo = smtpTo env.GotifyURL = gotifyURL env.GotifyToken = gotifyToken + env.GotifyTitle = gotifyTitle + env.GotifyPriority = gotifyPriority return &env, nil } @@ -105,6 +109,8 @@ func loadEnvFromFile(profile string) (*config.Config, error) { gotifyURL := viper.GetString(fmt.Sprintf("%s.GOTIFY_URL", profile)) gotifyToken := viper.GetString(fmt.Sprintf("%s.GOTIFY_TOKEN", profile)) + gotifyTitle := viper.GetString(fmt.Sprintf("%s.GOTIFY_TITLE", profile)) + gotifyPriority := viper.GetInt(fmt.Sprintf("%s.GOTIFY_PRIORITY", profile)) var env config.Config env.Provider = provider @@ -124,6 +130,8 @@ func loadEnvFromFile(profile string) (*config.Config, error) { env.SMTPTo = smtpTo env.GotifyURL = gotifyURL env.GotifyToken = gotifyToken + env.GotifyTitle = gotifyTitle + env.GotifyPriority = gotifyPriority return &env, nil } From bfdaa41a6a2abd63232d2b5af868b149555962c0 Mon Sep 17 00:00:00 2001 From: Asif Mohammad Mollah Date: Sat, 23 Aug 2025 23:02:31 +0400 Subject: [PATCH 4/4] optimizing code structure... --- main.go | 27 +++++++-------------- models/base.go | 7 ++++++ models/slack.go | 42 ++++++++++++++++----------------- providers/agent.go | 5 ++-- providers/discord.go | 30 +++++++++++------------ providers/google.go | 44 +++++++++++++++++----------------- providers/gotify.go | 35 ++++++++++++++++----------- providers/redis.go | 34 +++++++++++++------------- providers/slack.go | 44 +++++++++++++++++----------------- providers/smtp.go | 27 ++++++++++----------- providers/telegram.go | 55 ++++++++++++++++++++++--------------------- utils/pointers.go | 9 +++++++ 12 files changed, 187 insertions(+), 172 deletions(-) create mode 100644 models/base.go create mode 100644 utils/pointers.go diff --git a/main.go b/main.go index 088257c..96ffe51 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "fmt" "os" + "github.com/tech-thinker/chatz/models" "github.com/tech-thinker/chatz/providers" "github.com/tech-thinker/chatz/utils" "github.com/urfave/cli/v2" @@ -106,31 +107,21 @@ func main() { return nil } - if len(threadId) > 0 { - res, _ := provider.Reply(threadId, message) - if output { - fmt.Println(res) - } - return nil - } - - if gotifyProvider, ok := provider.(*providers.GotifyProvider); ok { - res, err := gotifyProvider.PostWithTitleAndPriority(message, subject, priority) - if output { - fmt.Println(res) - } - return err + option := models.Option{ + Title: &subject, + Subject: &subject, + Priority: &priority, } - if smtpProvider, ok := provider.(*providers.SMTPProvider); ok { - res, err := smtpProvider.PostWithSubject(message, subject) + if len(threadId) > 0 { + res, _ := provider.Reply(threadId, message, option) if output { fmt.Println(res) } - return err + return nil } - res, err := provider.Post(message) + res, err := provider.Post(message, option) if output { fmt.Println(res) } diff --git a/models/base.go b/models/base.go new file mode 100644 index 0000000..c3f7804 --- /dev/null +++ b/models/base.go @@ -0,0 +1,7 @@ +package models + +type Option struct { + Priority *int `json:"priority"` + Title *string `json:"title"` + Subject *string `json:"subject"` +} diff --git a/models/slack.go b/models/slack.go index 46bf6cc..0b51a0e 100644 --- a/models/slack.go +++ b/models/slack.go @@ -2,36 +2,36 @@ package models // SlackRes represents the main structure of the JSON response. type SlackRes struct { - Ok bool `json:"ok"` - Channel string `json:"channel"` - Ts string `json:"ts"` + Ok bool `json:"ok"` + Channel string `json:"channel"` + Ts string `json:"ts"` Message SlackMessage `json:"message"` - Warning string `json:"warning"` + Warning string `json:"warning"` ResponseMetadata SlackResponseMetadata `json:"response_metadata"` } // SlackMessage represents the message object within the main response. type SlackMessage struct { - User string `json:"user"` - Type string `json:"type"` - Ts string `json:"ts"` - BotID string `json:"bot_id"` - AppID string `json:"app_id"` - Text string `json:"text"` - Team string `json:"team"` + User string `json:"user"` + Type string `json:"type"` + Ts string `json:"ts"` + BotID string `json:"bot_id"` + AppID string `json:"app_id"` + Text string `json:"text"` + Team string `json:"team"` BotProfile SlackBotProfile `json:"bot_profile"` Blocks []SlackBlock `json:"blocks"` } // SlackBotProfile represents the profile of the bot that sent the message. type SlackBotProfile struct { - ID string `json:"id"` - AppID string `json:"app_id"` - Name string `json:"name"` - Icons SlackIcons `json:"icons"` - Deleted bool `json:"deleted"` - Updated int64 `json:"updated"` - TeamID string `json:"team_id"` + ID string `json:"id"` + AppID string `json:"app_id"` + Name string `json:"name"` + Icons SlackIcons `json:"icons"` + Deleted bool `json:"deleted"` + Updated int64 `json:"updated"` + TeamID string `json:"team_id"` } // SlackIcons represents the bot's profile images of different sizes. @@ -43,14 +43,14 @@ type SlackIcons struct { // Block represents the block object in the message. type SlackBlock struct { - Type string `json:"type"` - BlockID string `json:"block_id"` + Type string `json:"type"` + BlockID string `json:"block_id"` Elements []SlackElement `json:"elements"` } // SlackElement represents a section within the block. type SlackElement struct { - Type string `json:"type"` + Type string `json:"type"` Elements []SlackInnerElement `json:"elements"` } diff --git a/providers/agent.go b/providers/agent.go index c987a8a..0ce7102 100644 --- a/providers/agent.go +++ b/providers/agent.go @@ -5,11 +5,12 @@ import ( "github.com/tech-thinker/chatz/config" "github.com/tech-thinker/chatz/constants" + "github.com/tech-thinker/chatz/models" ) type Provider interface { - Post(message string) (interface{}, error) - Reply(threadId string, message string) (interface{}, error) + Post(message string, option models.Option) (any, error) + Reply(threadId string, message string, option models.Option) (any, error) } func NewProvider(env *config.Config) (Provider, error) { diff --git a/providers/discord.go b/providers/discord.go index 59e9e85..57c85b9 100644 --- a/providers/discord.go +++ b/providers/discord.go @@ -8,21 +8,22 @@ import ( "strings" "github.com/tech-thinker/chatz/config" + "github.com/tech-thinker/chatz/models" ) type DiscordProvider struct { - config *config.Config + config *config.Config } -func (agent *DiscordProvider) Post(message string) (interface{}, error) { - url := agent.config.WebHookURL +func (agent *DiscordProvider) Post(message string, option models.Option) (any, error) { + url := agent.config.WebHookURL payloadStr := fmt.Sprintf( - `{"content": "%s"}`, - message, - ) + `{"content": "%s"}`, + message, + ) - payload := strings.NewReader(payloadStr) + payload := strings.NewReader(payloadStr) req, _ := http.NewRequest("POST", url, payload) @@ -30,17 +31,16 @@ func (agent *DiscordProvider) Post(message string) (interface{}, error) { req.Header.Add("User-Agent", "tech-thinker/chatz") res, err := http.DefaultClient.Do(req) - if err!=nil { - return nil, err - } + if err != nil { + return nil, err + } defer res.Body.Close() body, err := io.ReadAll(res.Body) - return string(body), err + return string(body), err } -func (agent *DiscordProvider) Reply(threadId string, message string) (interface{}, error) { - fmt.Println("Reply to discord not supported yet.") - return nil, errors.New("Reply to discord not supported yet.") +func (agent *DiscordProvider) Reply(threadId string, message string, option models.Option) (any, error) { + fmt.Println("Reply to discord not supported yet.") + return nil, errors.New("Reply to discord not supported yet.") } - diff --git a/providers/google.go b/providers/google.go index bf7babe..67bfadc 100644 --- a/providers/google.go +++ b/providers/google.go @@ -7,21 +7,22 @@ import ( "strings" "github.com/tech-thinker/chatz/config" + "github.com/tech-thinker/chatz/models" ) type GoogleProvider struct { - config *config.Config + config *config.Config } -func (agent *GoogleProvider) Post(message string) (interface{}, error) { - url := agent.config.WebHookURL +func (agent *GoogleProvider) Post(message string, option models.Option) (any, error) { + url := agent.config.WebHookURL payloadStr := fmt.Sprintf( - `{"text": "%s"}`, - message, - ) + `{"text": "%s"}`, + message, + ) - payload := strings.NewReader(payloadStr) + payload := strings.NewReader(payloadStr) req, _ := http.NewRequest("POST", url, payload) @@ -29,24 +30,24 @@ func (agent *GoogleProvider) Post(message string) (interface{}, error) { req.Header.Add("User-Agent", "tech-thinker/chatz") res, err := http.DefaultClient.Do(req) - if err!=nil { - return nil, err - } + if err != nil { + return nil, err + } defer res.Body.Close() body, err := io.ReadAll(res.Body) - return string(body), err + return string(body), err } -func (agent *GoogleProvider) Reply(threadId string, message string) (interface{}, error) { - url := fmt.Sprintf("%s&messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD",agent.config.WebHookURL) +func (agent *GoogleProvider) Reply(threadId string, message string, option models.Option) (any, error) { + url := fmt.Sprintf("%s&messageReplyOption=REPLY_MESSAGE_FALLBACK_TO_NEW_THREAD", agent.config.WebHookURL) payloadStr := fmt.Sprintf( - `{"text": "%s", "thread": {"name": "%s"}}`, - message, threadId, - ) + `{"text": "%s", "thread": {"name": "%s"}}`, + message, threadId, + ) - payload := strings.NewReader(payloadStr) + payload := strings.NewReader(payloadStr) req, _ := http.NewRequest("POST", url, payload) @@ -54,12 +55,11 @@ func (agent *GoogleProvider) Reply(threadId string, message string) (interface{} req.Header.Add("User-Agent", "tech-thinker/chatz") res, err := http.DefaultClient.Do(req) - if err!=nil { - return nil, err - } + if err != nil { + return nil, err + } defer res.Body.Close() body, err := io.ReadAll(res.Body) - return string(body), err + return string(body), err } - diff --git a/providers/gotify.go b/providers/gotify.go index 1b41504..1be2019 100644 --- a/providers/gotify.go +++ b/providers/gotify.go @@ -8,31 +8,38 @@ import ( "strings" "github.com/tech-thinker/chatz/config" + "github.com/tech-thinker/chatz/models" + "github.com/tech-thinker/chatz/utils" ) type GotifyProvider struct { config *config.Config } -func (agent *GotifyProvider) Post(message string) (interface{}, error) { - return agent.PostWithTitleAndPriority(message, agent.config.GotifyTitle, agent.config.GotifyPriority) -} - -func (agent *GotifyProvider) PostWithTitleAndPriority(message string, title string, priority int) (interface{}, error) { - url := fmt.Sprintf("%s/message", agent.config.GotifyURL) - - if len(title) == 0 { - title = "Chatz Notification" +func (agent *GotifyProvider) Post(message string, option models.Option) (any, error) { + if option.Title == nil { + if len(agent.config.GotifyTitle) > 0 { + option.Title = &agent.config.GotifyTitle + } else { + option.Title = utils.NewString("Chatz Notification") + } } - if priority == 0 { - priority = 5 + + if option.Priority == nil { + if agent.config.GotifyPriority > 0 { + option.Priority = utils.NewInt(agent.config.GotifyPriority) + } else { + option.Priority = utils.NewInt(5) + } } + url := fmt.Sprintf("%s/message", agent.config.GotifyURL) + payloadStr := fmt.Sprintf( `{"message": "%s", "priority": %d, "title": "%s"}`, message, - priority, - title, + *option.Priority, + *option.Title, ) payload := strings.NewReader(payloadStr) @@ -53,7 +60,7 @@ func (agent *GotifyProvider) PostWithTitleAndPriority(message string, title stri return string(body), err } -func (agent *GotifyProvider) Reply(threadId string, message string) (interface{}, error) { +func (agent *GotifyProvider) Reply(threadId string, message string, option models.Option) (any, error) { fmt.Println("Reply to gotify not supported yet.") return nil, errors.New("reply to gotify not supported yet") } diff --git a/providers/redis.go b/providers/redis.go index 2ae9522..4dc094e 100644 --- a/providers/redis.go +++ b/providers/redis.go @@ -7,36 +7,36 @@ import ( "github.com/redis/go-redis/v9" "github.com/tech-thinker/chatz/config" + "github.com/tech-thinker/chatz/models" ) type RedisProvider struct { - config *config.Config + config *config.Config } -func (agent *RedisProvider) Post(message string) (interface{}, error) { - options, err := redis.ParseURL(agent.config.ConnectionURL) +func (agent *RedisProvider) Post(message string, option models.Option) (any, error) { + options, err := redis.ParseURL(agent.config.ConnectionURL) if err != nil { return nil, err } - - rdb := redis.NewClient(options) - ctx := context.Background() - - _, err = rdb.Ping(ctx).Result() + + rdb := redis.NewClient(options) + ctx := context.Background() + + _, err = rdb.Ping(ctx).Result() if err != nil { - return nil, err + return nil, err } - err = rdb.Publish(ctx, agent.config.ChannelId, message).Err() + err = rdb.Publish(ctx, agent.config.ChannelId, message).Err() if err != nil { - return nil, err + return nil, err } - - return "Published", nil -} -func (agent *RedisProvider) Reply(threadId string, message string) (interface{}, error) { - fmt.Println("Reply to redis not supported yet.") - return nil, errors.New("Reply to redis not supported yet.") + return "Published", nil } +func (agent *RedisProvider) Reply(threadId string, message string, option models.Option) (any, error) { + fmt.Println("Reply to redis not supported yet.") + return nil, errors.New("Reply to redis not supported yet.") +} diff --git a/providers/slack.go b/providers/slack.go index 3bf24bd..ccb0622 100644 --- a/providers/slack.go +++ b/providers/slack.go @@ -7,21 +7,22 @@ import ( "strings" "github.com/tech-thinker/chatz/config" + "github.com/tech-thinker/chatz/models" ) type SlackProvider struct { - config *config.Config + config *config.Config } -func (agent *SlackProvider) Post(message string) (interface{}, error) { - url := "https://slack.com/api/chat.postMessage" +func (agent *SlackProvider) Post(message string, option models.Option) (any, error) { + url := "https://slack.com/api/chat.postMessage" payloadStr := fmt.Sprintf( - `{"channel": "%s","text": "%s"}`, - agent.config.ChannelId, message, - ) + `{"channel": "%s","text": "%s"}`, + agent.config.ChannelId, message, + ) - payload := strings.NewReader(payloadStr) + payload := strings.NewReader(payloadStr) req, _ := http.NewRequest("POST", url, payload) @@ -30,24 +31,24 @@ func (agent *SlackProvider) Post(message string) (interface{}, error) { req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", agent.config.Token)) res, err := http.DefaultClient.Do(req) - if err!=nil { - return nil, err - } + if err != nil { + return nil, err + } defer res.Body.Close() body, err := io.ReadAll(res.Body) - return string(body), err + return string(body), err } -func (agent *SlackProvider) Reply(threadId string, message string) (interface{}, error) { - url := "https://slack.com/api/chat.postMessage" +func (agent *SlackProvider) Reply(threadId string, message string, option models.Option) (any, error) { + url := "https://slack.com/api/chat.postMessage" payloadStr := fmt.Sprintf( - `{"channel": "%s", "text": "%s", "thread_ts": "%s"}`, - agent.config.ChannelId, message, threadId, - ) + `{"channel": "%s", "text": "%s", "thread_ts": "%s"}`, + agent.config.ChannelId, message, threadId, + ) - payload := strings.NewReader(payloadStr) + payload := strings.NewReader(payloadStr) req, _ := http.NewRequest("POST", url, payload) @@ -56,12 +57,11 @@ func (agent *SlackProvider) Reply(threadId string, message string) (interface{}, req.Header.Add("Authorization", fmt.Sprintf("Bearer %s", agent.config.Token)) res, err := http.DefaultClient.Do(req) - if err!=nil { - return nil, err - } + if err != nil { + return nil, err + } defer res.Body.Close() body, err := io.ReadAll(res.Body) - return string(body), err + return string(body), err } - diff --git a/providers/smtp.go b/providers/smtp.go index 824647c..a6eab96 100644 --- a/providers/smtp.go +++ b/providers/smtp.go @@ -9,26 +9,29 @@ import ( "strings" "github.com/tech-thinker/chatz/config" + "github.com/tech-thinker/chatz/models" + "github.com/tech-thinker/chatz/utils" ) type SMTPProvider struct { config *config.Config } -func (agent *SMTPProvider) Post(message string) (interface{}, error) { - return agent.PostWithSubject(message, "") -} - -func (agent *SMTPProvider) PostWithSubject(message string, subject string) (interface{}, error) { +func (agent *SMTPProvider) Post(message string, option models.Option) (any, error) { host := agent.config.SMTPHost port := agent.config.SMTPPort smtpServer := fmt.Sprintf("%s:%s", host, port) user := agent.config.SMTPUser password := agent.config.SMTPPassword - if len(subject) == 0 { - subject = agent.config.SMTPSubject + if option.Subject == nil { + if len(agent.config.SMTPSubject) > 0 { + option.Subject = &agent.config.SMTPSubject + } else { + option.Subject = utils.NewString("Chatz Notification") + } } + from := agent.config.SMTPFrom recipients := agent.config.SMTPTo @@ -36,14 +39,10 @@ func (agent *SMTPProvider) PostWithSubject(message string, subject string) (inte from = user } - if len(subject) == 0 { - subject = "Chatz Notification" - } - // Create the message with proper headers msg := []byte(fmt.Sprintf( "From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s", - from, recipients, subject, message, + from, recipients, *option.Subject, message, )) // Split recipients into a slice @@ -114,7 +113,7 @@ func (agent *SMTPProvider) PostWithSubject(message string, subject string) (inte } // Helper function to send email -func sendEmail(client *smtp.Client, from string, to []string, msg []byte) (interface{}, error) { +func sendEmail(client *smtp.Client, from string, to []string, msg []byte) (any, error) { // Set the sender and recipients if err := client.Mail(from); err != nil { return nil, fmt.Errorf("failed to set sender: %w", err) @@ -142,7 +141,7 @@ func sendEmail(client *smtp.Client, from string, to []string, msg []byte) (inter return `{"status": "success"}`, nil } -func (agent *SMTPProvider) Reply(threadId string, message string) (interface{}, error) { +func (agent *SMTPProvider) Reply(threadId string, message string, option models.Option) (any, error) { fmt.Println("Reply to SMTP not supported yet.") return nil, errors.New("Reply to SMTP not supported yet.") } diff --git a/providers/telegram.go b/providers/telegram.go index d1da8a0..989f76b 100644 --- a/providers/telegram.go +++ b/providers/telegram.go @@ -7,24 +7,25 @@ import ( "strings" "github.com/tech-thinker/chatz/config" + "github.com/tech-thinker/chatz/models" ) type TelegramProvider struct { - config *config.Config + config *config.Config } -func (agent *TelegramProvider) Post(message string) (interface{}, error) { - url := fmt.Sprintf( - `https://api.telegram.org/bot%s/sendMessage`, - agent.config.Token, - ) +func (agent *TelegramProvider) Post(message string, option models.Option) (any, error) { + url := fmt.Sprintf( + `https://api.telegram.org/bot%s/sendMessage`, + agent.config.Token, + ) payloadStr := fmt.Sprintf( - `{"chat_id": "%s","text": "%s"}`, - agent.config.ChatId, message, - ) + `{"chat_id": "%s","text": "%s"}`, + agent.config.ChatId, message, + ) - payload := strings.NewReader(payloadStr) + payload := strings.NewReader(payloadStr) req, _ := http.NewRequest("POST", url, payload) @@ -32,27 +33,27 @@ func (agent *TelegramProvider) Post(message string) (interface{}, error) { req.Header.Add("User-Agent", "tech-thinker/chatz") res, err := http.DefaultClient.Do(req) - if err!=nil { - return nil, err - } + if err != nil { + return nil, err + } defer res.Body.Close() body, err := io.ReadAll(res.Body) - return string(body), err + return string(body), err } -func (agent *TelegramProvider) Reply(threadId string, message string) (interface{}, error) { - url := fmt.Sprintf( - `https://api.telegram.org/bot%s/sendMessage`, - agent.config.Token, - ) +func (agent *TelegramProvider) Reply(threadId string, message string, option models.Option) (any, error) { + url := fmt.Sprintf( + `https://api.telegram.org/bot%s/sendMessage`, + agent.config.Token, + ) payloadStr := fmt.Sprintf( - `{"chat_id": "%s", "text": "%s", "reply_to_message_id": "%s"}`, - agent.config.ChatId, message, threadId, - ) + `{"chat_id": "%s", "text": "%s", "reply_to_message_id": "%s"}`, + agent.config.ChatId, message, threadId, + ) - payload := strings.NewReader(payloadStr) + payload := strings.NewReader(payloadStr) req, _ := http.NewRequest("POST", url, payload) @@ -60,11 +61,11 @@ func (agent *TelegramProvider) Reply(threadId string, message string) (interface req.Header.Add("User-Agent", "tech-thinker/chatz") res, err := http.DefaultClient.Do(req) - if err!=nil { - return nil, err - } + if err != nil { + return nil, err + } defer res.Body.Close() body, err := io.ReadAll(res.Body) - return string(body), err + return string(body), err } diff --git a/utils/pointers.go b/utils/pointers.go new file mode 100644 index 0000000..3eb0748 --- /dev/null +++ b/utils/pointers.go @@ -0,0 +1,9 @@ +package utils + +func NewInt(value int) *int { + return &value +} + +func NewString(value string) *string { + return &value +}