From 3ec330e3a2bf7fc3fbc1c84969169d3eba8db345 Mon Sep 17 00:00:00 2001 From: Noah Theus Date: Fri, 20 Dec 2024 02:09:07 +0100 Subject: [PATCH] v0.1 --- .github/workflows/docker-on-push.yml | 38 +++++++++++++++ Dockerfile | 16 +++++++ README.md | 2 +- bot.go | 10 ++++ botIO/authenticate.go | 20 ++++++++ botIO/sending.go | 71 ++++++++++++++++++++++++++++ config/config.go | 24 ++++++++++ config/types.go | 9 ++++ docker-compose.yaml | 11 +++++ go.mod | 17 +++++++ go.sum | 25 ++++++++++ main.go | 35 ++++++++++++++ steam/api.go | 17 +++++++ steam/http.go | 48 +++++++++++++++++++ steam/profile.go | 57 ++++++++++++++++++++++ steam/types.go | 9 ++++ 16 files changed, 408 insertions(+), 1 deletion(-) create mode 100644 .github/workflows/docker-on-push.yml create mode 100644 Dockerfile create mode 100644 bot.go create mode 100644 botIO/authenticate.go create mode 100644 botIO/sending.go create mode 100644 config/config.go create mode 100644 config/types.go create mode 100644 docker-compose.yaml create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 steam/api.go create mode 100644 steam/http.go create mode 100644 steam/profile.go create mode 100644 steam/types.go diff --git a/.github/workflows/docker-on-push.yml b/.github/workflows/docker-on-push.yml new file mode 100644 index 0000000..57d2c42 --- /dev/null +++ b/.github/workflows/docker-on-push.yml @@ -0,0 +1,38 @@ +name: Build and Push to Docker Hub on changes to master branch + +on: + push: + branches: + - master + +jobs: + docker: + runs-on: ubuntu-latest + steps: + - name: Check out the repo + uses: actions/checkout@v3 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{github.actor}} + password: ${{secrets.GHCR_TOKEN}} # needs read:packages, write:packages. delete:packages + + + - name: Build and push + uses: docker/build-push-action@v6 + with: + platforms: linux/amd64,linux/arm64 + push: true + tags: | + ghcr.io/${{ github.repository }}:master + ghcr.io/${{ github.repository }}:commit-${{ github.sha }} + + diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..a72a7e1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,16 @@ +FROM golang:alpine AS builder + +WORKDIR /tmp/build + +COPY . . +RUN go mod download +RUN go build -o /tmp/build/bin/steamsalty + + + +FROM scratch +WORKDIR /app +COPY --from=builder /tmp/build/bin/steamsalty /app/steamsalty + + +ENTRYPOINT ["/app/steamsalty"] \ No newline at end of file diff --git a/README.md b/README.md index 45229bb..383a233 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,2 @@ # steamsalty -A bot that notifies you about new Steam Profile comments on telegram +Get notifications about Steam Comments on Telegram diff --git a/bot.go b/bot.go new file mode 100644 index 0000000..de8fc00 --- /dev/null +++ b/bot.go @@ -0,0 +1,10 @@ +package main + +import ( + "watn3y/steamsalty/botIO" + "watn3y/steamsalty/steam" +) + +func bot() { + steam.StartWatchers(botIO.Authenticate()) +} diff --git a/botIO/authenticate.go b/botIO/authenticate.go new file mode 100644 index 0000000..d940bfa --- /dev/null +++ b/botIO/authenticate.go @@ -0,0 +1,20 @@ +package botIO + +import ( + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "github.com/rs/zerolog/log" + "watn3y/steamsalty/config" +) + +func Authenticate() *tgbotapi.BotAPI { + bot, err := tgbotapi.NewBotAPI(config.BotConfig.TelegramAPIToken) + if err != nil { + log.Panic().Err(err).Msg("Failed to authenticate") + } + + bot.Debug = config.BotConfig.DebugMode + + log.Info().Int64("ID", bot.Self.ID).Str("username", bot.Self.UserName).Msg("Successfully authenticated to Telegram API") + + return bot +} diff --git a/botIO/sending.go b/botIO/sending.go new file mode 100644 index 0000000..4576603 --- /dev/null +++ b/botIO/sending.go @@ -0,0 +1,71 @@ +package botIO + +import ( + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "github.com/rs/zerolog/log" +) + +func SendMessage(message tgbotapi.MessageConfig, bot *tgbotapi.BotAPI) (result tgbotapi.Message) { + result, err := bot.Send(message) + if err != nil { + log.Error().Err(err).Msg("Failed to send message") + return + } + + log.Info().Int64("chat", result.Chat.ID).Str("msg", result.Text).Msg("Sent message") + log.Debug().Interface("msg", result).Msg("") + + return result +} + +func EditMessage(message tgbotapi.EditMessageTextConfig, bot *tgbotapi.BotAPI) (result tgbotapi.Message) { + result, err := bot.Send(message) + if err != nil { + log.Error().Err(err).Msg("Failed to edit message") + return + } + + log.Info().Int64("chat", result.Chat.ID).Str("msg", result.Text).Msg("Edited message") + log.Debug().Interface("msg", result).Msg("") + + return result +} + +func SendVideo(message tgbotapi.VideoConfig, bot *tgbotapi.BotAPI) (result tgbotapi.Message) { + result, err := bot.Send(message) + if err != nil { + log.Error().Err(err).Msg("Failed to send video") + return + } + + log.Info().Int64("chat", result.Chat.ID).Msg("Sent video") + log.Debug().Interface("video", result).Msg("") + + return result +} + +func SendPhoto(message tgbotapi.PhotoConfig, bot *tgbotapi.BotAPI) (result tgbotapi.Message) { + result, err := bot.Send(message) + if err != nil { + log.Error().Err(err).Msg("Failed to send photo") + return + } + + log.Info().Int64("chat", result.Chat.ID).Msg("Sent photo") + log.Debug().Interface("photo", result).Msg("") + + return result +} + +func SendSticker(message tgbotapi.StickerConfig, bot *tgbotapi.BotAPI) (result tgbotapi.Message) { + result, err := bot.Send(message) + if err != nil { + log.Error().Err(err).Msg("Failed to send sticker") + return + } + + log.Info().Int64("chat", result.Chat.ID).Msg("Sent sticker") + log.Debug().Interface("sticker", result).Msg("") + + return result +} diff --git a/config/config.go b/config/config.go new file mode 100644 index 0000000..1d8ce2e --- /dev/null +++ b/config/config.go @@ -0,0 +1,24 @@ +package config + +import ( + "context" + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + envconfig "github.com/sethvargo/go-envconfig" +) + +var BotConfig config + +func LoadConfig() { + if err := envconfig.Process(context.Background(), &BotConfig); err != nil { + log.Panic().Err(err).Msg("error parsing config from env variables") + } + + if !BotConfig.DebugMode { + zerolog.SetGlobalLevel(zerolog.InfoLevel) + } + + log.Info().Msg("Loaded config") + log.Debug().Interface("config", BotConfig).Msg("") + +} diff --git a/config/types.go b/config/types.go new file mode 100644 index 0000000..a80658c --- /dev/null +++ b/config/types.go @@ -0,0 +1,9 @@ +package config + +type config struct { + TelegramAPIToken string `env:"TELEGRAMAPITOKEN, required"` + SteamAPIKey string `env:"STEAMAPIKEY, required"` + DebugMode bool `env:"DEBUGMODE, default=false"` + ChatID int64 `env:"CHATID"` + Watchers []uint64 `env:"WATCHERS"` +} diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..4245fb3 --- /dev/null +++ b/docker-compose.yaml @@ -0,0 +1,11 @@ +services: + steamsalty: + image: ghcr.io/watn3y/steamsalty:master + container_name: steamsalty + restart: unless-stopped + environment: + - TELEGRAMAPITOKEN= + - STEAMAPIKEY= + - DebugMode=false + - CHATID=123 + - WATCHERS=123,456,789 \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..66ba8b4 --- /dev/null +++ b/go.mod @@ -0,0 +1,17 @@ +module watn3y/steamsalty + +go 1.23.4 + +require ( + github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 + github.com/rs/zerolog v1.33.0 + github.com/sethvargo/go-envconfig v1.1.0 + github.com/Philipp15b/go-steamapi v0.0.0-20210114153316-ec4fdd23b4c1 +) + +require ( + + github.com/mattn/go-colorable v0.1.13 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect + golang.org/x/sys v0.28.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..1a1a2d7 --- /dev/null +++ b/go.sum @@ -0,0 +1,25 @@ +github.com/Philipp15b/go-steamapi v0.0.0-20210114153316-ec4fdd23b4c1 h1:PD13eMe9XAgPQ0SYWyirqwyOJG90TlEWApCw8A699l0= +github.com/Philipp15b/go-steamapi v0.0.0-20210114153316-ec4fdd23b4c1/go.mod h1:eQR7Xf64m2ALDAQE7Nr9ylFZhav1izvF3zzysKPhb0I= +github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1 h1:wG8n/XJQ07TmjbITcGiUaOtXxdrINDz1b0J1w0SzqDc= +github.com/go-telegram-bot-api/telegram-bot-api/v5 v5.5.1/go.mod h1:A2S0CWkNylc2phvKXWBBdD3K0iGnDBGbzRpISP2zBl8= +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/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= +github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= +github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/rs/xid v1.5.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.33.0 h1:1cU2KZkvPxNyfgEmhHAz/1A9Bz+llsdYzklWFzgp0r8= +github.com/rs/zerolog v1.33.0/go.mod h1:/7mN4D5sKwJLZQ2b/znpjC3/GQWY/xaDXUM0kKWRHss= +github.com/sethvargo/go-envconfig v1.1.0 h1:cWZiJxeTm7AlCvzGXrEXaSTCNgip5oJepekh/BOQuog= +github.com/sethvargo/go-envconfig v1.1.0/go.mod h1:JLd0KFWQYzyENqnEPWWZ49i4vzZo/6nRidxI8YvGiHw= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= diff --git a/main.go b/main.go new file mode 100644 index 0000000..5b52270 --- /dev/null +++ b/main.go @@ -0,0 +1,35 @@ +package main + +import ( + "fmt" + "os" + "time" + + "github.com/rs/zerolog" + "github.com/rs/zerolog/log" + + "watn3y/steamsalty/config" +) + +func main() { + fmt.Println("Starting SteamSalty...") + + configureLogger() + + config.LoadConfig() + + bot() + +} + +func configureLogger() { + output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.DateTime} + + log.Logger = zerolog.New(output).With().Timestamp().Caller().Logger() + + //! note that we overwrite the loglevel after loading the config in config/config.go. This is just the default + zerolog.SetGlobalLevel(zerolog.TraceLevel) + + log.Info().Msg("Started Logger") + +} diff --git a/steam/api.go b/steam/api.go new file mode 100644 index 0000000..db12e11 --- /dev/null +++ b/steam/api.go @@ -0,0 +1,17 @@ +package steam + +import ( + "github.com/Philipp15b/go-steamapi" + "github.com/rs/zerolog/log" + "watn3y/steamsalty/config" +) + +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") + } + + return response[0] +} diff --git a/steam/http.go b/steam/http.go new file mode 100644 index 0000000..28e883d --- /dev/null +++ b/steam/http.go @@ -0,0 +1,48 @@ +package steam + +import ( + "encoding/json" + "github.com/rs/zerolog/log" + "io" + "net/http" + "net/url" + "strconv" +) + +func getComments(steamID uint64, start int, count int) (comments CommentResponse) { + + baseURL := "https://steamcommunity.com/comment/Profile/render/" + + url, err := url.Parse(baseURL + strconv.FormatUint(steamID, 10)) + if err != nil { + log.Error().Err(err).Msg("Unable to Parse SteamID into URL") + return + } + + query := url.Query() + query.Set("start", strconv.Itoa(start)) + query.Set("count", strconv.Itoa(count)) + url.RawQuery = query.Encode() + + 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") + } + + defer resp.Body.Close() + + // Read the response body + 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, &comments) + if err != nil { + log.Error().Err(err).Msg("Failed to parse Comments as JSON") + } + + log.Debug().Interface("CommentPage", comments).Msg("Successfully got Comment Page") + return comments +} diff --git a/steam/profile.go b/steam/profile.go new file mode 100644 index 0000000..b78f252 --- /dev/null +++ b/steam/profile.go @@ -0,0 +1,57 @@ +package steam + +import ( + "fmt" + tgbotapi "github.com/go-telegram-bot-api/telegram-bot-api/v5" + "github.com/rs/zerolog/log" + "math" + "sync" + "time" + "watn3y/steamsalty/botIO" + "watn3y/steamsalty/config" +) + +var sleeptime time.Duration = 10 * time.Second + +func StartWatchers(bot *tgbotapi.BotAPI) { + var wg sync.WaitGroup + + for _, steamID := range config.BotConfig.Watchers { + wg.Add(1) + go func(steamID uint64) { + defer wg.Done() + watcher(bot, steamID) + }(steamID) + } + + wg.Wait() +} + +func watcher(bot *tgbotapi.BotAPI, steamID uint64) { + log.Info().Uint64("SteamID", steamID).Msg("Started Watcher") + var previousCount int + for { + currentCount := getComments(steamID, math.MaxInt32, 0).TotalCount + if previousCount == 0 || currentCount <= previousCount { + previousCount = currentCount + time.Sleep(sleeptime) + continue + } + + log.Info().Int("NumComment", currentCount).Uint64("SteamID", steamID).Msg("Found new comment") + + player := getPlayerDetails(steamID) + + msg := tgbotapi.MessageConfig{ + BaseChat: tgbotapi.BaseChat{ChatID: config.BotConfig.ChatID}, + ParseMode: "html", + DisableWebPagePreview: false, + Text: fmt.Sprintf(`New comment on %s's profile`, player.ProfileURL, player.PersonaName), + } + + botIO.SendMessage(msg, bot) + + previousCount = currentCount + time.Sleep(sleeptime) + } +} diff --git a/steam/types.go b/steam/types.go new file mode 100644 index 0000000..4d3ae86 --- /dev/null +++ b/steam/types.go @@ -0,0 +1,9 @@ +package steam + +type CommentResponse struct { + Success bool `json:"success"` + Start int `json:"start"` + TotalCount int `json:"total_count"` + CommentsHTML string `json:"comments_html"` + Timelastpost int `json:"timelastpost"` +}