From 1f4eba49cf8405b31864acbcb0a2ec3401c016df Mon Sep 17 00:00:00 2001 From: Mathias Dollenbacher Date: Wed, 29 Apr 2026 00:02:58 +0200 Subject: [PATCH] feat: Initial commit --- .gitignore | 3 + Dockerfile | 28 +++ README.md | 104 ++++++++++ docker-compose.yml | 54 +++++ example-payloads/example.json | 12 ++ example-payloads/movie_request.json | 25 +++ pom.xml | 78 ++++++++ .../webhookproxy/WebhookProxyApplication.java | 14 ++ .../dev/webhookproxy/config/ProxyConfig.java | 30 +++ .../controller/WebhookController.java | 67 +++++++ .../dev/webhookproxy/model/SeerrPayload.java | 137 +++++++++++++ .../service/DiscordForwardingService.java | 189 ++++++++++++++++++ src/main/resources/application.yml | 20 ++ target/classes/application.yml | 20 ++ .../compile/default-compile/inputFiles.lst | 5 + 15 files changed, 786 insertions(+) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 README.md create mode 100644 docker-compose.yml create mode 100644 example-payloads/example.json create mode 100644 example-payloads/movie_request.json create mode 100644 pom.xml create mode 100644 src/main/java/dev/webhookproxy/WebhookProxyApplication.java create mode 100644 src/main/java/dev/webhookproxy/config/ProxyConfig.java create mode 100644 src/main/java/dev/webhookproxy/controller/WebhookController.java create mode 100644 src/main/java/dev/webhookproxy/model/SeerrPayload.java create mode 100644 src/main/java/dev/webhookproxy/service/DiscordForwardingService.java create mode 100644 src/main/resources/application.yml create mode 100644 target/classes/application.yml create mode 100644 target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..084e2df --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea/** +target/** +**.iml diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..56d6524 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,28 @@ +# ── Stage 1: Build ──────────────────────────────────────────────────────────── +FROM eclipse-temurin:21-jdk-alpine AS build +WORKDIR /app + +COPY pom.xml . +COPY src ./src + +# Download dependencies (cached layer), then build +RUN apk add --no-cache maven && \ + mvn dependency:go-offline -q && \ + mvn package -DskipTests -q + +# ── Stage 2: Runtime ────────────────────────────────────────────────────────── +FROM eclipse-temurin:21-jre-alpine +WORKDIR /app + +# Copy the fat JAR from the build stage +COPY --from=build /app/target/overseerr-webhook-proxy-*.jar app.jar + +# The application.yml can be overridden by mounting a file at: +# /app/config/application.yml +VOLUME ["/app/config"] + +EXPOSE 8080 + +ENTRYPOINT ["java", \ + "-Dspring.config.additional-location=optional:file:/app/config/application.yml", \ + "-jar", "app.jar"] diff --git a/README.md b/README.md new file mode 100644 index 0000000..5d91431 --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# Overseerr Webhook Proxy + +A lightweight Spring Boot proxy that receives [Overseerr](https://overseerr.dev/) / +[Jellyseerr](https://github.com/Fallenbagel/jellyseerr) webhook notifications and +forwards them to **multiple Discord servers**, with per-notification-type routing. + +## How it works + +``` +Overseerr ──POST /webhook──► Proxy ──► Discord Server A (Admins) + ──► Discord Server B (General) + ──► Discord Server C (Film fans) +``` + +Each Discord embed is colour-coded by notification type and includes the media +title, requester name, and media type. + +--- + +## Quick start + +### 1. Clone & configure + +```bash +git clone https://github.com/yourname/overseerr-webhook-proxy +cd overseerr-webhook-proxy +mkdir config +cp src/main/resources/application.yml config/application.yml +``` + +Edit `config/application.yml` and replace the placeholder webhook URLs with your +real Discord webhook URLs. + +### 2. Run with Docker Compose + +```bash +docker compose up -d +``` + +### 3. Configure Overseerr + +In Overseerr → **Settings → Notifications → Webhook**: + +| Field | Value | +|-------|-------| +| Webhook URL | `http://:8080/webhook` | +| JSON payload | *(leave as default)* | + +Click **Test** to send a test notification and verify it arrives in Discord. + +--- + +## Configuration reference (`application.yml`) + +```yaml +proxy: + routes: + MEDIA_APPROVED: + - https://discord.com/api/webhooks/ID1/TOKEN1 # admins + - https://discord.com/api/webhooks/ID2/TOKEN2 # general + + MEDIA_AVAILABLE: + - https://discord.com/api/webhooks/ID2/TOKEN2 # general only + + MEDIA_FAILED: + - https://discord.com/api/webhooks/ID1/TOKEN1 # admins only + + default: # catch-all + - https://discord.com/api/webhooks/ID1/TOKEN1 +``` + +### Supported notification types + +| Type | Description | +|------|-------------| +| `MEDIA_PENDING` | New request submitted | +| `MEDIA_APPROVED` | Request approved | +| `MEDIA_DECLINED` | Request declined | +| `MEDIA_AVAILABLE` | Media is now available | +| `MEDIA_FAILED` | Download/processing failed | +| `TEST_NOTIFICATION` | Test from Overseerr settings | +| `default` | Catches any type not listed above | + +--- + +## Building without Docker + +Requirements: Java 21, Maven 3.9+ + +```bash +mvn package -DskipTests +java -jar target/overseerr-webhook-proxy-1.0.0.jar \ + --spring.config.additional-location=file:./config/application.yml +``` + +--- + +## Endpoints + +| Method | Path | Description | +|--------|------|-------------| +| `POST` | `/webhook` | Receives Overseerr notifications | +| `GET` | `/health` | Simple liveness check | +| `GET` | `/actuator/health` | Spring Boot Actuator health | diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..d5fc59f --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,54 @@ +services: + webhook-proxy: + build: . + restart: unless-stopped + ports: + - "8080:8080" + volumes: + # Mount your custom config here — overrides the defaults inside the image + - ./config:/app/config:ro + environment: + # Optional: override the port + - LOG_LEVEL=debug + - SERVER_PORT=8080 + networks: + - seerr-net + + # ── Jellyseerr (local test instance) ───────────────────────────────────── + # Access the UI at: http://localhost:5055 + # + # After first start, go through the setup wizard. When asked for a media + # server you can skip it or point to a local Jellyfin (see below). + # + # Webhook setup in Jellyseerr: + # Settings → Notifications → Webhook + # URL: http://webhook-proxy:8080/webhook ← uses the internal Docker hostname + # Payload: leave as default + seerr: + image: seerr/seerr:latest + container_name: seerr + restart: unless-stopped + ports: + - "5055:5055" + environment: + - LOG_LEVEL=debug + - TZ=Europe/Berlin + networks: + - seerr-net + + # ── Jellyfin (optional — only needed if Jellyseerr asks for a media server) + # Access the UI at: http://localhost:8096 + # Remove this block if you don't want a full media server locally. + jellyfin: + image: jellyfin/jellyfin:latest + container_name: jellyfin + restart: unless-stopped + ports: + - "8096:8096" + environment: + - TZ=Europe/Berlin + networks: + - seerr-net + +networks: + seerr-net: diff --git a/example-payloads/example.json b/example-payloads/example.json new file mode 100644 index 0000000..3398c14 --- /dev/null +++ b/example-payloads/example.json @@ -0,0 +1,12 @@ +{ + "notification_type": "TEST_NOTIFICATION", + "event": "", + "subject": "Test Notification", + "message": "Check check, 1, 2, 3. Are we coming in clear?", + "image": "", + "media": null, + "request": null, + "issue": null, + "comment": null, + "extra": [] +} \ No newline at end of file diff --git a/example-payloads/movie_request.json b/example-payloads/movie_request.json new file mode 100644 index 0000000..654c0be --- /dev/null +++ b/example-payloads/movie_request.json @@ -0,0 +1,25 @@ +{ + "notification_type": "MEDIA_AUTO_APPROVED", + "event": "Movie Request Automatically Approved", + "subject": "Crime 101 (2026)", + "message": "When an elusive thief whose high-stakes heists unfold along the iconic 101 freeway in Los Angeles eyes the score of a lifetime, with hopes of this being his final job, his path collides with a disillusioned insurance broker who is facing her own crossroads. Determined to crack the case, a relentless detective closes in on the operation, raising the stakes even higher.", + "image": "https://image.tmdb.org/t/p/w600_and_h900_bestv2/8L1IKxEfrFHmo2Zg0qjL9yAMnbP.jpg", + "media": { + "media_type": "movie", + "tmdbId": "1171145", + "tvdbId": "", + "status": "PENDING", + "status4k": "UNKNOWN" + }, + "request": { + "request_id": "2", + "requestedBy_email": "test@example.com", + "requestedBy_username": "root", + "requestedBy_avatar": "/avatarproxy/8d952445af6245d992e193d7e5e092c3?v=undefined", + "requestedBy_settings_discordId": "", + "requestedBy_settings_telegramChatId": "" + }, + "issue": null, + "comment": null, + "extra": [] +} \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..9b63fa0 --- /dev/null +++ b/pom.xml @@ -0,0 +1,78 @@ + + + 4.0.0 + + + org.springframework.boot + spring-boot-starter-parent + 3.2.4 + + + + dev.webhookproxy + overseerr-webhook-proxy + 1.0.0 + overseerr-webhook-proxy + Proxy that forwards Overseerr webhooks to multiple Discord servers + + + 21 + + + + + org.springframework.boot + spring-boot-starter-web + + + org.springframework.boot + spring-boot-starter-actuator + + + org.springframework.boot + spring-boot-configuration-processor + true + + + com.fasterxml.jackson.core + jackson-databind + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 11 + 11 + + + + + diff --git a/src/main/java/dev/webhookproxy/WebhookProxyApplication.java b/src/main/java/dev/webhookproxy/WebhookProxyApplication.java new file mode 100644 index 0000000..f4dc9b5 --- /dev/null +++ b/src/main/java/dev/webhookproxy/WebhookProxyApplication.java @@ -0,0 +1,14 @@ +package dev.webhookproxy; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.EnableConfigurationProperties; + +@SpringBootApplication +@EnableConfigurationProperties +public class WebhookProxyApplication { + + public static void main(String[] args) { + SpringApplication.run(WebhookProxyApplication.class, args); + } +} diff --git a/src/main/java/dev/webhookproxy/config/ProxyConfig.java b/src/main/java/dev/webhookproxy/config/ProxyConfig.java new file mode 100644 index 0000000..2173d02 --- /dev/null +++ b/src/main/java/dev/webhookproxy/config/ProxyConfig.java @@ -0,0 +1,30 @@ +package dev.webhookproxy.config; + +import lombok.*; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.stereotype.Component; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +@Data +@Component +@ConfigurationProperties(prefix = "proxy") +public class ProxyConfig { + + /** + * Map of notification_type → list of Discord webhook URLs. + * Use the key "default" to catch all unmatched notification types. + */ + private List routes = List.of(); + private Filters filters; + + @Getter + @Setter + @NoArgsConstructor + @AllArgsConstructor + public static class Filters { + private List usernameFilter; + } +} diff --git a/src/main/java/dev/webhookproxy/controller/WebhookController.java b/src/main/java/dev/webhookproxy/controller/WebhookController.java new file mode 100644 index 0000000..6633902 --- /dev/null +++ b/src/main/java/dev/webhookproxy/controller/WebhookController.java @@ -0,0 +1,67 @@ +package dev.webhookproxy.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.webhookproxy.config.ProxyConfig; +import dev.webhookproxy.model.SeerrPayload; +import dev.webhookproxy.service.DiscordForwardingService; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +/** + * Receives incoming Overseerr webhook POST requests and + * delegates forwarding to {@link DiscordForwardingService}. + * + * Endpoint: POST /webhook + */ +@Slf4j +@RestController +@RequiredArgsConstructor +public class WebhookController { + + private final DiscordForwardingService forwardingService; + private final ObjectMapper objectMapper; + private final ProxyConfig proxyConfig; + + /** + * Main webhook endpoint. + * Overseerr should be configured to POST to: http://:/webhook + */ + @PostMapping("/webhook") + public ResponseEntity receiveWebhook(@RequestBody String rawBody) { + log.info("Received webhook payload: {}", rawBody); + + SeerrPayload payload; + try { + payload = objectMapper.readValue(rawBody, SeerrPayload.class); + } catch (Exception e) { + log.error("Failed to parse webhook payload: {}", e.getMessage()); + return ResponseEntity.badRequest().body("Invalid JSON payload"); + } + + log.info("Incoming notification type: {}", payload.getNotificationType()); + + if (payload.getRequest() != null) { + if (proxyConfig.getFilters().getUsernameFilter().contains(payload.getRequest().getRequestedByUsername())) { + String user = payload.getRequest().getRequestedByUsername(); + String movie = payload.getSubject(); + log.info("Blocked notifications for user {} and movie {}", user, movie); + return ResponseEntity.ok("OK"); + } + } + + // Forward asynchronously — we return 200 immediately to Overseerr + forwardingService.forward(payload, rawBody); + + return ResponseEntity.ok("OK"); + } + + /** + * Simple health / test endpoint. + */ + @GetMapping("/health") + public ResponseEntity health() { + return ResponseEntity.ok("Webhook proxy is running."); + } +} diff --git a/src/main/java/dev/webhookproxy/model/SeerrPayload.java b/src/main/java/dev/webhookproxy/model/SeerrPayload.java new file mode 100644 index 0000000..2b69c24 --- /dev/null +++ b/src/main/java/dev/webhookproxy/model/SeerrPayload.java @@ -0,0 +1,137 @@ +package dev.webhookproxy.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.extern.slf4j.Slf4j; + +import java.util.List; + +@Slf4j +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class SeerrPayload { + + @JsonProperty("notification_type") + private String notificationType; + + /** Human-friendly description of the event, e.g. "Movie Request Approved". */ + private String event; + + /** Media title (or notification subject for non-media events). */ + private String subject; + + /** Media overview / synopsis, or issue description. */ + private String message; + + /** URL to the media poster image. */ + private String image; + + private Media media; + private Request request; + private Issue issue; + private Comment comment; + private List extra; + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Media { + + @JsonProperty("media_type") + private String mediaType; + + private String tmdbId; + private String tvdbId; + + /** Availability status: UNKNOWN | PENDING | PROCESSING | PARTIALLY_AVAILABLE | AVAILABLE */ + private String status; + + /** 4K availability status (same values as status). */ + private String status4k; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Request { + + @JsonProperty("request_id") + private String requestId; + + @JsonProperty("requestedBy_email") + private String requestedByEmail; + + @JsonProperty("requestedBy_username") + private String requestedByUsername; + + @JsonProperty("requestedBy_avatar") + private String requestedByAvatar; + + @JsonProperty("requestedBy_settings_discordId") + private String requestedByDiscordId; + + @JsonProperty("requestedBy_settings_telegramChatId") + private String requestedByTelegramChatId; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Issue { + + @JsonProperty("issue_id") + private String issueId; + + /** VIDEO | AUDIO | SUBTITLES | OTHER */ + @JsonProperty("issue_type") + private String issueType; + + /** OPEN | IN_PROGRESS | RESOLVED */ + @JsonProperty("issue_status") + private String issueStatus; + + @JsonProperty("reportedBy_email") + private String reportedByEmail; + + @JsonProperty("reportedBy_username") + private String reportedByUsername; + + @JsonProperty("reportedBy_avatar") + private String reportedByAvatar; + + @JsonProperty("reportedBy_settings_discordId") + private String reportedByDiscordId; + + @JsonProperty("reportedBy_settings_telegramChatId") + private String reportedByTelegramChatId; + } + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Comment { + + @JsonProperty("comment_message") + private String commentMessage; + + @JsonProperty("commentedBy_email") + private String commentedByEmail; + + @JsonProperty("commentedBy_username") + private String commentedByUsername; + + @JsonProperty("commentedBy_avatar") + private String commentedByAvatar; + + @JsonProperty("commentedBy_settings_discordId") + private String commentedByDiscordId; + + @JsonProperty("commentedBy_settings_telegramChatId") + private String commentedByTelegramChatId; + } + + + @Data + @JsonIgnoreProperties(ignoreUnknown = true) + public static class Extra { + private String name; + private String value; + } +} diff --git a/src/main/java/dev/webhookproxy/service/DiscordForwardingService.java b/src/main/java/dev/webhookproxy/service/DiscordForwardingService.java new file mode 100644 index 0000000..74e710d --- /dev/null +++ b/src/main/java/dev/webhookproxy/service/DiscordForwardingService.java @@ -0,0 +1,189 @@ +package dev.webhookproxy.service; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.webhookproxy.config.ProxyConfig; +import dev.webhookproxy.model.SeerrPayload; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; + +import java.net.URI; +import java.net.http.HttpClient; +import java.net.http.HttpRequest; +import java.net.http.HttpResponse; +import java.time.Duration; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +/** + * Builds a Discord embed message from the Overseerr payload + * and forwards it concurrently to all configured webhook URLs. + */ +@Slf4j +@Service +@RequiredArgsConstructor +public class DiscordForwardingService { + + private final ProxyConfig proxyConfig; + private final ObjectMapper objectMapper; + + private final HttpClient httpClient = HttpClient.newBuilder() + .connectTimeout(Duration.ofSeconds(5)) + .build(); + + /** + * Forwards the payload to all Discord webhooks matching the notification type. + * + * @param payload the parsed Overseerr payload + * @param rawBody the original raw JSON body (used as fallback) + */ + public void forward(SeerrPayload payload, String rawBody) { + String mediaName = payload.getSubject(); + List targets = proxyConfig.getRoutes(); + + if (targets.isEmpty()) { + log.warn("No Discord webhooks configured — skipping."); + return; + } + + log.info("Forwarding notification for media {} to {} Discord webhook(s).", mediaName, targets.size()); + + String discordBody = buildDiscordPayload(payload); + + // Fire all requests concurrently + List> futures = new ArrayList<>(); + for (String webhookUrl : targets) { + futures.add(sendAsync(webhookUrl, discordBody, mediaName)); + } + + // Wait for all to complete (best-effort, errors are logged but not re-thrown) + CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join(); + } + + // ------------------------------------------------------------------------- + // Internal helpers + // ------------------------------------------------------------------------- + + private CompletableFuture sendAsync(String webhookUrl, String body, String notificationType) { + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(webhookUrl)) + .header("Content-Type", "application/json") + .POST(HttpRequest.BodyPublishers.ofString(body)) + .timeout(Duration.ofSeconds(10)) + .build(); + + return httpClient.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenAccept(response -> { + if (response.statusCode() >= 200 && response.statusCode() < 300) { + log.info("[{}] ✓ Delivered to {} (HTTP {})", notificationType, + maskUrl(webhookUrl), response.statusCode()); + } else { + log.error("[{}] ✗ Failed for {} — HTTP {} — Body: {}", notificationType, + maskUrl(webhookUrl), response.statusCode(), response.body()); + } + }) + .exceptionally(ex -> { + log.error("[{}] ✗ Exception while sending to {}: {}", + notificationType, maskUrl(webhookUrl), ex.getMessage()); + return null; + }); + } + + /** + * Builds a Discord-compatible JSON payload with an embed. + * Discord webhooks expect: { "embeds": [ { "title": "...", ... } ] } + */ + private String buildDiscordPayload(SeerrPayload payload) { + try { + int color = colorForType(payload.getNotificationType()); + + Map embed = new java.util.LinkedHashMap<>(); + embed.put("title", payload.getSubject() != null ? payload.getSubject() : "Overseerr Notification"); + embed.put("description", payload.getMessage() != null ? payload.getMessage() : ""); + embed.put("color", color); + embed.put("author", Map.of("name", payload.getEvent())); + + if (payload.getImage() != null && !payload.getImage().isBlank()) { + embed.put("thumbnail", Map.of("url", payload.getImage())); + } + + // Field: requested by + List> fields = new ArrayList<>(); + if (payload.getRequest() != null && payload.getRequest().getRequestedByUsername() != null) { + fields.add(field("Requested by", payload.getRequest().getRequestedByUsername(), true)); + } + if (payload.getMedia() != null && payload.getMedia().getMediaType() != null) { + fields.add(field("Media type", payload.getMedia().getMediaType(), true)); + } + + if (payload.getMedia() != null) { + fields.add(field("Request Status", capitalize(payload.getMedia().getStatus()), true)); + } + + if (payload.getMedia() != null && payload.getMedia().getMediaType().equalsIgnoreCase("tv")) { + String requestedSeasons = payload.getExtra() + .stream() + .filter(extra -> extra.getName().equalsIgnoreCase("Requested Seasons")) + .findFirst() + .map(SeerrPayload.Extra::getValue) + .orElse(""); + + if (!requestedSeasons.isBlank()) { + fields.add(field("Requested Seasons", requestedSeasons, true)); + } + } + + embed.put("fields", fields); + embed.put("timestamp", Instant.now().toString()); + + Map discordPayload = Map.of("embeds", List.of(embed)); + return objectMapper.writeValueAsString(discordPayload); + + } catch (Exception e) { + log.error("Failed to build Discord payload, using fallback.", e); + // Minimal fallback + return "{\"content\":\"Overseerr notification: " + payload.getNotificationType() + "\"}"; + } + } + + private Map field(String name, String value, boolean inline) { + return Map.of("name", name, "value", value, "inline", inline); + } + + /** + * Returns a Discord embed color (decimal) based on the notification type. + */ + private int colorForType(String type) { + if (type == null) return 0x95A5A6; // grey + return switch (type) { + case "MEDIA_APPROVED" -> 0x6366F1; // green + case "MEDIA_AVAILABLE" -> 0x2ECC71; // blue + case "MEDIA_AUTO_APPROVED" -> 0x6366F1; // indigo/lila + default -> 0x95A5A6; // grey + }; + } + + /** + * Masks the webhook token in the URL for safe logging. + * Input: https://discord.com/api/webhooks/1234567890/AbCdEfGhIj... + * Output: https://discord.com/api/webhooks/1234567890/Ab*** + */ + private String maskUrl(String url) { + int lastSlash = url.lastIndexOf('/'); + if (lastSlash < 0 || lastSlash >= url.length() - 1) return url; + String token = url.substring(lastSlash + 1); + String masked = token.substring(0, Math.min(2, token.length())) + "***"; + return url.substring(0, lastSlash + 1) + masked; + } + + private String capitalize(String input) { + if (input == null || input.isBlank()) return ""; + char firstChar = input.charAt(0); + char upperCasedFirstChar = Character.toUpperCase(firstChar); + String lowerCasLastPart = input.substring(1).toLowerCase(); + return upperCasedFirstChar + lowerCasLastPart; + } +} diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..b8a91d4 --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,20 @@ +server: + port: 8080 + +proxy: + filters: + usernameFilter: + - private + routes: + - https://discord.com/api/webhooks/1498794785475920022/bwoytZA_iDvza86fbR9aOFXNOcv9_Fl5P5taoOaMHjey_X3YLRt5FGbDjt9uPiKHJ8yi + +# Spring Boot Actuator — exposes /actuator/health endpoint +management: + endpoints: + web: + exposure: + include: health, info + +logging: + level: + dev.webhookproxy: DEBUG diff --git a/target/classes/application.yml b/target/classes/application.yml new file mode 100644 index 0000000..b8a91d4 --- /dev/null +++ b/target/classes/application.yml @@ -0,0 +1,20 @@ +server: + port: 8080 + +proxy: + filters: + usernameFilter: + - private + routes: + - https://discord.com/api/webhooks/1498794785475920022/bwoytZA_iDvza86fbR9aOFXNOcv9_Fl5P5taoOaMHjey_X3YLRt5FGbDjt9uPiKHJ8yi + +# Spring Boot Actuator — exposes /actuator/health endpoint +management: + endpoints: + web: + exposure: + include: health, info + +logging: + level: + dev.webhookproxy: DEBUG diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst new file mode 100644 index 0000000..90a43d2 --- /dev/null +++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst @@ -0,0 +1,5 @@ +/home/mathias/Downloads/overseerr-webhook-proxy/src/main/java/dev/webhookproxy/WebhookProxyApplication.java +/home/mathias/Downloads/overseerr-webhook-proxy/src/main/java/dev/webhookproxy/config/ProxyConfig.java +/home/mathias/Downloads/overseerr-webhook-proxy/src/main/java/dev/webhookproxy/model/SeerrPayload.java +/home/mathias/Downloads/overseerr-webhook-proxy/src/main/java/dev/webhookproxy/service/DiscordForwardingService.java +/home/mathias/Downloads/overseerr-webhook-proxy/src/main/java/dev/webhookproxy/controller/WebhookController.java