From 1e94b1fc1e59f542415901d45647d5f4988fda4f Mon Sep 17 00:00:00 2001 From: Noah Theus Date: Mon, 13 Oct 2025 06:08:26 +0200 Subject: [PATCH 01/11] feat: Automatic comment translation using DeepL --- botIO/authenticate.go | 2 +- config/config.go | 11 +++-- config/types.go | 18 ++++--- deepL/http.go | 60 +++++++++++++++++++++++ deepL/translate.go | 108 ++++++++++++++++++++++++++++++++++++++++++ deepL/types.go | 35 ++++++++++++++ go.mod | 2 +- main.go | 8 ++-- steam/http.go | 1 - steam/profile.go | 25 ++++++++-- 10 files changed, 250 insertions(+), 20 deletions(-) create mode 100644 deepL/http.go create mode 100644 deepL/translate.go create mode 100644 deepL/types.go diff --git a/botIO/authenticate.go b/botIO/authenticate.go index 65ce465..eb856c8 100644 --- a/botIO/authenticate.go +++ b/botIO/authenticate.go @@ -9,7 +9,7 @@ import ( func Authenticate() (tgbotapi.UpdatesChannel, *tgbotapi.BotAPI) { bot, err := tgbotapi.NewBotAPI(config.BotConfig.TelegramAPIToken) if err != nil { - log.Panic().Err(err).Msg("Failed to authenticate to Telegram") + log.Fatal().Err(err).Msg("Failed to authenticate to Telegram") } bot.Debug = false diff --git a/config/config.go b/config/config.go index dec9dbb..b8af148 100644 --- a/config/config.go +++ b/config/config.go @@ -2,28 +2,31 @@ package config import ( "context" + "strings" + "github.com/joho/godotenv" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - "github.com/joho/godotenv" envconfig "github.com/sethvargo/go-envconfig" ) var BotConfig config func LoadConfig() { - - if err := godotenv.Load(); err != nil { + if err := godotenv.Load(); err != nil { log.Info().Err(err).Msg("Failed to load .env file, using the system environment") } else { log.Info().Err(err).Msg(".env file loaded successfully") } if err := envconfig.Process(context.Background(), &BotConfig); err != nil { - log.Panic().Err(err).Msg("Failed to parse config from env variables") + log.Fatal().Err(err).Msg("Failed to parse config from env variables") } zerolog.SetGlobalLevel(zerolog.Level(BotConfig.LogLevel)) + BotConfig.TranslateLanguage = strings.ToUpper(BotConfig.TranslateLanguage) + + log.Info().Msg("Config loaded successfully") log.Debug().Interface("config", BotConfig).Msg("") diff --git a/config/types.go b/config/types.go index 63f916b..f211063 100644 --- a/config/types.go +++ b/config/types.go @@ -1,10 +1,16 @@ package config type config struct { - LogLevel int `env:"STEAMSALTY_LOGLEVEL, default=1"` - TelegramAPIToken string `env:"STEAMSALTY_TELEGRAMAPITOKEN, required"` - SteamAPIKey string `env:"STEAMSALTY_STEAMAPIKEY, required"` - ChatID int64 `env:"STEAMSALTY_CHATID, required"` - Watchers []uint64 `env:"STEAMSALTY_WATCHERS, required"` - SleepInterval int `env:"STEAMSALTY_SLEEPINTERVAL, default=60"` + LogLevel int `env:"STEAMSALTY_LOGLEVEL" default:"1"` + + SleepInterval int `env:"STEAMSALTY_SLEEPINTERVAL" default:"60"` + ChatID int64 `env:"STEAMSALTY_CHATID" required:"true"` + Watchers []uint64 `env:"STEAMSALTY_WATCHERS" required:"true"` + TranslateEnabled bool `env:"STEAMSALTY_TRANSLATE_ENABLED" default:"false"` + TranslateLanguage string `env:"STEAMSALTY_TRANSLATE_LANGUAGE" default:"EN-US"` + + TelegramAPIToken string `env:"STEAMSALTY_TELEGRAMAPITOKEN" required:"true"` + SteamAPIKey string `env:"STEAMSALTY_STEAMAPIKEY" required:"true"` + DeepLAPIKey string `env:"STEAMSALTY_DEEPLAPIKEY"` + DeepLFreeTier bool `env:"STEAMSALTY_DEEPLAPIFREETIER" default:"true"` } diff --git a/deepL/http.go b/deepL/http.go new file mode 100644 index 0000000..c186529 --- /dev/null +++ b/deepL/http.go @@ -0,0 +1,60 @@ +package deepl + +import ( + "bytes" + "io" + "net/http" + + "github.com/rs/zerolog/log" +) + +func get(endpoint string) (responseBody []byte, err error) { + httpReq, _ := http.NewRequest("GET", client.baseURL+endpoint, nil) + httpReq.Header.Set("Authorization", "DeepL-Auth-Key "+client.authKey) + + resp, err := client.httpClient.Do(httpReq) + if err != nil { + log.Error().Err(err).Str("endpoint", endpoint).Msg("Failed to send GET request to DeepL API") + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + log.Error().Str("response", resp.Status).Str("endpoint", endpoint).Msg("Failed to send GET request to DeepL API, non 200 HTTP response") + return nil, err + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + log.Error().Err(err).Str("endpoint", endpoint).Msg("Failed to read DeepL API response") + return nil, err + } + + return respBody, nil +} + +func post(endpoint string, data []byte) (responseBody []byte, err error) { + httpReq, _ := http.NewRequest("POST", client.baseURL+endpoint, bytes.NewReader(data)) + httpReq.Header.Set("Authorization", "DeepL-Auth-Key "+client.authKey) + httpReq.Header.Set("Content-Type", "application/json") + + resp, err := client.httpClient.Do(httpReq) + if err != nil { + log.Error().Err(err).Str("endpoint", endpoint).Msg("Failed to send request to POST DeepL API") + return nil, err + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + log.Error().Str("response", resp.Status).Str("endpoint", endpoint).Msg("Failed to send POST request to DeepL API, non 200 HTTP response") + return nil, err + } + + respBody, err := io.ReadAll(resp.Body) + if err != nil { + log.Error().Err(err).Str("endpoint", endpoint).Msg("Failed to read DeepL API response") + return nil, err + } + + return respBody, nil +} diff --git a/deepL/translate.go b/deepL/translate.go new file mode 100644 index 0000000..77fff6f --- /dev/null +++ b/deepL/translate.go @@ -0,0 +1,108 @@ +package deepl + +import ( + "encoding/json" + "fmt" + "net/http" + "time" + "watn3y/steamsalty/config" + + "github.com/rs/zerolog/log" +) + +var client *apiClient + +var SourceLanguages map[string]string +var TargetLanguages map[string]string + +func Init() { + log.Info().Msg("Translation is enabled, creating HTTP client for DeepL API") + baseURL := baseURLPro + if config.BotConfig.DeepLFreeTier { + baseURL = baseURLFree + } + + client = &apiClient{ + authKey: config.BotConfig.DeepLAPIKey, + baseURL: baseURL, + httpClient: &http.Client{Timeout: 30 * time.Second}, + } + + err := getAndValidateLanguages() + if err != nil { + log.Fatal().Err(err).Msg("Failed to set up languages") + } +} + +func Translate(text string) (translatedText string, sourceLanguage string, err error) { + log.Debug().Str("text", text).Msg("Starting translation") + req := translateRequest{ + Text: []string{text}, + TargetLang: config.BotConfig.TranslateLanguage, + } + + body, err := json.Marshal(req) + if err != nil { + log.Error().Err(err).Msg("Failed to ") + return "", "", err + } + + respBody, err := post("/translate", body) + if err != nil { + log.Error().Err(err).Msg("Failed to make DeepL API request") + return "", "", err + } + + var result translateResponse + err = json.Unmarshal(respBody, &result) + if err != nil { + log.Error().Err(err).Msg("Failed to parse DeepL API response") + return "", "", err + } + + return result.Translations[0].Text, result.Translations[0].DetectedSourceLanguage, nil +} + +func getAndValidateLanguages() (err error) { + log.Info().Msg("Setting up supported languages") + respBody, err := get("/languages?type=source") + if err != nil { + log.Error().Err(err).Msg("Failed to make DeepL API request") + return err + } + + var parsedResp languagesResponse + err = json.Unmarshal(respBody, &parsedResp) + if err != nil { + log.Error().Err(err).Msg("Failed to parse DeepL API response") + return err + } + + SourceLanguages = make(map[string]string) + for _, l := range parsedResp { + SourceLanguages[l.Language] = l.Name + } + + respBody, err = get("/languages?type=target") + if err != nil { + log.Error().Err(err).Msg("Failed to make DeepL API request") + return err + } + + err = json.Unmarshal(respBody, &parsedResp) + if err != nil { + log.Error().Err(err).Msg("Failed to parse DeepL API response") + return err + } + + TargetLanguages = make(map[string]string) + for _, l := range parsedResp { + TargetLanguages[l.Language] = l.Name + } + + if _, ok := TargetLanguages[config.BotConfig.TranslateLanguage]; !ok { + return fmt.Errorf("Selected language not supported by DeepL") + } + + return nil +} diff --git a/deepL/types.go b/deepL/types.go new file mode 100644 index 0000000..fef02eb --- /dev/null +++ b/deepL/types.go @@ -0,0 +1,35 @@ +package deepl + +import ( + "net/http" +) + +type translateResponse struct { + Translations []struct { + DetectedSourceLanguage string `json:"detected_source_language"` + Text string `json:"text"` + } `json:"translations"` +} + +type translateRequest struct { + Text []string `json:"text"` + TargetLang string `json:"target_lang"` +} + +type languagesResponse []struct { + Language string `json:"language"` + Name string `json:"name"` + SupportsFormality bool `json:"supports_formality"` //unused +} + + +type apiClient struct { + authKey string + baseURL string + httpClient *http.Client +} + +const ( + baseURLPro = "https://api.deepl.com/v2" + baseURLFree = "https://api-free.deepl.com/v2" +) diff --git a/go.mod b/go.mod index 8502be1..8e9eb4f 100644 --- a/go.mod +++ b/go.mod @@ -6,13 +6,13 @@ require ( github.com/Philipp15b/go-steamapi v0.0.0-20210114153316-ec4fdd23b4c1 github.com/PuerkitoBio/goquery v1.10.3 github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 + github.com/joho/godotenv v1.5.1 github.com/rs/zerolog v1.34.0 github.com/sethvargo/go-envconfig v1.3.0 ) require ( github.com/andybalholm/cascadia v1.3.3 // indirect - github.com/joho/godotenv v1.5.1 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect golang.org/x/net v0.43.0 // indirect diff --git a/main.go b/main.go index 345adc1..b25c635 100644 --- a/main.go +++ b/main.go @@ -8,16 +8,18 @@ import ( "github.com/rs/zerolog" "github.com/rs/zerolog/log" - + "watn3y/steamsalty/config" + deepl "watn3y/steamsalty/deepL" ) func main() { println("Starting SteamSalty...") - configureLogger() - config.LoadConfig() + if config.BotConfig.TranslateEnabled { + deepl.Init() + } bot() diff --git a/steam/http.go b/steam/http.go index 8fca3d0..55eba98 100644 --- a/steam/http.go +++ b/steam/http.go @@ -39,7 +39,6 @@ func GetComments(steamID uint64, start int, count int) (page CommentsPage) { body, err := io.ReadAll(resp.Body) if err != nil { log.Error().Err(err).Msg("Failed to parse comments") - log.Trace().Interface("Body", resp.Body) } err = json.Unmarshal(body, &page) diff --git a/steam/profile.go b/steam/profile.go index 2711cce..93264fc 100644 --- a/steam/profile.go +++ b/steam/profile.go @@ -3,19 +3,18 @@ package steam import ( "fmt" "math" - "strings" "sync" "time" - "watn3y/steamsalty/botIO" "watn3y/steamsalty/config" + deepl "watn3y/steamsalty/deepL" tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" "github.com/rs/zerolog/log" ) -var steamContentCheckText string = "This comment is awaiting analysis by our automated content check system. It will be temporarily hidden until we verify that it does not contain harmful content (e.g. links to websites that attempt to steal information)." +const steamContentCheckText string = "This comment is awaiting analysis by our automated content check system. It will be temporarily hidden until we verify that it does not contain harmful content (e.g. links to websites that attempt to steal information)." func StartWatchers(bot *tgbotapi.BotAPI) { @@ -69,9 +68,27 @@ func watcher(bot *tgbotapi.BotAPI, steamID uint64, sleeptime time.Duration) { Text: fmt.Sprintf(`%s just commented on %s's profile:`, comment.AuthorProfileURL, comment.Author, profileOwner.ProfileURL, profileOwner.PersonaName) + "\n" + "
" + comment.Text + "
", } + + if config.BotConfig.TranslateEnabled { + translatedText, translatedTextLanguage, err := deepl.Translate(comment.Text) + if translatedTextLanguage == config.BotConfig.TranslateLanguage { + continue + } + if err != nil { + log.Error().Err(err).Msg("Failed to translate comment, continuing without translation") + msg.Text += "\n" + "Translation failed" + + } else { + msg.Text += "\n" + + fmt.Sprintf(`Translated from %s:`, deepl.SourceLanguages[translatedTextLanguage]) + "\n" + + "
" + translatedText + "
" + } + + } + log.Info().Interface("Comment", comment).Msg("Notifying about new comment") botIO.SendMessage(msg, bot) - time.Sleep(time.Minute / 20) + time.Sleep(time.Second * 3) //I have no Idea why this is here } newestProcessedComment = currentCommentsPage.TimeLastPost From f3dec30b568a4252842e0ea968f29bb2b4fad02b Mon Sep 17 00:00:00 2001 From: Noah Theus Date: Mon, 13 Oct 2025 14:37:43 +0200 Subject: [PATCH 02/11] docs: Add documentation for translation feature --- README.md | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 8a6c28d..757b6c4 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SteamSalty -SteamSalty notifies you on telegram about new comments on any steam profile. +SteamSalty notifies you on telegram about new comments on any steam profile **with build in auto translation**. ## Running with Docker Compose @@ -34,11 +34,15 @@ Grab a release from the [releases page](https://github.com/watn3y/steamsalty/rel > [!NOTE] > For development purposes, SteamSalty supports loading environment variables from a .env file placed in the project root directory. -| Variable | Description | Default | Required | -| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------| ------------------ | -------------- | -| `STEAMSALTY_LOGLEVEL` | LogLevel as described [in the zerolog documentation](https://pkg.go.dev/github.com/rs/zerolog@v1.34.0#readme-simple-leveled-logging-example) | 1 (Info) | ❌ | -| `STEAMSALTY_TELEGRAMAPITOKEN` | Telegram BotToken, get it from [@BotFather on Telegram](https://t.me/BotFather) | None | ✅ | -| `STEAMSALTY_STEAMAPIKEY` | Steam API Key, get it from [steamcommunity.com/dev/apikey](https://steamcommunity.com/dev/apikey) | None | ✅ | -| `STEAMSALTY_CHATID` | Chat to notify about new comments | None | ✅ | -| `STEAMSALTY_WATCHERS` | SteamIDs (in SteamID64 format) to check for new profile comments | None | ✅ | -| `STEAMSALTY_SLEEPINTERVAL` | Amount of time to wait between requests to Steam in seconds | 60 | ❌ | +| Variable | Description | Default | Required | +|---------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|----------| +| `STEAMSALTY_LOGLEVEL` | LogLevel as described [in the zerolog documentation](https://pkg.go.dev/github.com/rs/zerolog@v1.34.0#readme-simple-leveled-logging-example) | 1 (Info) | ❌ | +| `STEAMSALTY_SLEEPINTERVAL` | Amount of time to wait between requests to Steam in seconds | 60 | ❌ | +| `STEAMSALTY_TRANSLATE_ENABLED` | Whether to enable translation of comments | False | ❌ | +| `STEAMSALTY_TRANSLATE_LANGUAGE` | Language to translate as described [in the DeepL API documentation](https://developers.deepl.com/docs/getting-started/supported-languages#translation-target-languages) | EN-US | ❌ | +| `STEAMSALTY_CHATID` | Chat to notify about new comments | None | ✅ | +| `STEAMSALTY_WATCHERS` | SteamIDs (in SteamID64 format) to check for new profile comments | None | ✅ | +| `STEAMSALTY_TELEGRAMAPITOKEN` | Telegram BotToken, get it from [@BotFather on Telegram](https://t.me/BotFather) | None | ✅ | +| `STEAMSALTY_STEAMAPIKEY` | Steam API Key, get it from [steamcommunity.com/dev/apikey](https://steamcommunity.com/dev/apikey) | None | ✅ | +| `STEAMSALTY_DEEPL_APIKEY` | DeepL API Key, get it from [deepl.com/en/your-account/keys](https://www.deepl.com/en/your-account/keys) | None | ❌ | +| `STEAMSALTY_DEEPL_FREETIER` | Whether you are using the DeepL Free Tier | True | ❌ | From f445fef85a67bcd5f42120a93140135c2e7c011a Mon Sep 17 00:00:00 2001 From: Noah Theus Date: Mon, 13 Oct 2025 14:38:15 +0200 Subject: [PATCH 03/11] refactor: change translation config --- config/types.go | 13 ++++++++----- deepL/translate.go | 4 ++-- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/config/types.go b/config/types.go index f211063..b331542 100644 --- a/config/types.go +++ b/config/types.go @@ -8,9 +8,12 @@ type config struct { Watchers []uint64 `env:"STEAMSALTY_WATCHERS" required:"true"` TranslateEnabled bool `env:"STEAMSALTY_TRANSLATE_ENABLED" default:"false"` TranslateLanguage string `env:"STEAMSALTY_TRANSLATE_LANGUAGE" default:"EN-US"` - - TelegramAPIToken string `env:"STEAMSALTY_TELEGRAMAPITOKEN" required:"true"` - SteamAPIKey string `env:"STEAMSALTY_STEAMAPIKEY" required:"true"` - DeepLAPIKey string `env:"STEAMSALTY_DEEPLAPIKEY"` - DeepLFreeTier bool `env:"STEAMSALTY_DEEPLAPIFREETIER" default:"true"` + TelegramAPIToken string `env:"STEAMSALTY_TELEGRAMAPITOKEN" required:"true"` + SteamAPIKey string `env:"STEAMSALTY_STEAMAPIKEY" required:"true"` + + DeepL struct { + APIKey string `env:"APIKEY"` + FreeTier bool `env:"FREETIER" default:"true"` + } `env:"STEAMSALTY_DEEPL_"` } + diff --git a/deepL/translate.go b/deepL/translate.go index 77fff6f..3cc0c4d 100644 --- a/deepL/translate.go +++ b/deepL/translate.go @@ -18,12 +18,12 @@ var TargetLanguages map[string]string func Init() { log.Info().Msg("Translation is enabled, creating HTTP client for DeepL API") baseURL := baseURLPro - if config.BotConfig.DeepLFreeTier { + if config.BotConfig.DeepL.FreeTier { baseURL = baseURLFree } client = &apiClient{ - authKey: config.BotConfig.DeepLAPIKey, + authKey: config.BotConfig.DeepL.APIKey, baseURL: baseURL, httpClient: &http.Client{Timeout: 30 * time.Second}, } From 6e4575f29802afe60be90806e242d76558edc362 Mon Sep 17 00:00:00 2001 From: Noah Theus Date: Mon, 13 Oct 2025 14:51:36 +0200 Subject: [PATCH 04/11] fix: config parsing --- config/types.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/types.go b/config/types.go index b331542..c169b33 100644 --- a/config/types.go +++ b/config/types.go @@ -12,8 +12,8 @@ type config struct { SteamAPIKey string `env:"STEAMSALTY_STEAMAPIKEY" required:"true"` DeepL struct { - APIKey string `env:"APIKEY"` - FreeTier bool `env:"FREETIER" default:"true"` - } `env:"STEAMSALTY_DEEPL_"` + APIKey string `env:"STEAMSALTY_DEEPL_APIKEY"` + FreeTier bool `env:"STEAMSALTY_DEEPL_FREETIER" default:"true"` + } } From f17601c470dbc956adc8e9a838a06123d043bec0 Mon Sep 17 00:00:00 2001 From: Noah Theus Date: Mon, 13 Oct 2025 16:10:06 +0200 Subject: [PATCH 05/11] chore: add vscode launch config --- .vscode/launch.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 .vscode/launch.json diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..ffe4062 --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,12 @@ +{ + "version": "0.2.0", + "configurations": [ + { + "name": "Launch Package", + "type": "go", + "request": "launch", + "mode": "auto", + "program": "${workspaceRoot}" + } + ] +} \ No newline at end of file From 038d375ae727afd24738d583b02653b5976d55e2 Mon Sep 17 00:00:00 2001 From: Noah Theus Date: Mon, 13 Oct 2025 17:02:20 +0200 Subject: [PATCH 06/11] refactor!: change config structure to be more readable --- botIO/authenticate.go | 5 +++-- config/config.go | 2 +- config/types.go | 34 +++++++++++++++++++++------------- deepL/translate.go | 4 ++-- main.go | 2 +- steam/api.go | 2 +- steam/profile.go | 4 ++-- 7 files changed, 31 insertions(+), 22 deletions(-) diff --git a/botIO/authenticate.go b/botIO/authenticate.go index eb856c8..ea61f2f 100644 --- a/botIO/authenticate.go +++ b/botIO/authenticate.go @@ -1,13 +1,14 @@ package botIO import ( + "watn3y/steamsalty/config" + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" "github.com/rs/zerolog/log" - "watn3y/steamsalty/config" ) func Authenticate() (tgbotapi.UpdatesChannel, *tgbotapi.BotAPI) { - bot, err := tgbotapi.NewBotAPI(config.BotConfig.TelegramAPIToken) + bot, err := tgbotapi.NewBotAPI(config.BotConfig.Telegram.APIToken) if err != nil { log.Fatal().Err(err).Msg("Failed to authenticate to Telegram") } diff --git a/config/config.go b/config/config.go index b8af148..081d7d5 100644 --- a/config/config.go +++ b/config/config.go @@ -24,7 +24,7 @@ func LoadConfig() { } zerolog.SetGlobalLevel(zerolog.Level(BotConfig.LogLevel)) - BotConfig.TranslateLanguage = strings.ToUpper(BotConfig.TranslateLanguage) + BotConfig.Translate.Language = strings.ToUpper(BotConfig.Translate.Language) log.Info().Msg("Config loaded successfully") diff --git a/config/types.go b/config/types.go index c169b33..716a2e0 100644 --- a/config/types.go +++ b/config/types.go @@ -1,19 +1,27 @@ package config type config struct { - LogLevel int `env:"STEAMSALTY_LOGLEVEL" default:"1"` + LogLevel int `env:"STEAMSALTY_LOGLEVEL, default=1"` + + ChatID int64 `env:"STEAMSALTY_CHATID, required` + Watchers []uint64 `env:"STEAMSALTY_WATCHERS, required` + SleepInterval int `env:"STEAMSALTY_SLEEPINTERVAL, default=60"` + + Translate struct { + Enabled bool `env:"STEAMSALTY_TRANSLATE_ENABLED, default=False"` + Language string `env:"STEAMSALTY_TRANSLATE_LANGUAGE, default=EN-US"` + } + + Telegram struct { + APIToken string `env:"STEAMSALTY_TELEGRAM_APITOKEN, required"` + } + + Steam struct { + APIKey string `env:"STEAMSALTY_STEAMAPIKEY, required` + } - SleepInterval int `env:"STEAMSALTY_SLEEPINTERVAL" default:"60"` - ChatID int64 `env:"STEAMSALTY_CHATID" required:"true"` - Watchers []uint64 `env:"STEAMSALTY_WATCHERS" required:"true"` - TranslateEnabled bool `env:"STEAMSALTY_TRANSLATE_ENABLED" default:"false"` - TranslateLanguage string `env:"STEAMSALTY_TRANSLATE_LANGUAGE" default:"EN-US"` - TelegramAPIToken string `env:"STEAMSALTY_TELEGRAMAPITOKEN" required:"true"` - SteamAPIKey string `env:"STEAMSALTY_STEAMAPIKEY" required:"true"` - DeepL struct { - APIKey string `env:"STEAMSALTY_DEEPL_APIKEY"` - FreeTier bool `env:"STEAMSALTY_DEEPL_FREETIER" default:"true"` - } + APIKey string `env:"STEAMSALTY_DEEPL_APIKEY"` + FreeTier bool `env:"STEAMSALTY_DEEPL_FREETIER, default=True"` + } } - diff --git a/deepL/translate.go b/deepL/translate.go index 3cc0c4d..72beeda 100644 --- a/deepL/translate.go +++ b/deepL/translate.go @@ -38,7 +38,7 @@ func Translate(text string) (translatedText string, sourceLanguage string, err e log.Debug().Str("text", text).Msg("Starting translation") req := translateRequest{ Text: []string{text}, - TargetLang: config.BotConfig.TranslateLanguage, + TargetLang: config.BotConfig.Translate.Language, } body, err := json.Marshal(req) @@ -100,7 +100,7 @@ func getAndValidateLanguages() (err error) { TargetLanguages[l.Language] = l.Name } - if _, ok := TargetLanguages[config.BotConfig.TranslateLanguage]; !ok { + if _, ok := TargetLanguages[config.BotConfig.Translate.Language]; !ok { return fmt.Errorf("Selected language not supported by DeepL") } diff --git a/main.go b/main.go index b25c635..2476176 100644 --- a/main.go +++ b/main.go @@ -17,7 +17,7 @@ func main() { println("Starting SteamSalty...") configureLogger() config.LoadConfig() - if config.BotConfig.TranslateEnabled { + if config.BotConfig.Translate.Enabled { deepl.Init() } diff --git a/steam/api.go b/steam/api.go index f73f19b..5896f5e 100644 --- a/steam/api.go +++ b/steam/api.go @@ -9,7 +9,7 @@ import ( func GetPlayerDetails(steamID uint64) (summary steamapi.PlayerSummary) { - response, err := steamapi.GetPlayerSummaries([]uint64{steamID}, config.BotConfig.SteamAPIKey) + response, err := steamapi.GetPlayerSummaries([]uint64{steamID}, config.BotConfig.Steam.APIKey) if err != nil { log.Error().Err(err).Msg("Failed to retrive player summary") } diff --git a/steam/profile.go b/steam/profile.go index 93264fc..1c903ab 100644 --- a/steam/profile.go +++ b/steam/profile.go @@ -69,9 +69,9 @@ func watcher(bot *tgbotapi.BotAPI, steamID uint64, sleeptime time.Duration) { "
" + comment.Text + "
", } - if config.BotConfig.TranslateEnabled { + if config.BotConfig.Translate.Enabled { translatedText, translatedTextLanguage, err := deepl.Translate(comment.Text) - if translatedTextLanguage == config.BotConfig.TranslateLanguage { + if translatedTextLanguage == config.BotConfig.Translate.Language { continue } if err != nil { From 9f582e4c75755aaa24ef72fae0a31e501059e673 Mon Sep 17 00:00:00 2001 From: Noah Theus Date: Mon, 13 Oct 2025 17:24:32 +0200 Subject: [PATCH 07/11] docs: Add documentation for new and changes features --- .env.example | 10 ++++++++++ README.md | 34 +++++++++++++++++++--------------- 2 files changed, 29 insertions(+), 15 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..a17f3d3 --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +STEAMSALTY_LOGLEVEL= +STEAMSALTY_CHATID= +STEAMSALTY_WATCHERS= +STEAMSALTY_SLEEPINTERVAL= +STEAMSALTY_TRANSLATE_ENABLED= +STEAMSALTY_TRANSLATE_LANGUAGE= +STEAMSALTY_TELEGRAM_APITOKEN= +STEAMSALTY_STEAMAPIKEY= +STEAMSALTY_DEEPL_APIKEY= +STEAMSALTY_DEEPL_FREETIER= \ No newline at end of file diff --git a/README.md b/README.md index 757b6c4..238c631 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,15 @@ services: - /etc/localtime:/etc/localtime:ro environment: #- STEAMSALTY_LOGLEVEL= - - STEAMSALTY_TELEGRAMAPITOKEN= - - STEAMSALTY_STEAMAPIKEY= - STEAMSALTY_CHATID= - STEAMSALTY_WATCHERS= - #- STEAMSALTY_SLEEPINTERVAL= + - STEAMSALTY_SLEEPINTERVAL= + #- STEAMSALTY_TRANSLATE_ENABLED= + #- STEAMSALTY_TRANSLATE_LANGUAGE= + - STEAMSALTY_TELEGRAM_APITOKEN= + - STEAMSALTY_STEAMAPIKEY= + #- STEAMSALTY_DEEPL_APIKEY= + #- STEAMSALTY_DEEPL_FREETIER= ``` ## Running on Linux @@ -34,15 +38,15 @@ Grab a release from the [releases page](https://github.com/watn3y/steamsalty/rel > [!NOTE] > For development purposes, SteamSalty supports loading environment variables from a .env file placed in the project root directory. -| Variable | Description | Default | Required | -|---------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|----------| -| `STEAMSALTY_LOGLEVEL` | LogLevel as described [in the zerolog documentation](https://pkg.go.dev/github.com/rs/zerolog@v1.34.0#readme-simple-leveled-logging-example) | 1 (Info) | ❌ | -| `STEAMSALTY_SLEEPINTERVAL` | Amount of time to wait between requests to Steam in seconds | 60 | ❌ | -| `STEAMSALTY_TRANSLATE_ENABLED` | Whether to enable translation of comments | False | ❌ | -| `STEAMSALTY_TRANSLATE_LANGUAGE` | Language to translate as described [in the DeepL API documentation](https://developers.deepl.com/docs/getting-started/supported-languages#translation-target-languages) | EN-US | ❌ | -| `STEAMSALTY_CHATID` | Chat to notify about new comments | None | ✅ | -| `STEAMSALTY_WATCHERS` | SteamIDs (in SteamID64 format) to check for new profile comments | None | ✅ | -| `STEAMSALTY_TELEGRAMAPITOKEN` | Telegram BotToken, get it from [@BotFather on Telegram](https://t.me/BotFather) | None | ✅ | -| `STEAMSALTY_STEAMAPIKEY` | Steam API Key, get it from [steamcommunity.com/dev/apikey](https://steamcommunity.com/dev/apikey) | None | ✅ | -| `STEAMSALTY_DEEPL_APIKEY` | DeepL API Key, get it from [deepl.com/en/your-account/keys](https://www.deepl.com/en/your-account/keys) | None | ❌ | -| `STEAMSALTY_DEEPL_FREETIER` | Whether you are using the DeepL Free Tier | True | ❌ | +| Variable | Description | Default | Required | Example | +|---------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------------------------------|----------|----------|-----------------------------------------------| +| `STEAMSALTY_LOGLEVEL` | LogLevel as described [in the zerolog documentation](https://pkg.go.dev/github.com/rs/zerolog@v1.34.0#readme-simple-leveled-logging-example) | 1 (Info) | ❌ | 1 | +| `STEAMSALTY_SLEEPINTERVAL` | Amount of time to wait between requests to Steam in seconds | 60 | ❌ | 60 | +| `STEAMSALTY_TRANSLATE_ENABLED` | Whether to enable translation of comments | False | ❌ | True, False | +| `STEAMSALTY_TRANSLATE_LANGUAGE` | Language to translate as described [in the DeepL API documentation](https://developers.deepl.com/docs/getting-started/supported-languages#translation-target-languages) | EN-US | ❌ | EN-US,DE | +| `STEAMSALTY_CHATID` | Chat to notify about new comments | None | ✅ | -1001234567890 | +| `STEAMSALTY_WATCHERS` | SteamIDs (in SteamID64 format) to check for new profile comments | None | ✅ | 76561198012345678,76561198087654321 | +| `STEAMSALTY_TELEGRAMAPITOKEN` | Telegram BotToken, get it from [@BotFather on Telegram](https://t.me/BotFather) | None | ✅ | 1234567890:AAHdqTcvCH1vGWJxfSeofSAs0K5PALDsaw | +| `STEAMSALTY_STEAMAPIKEY` | Steam API Key, get it from [steamcommunity.com/dev/apikey](https://steamcommunity.com/dev/apikey) | None | ✅ | A7B3C9D2E5F1A4B8C6D9E2F5A8B1C4D7E0F3A6B9 | +| `STEAMSALTY_DEEPL_APIKEY` | DeepL API Key, get it from [deepl.com/en/your-account/keys](https://www.deepl.com/en/your-account/keys) | None | ❌ | a1b2c3d4-56e7-89f0-a1b2-c3d4e5f6a7b8:fx | +| `STEAMSALTY_DEEPL_FREETIER` | Whether you are using the DeepL Free Tier | True | ❌ | True, False | From 22c01b54b8402083487574841e5ada3ba4e0b221 Mon Sep 17 00:00:00 2001 From: Noah Theus Date: Mon, 13 Oct 2025 17:27:26 +0200 Subject: [PATCH 08/11] chore: Only tag images with branchname if changes are on master branch --- .github/workflows/on-push.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/on-push.yml b/.github/workflows/on-push.yml index cd0ef2e..7a7f607 100644 --- a/.github/workflows/on-push.yml +++ b/.github/workflows/on-push.yml @@ -8,6 +8,7 @@ on: - 'go.sum' - 'Dockerfile' - '.github/workflows/**' + jobs: docker: runs-on: ubuntu-latest @@ -21,14 +22,14 @@ jobs: - name: Login to Docker Hub uses: docker/login-action@v3 with: - username: ${{secrets.DOCKERHUB_USERNAME}} - password: ${{secrets.DOCKERHUB_TOKEN}} + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push uses: docker/build-push-action@v6 with: - platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/riscv64 + platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/riscv64 push: true tags: | - ${{ github.repository }}:${{ github.ref_name }} - ${{ github.repository }}:commit-${{ github.sha }} \ No newline at end of file + ${{ github.repository }}:commit-${{ github.sha }} + ${{ github.ref_name == 'master' && format('{0}:{1}', github.repository, github.ref_name) || '' }} From 70d36e32d25cb4cf644dbf0c7a912c0eaf6e7457 Mon Sep 17 00:00:00 2001 From: Noah Theus Date: Mon, 13 Oct 2025 17:31:15 +0200 Subject: [PATCH 09/11] docs: clarify versioning --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 238c631..75ba2e2 100644 --- a/README.md +++ b/README.md @@ -50,3 +50,9 @@ Grab a release from the [releases page](https://github.com/watn3y/steamsalty/rel | `STEAMSALTY_STEAMAPIKEY` | Steam API Key, get it from [steamcommunity.com/dev/apikey](https://steamcommunity.com/dev/apikey) | None | ✅ | A7B3C9D2E5F1A4B8C6D9E2F5A8B1C4D7E0F3A6B9 | | `STEAMSALTY_DEEPL_APIKEY` | DeepL API Key, get it from [deepl.com/en/your-account/keys](https://www.deepl.com/en/your-account/keys) | None | ❌ | a1b2c3d4-56e7-89f0-a1b2-c3d4e5f6a7b8:fx | | `STEAMSALTY_DEEPL_FREETIER` | Whether you are using the DeepL Free Tier | True | ❌ | True, False | + +## Nice to know + +### Semantic Versioning + +This project does it's best to follow [Semantic Versioning](https://semver.org/#semantic-versioning-200), however I can't guarantee anything. From 1b93bab69f9c5b1d6e64e2b2fe6e63e5e450a286 Mon Sep 17 00:00:00 2001 From: Noah Theus Date: Mon, 13 Oct 2025 18:01:55 +0200 Subject: [PATCH 10/11] docs: fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 75ba2e2..7dfcc65 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # SteamSalty -SteamSalty notifies you on telegram about new comments on any steam profile **with build in auto translation**. +SteamSalty notifies you on telegram about new comments on any steam profile **with built in auto translation**. ## Running with Docker Compose From b55e60bcc7253d8ab05eb12e1c9380a9423a521b Mon Sep 17 00:00:00 2001 From: Noah Theus Date: Mon, 13 Oct 2025 20:58:46 +0200 Subject: [PATCH 11/11] fix: Fix config parsing --- config/types.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/config/types.go b/config/types.go index 716a2e0..69edceb 100644 --- a/config/types.go +++ b/config/types.go @@ -3,8 +3,8 @@ package config type config struct { LogLevel int `env:"STEAMSALTY_LOGLEVEL, default=1"` - ChatID int64 `env:"STEAMSALTY_CHATID, required` - Watchers []uint64 `env:"STEAMSALTY_WATCHERS, required` + ChatID int64 `env:"STEAMSALTY_CHATID, required"` + Watchers []uint64 `env:"STEAMSALTY_WATCHERS, required"` SleepInterval int `env:"STEAMSALTY_SLEEPINTERVAL, default=60"` Translate struct { @@ -17,7 +17,7 @@ type config struct { } Steam struct { - APIKey string `env:"STEAMSALTY_STEAMAPIKEY, required` + APIKey string `env:"STEAMSALTY_STEAMAPIKEY, required"` } DeepL struct {