Fix Qui API compatibility: categories, response format, mappings, and equality

Qui's API returns categories as objects (not strings) and indexer responses
as flat JSON (not nested under torznab_indexer). The API also never returns
api_key in responses, which caused GetIndexerMappings to never match.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
nitrobass24 2026-04-15 00:21:04 -05:00
parent df53c980d1
commit 9287eaede0
3 changed files with 34 additions and 22 deletions

View file

@ -45,7 +45,6 @@ public override List<AppIndexerMap> GetIndexerMappings()
if (indexer.Backend == "prowlarr" &&
(indexer.BaseUrl?.TrimEnd('/').Equals(baseUrl, StringComparison.OrdinalIgnoreCase) == true ||
indexer.BaseUrl?.StartsWith(baseUrl + "/", StringComparison.OrdinalIgnoreCase) == true) &&
indexer.ApiKey == _configFileProvider.ApiKey &&
int.TryParse(indexer.IndexerId, out var indexerId))
{
mappings.Add(new AppIndexerMap { IndexerId = indexerId, RemoteIndexerId = indexer.Id });
@ -132,7 +131,11 @@ public override void UpdateIndexer(IndexerDefinition indexer, bool forceSync = f
else
{
_quiProxy.RemoveIndexer(remoteIndexer.Id, Settings);
_appIndexerMapService.Delete(indexerMapping.Id);
if (indexerMapping != null)
{
_appIndexerMapService.Delete(indexerMapping.Id);
}
}
}
}
@ -204,7 +207,7 @@ private QuiIndexer BuildQuiIndexer(IndexerDefinition indexer, IndexerCapabilitie
LimitMax = 200,
IndexerId = indexer.Id.ToString(),
Capabilities = capabilities,
Categories = supportedCategories.Select(c => c.ToString()).ToList()
Categories = supportedCategories.Select(c => new QuiCategory { CategoryId = c, CategoryName = NewznabStandardCategory.GetCatDesc(c) }).ToList()
};
}
}

View file

@ -5,13 +5,22 @@
namespace NzbDrone.Core.Applications.Qui
{
public class QuiIndexerResponse
public class QuiCategory
{
[JsonProperty("torznab_indexer")]
public QuiIndexer TorznabIndexer { get; set; }
[JsonProperty("indexer_id")]
public int IndexerId { get; set; }
[JsonProperty("category_id")]
public int CategoryId { get; set; }
[JsonProperty("category_name")]
public string CategoryName { get; set; }
[JsonProperty("parent_category_id")]
public int? ParentCategoryId { get; set; }
}
public class QuiIndexer
public class QuiIndexer : IEquatable<QuiIndexer>
{
[JsonProperty("id")]
public int Id { get; set; }
@ -50,7 +59,7 @@ public class QuiIndexer
public List<string> Capabilities { get; set; }
[JsonProperty("categories")]
public List<string> Categories { get; set; }
public List<QuiCategory> Categories { get; set; }
public bool Equals(QuiIndexer other)
{
@ -59,8 +68,8 @@ public bool Equals(QuiIndexer other)
return false;
}
var thisCategories = (Categories ?? Enumerable.Empty<string>()).OrderBy(c => c);
var otherCategories = (other.Categories ?? Enumerable.Empty<string>()).OrderBy(c => c);
var thisCategories = (Categories ?? Enumerable.Empty<QuiCategory>()).Select(c => c.CategoryId).OrderBy(c => c);
var otherCategories = (other.Categories ?? Enumerable.Empty<QuiCategory>()).Select(c => c.CategoryId).OrderBy(c => c);
return other.BaseUrl == BaseUrl &&
other.ApiKey == ApiKey &&
@ -72,6 +81,8 @@ public bool Equals(QuiIndexer other)
otherCategories.SequenceEqual(thisCategories);
}
public override bool Equals(object obj) => Equals(obj as QuiIndexer);
public override int GetHashCode()
{
return HashCode.Combine(BaseUrl, ApiKey, Name, Backend, Enabled, Priority, IndexerId);

View file

@ -64,8 +64,7 @@ public QuiIndexer AddIndexer(QuiIndexer indexer, QuiSettings settings)
request.SetContent(indexer.ToJson());
request.ContentSummary = indexer.ToJson(Formatting.None);
var response = Execute<QuiIndexerResponse>(request);
return response.TorznabIndexer;
return Execute<QuiIndexer>(request);
}
public QuiIndexer UpdateIndexer(QuiIndexer indexer, QuiSettings settings)
@ -75,23 +74,22 @@ public QuiIndexer UpdateIndexer(QuiIndexer indexer, QuiSettings settings)
request.SetContent(indexer.ToJson());
request.ContentSummary = indexer.ToJson(Formatting.None);
var response = Execute<QuiIndexerResponse>(request);
return response.TorznabIndexer;
return Execute<QuiIndexer>(request);
}
public void RemoveIndexer(int indexerId, QuiSettings settings)
{
var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Delete);
var response = _httpClient.Execute(request);
if (response.StatusCode == HttpStatusCode.NotFound)
try
{
return;
var request = BuildRequest(settings, $"{AppIndexerApiRoute}/{indexerId}", HttpMethod.Delete);
_httpClient.Execute(request);
}
if ((int)response.StatusCode >= 300)
catch (HttpException ex)
{
throw new HttpException(response);
if (ex.Response.StatusCode != HttpStatusCode.NotFound)
{
throw;
}
}
}