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/.github/workflows/on-push.yml b/.github/workflows/on-push.yml deleted file mode 100644 index cd0ef2e..0000000 --- a/.github/workflows/on-push.yml +++ /dev/null @@ -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 }} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 14beab4..6f72f89 100644 --- a/.gitignore +++ b/.gitignore @@ -23,9 +23,3 @@ go.work.sum # env file .env - -# Build ouput -build/ - -# vscode config -.vscode/ \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index e28b370..0a744a3 100644 --- a/Dockerfile +++ b/Dockerfile @@ -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"] +ENTRYPOINT ["/app/steamsalty"] \ No newline at end of file diff --git a/README.md b/README.md index 54840c9..383a233 100644 --- a/README.md +++ b/README.md @@ -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 : 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 diff --git a/bot.go b/bot.go index e2e15b9..2f57968 100644 --- a/bot.go +++ b/bot.go @@ -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) } } diff --git a/botIO/authenticate.go b/botIO/authenticate.go index 747f8b6..64f5d13 100644 --- a/botIO/authenticate.go +++ b/botIO/authenticate.go @@ -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 diff --git a/build.sh b/build.sh deleted file mode 100644 index 49d6e91..0000000 --- a/build.sh +++ /dev/null @@ -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 \ No newline at end of file diff --git a/commands/commands.go b/commands/commands.go index 2291e6d..70fe4b4 100644 --- a/commands/commands.go +++ b/commands/commands.go @@ -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(`- %s`, profile.ProfileURL, profile.PersonaName) + "\n" - textInfo += fmt.Sprintf(`%s:`, 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{ diff --git a/config/config.go b/config/config.go index 53e9257..1d8ce2e 100644 --- a/config/config.go +++ b/config/config.go @@ -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("") diff --git a/config/types.go b/config/types.go index 63f916b..5abf464 100644 --- a/config/types.go +++ b/config/types.go @@ -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"` } diff --git a/docker-compose.yaml b/docker-compose.yaml new file mode 100644 index 0000000..e3906bc --- /dev/null +++ b/docker-compose.yaml @@ -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 \ No newline at end of file diff --git a/main.go b/main.go index fcc9cfb..5b52270 100644 --- a/main.go +++ b/main.go @@ -1,19 +1,18 @@ package main import ( + "fmt" "os" - "strconv" - "strings" "time" "github.com/rs/zerolog" "github.com/rs/zerolog/log" - + "watn3y/steamsalty/config" ) 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") } diff --git a/steam/api.go b/steam/api.go index 9a41911..e79f8b9 100644 --- a/steam/api.go +++ b/steam/api.go @@ -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] } diff --git a/steam/http.go b/steam/http.go index 2c734fa..70b0337 100644 --- a/steam/http.go +++ b/steam/http.go @@ -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 } diff --git a/steam/profile.go b/steam/profile.go index ec0b1b0..1154020 100644 --- a/steam/profile.go +++ b/steam/profile.go @@ -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)