mirror of
https://github.com/watn3y/steamsalty.git
synced 2025-04-20 00:11:23 +02:00
v0.1
This commit is contained in:
parent
6aa64b6287
commit
3ec330e3a2
16 changed files with 408 additions and 1 deletions
38
.github/workflows/docker-on-push.yml
vendored
Normal file
38
.github/workflows/docker-on-push.yml
vendored
Normal 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
16
Dockerfile
Normal 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"]
|
|
@ -1,2 +1,2 @@
|
||||||
# steamsalty
|
# 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
10
bot.go
Normal 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
20
botIO/authenticate.go
Normal 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
71
botIO/sending.go
Normal 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
24
config/config.go
Normal 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
9
config/types.go
Normal 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
11
docker-compose.yaml
Normal 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
17
go.mod
Normal 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
25
go.sum
Normal 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
35
main.go
Normal 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
17
steam/api.go
Normal 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
48
steam/http.go
Normal 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
57
steam/profile.go
Normal 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
9
steam/types.go
Normal 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"`
|
||||||
|
}
|
Loading…
Reference in a new issue