Skip to content
This repository has been archived by the owner on Dec 23, 2024. It is now read-only.

Commit

Permalink
full refactor (#30)
Browse files Browse the repository at this point in the history
* refactor: full refactor with improvements

* fix: tests

* fix: update readme

* fix: remove comment

* fix: log statements for cached entries

* refactor: cache refactor

* fix: make sure webhook data overwrites config data if present

* fix(logs): disable cache removal trace

* feat(errors): improved http error responses

* refactor: improved error handling

* fix: remove redundant log statement
  • Loading branch information
s0up4200 authored Jun 1, 2024
1 parent 3c48a9d commit 2ca8a0c
Show file tree
Hide file tree
Showing 23 changed files with 659 additions and 537 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ redactedhook/
__debug_bin
.RedactedHook
.DS_Store

## tests
internal/config/testconfig_updated.toml
130 changes: 66 additions & 64 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,29 @@
# RedactedHook

RedactedHook is a webhook companion service for [autobrr](https://github.com/autobrr/autobrr) designed to check the names of uploaders, your ratio, and record labels associated with torrents on **Redacted** and **Orpheus**. It provides a simple and efficient way to validate if uploaders are blacklisted or whitelisted, to stop racing in case your ratio falls below a certain point, and to verify if a torrent's record label matches against a specified list.
RedactedHook is a webhook companion service for [autobrr](https://github.com/autobrr/autobrr) designed to check the names of uploaders, your ratio, torrent size and record labels associated with torrents on **Redacted** and **Orpheus**. It provides a simple and efficient way to validate if uploaders are blacklisted or whitelisted, to stop racing in case your ratio falls below a certain point, and to verify if a torrent's record label matches against a specified list.

## Table of Contents

- [Features](#features)
- [Getting Started](#getting-started)
- [Warning](#warning)
- [Installation](#installation)
- [Docker](#docker)
- [Docker Compose](#docker-compose)
- [Using precompiled binaries](#using-precompiled-binaries)
- [Building from source](#building-from-source)
- [Warning](#warning)
- [Installation](#installation)
- [Docker](#docker)
- [Docker Compose](#docker-compose)
- [Using precompiled binaries](#using-precompiled-binaries)
- [Building from source](#building-from-source)
- [Usage](#usage)
- [Config](#config)
- [Authorization](#authorization)
- [Payload](#payload)
- [Commands](#commands)
- [Config](#config)
- [Authorization](#authorization)
- [Payload](#payload)

## Features

- Verify if an uploader's name is on a provided whitelist or blacklist.
- Check for record labels. Useful for grabbing torrents from a specific record label.
- Check if a user's ratio meets a specified minimum value.
- Check the torrentSize (Useful for not hitting the API from both autobrr and redactedhook)
- Check the torrentSize (Useful for not hitting the API from both autobrr and redactedhook).
- Easy to integrate with other applications via webhook.
- Rate-limited to comply with tracker API request policies.
- With a 5-minute data cache to reduce frequent API calls for the same data.
Expand All @@ -31,22 +32,22 @@ It was made with [autobrr](https://github.com/autobrr/autobrr) in mind.

## Getting Started

### Warning
## Warning

> \[!IMPORTANT]
>
> Remember that autobrr also checks the RED/OPS API if you have min/max sizes set. This will result in you hitting the API 2x.
> So for your own good, **only** set size checks in RedactedHook.
### Installation
## Installation

#### Docker
### Docker

```bash
docker pull ghcr.io/s0up4200/redactedhook:latest
```

#### Docker Compose
### Docker Compose

```docker
services:
Expand All @@ -61,8 +62,9 @@ services:
#cap_drop:
# - ALL
environment:
- REDACTEDHOOK__HOST=0.0.0.0 # binds to 127.0.0.1 by default
- REDACTEDHOOK__PORT=42135 # defaults to 42135
#- REDACTEDHOOK__HOST=127.0.0.1 # Override the host from config.toml
#- REDACTEDHOOK__PORT=42135 # Override the port from config.toml
#- REDACTEDHOOK__API_TOKEN= # Override the api_token from config.toml
- TZ=UTC
ports:
- "42135:42135"
Expand All @@ -71,41 +73,37 @@ services:
restart: unless-stopped
```

#### Using precompiled binaries
### Using precompiled binaries

Download the appropriate binary for your platform from the [releases](https://github.com/s0up4200/RedactedHook/releases/latest) page.

#### Building from source
### Building from source

1. Clone the repository:

```bash
git clone https://github.com/s0up4200/RedactedHook.git
```
```bash
git clone https://github.com/s0up4200/RedactedHook.git
```

2. Navigate to the project directory:

```bash
cd RedactedHook
```
```bash
cd RedactedHook
```

3. Build the project:

```go
go build
```

or

```shell
make build
```
```bash
go build
or
make build
```

4. Run the compiled binary:

```bash
./bin/RedactedHook --config /path/to/config.toml # config flag not necessary if file is next to binary
```
```bash
./bin/RedactedHook --config /path/to/config.toml # config flag not necessary if file is next to binary
```

## Usage

Expand All @@ -120,17 +118,27 @@ Expected HTTP Status: 200

You can check ratio, uploader (whitelist and blacklist), minsize, maxsize, and record labels in a single request, or separately.

### Config
### Commands

- `generate-apitoken`: Generate a new API token and print it.
- `create-config`: Create a default configuration file.
- `help`: Display this help message.

Most of `requestData` can be set in `config.toml` to reduce the payload from autobrr.
## Config

Config can be created with: `redactedhook create-config`
Most of requestData can be set in config.toml to reduce the payload from autobrr.

### Example config.toml

```toml
[server]
host = "127.0.0.1" # Server host
port = 42135 # Server port

[authorization]
api_token = "" # generate with "redactedhook generate-apitoken"
# the api_token needs to be set as a header for the webhook to work
# eg. Header=X-API-Token asd987gsd98g7324kjh142kjh
# eg. Header=X-API-Token=asd987gsd98g7324kjh142kjh

[indexer_keys]
#red_apikey = "" # generate in user settings, needs torrent and user privileges
Expand Down Expand Up @@ -164,7 +172,7 @@ maxage = 28 # Max age in days to keep a log file
compress = false # Whether to compress old log files
```

### Authorization
## Authorization

API Token can be generated like this: `redactedhook generate-apitoken`

Expand All @@ -182,9 +190,9 @@ curl -X POST \
http://127.0.0.1:42135/hook
```

### Payload
## Payload

**The minimum required data to send with the webhook:**
The minimum required data to send with the webhook:

```json
{
Expand All @@ -193,26 +201,20 @@ curl -X POST \
}
```

Everything else can be set in the `config.toml`, but you can set them in the webhook as well, if you want to filter by different things in different filters.

`indexer` - `"{{ .Indexer | js }}"` this is the indexer that pushed the release within autobrr.

`torrent_id` - `{{.TorrentID}}` this is the TorrentID of the pushed release within autobrr.

`red_user_id` is the number in the URL when you visit your profile.

`ops_user_id` is the number in the URL when you visit your profile.

`red_apikey` is your Redacted API key. Needs user and torrents privileges.

`ops_apikey` is your Orpheus API key. Needs user and torrents privileges.

`record_labels` is a comma-separated list of record labels to check against.

`minsize` is the minimum allowed size you want to grab. Eg. `100MB`
Everything else can be set in the config.toml, but you can set them in the webhook as well, if you want to filter by different things in different filters.

`maxsize` is the max allowed size you want to grab. Eg. `500MB`
- `indexer` - `"{{ .Indexer | js }}"` this is the indexer that pushed the release within autobrr.
- `torrent_id` - `{{.TorrentID}}` this is the TorrentID of the pushed release within autobrr.

`uploaders` is a comma-separated list of uploaders to check against.
### Additional Keys

`mode` is either blacklist or whitelist. If blacklist is used, the torrent will be stopped if the uploader is found in the list. If whitelist is used, the torrent will be stopped if the uploader is not found in the list.
- `red_user_id` is the number in the URL when you visit your profile.
- `ops_user_id` is the number in the URL when you visit your profile.
- `red_apikey` is your Redacted API key. Needs user and torrents privileges.
- `ops_apikey` is your Orpheus API key. Needs user and torrents privileges.
- `record_labels` is a comma-separated list of record labels to check against.
- `minsize` is the minimum allowed size you want to grab. Eg. 100MB
- `maxsize` is the max allowed size you want to grab. Eg. 500MB
- `uploaders` is a comma-separated list of uploaders to check against.
- `mode` is either blacklist or whitelist. If blacklist is used, the torrent will be stopped if the uploader is found in the list. If whitelist is used, the torrent will be stopped if the uploader is not found in the list.
`
90 changes: 53 additions & 37 deletions cmd/redactedhook/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,102 +19,118 @@ import (
)

var (
version string
commit string
buildDate string
version = "dev"
commit = "none"
buildDate = "unknown"
)

const (
path = "/hook"
EnvServerAddress = "REDACTEDHOOK__HOST"
EnvServerPort = "REDACTEDHOOK__PORT"
path = "/hook"
tokenLength = 16
)

func generateAPIToken(length int) string {
b := make([]byte, length)
func generateAPIToken() string {
b := make([]byte, tokenLength)
if _, err := rand.Read(b); err != nil {
log.Fatal().Err(err).Msg("Failed to generate API key")
return ""
}
apiKey := hex.EncodeToString(b)
// codeql-ignore-next-line: go/clear-text-logging-of-sensitive-information
fmt.Fprintf(os.Stdout, "API Token: %v, copy and paste into your config.toml\n", apiKey)
return apiKey
}

func flagCommands() (string, bool) {
func printHelp() {
fmt.Println("Usage: redactedhook [options] [command]")
fmt.Println()
fmt.Println("Options:")
flag.PrintDefaults()
fmt.Println()
fmt.Println("Commands:")
fmt.Println(" generate-apitoken Generate a new API token and print it.")
fmt.Println(" create-config Create a default configuration file.")
fmt.Println(" help Display this help message.")
}

func parseFlags() (string, bool) {
var configPath string
flag.StringVar(&configPath, "config", "config.toml", "Path to the configuration file")
flag.Parse()

if len(flag.Args()) > 0 {
switch flag.Arg(0) {
case "generate-apitoken":
return generateAPIToken(16), true
generateAPIToken()
return "", true
case "create-config":
return config.CreateConfigFile(), true
config.CreateConfigFile()
return "", true
case "help":
printHelp()
return "", true
default:
log.Fatal().Msgf("Unknown command: %s", flag.Arg(0))
log.Fatal().Msgf("Unknown command: %s. Use 'redactedhook help' to see available commands.", flag.Arg(0))
}
}
return configPath, false
}

func getEnv(key, defaultValue string) string {
value := os.Getenv(key)
if value == "" {
return defaultValue
if value, exists := os.LookupEnv(key); exists {
return value
}
return value
return defaultValue
}

func initLogger() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "2006-01-02 15:04:05"})
}

func startHTTPServer(address, port string) {
server := &http.Server{Addr: address + ":" + port}
func startHTTPServer(address string) {
server := &http.Server{Addr: address}

go func() {
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Fatal().Err(err).Msg("Failed to start server")
log.Fatal().Err(err).Msg("HTTP server crashed")
}
}()

log.Info().Msgf("Starting server on %s", address+":"+port)
log.Info().Msgf("Starting server on %s", address)
log.Info().Msgf("Version: %s, Commit: %s, Build Date: %s", version, commit, buildDate)

c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
<-c
sig := make(chan os.Signal, 1)
signal.Notify(sig, os.Interrupt)
<-sig

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

if err := server.Shutdown(ctx); err != nil {
log.Error().Err(err).Msg("Server shutdown failed")
} else {
log.Info().Msg("Server gracefully stopped")
}
}

func main() {
log.Logger = log.Output(zerolog.ConsoleWriter{Out: os.Stderr, TimeFormat: "2006-01-02 15:04:05", NoColor: false})
initLogger()

configPath, isCommandExecuted := flagCommands()
configPath, isCommandExecuted := parseFlags()
if isCommandExecuted {
return
}

config.InitConfig(configPath)

err := config.ValidateConfig()
if err != nil {
if err := config.ValidateConfig(); err != nil {
log.Fatal().Err(err).Msg("Invalid configuration")
} else {
log.Debug().Msg("Configuration is valid.")
}

http.HandleFunc(path, api.WebhookHandler)

address := getEnv(EnvServerAddress, "127.0.0.1")
port := getEnv(EnvServerPort, "42135")
host := getEnv("REDACTEDHOOK__HOST", config.GetConfig().Server.Host)
port := getEnv("REDACTEDHOOK__PORT", fmt.Sprintf("%d", config.GetConfig().Server.Port))
apiToken := getEnv("REDACTEDHOOK__API_TOKEN", config.GetConfig().Authorization.APIToken)

config.GetConfig().Authorization.APIToken = apiToken

address := fmt.Sprintf("%s:%s", host, port)

startHTTPServer(address, port)
startHTTPServer(address)
}
Loading

0 comments on commit 2ca8a0c

Please sign in to comment.