Skip to content

Remove pre-deploy legacy webhook compatibility paths - #5166

Open
jamesrudolph with Copilot wants to merge 10 commits into
masterfrom
copilot/add-turn-phase-webhook-feature
Open

Remove pre-deploy legacy webhook compatibility paths#5166
jamesrudolph with Copilot wants to merge 10 commits into
masterfrom
copilot/add-turn-phase-webhook-feature

Conversation

Copilot AI commented May 31, 2026

Copy link
Copy Markdown
Contributor

This branch introduced webhook functionality with temporary legacy compatibility, but the branch has not been deployed yet. This PR removes those compatibility paths so the feature ships with a single, clean subscription-based model.

  • Legacy path removal

    • Removed game-stored webhook config flow (/game webhook) and deleted GameWebhook + GameWebhookConfig.
    • Removed legacy dispatch branch from WebhookGameEventNotifier (dispatchLegacyWebhook), leaving subscription dispatch as the only delivery path.
  • Subscription-only delivery model

    • Kept notifier dispatch wired exclusively through GameWebhookSubscriptionService#getCallbackUrls(...).
    • Moved URL validation responsibility into WebhookDispatchService so dispatch remains self-contained after config deletion.
  • Behavior/documentation alignment

    • Updated webhook notifier tests to assert subscription-driven dispatch (and removed legacy-config gating expectations).
    • Updated integration docs to describe API-managed webhook subscriptions instead of /game webhook command configuration.
// before
dispatchLegacyWebhook(game, payload);
dispatchWebhookSubscriptions(game, eventType, payload);

// after
dispatchWebhookSubscriptions(game, eventType, payload);

Copilot AI and others added 2 commits May 31, 2026 02:30
Co-authored-by: jamesrudolph <39609802+jamesrudolph@users.noreply.github.com>
Co-authored-by: jamesrudolph <39609802+jamesrudolph@users.noreply.github.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a per-game outbound webhook feature so external systems can subscribe to a fixed set of TI4 game-state transitions (active player change, phase change, agenda voting started/resolved, player passed, game ended). Configuration is persisted in the per-game stored-values map via a new /game webhook slash subcommand, and a Spring service dispatches JSON payloads asynchronously with timeouts and a small retry.

Changes:

  • New ti4.service.webhook package: event-type enum, payload record, config helper, notifier interface + Spring impl + static facade, async dispatch service.
  • Wired emission into core game-flow choke points (Game.updateActivePlayer, StartPhaseService phase transitions, AgendaHelper.startTheVoting/resolveTime, PassService.passPlayerForRound, EndGameService.gameEndStuff).
  • Added /game webhook slash subcommand, integrator docs in WEBSITE_INTEGRATION.md, and unit tests for WebhookGameEventNotifier.

Reviewed changes

Copilot reviewed 16 out of 16 changed files in this pull request and generated 11 comments.

Show a summary per file
File Description
WEBSITE_INTEGRATION.md Documents webhook setup, event types, payload schema, delivery semantics.
src/main/java/ti4/service/webhook/GameEventNotifier.java Notifier interface for the six game events.
src/main/java/ti4/service/webhook/GameEventNotifierFacade.java Static facade resolving the notifier bean from Spring with a silent no-op fallback.
src/main/java/ti4/service/webhook/WebhookGameEventNotifier.java Spring impl that builds payloads, gates on config (enabled/FoW), and dispatches.
src/main/java/ti4/service/webhook/WebhookDispatchService.java Async HTTP POST with timeout + simple retry/backoff using EgressClientManager.
src/main/java/ti4/service/webhook/GameWebhookEventPayload.java Record describing the JSON payload schema.
src/main/java/ti4/service/webhook/GameWebhookEventType.java Enum of supported event-type identifiers.
src/main/java/ti4/service/webhook/GameWebhookConfig.java Stored-value helpers for URL/enabled/FoW-allowed and basic URL validation.
src/main/java/ti4/discord/interactions/commands/game/GameWebhook.java New /game webhook slash subcommand for set/inspect/clear/toggle.
src/main/java/ti4/discord/interactions/commands/game/GameCommand.java Registers the new GameWebhook subcommand.
src/main/java/ti4/game/Game.java Emits active_player_changed from updateActivePlayer.
src/main/java/ti4/service/game/StartPhaseService.java Emits phase_changed for strategy/action/statusHomework/agenda transitions.
src/main/java/ti4/service/game/EndGameService.java Emits game_ended in gameEndStuff.
src/main/java/ti4/helpers/AgendaHelper.java Emits agenda_voting_started/agenda_resolved from voting flow.
src/main/java/ti4/service/turn/PassService.java Emits player_passed when a player passes.
src/test/java/ti4/service/webhook/WebhookGameEventNotifierTest.java Tests notifier gating, payload building, and exception containment.

