fix: support PascalCase properties in Ombi webhooks (#42)
This commit is contained in:
@@ -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}`);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user