mirror of
https://github.com/watn3y/steamsalty.git
synced 2025-04-19 16:01:22 +02:00
v1.0
- Fixed SLEEPTIME not working (always 0) - Refined logging - Added metadata to /info command - Bot now automatically sets own commands for autocompletion
This commit is contained in:
parent
eb0482b8e0
commit
6be403fc58
14 changed files with 116 additions and 53 deletions
|
@ -27,7 +27,7 @@ jobs:
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@v6
|
||||||
with:
|
with:
|
||||||
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7
|
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/riscv64
|
||||||
push: true
|
push: true
|
||||||
tags: |
|
tags: |
|
||||||
${{ github.repository }}:${{ github.ref_name }}
|
${{ github.repository }}:${{ github.ref_name }}
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -23,3 +23,6 @@ go.work.sum
|
||||||
|
|
||||||
# env file
|
# env file
|
||||||
.env
|
.env
|
||||||
|
|
||||||
|
# Build ouput
|
||||||
|
build/
|
|
@ -9,7 +9,7 @@ RUN go mod download
|
||||||
|
|
||||||
COPY . .
|
COPY . .
|
||||||
ARG TARGETOS TARGETARCH
|
ARG TARGETOS TARGETARCH
|
||||||
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o /app/steamsalty
|
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o ./steamsalty
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -19,6 +19,6 @@ WORKDIR /app
|
||||||
COPY --from=builder /etc/ssl/certs /etc/ssl/certs
|
COPY --from=builder /etc/ssl/certs /etc/ssl/certs
|
||||||
COPY --from=builder /usr/share/ca-certificates /usr/share/ca-certificates
|
COPY --from=builder /usr/share/ca-certificates /usr/share/ca-certificates
|
||||||
|
|
||||||
COPY --from=builder /app/steamsalty /app/steamsalty
|
COPY --from=builder /steamsalty/steamsalty /app/steamsalty
|
||||||
|
|
||||||
ENTRYPOINT ["/app/steamsalty"]
|
ENTRYPOINT ["/app/steamsalty"]
|
||||||
|
|
42
README.md
42
README.md
|
@ -1,2 +1,40 @@
|
||||||
# steamsalty
|
# SteamSalty
|
||||||
Get notifications about Steam Comments on Telegram
|
SteamSalty notifies you on telegram about new comments on any steam profile.
|
||||||
|
|
||||||
|
## Running with Docker Compose
|
||||||
|
|
||||||
|
Docker image: https://hub.docker.com/r/watn3y/steamsalty
|
||||||
|
|
||||||
|
Example compose file:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
services:
|
||||||
|
steamsalty:
|
||||||
|
image: watn3y/steamsalty:latest # use :<branchname> to be up-to-date with any branch
|
||||||
|
container_name: steamsalty
|
||||||
|
restart: unless-stopped
|
||||||
|
volumes:
|
||||||
|
- /etc/localtime:/etc/localtime:ro
|
||||||
|
environment:
|
||||||
|
#- STEAMSALTY_LOGLEVEL=
|
||||||
|
- STEAMSALTY_TELEGRAMAPITOKEN=
|
||||||
|
- STEAMSALTY_STEAMAPIKEY=
|
||||||
|
- STEAMSALTY_CHATID=
|
||||||
|
- STEAMSALTY_WATCHERS=
|
||||||
|
#- STEAMSALTY_SLEEPINTERVAL=
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running on Linux
|
||||||
|
|
||||||
|
Grab a release from the [releases page](https://github.com/watn3y/steamsalty/releases). Make sure to set your **environment variables** accordingly.
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
| Variable | Description | Default |
|
||||||
|
| ----------------------------- | -------------------------------------------------------------------------------------------------------------------- | ------------------ |
|
||||||
|
| `STEAMSALTY_LOGLEVEL` | LogLevel as described [here](https://pkg.go.dev/github.com/rs/zerolog@v1.33.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 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 |
|
||||||
|
|
2
bot.go
2
bot.go
|
@ -13,6 +13,8 @@ import (
|
||||||
func bot() {
|
func bot() {
|
||||||
updates, bot := botIO.Authenticate()
|
updates, bot := botIO.Authenticate()
|
||||||
|
|
||||||
|
go commands.SetBotCommands(bot)
|
||||||
|
|
||||||
go steam.StartWatchers(bot)
|
go steam.StartWatchers(bot)
|
||||||
|
|
||||||
for update := range updates {
|
for update := range updates {
|
||||||
|
|
|
@ -12,12 +12,15 @@ func Authenticate() (tgbotapi.UpdatesChannel, *tgbotapi.BotAPI) {
|
||||||
log.Panic().Err(err).Msg("Failed to authenticate")
|
log.Panic().Err(err).Msg("Failed to authenticate")
|
||||||
}
|
}
|
||||||
|
|
||||||
bot.Debug = config.BotConfig.DebugMode
|
bot.Debug = false
|
||||||
|
if config.BotConfig.LogLevel == -1 {
|
||||||
|
bot.Debug = true
|
||||||
|
}
|
||||||
|
|
||||||
updates := tgbotapi.NewUpdate(0)
|
updates := tgbotapi.NewUpdate(0)
|
||||||
updates.Timeout = 60
|
updates.Timeout = 60
|
||||||
|
|
||||||
log.Info().Int64("ID", bot.Self.ID).Str("username", bot.Self.UserName).Msg("Successfully authenticated to Telegram API")
|
log.Info().Int64("ID", bot.Self.ID).Str("username", bot.Self.UserName).Msg("Authenticated to Telegram API")
|
||||||
|
|
||||||
return bot.GetUpdatesChan(updates), bot
|
return bot.GetUpdatesChan(updates), bot
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,10 @@ package commands
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
|
|
||||||
"watn3y/steamsalty/botIO"
|
"watn3y/steamsalty/botIO"
|
||||||
"watn3y/steamsalty/config"
|
"watn3y/steamsalty/config"
|
||||||
"watn3y/steamsalty/steam"
|
"watn3y/steamsalty/steam"
|
||||||
|
@ -11,26 +14,41 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func SetBotCommands(bot *tgbotapi.BotAPI) {
|
||||||
|
github := tgbotapi.BotCommand{Command: "github", Description: "Source GitHub repo"}
|
||||||
|
info := tgbotapi.BotCommand{Command: "info", Description: "Summary of watched profiles"}
|
||||||
|
|
||||||
|
commands := tgbotapi.NewSetMyCommands(github, info)
|
||||||
|
|
||||||
|
result, err := bot.Request(commands)
|
||||||
|
if err != nil {
|
||||||
|
log.Error().Err(err).Msg("Failed to set own commands")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
log.Debug().Interface("commands", result).Msg("Set own commands")
|
||||||
|
}
|
||||||
|
|
||||||
func Commands(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
|
func Commands(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
|
||||||
|
|
||||||
cmd := strings.ToLower(update.Message.Command())
|
cmd := strings.ToLower(update.Message.Command())
|
||||||
|
|
||||||
log.Debug().Str("cmd", cmd).Msg("Matching command")
|
|
||||||
|
|
||||||
switch cmd {
|
switch cmd {
|
||||||
case "start":
|
case "start":
|
||||||
start(update, bot)
|
startGithub(update, bot)
|
||||||
|
case "github":
|
||||||
|
startGithub(update, bot)
|
||||||
case "info":
|
case "info":
|
||||||
info(update, bot)
|
info(update, bot)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func start(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
|
func startGithub(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
|
||||||
message := tgbotapi.MessageConfig{
|
message := tgbotapi.MessageConfig{
|
||||||
BaseChat: tgbotapi.BaseChat{ChatID: update.Message.Chat.ID, ReplyToMessageID: update.Message.MessageID},
|
BaseChat: tgbotapi.BaseChat{ChatID: update.Message.Chat.ID, ReplyToMessageID: update.Message.MessageID},
|
||||||
ParseMode: "html",
|
ParseMode: "html",
|
||||||
DisableWebPagePreview: false,
|
DisableWebPagePreview: false,
|
||||||
Text: "https://github.com/watn3y/steamsalty",
|
Text: "Check out: https://github.com/watn3y/steamsalty",
|
||||||
}
|
}
|
||||||
botIO.SendMessage(message, bot)
|
botIO.SendMessage(message, bot)
|
||||||
}
|
}
|
||||||
|
@ -44,9 +62,16 @@ func info(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
|
||||||
|
|
||||||
for _, steamID := range config.BotConfig.Watchers {
|
for _, steamID := range config.BotConfig.Watchers {
|
||||||
profile := steam.GetPlayerDetails(steamID)
|
profile := steam.GetPlayerDetails(steamID)
|
||||||
|
comments := steam.GetComments(steamID, 0, 0)
|
||||||
|
|
||||||
textInfo += fmt.Sprintf(`- <a href="%s">%s</a>`, profile.ProfileURL, profile.PersonaName) + "\n"
|
lastComment := "never :("
|
||||||
|
if comments.TimeLastPost > 0 {
|
||||||
|
lastComment = time.Unix(comments.TimeLastPost, 0).Format(time.RFC1123)
|
||||||
|
}
|
||||||
|
|
||||||
|
textInfo += fmt.Sprintf(`<b><a href="%s">%s</a></b>:`, profile.ProfileURL, profile.PersonaName) + "\n" +
|
||||||
|
fmt.Sprintf(`Last Comment: %s`, lastComment) + "\n" +
|
||||||
|
fmt.Sprintf(`Number of Comments: %s`, strconv.Itoa(comments.TotalCount)) + "\n\n"
|
||||||
}
|
}
|
||||||
|
|
||||||
message := tgbotapi.MessageConfig{
|
message := tgbotapi.MessageConfig{
|
||||||
|
|
|
@ -2,6 +2,7 @@ package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
envconfig "github.com/sethvargo/go-envconfig"
|
envconfig "github.com/sethvargo/go-envconfig"
|
||||||
|
@ -11,12 +12,9 @@ var BotConfig config
|
||||||
|
|
||||||
func LoadConfig() {
|
func LoadConfig() {
|
||||||
if err := envconfig.Process(context.Background(), &BotConfig); err != nil {
|
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("Error parsing config from env variables")
|
||||||
}
|
|
||||||
|
|
||||||
if !BotConfig.DebugMode {
|
|
||||||
zerolog.SetGlobalLevel(zerolog.InfoLevel)
|
|
||||||
}
|
}
|
||||||
|
zerolog.SetGlobalLevel(zerolog.Level(BotConfig.LogLevel))
|
||||||
|
|
||||||
log.Info().Msg("Loaded config")
|
log.Info().Msg("Loaded config")
|
||||||
log.Debug().Interface("config", BotConfig).Msg("")
|
log.Debug().Interface("config", BotConfig).Msg("")
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
package config
|
package config
|
||||||
|
|
||||||
type config struct {
|
type config struct {
|
||||||
TelegramAPIToken string `env:"TELEGRAMAPITOKEN, required"`
|
LogLevel int `env:"STEAMSALTY_LOGLEVEL, default=1"`
|
||||||
SteamAPIKey string `env:"STEAMAPIKEY, required"`
|
TelegramAPIToken string `env:"STEAMSALTY_TELEGRAMAPITOKEN, required"`
|
||||||
DebugMode bool `env:"DEBUGMODE, default=false"`
|
SteamAPIKey string `env:"STEAMSALTY_STEAMAPIKEY, required"`
|
||||||
ChatID int64 `env:"CHATID"`
|
ChatID int64 `env:"STEAMSALTY_CHATID, required"`
|
||||||
Watchers []uint64 `env:"WATCHERS"`
|
Watchers []uint64 `env:"STEAMSALTY_WATCHERS, required"`
|
||||||
SleepInterval int `env:"SLEEPINTERVAL"`
|
SleepInterval int `env:"STEAMSALTY_SLEEPINTERVAL, default=60"`
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
services:
|
|
||||||
steamsalty:
|
|
||||||
image: watn3y/steamsalty:master
|
|
||||||
container_name: steamsalty
|
|
||||||
restart: unless-stopped
|
|
||||||
volumes:
|
|
||||||
- /etc/localtime:/etc/localtime:ro
|
|
||||||
environment:
|
|
||||||
- TELEGRAMAPITOKEN=
|
|
||||||
- STEAMAPIKEY=
|
|
||||||
- DebugMode=false
|
|
||||||
- CHATID=123
|
|
||||||
- WATCHERS=123,456,789
|
|
||||||
- SLEEPINTERVAL=60
|
|
20
main.go
20
main.go
|
@ -1,8 +1,9 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"os"
|
"os"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
|
@ -12,7 +13,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
fmt.Println("Starting SteamSalty...")
|
println("Starting SteamSalty...")
|
||||||
|
|
||||||
configureLogger()
|
configureLogger()
|
||||||
|
|
||||||
|
@ -23,13 +24,20 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func configureLogger() {
|
func configureLogger() {
|
||||||
|
|
||||||
|
zerolog.CallerMarshalFunc = func(pc uintptr, file string, line int) string {
|
||||||
|
const prefix = "steamsalty/"
|
||||||
|
|
||||||
|
index := strings.Index(file, prefix)
|
||||||
|
if index != -1 {
|
||||||
|
return file[index+len(prefix):] + ":" + strconv.Itoa(line)
|
||||||
|
}
|
||||||
|
return file + ":" + strconv.Itoa(line)
|
||||||
|
}
|
||||||
|
|
||||||
output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.DateTime}
|
output := zerolog.ConsoleWriter{Out: os.Stdout, TimeFormat: time.DateTime}
|
||||||
|
|
||||||
log.Logger = zerolog.New(output).With().Timestamp().Caller().Logger()
|
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")
|
log.Info().Msg("Started Logger")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,6 +13,6 @@ func GetPlayerDetails(steamID uint64) (summary steamapi.PlayerSummary) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Error().Err(err).Msg("Failed to get Player Summary")
|
log.Error().Err(err).Msg("Failed to get Player Summary")
|
||||||
}
|
}
|
||||||
log.Debug().Interface("Player", response[0]).Msg("Successfully got PlayerSummary from Steam API")
|
log.Debug().Interface("Player", response[0]).Msg("Got PlayerSummary from Steam API")
|
||||||
return response[0]
|
return response[0]
|
||||||
}
|
}
|
||||||
|
|
|
@ -13,7 +13,7 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
func getComments(steamID uint64, start int, count int) (page CommentsPage) {
|
func GetComments(steamID uint64, start int, count int) (page CommentsPage) {
|
||||||
|
|
||||||
baseURL := "https://steamcommunity.com/comment/Profile/render/"
|
baseURL := "https://steamcommunity.com/comment/Profile/render/"
|
||||||
|
|
||||||
|
@ -47,7 +47,7 @@ func getComments(steamID uint64, start int, count int) (page CommentsPage) {
|
||||||
log.Error().Err(err).Msg("Failed to parse Comments as JSON")
|
log.Error().Err(err).Msg("Failed to parse Comments as JSON")
|
||||||
}
|
}
|
||||||
|
|
||||||
log.Debug().Interface("CommentPage", page).Uint64("ProfileID", steamID).Msg("Successfully got Comment Page")
|
log.Trace().Interface("CommentPage", page).Uint64("ProfileID", steamID).Msg("Got Comment Page")
|
||||||
|
|
||||||
return page
|
return page
|
||||||
}
|
}
|
||||||
|
@ -85,6 +85,6 @@ func parseComments(rawComments CommentsPage) (comments []Comment) {
|
||||||
})
|
})
|
||||||
|
|
||||||
slices.Reverse(comments)
|
slices.Reverse(comments)
|
||||||
log.Debug().Interface("Comments", comments).Msg("Successfully parsed Comment Page")
|
log.Trace().Interface("Comments", comments).Msg("Parsed Comment Page")
|
||||||
return comments
|
return comments
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,30 +15,30 @@ import (
|
||||||
"github.com/rs/zerolog/log"
|
"github.com/rs/zerolog/log"
|
||||||
)
|
)
|
||||||
|
|
||||||
var sleeptime time.Duration = time.Duration(config.BotConfig.SleepInterval) * time.Second
|
|
||||||
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)."
|
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)."
|
||||||
|
|
||||||
func StartWatchers(bot *tgbotapi.BotAPI) {
|
func StartWatchers(bot *tgbotapi.BotAPI) {
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
var wg sync.WaitGroup
|
||||||
|
|
||||||
for _, steamID := range config.BotConfig.Watchers {
|
for _, steamID := range config.BotConfig.Watchers {
|
||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
go func(steamID uint64) {
|
go func(steamID uint64) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
watcher(bot, steamID)
|
watcher(bot, steamID, time.Duration(config.BotConfig.SleepInterval)*time.Second)
|
||||||
}(steamID)
|
}(steamID)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
}
|
}
|
||||||
|
|
||||||
func watcher(bot *tgbotapi.BotAPI, steamID uint64) {
|
func watcher(bot *tgbotapi.BotAPI, steamID uint64, sleeptime time.Duration) {
|
||||||
log.Info().Uint64("SteamID", steamID).Msg("Started Watcher")
|
log.Info().Uint64("SteamID", steamID).Msg("Started Watcher")
|
||||||
|
|
||||||
var newestProcessedComment int64 = 0
|
var newestProcessedComment int64 = 0
|
||||||
|
|
||||||
for {
|
for {
|
||||||
currentCommentsPage := getComments(steamID, 0, math.MaxInt32)
|
currentCommentsPage := GetComments(steamID, 0, math.MaxInt32)
|
||||||
if newestProcessedComment == 0 || newestProcessedComment == currentCommentsPage.TimeLastPost {
|
if newestProcessedComment == 0 || newestProcessedComment == currentCommentsPage.TimeLastPost {
|
||||||
newestProcessedComment = currentCommentsPage.TimeLastPost
|
newestProcessedComment = currentCommentsPage.TimeLastPost
|
||||||
time.Sleep(sleeptime)
|
time.Sleep(sleeptime)
|
||||||
|
|
Loading…
Reference in a new issue