From 86c67bcf290673bf4237e7c047cc1afa81f60168 Mon Sep 17 00:00:00 2001 From: Gronod Date: Sat, 23 May 2026 20:57:55 +0100 Subject: [PATCH 1/2] fix: support PascalCase properties in Ombi webhooks (#42) --- server/routes/webhook.js | 11 +++++++---- server/utils/ombiHelpers.js | 16 +++++++++------- tests/integration/webhook.test.js | 27 +++++++++++++++++++++++++++ 3 files changed, 43 insertions(+), 11 deletions(-) diff --git a/server/routes/webhook.js b/server/routes/webhook.js index 68d5d23..bccc8f5 100644 --- a/server/routes/webhook.js +++ b/server/routes/webhook.js @@ -717,9 +717,12 @@ router.post('/ombi', webhookLimiter, (req, res) => { return res.status(401).json({ error: 'Unauthorized' }); } - // Ombi uses notificationType instead of eventType - const { notificationType, requestId, requestedUser, applicationUrl } = req.body; - const eventType = notificationType || req.body.eventType; + // Ombi uses notificationType instead of eventType. Support PascalCase for .NET apps + const notificationType = req.body.notificationType || req.body.NotificationType; + const requestId = req.body.requestId || req.body.RequestId; + const applicationUrl = req.body.applicationUrl || req.body.ApplicationUrl; + + const eventType = notificationType || req.body.eventType || req.body.EventType; // Extract username from requestedUser (handles both object and string formats) const username = extractRequestedUser(req.body); @@ -732,7 +735,7 @@ router.post('/ombi', webhookLimiter, (req, res) => { // Use applicationUrl as instance identifier for replay protection const instanceName = applicationUrl || 'ombi'; // Use requestId + eventType + current time as replay key - const eventDate = req.body.requestedDate || new Date().toISOString(); + const eventDate = req.body.requestedDate || req.body.RequestedDate || new Date().toISOString(); if (isReplay(eventType, instanceName, `${requestId}-${eventDate}`)) { logToFile(`[Webhook] Ombi duplicate event ignored: ${eventType} requestId=${requestId}`); diff --git a/server/utils/ombiHelpers.js b/server/utils/ombiHelpers.js index 864939a..42cff9c 100644 --- a/server/utils/ombiHelpers.js +++ b/server/utils/ombiHelpers.js @@ -15,17 +15,19 @@ function extractRequestedUser(request) { if (!request) return ''; + const requestedUser = request.requestedUser || request.RequestedUser; + // Handle object format: OmbiStore.Entities.OmbiUser - if (request.requestedUser && typeof request.requestedUser === 'object') { + if (requestedUser && typeof requestedUser === 'object') { // Priority: alias > userAlias > userName > normalizedUserName > requestedByAlias - return request.requestedUser.alias || - request.requestedUser.userAlias || - request.requestedUser.userName || - request.requestedUser.normalizedUserName || - request.requestedByAlias || ''; + return requestedUser.alias || requestedUser.Alias || + requestedUser.userAlias || requestedUser.UserAlias || + requestedUser.userName || requestedUser.UserName || + requestedUser.normalizedUserName || requestedUser.NormalizedUserName || + request.requestedByAlias || request.RequestedByAlias || ''; } // Handle string format (fallback for compatibility) - return request.requestedUser || request.requestedByAlias || ''; + return requestedUser || request.requestedByAlias || request.RequestedByAlias || ''; } function filterRequestsByUser(requests, username, showAll) { diff --git a/tests/integration/webhook.test.js b/tests/integration/webhook.test.js index 504b494..74bc154 100644 --- a/tests/integration/webhook.test.js +++ b/tests/integration/webhook.test.js @@ -647,5 +647,32 @@ describe('POST /api/webhook/ombi', () => { expect(res2.status).toBe(200); expect(res2.body.duplicate).toBe(true); }); + + it('returns 200 { received: true } for a valid NewRequest event with PascalCase payload', async () => { + const app = makeApp(); + + nock('https://ombi.test') + .get('/api/v1/Request/movie') + .reply(200, []); + nock('https://ombi.test') + .get('/api/v1/Request/tv') + .reply(200, []); + + const payload = { + NotificationType: 'NewRequest', + RequestId: 126, + RequestedUser: { UserName: 'gordon_pascal' }, + Title: 'Pascal Movie', + Type: 'Movie', + RequestStatus: 'Pending', + ApplicationUrl: 'https://ombi.test', + RequestedDate: '2026-05-23T20:33:00.000Z' + }; + + const res = await postOmbi(app, payload); + expect(res.status).toBe(200); + expect(res.body.received).toBe(true); + expect(res.body.duplicate).toBeUndefined(); + }); }); From 64c872423f682179b329214c6904048d24c8bc42 Mon Sep 17 00:00:00 2001 From: Gronod Date: Sat, 23 May 2026 20:58:09 +0100 Subject: [PATCH 2/2] chore: bump version to 1.7.9 and update CHANGELOG --- CHANGELOG.md | 8 ++++++++ package-lock.json | 4 ++-- package.json | 2 +- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index d7fc15f..c3e7b9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,14 @@ All notable changes to this project will be documented in this file. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [1.7.9] - 2026-05-23 + +### Fixed + +- **Ombi webhook PascalCase payload parsing (Re-release)** — Correctly packaged and released the Ombi webhook parsing logic to handle PascalCase property names (e.g., `NotificationType`, `RequestId`) sent by C#/.NET applications. This ensures standard Ombi webhook integrations function correctly. Resolves Gitea Issue [#42](https://git.i3omb.com/Gandalf/sofarr/issues/42). + +--- + ## [1.7.8] - 2026-05-23 ### Fixed diff --git a/package-lock.json b/package-lock.json index 3c49ef9..d20f148 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "sofarr", - "version": "1.7.8", + "version": "1.7.9", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "sofarr", - "version": "1.7.8", + "version": "1.7.9", "license": "MIT", "dependencies": { "axios": "^1.6.0", diff --git a/package.json b/package.json index 72e04da..adb8225 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sofarr", - "version": "1.7.8", + "version": "1.7.9", "description": "A personal media download dashboard that shows your downloads 'so far' while you relax on the sofa waiting for your *arr services to finish", "main": "server/index.js", "scripts": {