Compare commits

..

No commits in common. "b0d68f3a8371ea39aab2dca2bdf4e40b1e05525c" and "453f03d832e09c076b1d891c5c927420f3ddf46f" have entirely different histories.

16 changed files with 95 additions and 213 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 }}

View file

@ -1,34 +0,0 @@
name: Build and Push to Docker Hub on push to any branch
on:
push:
paths:
- '**.go'
- 'go.mod'
- 'go.sum'
- 'Dockerfile'
- '.github/workflows/**'
jobs:
docker:
runs-on: ubuntu-latest
steps:
- name: Check out the repo
uses: actions/checkout@v3
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Login to Docker Hub
uses: docker/login-action@v3
with:
username: ${{secrets.DOCKERHUB_USERNAME}}
password: ${{secrets.DOCKERHUB_TOKEN}}
- name: Build and push
uses: docker/build-push-action@v6
with:
platforms: linux/amd64,linux/386,linux/arm64,linux/arm/v7,linux/riscv64
push: true
tags: |
${{ github.repository }}:${{ github.ref_name }}
${{ github.repository }}:commit-${{ github.sha }}

6
.gitignore vendored
View file

@ -23,9 +23,3 @@ go.work.sum
# env file
.env
# Build ouput
build/
# vscode config
.vscode/

View file

@ -1,15 +1,12 @@
FROM --platform=$BUILDPLATFORM golang:1.23.4-alpine AS builder
FROM golang:alpine AS builder
WORKDIR /steamsalty
RUN apk update && apk add --no-cache ca-certificates
COPY go.mod go.sum ./
RUN go mod download
COPY . .
ARG TARGETOS TARGETARCH
RUN GOOS=$TARGETOS GOARCH=$TARGETARCH go build -o ./steamsalty
RUN go mod download
RUN go build -o /app/steamsalty
@ -19,6 +16,6 @@ WORKDIR /app
COPY --from=builder /etc/ssl/certs /etc/ssl/certs
COPY --from=builder /usr/share/ca-certificates /usr/share/ca-certificates
COPY --from=builder /steamsalty/steamsalty /app/steamsalty
COPY --from=builder /app/steamsalty /app/steamsalty
ENTRYPOINT ["/app/steamsalty"]

View file

@ -1,40 +1,2 @@
# SteamSalty
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 |
# steamsalty
Get notifications about Steam Comments on Telegram

5
bot.go
View file

