fix: support PascalCase properties in Ombi webhooks (#42)

This commit is contained in:
2026-05-23 20:57:55 +01:00
parent d1db3118f0
commit 86c67bcf29
3 changed files with 43 additions and 11 deletions
+7 -4
View File
@@ -717,9 +717,12 @@ router.post('/ombi', webhookLimiter, (req, res) => {
return res.status(401).json({ error: 'Unauthorized' }); return res.status(401).json({ error: 'Unauthorized' });
} }
// Ombi uses notificationType instead of eventType // Ombi uses notificationType instead of eventType. Support PascalCase for .NET apps
const { notificationType, requestId, requestedUser, applicationUrl } = req.body; const notificationType = req.body.notificationType || req.body.NotificationType;
const eventType = notificationType || req.body.eventType; 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) // Extract username from requestedUser (handles both object and string formats)
const username = extractRequestedUser(req.body); const username = extractRequestedUser(req.body);
@@ -732,7 +735,7 @@ router.post('/ombi', webhookLimiter, (req, res) => {
// Use applicationUrl as instance identifier for replay protection // Use applicationUrl as instance identifier for replay protection
const instanceName = applicationUrl || 'ombi'; const instanceName = applicationUrl || 'ombi';
// Use requestId + eventType + current time as replay key // 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}`)) { if (isReplay(eventType, instanceName, `${requestId}-${eventDate}`)) {
logToFile(`[Webhook] Ombi duplicate event ignored: ${eventType} requestId=${requestId}`); logToFile(`[Webhook] Ombi duplicate event ignored: ${eventType} requestId=${requestId}`);
+9 -7
View File
@@ -15,17 +15,19 @@
function extractRequestedUser(request) { function extractRequestedUser(request) {
if (!request) return ''; if (!request) return '';
const requestedUser = request.requestedUser || request.RequestedUser;
// Handle object format: OmbiStore.Entities.OmbiUser // Handle object format: OmbiStore.Entities.OmbiUser
if (request.requestedUser && typeof request.requestedUser === 'object') { if (requestedUser && typeof requestedUser === 'object') {
// Priority: alias > userAlias > userName > normalizedUserName > requestedByAlias // Priority: alias > userAlias > userName > normalizedUserName > requestedByAlias
return request.requestedUser.alias || return requestedUser.alias || requestedUser.Alias ||
request.requestedUser.userAlias || requestedUser.userAlias || requestedUser.UserAlias ||
request.requestedUser.userName || requestedUser.userName || requestedUser.UserName ||
request.requestedUser.normalizedUserName || requestedUser.normalizedUserName || requestedUser.NormalizedUserName ||
request.requestedByAlias || ''; request.requestedByAlias || request.RequestedByAlias || '';
} }
// Handle string format (fallback for compatibility) // Handle string format (fallback for compatibility)
return request.requestedUser || request.requestedByAlias || ''; return requestedUser || request.requestedByAlias || request.RequestedByAlias || '';
} }
function filterRequestsByUser(requests, username, showAll) { function filterRequestsByUser(requests, username, showAll) {
+27
View File
@@ -647,5 +647,32 @@ describe('POST /api/webhook/ombi', () => {
expect(res2.status).toBe(200); expect(res2.status).toBe(200);
expect(res2.body.duplicate).toBe(true); 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();
});
}); });