merge branch 'develop' into 'main' - Release v1.7.9
This commit is contained in:
@@ -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
|
||||
|
||||
Generated
+2
-2
@@ -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",
|
||||
|
||||
+1
-1
@@ -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": {
|
||||
|
||||
@@ -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}`);
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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();
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user