Skip to content

Commit

Permalink
Support DETAILS function from Newznab API spec
Browse files Browse the repository at this point in the history
Closes #942
  • Loading branch information
theotherp committed May 12, 2024
1 parent 753f668 commit 9893060
Show file tree
Hide file tree
Showing 8 changed files with 142 additions and 5 deletions.
34 changes: 31 additions & 3 deletions core/src/main/java/org/nzbhydra/api/ExternalApi.java
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@
import org.nzbhydra.downloading.DownloadResult;
import org.nzbhydra.downloading.FileHandler;
import org.nzbhydra.downloading.InvalidSearchResultIdException;
import org.nzbhydra.indexers.DetailsResult;
import org.nzbhydra.logging.LoggingMarkers;
import org.nzbhydra.mapping.newznab.ActionAttribute;
import org.nzbhydra.mapping.newznab.NewznabParameters;
import org.nzbhydra.mapping.newznab.NewznabResponse;
import org.nzbhydra.mapping.newznab.OutputType;
import org.nzbhydra.mapping.newznab.json.NewznabJsonError;
import org.nzbhydra.mapping.newznab.xml.NewznabXmlError;
import org.nzbhydra.mediainfo.Imdb;
import org.nzbhydra.searching.CategoryProvider;
import org.nzbhydra.searching.CustomQueryAndTitleMappingHandler;
import org.nzbhydra.searching.DetailsProvider;
import org.nzbhydra.searching.SearchResult;
import org.nzbhydra.searching.Searcher;
import org.nzbhydra.searching.dtoseventsenums.SearchResultItem;
import org.nzbhydra.searching.searchrequests.SearchRequest;
import org.nzbhydra.searching.searchrequests.SearchRequestFactory;
import org.nzbhydra.springnative.ReflectionMarker;
Expand All @@ -52,6 +56,7 @@
import java.time.temporal.ChronoUnit;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
Expand Down Expand Up @@ -99,6 +104,8 @@ public class ExternalApi {

//When enabled search results will be mocked instead of indexers actually being searched. Only for configuration of external tools
private static boolean inMockingMode;
@Autowired
private DetailsProvider detailsProvider;

/**
* External API call.
Expand Down Expand Up @@ -139,6 +146,27 @@ public ResponseEntity<? extends Object> api(NewznabParameters params, @PathVaria
if (params.getT() == ActionAttribute.CAPS) {
return capsGenerator.getCaps(params.getO(), searchType);
}
if (params.getT() == ActionAttribute.DETAILS) {
DetailsResult details = detailsProvider.getDetails(params.getId());
NewznabResponse response;

List<SearchResultItem> searchResultItems = Collections.singletonList(details.getSearchResultItem());
if (details.isSuccessful()) {
boolean isNzb = details.getSearchResultItem().getDownloadType() == DownloadType.NZB;
if (params.getO() == OutputType.JSON) {
response = newznabJsonTransformer.transformToRoot(searchResultItems, 0, 0, isNzb);
} else {
response = newznabXmlTransformer.getRssRoot(searchResultItems, 0, 0, isNzb);
}
} else {
if (params.getO() == OutputType.JSON) {
response = new NewznabJsonError("100", details.getErrorMessage());
} else {
response = new NewznabXmlError("100", details.getErrorMessage());
}
}
return new ResponseEntity<>(response, null, HttpStatus.OK);
}

if (Stream.of(ActionAttribute.SEARCH, ActionAttribute.BOOK, ActionAttribute.TVSEARCH, ActionAttribute.MOVIE).anyMatch(x -> x == params.getT())) {
if (inMockingMode) {
Expand Down Expand Up @@ -344,7 +372,7 @@ private SearchRequest buildBaseSearchRequest(NewznabParameters params, int searc


@Data
@ReflectionMarker
@ReflectionMarker
@AllArgsConstructor
private static class CacheEntryValue {
private final NewznabParameters params;
Expand All @@ -361,8 +389,8 @@ public boolean equals(Object o) {
}
CacheEntryValue that = (CacheEntryValue) o;
return com.google.common.base.Objects.equal(params, that.params) &&
com.google.common.base.Objects.equal(lastUpdate, that.lastUpdate) &&
com.google.common.base.Objects.equal(searchResult, that.searchResult);
com.google.common.base.Objects.equal(lastUpdate, that.lastUpdate) &&
com.google.common.base.Objects.equal(searchResult, that.searchResult);
}

@Override
Expand Down
29 changes: 29 additions & 0 deletions core/src/main/java/org/nzbhydra/indexers/DetailsResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package org.nzbhydra.indexers;

import lombok.AllArgsConstructor;
import lombok.Data;
import org.nzbhydra.searching.dtoseventsenums.SearchResultItem;
import org.nzbhydra.springnative.ReflectionMarker;

import java.io.Serializable;

@Data
@ReflectionMarker
@AllArgsConstructor
public class DetailsResult implements Serializable {

private boolean successful;
private SearchResultItem searchResultItem;
private String errorMessage;


public static DetailsResult unsuccessful(String error) {
return new DetailsResult(false, null, error);
}

public static DetailsResult withItem(SearchResultItem item) {
return new DetailsResult(true, item, null);
}


}
4 changes: 4 additions & 0 deletions core/src/main/java/org/nzbhydra/indexers/Indexer.java
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,10 @@ protected IndexerSearchResult searchInternal(SearchRequest searchRequest, int of

public abstract NfoResult getNfo(String guid);

public DetailsResult getDetails(String guid) throws IndexerAccessException {
return DetailsResult.unsuccessful("Unsupported");
}

//May be overwritten by specific indexer implementations
protected String cleanupQuery(String query) {
return query;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
public enum IndexerApiAccessType {
NFO,
NZB,
SEARCH
SEARCH,
DETAILS
}
20 changes: 20 additions & 0 deletions core/src/main/java/org/nzbhydra/indexers/Newznab.java
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,26 @@ public NfoResult getNfo(String guid) {
}
}

@Override
public DetailsResult getDetails(String guid) throws IndexerAccessException {
UriComponentsBuilder baseUri = getBaseUri().queryParam("t", "details").queryParam("id", guid);
Xml xml = null;
try {
xml = getAndStoreResultToDatabase(baseUri.build().toUri(), IndexerApiAccessType.DETAILS);
} catch (IndexerAccessException e) {
return DetailsResult.unsuccessful(e.getMessage());
}
if (xml instanceof NewznabXmlError) {
handleRssError((NewznabXmlError) xml, baseUri.toUriString());
}
NewznabXmlRoot rssRoot = (NewznabXmlRoot) xml;
List<SearchResultItem> searchResultItems = getSearchResultItems(rssRoot, new SearchRequest());
if (searchResultItems.size() != 1) {
return DetailsResult.unsuccessful("Didn't find exactly one result for ID");
}
return DetailsResult.withItem(searchResultItems.get(0));
}

protected void handleRssError(NewznabXmlError response, String url) throws IndexerAccessException {
if (Stream.of("100", "101", "102").anyMatch(x -> x.equals(response.getCode())) && !(response.getDescription() != null && response.getDescription().contains("Hits Limit Reached"))) {
throw new IndexerAuthException(String.format("Indexer refused authentication. Error code: %s. Description: %s", response.getCode(), response.getDescription()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public Category fromSearchNewznabCategories(List<Integer> cats, Category default
* @return The matching configured category or "All" if none is found
*/
public Category fromResultNewznabCategories(List<Integer> cats) {
if (cats == null || cats.size() == 0) {
if (cats == null || cats.isEmpty()) {
logger.debug(LoggingMarkers.CATEGORY_MAPPING, "Empty newznab categories -> N/A");
return naCategory;
}
Expand Down
51 changes: 51 additions & 0 deletions core/src/main/java/org/nzbhydra/searching/DetailsProvider.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/*
* (C) Copyright 2024 TheOtherP ([email protected])
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.nzbhydra.searching;

import org.nzbhydra.indexers.DetailsResult;
import org.nzbhydra.indexers.Indexer;
import org.nzbhydra.indexers.exceptions.IndexerAccessException;
import org.nzbhydra.searching.db.SearchResultEntity;
import org.nzbhydra.searching.db.SearchResultRepository;
import org.springframework.stereotype.Component;

import java.util.Optional;

@Component
public class DetailsProvider {

private final SearchResultRepository searchResultRepository;
private final SearchModuleProvider searchModuleProvider;

public DetailsProvider(SearchResultRepository searchResultRepository, SearchModuleProvider searchModuleProvider) {
this.searchResultRepository = searchResultRepository;
this.searchModuleProvider = searchModuleProvider;
}

public DetailsResult getDetails(String resultId) {
Optional<SearchResultEntity> searchResult = searchResultRepository.findById(Long.parseLong(resultId));
if (searchResult.isEmpty()) {
return null;
}
Indexer indexer = searchModuleProvider.getIndexerByName(searchResult.get().getIndexer().getName());
try {
return indexer.getDetails(searchResult.get().getIndexerGuid());
} catch (IndexerAccessException e) {
return DetailsResult.unsuccessful(e.getMessage());
}
}
}
4 changes: 4 additions & 0 deletions core/src/main/resources/changelog.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
#@formatter:off
- version: "v6.2.0"
changes:
- type: "feature"
text: "Support DETAILS function from Newznab API spec. See #942"
- version: "v6.1.1"
changes:
- type: "fix"
Expand Down

0 comments on commit 9893060

Please sign in to comment.