Merge pull request #1 from watn3y/v1.2
Some checks failed
Build and Push to Docker Hub on push to any branch / docker (push) Has been cancelled

V1.2
This commit is contained in:
Noah 2025-08-31 04:15:13 +02:00 committed by GitHub
commit c6c7480493
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 53 additions and 39 deletions

View file

@ -27,15 +27,18 @@ services:
## Running on Linux
Grab a release from the [releases page](https://github.com/watn3y/steamsalty/releases). Make sure to set your **environment variables** accordingly.
Grab a release from the [releases page](https://github.com/watn3y/steamsalty/releases). Make sure to set your [environment variables](#environment-variables) accordingly.
## Environment Variables
| Variable | Description | Default |
| ----------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------| ------------------ |
| `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 Bot Token, get from @BotFather | None, **required** |
| `STEAMSALTY_STEAMAPIKEY` | Steam API Key, get from [steamcommunity.com/dev/apikey](https://steamcommunity.com/dev/apikey) | None, **required** |
| `STEAMSALTY_CHATID` | Chat to notify about new Comments | None, **required** |
| `STEAMSALTY_WATCHERS` | SteamIDs (in SteamID64 format) to check for new Profile Comments | None, **required** |
| `STEAMSALTY_SLEEPINTERVAL` | Amount of time to wait between requests to Steam in seconds | 60 |
> [!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 | ❌ |

6
bot.go
View file

@ -18,10 +18,10 @@ func bot() {
go steam.StartWatchers(bot)
for update := range updates {
log.Debug().Interface("update", update).Msg("Received update")
log.Debug().Interface("update", update).Msg("Update received")
if update.Message == nil || update.Message.Text == "" {
log.Debug().Int("UpdateID", update.UpdateID).Msg("Unable to parse update")
log.Debug().Int("UpdateID", update.UpdateID).Msg("Failed to parse update")
continue
}
if update.Message.Time().UTC().Unix() < time.Now().UTC().Unix() {
@ -30,7 +30,7 @@ func bot() {
}
if update.Message.IsCommand() {
log.Info().Int64("ChatID", update.Message.Chat.ID).Int64("UserID", update.Message.From.ID).Str("Text", update.Message.Text).Msg("Received Command")
log.Info().Int64("ChatID", update.Message.Chat.ID).Int64("UserID", update.Message.From.ID).Str("Text", update.Message.Text).Msg("Command received")
commands.Commands(update, bot)
}
}

View file

@ -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")
log.Panic().Err(err).Msg("Failed to authenticate to Telegram")
}
bot.Debug = false
@ -20,7 +20,7 @@ func Authenticate() (tgbotapi.UpdatesChannel, *tgbotapi.BotAPI) {
updates := tgbotapi.NewUpdate(0)
updates.Timeout = 60
log.Info().Int64("ID", bot.Self.ID).Str("username", bot.Self.UserName).Msg("Authenticated to Telegram API")
log.Info().Int64("ID", bot.Self.ID).Str("username", bot.Self.UserName).Msg("Authenticated to Telegram successfully")
return bot.GetUpdatesChan(updates), bot

View file

@ -12,7 +12,7 @@ func SendMessage(message tgbotapi.MessageConfig, bot *tgbotapi.BotAPI) (result t
return
}
log.Info().Int64("chat", result.Chat.ID).Str("msg", result.Text).Msg("Sent message")
log.Info().Int64("chat", result.Chat.ID).Str("msg", result.Text).Msg("Sent message successfully")
log.Debug().Interface("msg", result).Msg("")
return result
@ -25,7 +25,7 @@ func EditMessage(message tgbotapi.EditMessageTextConfig, bot *tgbotapi.BotAPI) (
return
}
log.Info().Int64("chat", result.Chat.ID).Str("msg", result.Text).Msg("Edited message")
log.Info().Int64("chat", result.Chat.ID).Str("msg", result.Text).Msg("Edited message successfully")
log.Debug().Interface("msg", result).Msg("")
return result
@ -38,7 +38,7 @@ func SendVideo(message tgbotapi.VideoConfig, bot *tgbotapi.BotAPI) (result tgbot
return
}
log.Info().Int64("chat", result.Chat.ID).Msg("Sent video")
log.Info().Int64("chat", result.Chat.ID).Msg("Sent video successfully")
log.Debug().Interface("video", result).Msg("")
return result
@ -51,7 +51,7 @@ func SendPhoto(message tgbotapi.PhotoConfig, bot *tgbotapi.BotAPI) (result tgbot
return
}
log.Info().Int64("chat", result.Chat.ID).Msg("Sent photo")
log.Info().Int64("chat", result.Chat.ID).Msg("Sent photo successfully")
log.Debug().Interface("photo", result).Msg("")
return result
@ -64,7 +64,7 @@ func SendSticker(message tgbotapi.StickerConfig, bot *tgbotapi.BotAPI) (result t
return
}
log.Info().Int64("chat", result.Chat.ID).Msg("Sent sticker")
log.Info().Int64("chat", result.Chat.ID).Msg("Sent sticker successfully")
log.Debug().Interface("sticker", result).Msg("")
return result

View file

@ -22,11 +22,11 @@ func SetBotCommands(bot *tgbotapi.BotAPI) {
result, err := bot.Request(commands)
if err != nil {
log.Error().Err(err).Msg("Failed to set own commands")
log.Error().Err(err).Msg("Failed to publish commands to Telegram")
return
}
log.Debug().Interface("commands", result).Msg("Set own commands")
log.Debug().Interface("commands", result).Msg("Published commands to Telegram successfully")
}
func Commands(update tgbotapi.Update, bot *tgbotapi.BotAPI) {

View file

@ -5,18 +5,26 @@ import (
"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 {
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("Error parsing config from env variables")
log.Panic().Err(err).Msg("Failed to parse config from env variables")
}
zerolog.SetGlobalLevel(zerolog.Level(BotConfig.LogLevel))
log.Info().Msg("Loaded config")
log.Info().Msg("Config loaded successfully")
log.Debug().Interface("config", BotConfig).Msg("")
}

1
go.mod
View file

@ -12,6 +12,7 @@ require (
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

2
go.sum
View file

@ -10,6 +10,8 @@ github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-colorable v0.1.14 h1:9A9LHSqF/7dyVVX6g0U9cwm9pG3kP9gSzcuIPHPsaIE=
github.com/mattn/go-colorable v0.1.14/go.mod h1:6LmQG8QLFO4G5z1gPvYEzlUgJ2wF+stgPZH1UqBm1s8=

View file

@ -38,6 +38,6 @@ func configureLogger() {
output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.DateTime}
log.Logger = zerolog.New(output).With().Timestamp().Caller().Logger()
log.Info().Msg("Started Logger")
log.Info().Msg("Logger started successfully")
}

View file

@ -11,8 +11,8 @@ func GetPlayerDetails(steamID uint64) (summary steamapi.PlayerSummary) {
response, err := steamapi.GetPlayerSummaries([]uint64{steamID}, config.BotConfig.SteamAPIKey)
if err != nil {
log.Error().Err(err).Msg("Failed to get Player Summary")
log.Error().Err(err).Msg("Failed to retrive player summary")
}
log.Debug().Interface("Player", response[0]).Msg("Got PlayerSummary from Steam API")
log.Debug().Interface("Player", response[0]).Msg("Retrived player summary from SteamAPI successfully")
return response[0]
}

View file

@ -19,7 +19,7 @@ func GetComments(steamID uint64, start int, count int) (page CommentsPage) {
url, err := url.Parse(baseURL + strconv.FormatUint(steamID, 10))
if err != nil {
log.Error().Err(err).Msg("Unable to Parse SteamID into URL")
log.Error().Err(err).Msg("Failed to parse SteamID into URL")
return
}
@ -30,7 +30,7 @@ func GetComments(steamID uint64, start int, count int) (page CommentsPage) {
resp, err := http.Get(url.String())
if err != nil || resp.StatusCode != http.StatusOK {
log.Error().Err(err).Int("Response Code", resp.StatusCode).Msg("Failed to get Comments")
log.Error().Err(err).Int("Response Code", resp.StatusCode).Msg("Failed to retrieve comments")
}
defer resp.Body.Close()
@ -38,16 +38,16 @@ func GetComments(steamID uint64, start int, count int) (page CommentsPage) {
// Read the response body
body, err := io.ReadAll(resp.Body)
if err != nil {
log.Error().Err(err).Msg("Failed to parse Comments")
log.Error().Err(err).Msg("Failed to parse comments")
log.Trace().Interface("Body", resp.Body)
}
err = json.Unmarshal(body, &page)
if err != nil {
log.Error().Err(err).Msg("Failed to parse Comments as JSON")
log.Error().Err(err).Msg("Failed to parse comments as JSON")
}
log.Trace().Interface("CommentPage", page).Uint64("ProfileID", steamID).Msg("Got Comment Page")
log.Trace().Interface("CommentPage", page).Uint64("ProfileID", steamID).Msg("Retrieved Comment Page successfully")
return page
}
@ -56,7 +56,7 @@ func parseComments(rawComments CommentsPage) (comments []Comment) {
doc, err := goquery.NewDocumentFromReader(strings.NewReader(rawComments.CommentsHTML))
if err != nil {
log.Error().Err(err).Msg("Error while parsing CommentsHTML")
log.Error().Err(err).Msg("Failed to parse CommentsHTML")
return
}
doc.Find(".commentthread_comment.responsive_body_text").Each(func(i int, s *goquery.Selection) {
@ -64,14 +64,14 @@ func parseComments(rawComments CommentsPage) (comments []Comment) {
parsedID, err := strconv.ParseUint(strings.TrimPrefix(s.AttrOr("id", ""), "comment_"), 10, 64)
if err != nil {
log.Error().Err(err).Msg("Error while parsing Comment ID")
log.Error().Err(err).Msg("Failed to parse Comment ID")
return
}
c.ID = parsedID
c.Timestamp, err = strconv.ParseInt(s.Find(".commentthread_comment_timestamp").AttrOr("data-timestamp", ""), 10, 64)
if err != nil {
log.Error().Err(err).Msg("Error while parsing Comment Timestamp")
log.Error().Err(err).Msg("Failed to parse Comment Timestamp")
return
}
@ -85,6 +85,6 @@ func parseComments(rawComments CommentsPage) (comments []Comment) {
})
slices.Reverse(comments)
log.Trace().Interface("Comments", comments).Msg("Parsed Comment Page")
log.Trace().Interface("Comments", comments).Msg("Parsed comment page successfully")
return comments
}

View file

@ -33,7 +33,7 @@ func StartWatchers(bot *tgbotapi.BotAPI) {
}
func watcher(bot *tgbotapi.BotAPI, steamID uint64, sleeptime time.Duration) {
log.Info().Uint64("SteamID", steamID).Msg("Started Watcher")
log.Info().Uint64("SteamID", steamID).Msg("Starting Watcher")
var newestProcessedComment int64 = 0
@ -56,9 +56,9 @@ func watcher(bot *tgbotapi.BotAPI, steamID uint64, sleeptime time.Duration) {
profileOwner := GetPlayerDetails(steamID)
for _, comment := range parseComments(currentCommentsPage) {
log.Debug().Interface("Comment", comment).Msg("Processing Comment")
log.Debug().Interface("Comment", comment).Msg("Processing comment")
if comment.Timestamp <= newestProcessedComment {
log.Debug().Uint64("CommentID", comment.ID).Msg("Skipping Comment")
log.Debug().Uint64("CommentID", comment.ID).Msg("Skipping comment")
continue
}
@ -69,7 +69,7 @@ func watcher(bot *tgbotapi.BotAPI, steamID uint64, sleeptime time.Duration) {
Text: fmt.Sprintf(`<b><a href="%s">%s</a> just commented on <a href="%s">%s</a>'s profile:</b>`, comment.AuthorProfileURL, comment.Author, profileOwner.ProfileURL, profileOwner.PersonaName) + "\n" +
"<blockquote>" + comment.Text + "</blockquote>",
}
log.Info().Interface("Comment", comment).Msg("Notifying about new Comment")
log.Info().Interface("Comment", comment).Msg("Notifying about new comment")
botIO.SendMessage(msg, bot)
time.Sleep(time.Minute / 20)
}