Comment on lines +55 to +63
public static boolean isWebhookUrlValid(String webhookUrl) {
try {
URI uri = URI.create(webhookUrl);
String scheme = uri.getScheme();
return uri.getHost() != null && ("http".equalsIgnoreCase(scheme) || "https".equalsIgnoreCase(scheme));
} catch (RuntimeException e) {
return false;
}
}
Comment on lines +61 to +67
String urlDisplay = GameWebhookConfig.getWebhookUrl(game).orElse("<not set>");
String response = "Webhook config for `" + game.getName() + "`"
+ (updated ? " updated.\n" : ":\n")
+ "- URL: " + urlDisplay + "\n"
+ "- Enabled: " + GameWebhookConfig.isWebhookEnabled(game) + "\n"
+ "- Allow FoW: " + GameWebhookConfig.isFowAllowed(game);
MessageHelper.sendMessageToEventChannel(event, response);
Comment on lines +37 to +42
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(webhookUrl))
.timeout(REQUEST_TIMEOUT)
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(jsonMapper.writeValueAsString(payload)))
.build();
Comment on lines +23 to +26
public static boolean isWebhookEnabled(Game game) {
String enabled = game.getStoredValue(WEBHOOK_ENABLED_KEY);
return enabled.isEmpty() || Boolean.parseBoolean(enabled);
}
Comment on lines +24 to +27
public void dispatch(Game game, String webhookUrl, GameWebhookEventPayload payload) {
ExecutorServiceManager.runAsync(
"webhook_dispatch_" + game.getName(), () -> sendWithRetry(game, webhookUrl, payload));
}
Comment on lines +51 to +58
BotLogger.warning(
new LogOrigin(game),
"Webhook dispatch returned status "
+ response.statusCode()
+ " on attempt "
+ attempt
+ ". Response: "
+ response.body());
Comment on lines +17 to +27
public class WebhookDispatchService {
private static final Duration REQUEST_TIMEOUT = Duration.ofSeconds(5);
private static final int MAX_ATTEMPTS = 2;
private static final long RETRY_DELAY_MILLIS = 500L;

private final JsonMapper jsonMapper = JsonMapperManager.basic();

public void dispatch(Game game, String webhookUrl, GameWebhookEventPayload payload) {
ExecutorServiceManager.runAsync(
"webhook_dispatch_" + game.getName(), () -> sendWithRetry(game, webhookUrl, payload));
}
Comment on lines 1869 to +1870
game.setPhaseOfGame("agendaVoting");
GameEventNotifierFacade.notifyAgendaVotingStarted(game);
Comment on lines +54 to +60
private static GameEventNotifier notifier() {
try {
return SpringContext.getBean(GameEventNotifier.class);
} catch (RuntimeException e) {
return NO_OP_NOTIFIER;
}
}
Comment on lines +18 to +25
public GameWebhook() {
super("webhook", "Set or inspect game webhook notifications", true, false);
addOptions(new OptionData(OptionType.STRING, OPTION_URL, "Webhook URL to send game event notifications to"));
addOptions(new OptionData(OptionType.BOOLEAN, OPTION_ENABLED, "Enable or disable webhook notifications"));
addOptions(new OptionData(
OptionType.BOOLEAN, OPTION_ALLOW_FOW, "Allow notifications for FoW games (default false for privacy)"));
addOptions(new OptionData(OptionType.BOOLEAN, OPTION_CLEAR, "Clear all webhook configuration"));
}
@github-actions

github-actions Bot commented May 31, 2026

Copy link
Copy Markdown

🔬 Test Results

✅ JSON Validation Passed

All changed JSON files are valid.


✅ Formatting Checks Passed

All files pass Spotless formatting.


✅ JUnit Tests Passed

All tests passed successfully.

Copilot AI and others added 3 commits May 31, 2026 13:14
Co-authored-by: jamesrudolph <39609802+jamesrudolph@users.noreply.github.com>
Co-authored-by: jamesrudolph <39609802+jamesrudolph@users.noreply.github.com>
Co-authored-by: jamesrudolph <39609802+jamesrudolph@users.noreply.github.com>
Copilot AI changed the title Add per-game webhook event notifications for core TI4 game-state transitions Add API-keyed public webhook subscriptions per game with event-type filtering May 31, 2026
Copilot AI requested a review from jamesrudolph May 31, 2026 13:18
Copilot AI and others added 4 commits May 31, 2026 13:54
Co-authored-by: jamesrudolph <39609802+jamesrudolph@users.noreply.github.com>
Co-authored-by: jamesrudolph <39609802+jamesrudolph@users.noreply.github.com>
Co-authored-by: jamesrudolph <39609802+jamesrudolph@users.noreply.github.com>
Co-authored-by: jamesrudolph <39609802+jamesrudolph@users.noreply.github.com>
Copilot AI changed the title Add API-keyed public webhook subscriptions per game with event-type filtering Remove pre-deploy legacy webhook compatibility paths May 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants