This commit is contained in:
Noah 2024-12-20 02:09:07 +01:00
parent 6aa64b6287
commit 3ec330e3a2
16 changed files with 408 additions and 1 deletions

38
.github/workflows/docker-on-push.yml vendored Normal file
View file

@ -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 }}

16
Dockerfile Normal file
View file

@ -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"]

View file

@ -1,2 +1,2 @@
# steamsalty
A bot that notifies you about new Steam Profile comments on telegram
Get notifications about Steam Comments on Telegram

10
bot.go Normal file
View file

@ -0,0 +1,10 @@
package main
import (
"watn3y/steamsalty/botIO"
"watn3y/steamsalty/steam"
)
func bot() {
steam.StartWatchers(botIO.Authenticate())
}

20
botIO/authenticate.go Normal file
View file

@ -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
}

71
botIO/sending.go Normal file
View file

@ -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
}

24
config/config.go Normal file
View file

@ -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("")
}

9
config/types.go Normal file
View file

@ -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"`
}

11
docker-compose.yaml Normal file
View file

@ -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

17
go.mod Normal file
View file

@ -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
)

25
go.sum Normal file
View file

@ -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=

35
main.go Normal file
View file

@ -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")
}

17
steam/api.go Normal file
View file

@ -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]
}

48
steam/http.go Normal file
View file

@ -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
}

57
steam/profile.go Normal file
View file

@ -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 <a href="%s">%s's</a> profile`, player.ProfileURL, player.PersonaName),
}
botIO.SendMessage(msg, bot)
previousCount = currentCount
time.Sleep(sleeptime)
}
}

9
steam/types.go Normal file
View file

@ -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"`
}