@ -13,8 +13,6 @@ import (
func bot() {
updates, bot := botIO.Authenticate()
go commands.SetBotCommands(bot)
go steam.StartWatchers(bot)
for update := range updates {
@ -29,8 +27,9 @@ func bot() {
continue
}
log.Info().Int64("ChatID", update.Message.Chat.ID).Int64("UserID", update.Message.From.ID).Str("Text", update.Message.Text).Msg("Recieved Message")
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")
commands.Commands(update, bot)
}
}

View file

@ -12,15 +12,12 @@ func Authenticate() (tgbotapi.UpdatesChannel, *tgbotapi.BotAPI) {
log.Panic().Err(err).Msg("Failed to authenticate")
}
bot.Debug = false
if config.BotConfig.LogLevel == -1 {
bot.Debug = true
}
bot.Debug = config.BotConfig.DebugMode
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("Successfully authenticated to Telegram API")
return bot.GetUpdatesChan(updates), bot

View file

@ -1,54 +0,0 @@
#!/usr/bin/env bash
# Get the current directory name as the project name
PROJECT_NAME=$(basename "$PWD")
# Change to the Go project directory (current directory)
cd "$PWD" || exit
# List of target architectures
architectures=(
"linux/amd64"
"linux/386"
"linux/arm64"
"linux/arm/v7"
"linux/riscv64"
"windows/amd64"
"darwin/amd64"
"darwin/arm64"
)
# Create a build directory if it doesn't exist
mkdir -p build
# Loop over architectures and build for each one
for arch in "${architectures[@]}"; do
os=$(echo $arch | cut -d '/' -f 1)
arch_type=$(echo $arch | cut -d '/' -f 2)
# Set the output file name using the project name, os, and arch
output_file="build/$PROJECT_NAME-$os-$arch_type"
# Build the app for the specific architecture
GOARCH=$arch_type GOOS=$os go build -o "$output_file"
# Provide feedback to the user
if [ $? -eq 0 ]; then
echo "Successfully built for $arch: $output_file"
# Create a tar.gz archive for the individual build
tar -czf "$output_file.tar.gz" -C build $(basename "$output_file")
if [ $? -eq 0 ]; then
echo "Successfully created archive: $output_file.tar.gz"
# Delete the executable after archiving
rm "$output_file"
echo "Deleted executable: $output_file"
else
echo "Failed to create archive for $output_file"
fi
else
echo "Failed to build for $arch"
fi
done

View file

@ -2,10 +2,7 @@ package commands
import (
"fmt"
"strconv"
"strings"
"time"
"watn3y/steamsalty/botIO"
"watn3y/steamsalty/config"
"watn3y/steamsalty/steam"
@ -14,41 +11,26 @@ import (
"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) {
cmd := strings.ToLower(update.Message.Command())
log.Debug().Str("cmd", cmd).Msg("Matching command")
switch cmd {
case "start":
startGithub(update, bot)
case "github":
startGithub(update, bot)
start(update, bot)
case "info":
info(update, bot)
}
}
func startGithub(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
func start(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
message := tgbotapi.MessageConfig{
BaseChat: tgbotapi.BaseChat{ChatID: update.Message.Chat.ID, ReplyToMessageID: update.Message.MessageID},
ParseMode: "html",
DisableWebPagePreview: false,
Text: "Check out: https://github.com/watn3y/steamsalty",
Text: "https://github.com/watn3y/steamsalty",
}
botIO.SendMessage(message, bot)
}
@ -62,16 +44,9 @@ func info(update tgbotapi.Update, bot *tgbotapi.BotAPI) {
for _, steamID := range config.BotConfig.Watchers {
profile := steam.GetPlayerDetails(steamID)
comments := steam.GetComments(steamID, 0, 0)
lastComment := "never :("
if comments.TimeLastPost > 0 {
lastComment = time.Unix(comments.TimeLastPost, 0).Format(time.RFC1123)
}
textInfo += fmt.Sprintf(`- <a href="%s">%s</a>`, profile.ProfileURL, profile.PersonaName) + "\n"
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{

View file

@ -2,7 +2,6 @@ package config
import (
"context"
"github.com/rs/zerolog"
"github.com/rs/zerolog/log"
envconfig "github.com/sethvargo/go-envconfig"
@ -12,9 +11,12 @@ 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")
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.Debug().Interface("config", BotConfig).Msg("")

View file

@ -1,10 +1,10 @@
package config
type config struct {
LogLevel int `env:"STEAMSALTY_LOGLEVEL, default=1"`
TelegramAPIToken string `env:"STEAMSALTY_TELEGRAMAPITOKEN, required"`
SteamAPIKey string `env:"STEAMSALTY_STEAMAPIKEY, required"`
ChatID int64 `env:"STEAMSALTY_CHATID, required"`
Watchers []uint64 `env:"STEAMSALTY_WATCHERS, required"`
SleepInterval int `env:"STEAMSALTY_SLEEPINTERVAL, default=60"`
TelegramAPIToken string `env:"TELEGRAMAPITOKEN, required"`
SteamAPIKey string `env:"STEAMAPIKEY, required"`
DebugMode bool `env:"DEBUGMODE, default=false"`
ChatID int64 `env:"CHATID"`
Watchers []uint64 `env:"WATCHERS"`
SleepInterval int `env:"SLEEPINTERVAL"`
}

14
docker-compose.yaml Normal file
View file

@ -0,0 +1,14 @@
services:
steamsalty:
image: ghcr.io/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
View file

@ -1,9 +1,8 @@
package main
import (
"fmt"
"os"
"strconv"
"strings"
"time"
"github.com/rs/zerolog"
@ -13,7 +12,7 @@ import (
)
func main() {
println("Starting SteamSalty...")
fmt.Println("Starting SteamSalty...")
configureLogger()
@ -24,20 +23,13 @@ func main() {
}
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}
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")
}

View file

@ -13,6 +13,6 @@ func GetPlayerDetails(steamID uint64) (summary steamapi.PlayerSummary) {
if err != nil {
log.Error().Err(err).Msg("Failed to get Player Summary")
}
log.Debug().Interface("Player", response[0]).Msg("Got PlayerSummary from Steam API")
log.Debug().Interface("Player", response[0]).Msg("Successfully got PlayerSummary from Steam API")
return response[0]
}

View file

@ -13,7 +13,7 @@ import (
"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/"
@ -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.Trace().Interface("CommentPage", page).Uint64("ProfileID", steamID).Msg("Got Comment Page")
log.Debug().Interface("CommentPage", page).Uint64("ProfileID", steamID).Msg("Successfully got Comment Page")
return page
}
@ -85,6 +85,6 @@ func parseComments(rawComments CommentsPage) (comments []Comment) {
})
slices.Reverse(comments)
log.Trace().Interface("Comments", comments).Msg("Parsed Comment Page")
log.Debug().Interface("Comments", comments).Msg("Successfully parsed Comment Page")
return comments
}

View file

@ -15,30 +15,30 @@ import (
"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)."
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, time.Duration(config.BotConfig.SleepInterval)*time.Second)
watcher(bot, steamID)
}(steamID)
}
wg.Wait()
}
func watcher(bot *tgbotapi.BotAPI, steamID uint64, sleeptime time.Duration) {
func watcher(bot *tgbotapi.BotAPI, steamID uint64) {
log.Info().Uint64("SteamID", steamID).Msg("Started Watcher")
var newestProcessedComment int64 = 0
for {
currentCommentsPage := GetComments(steamID, 0, math.MaxInt32)
currentCommentsPage := getComments(steamID, 0, math.MaxInt32)
if newestProcessedComment == 0 || newestProcessedComment == currentCommentsPage.TimeLastPost {
newestProcessedComment = currentCommentsPage.TimeLastPost
time.Sleep(sleeptime)