2014-04-19 02:30:29 -04:00
|
|
|
package net.filebot.web;
|
2007-12-23 14:28:04 -05:00
|
|
|
|
2014-12-10 13:53:58 -05:00
|
|
|
import static java.util.Collections.*;
|
2016-01-09 23:54:35 -05:00
|
|
|
import static net.filebot.util.StringUtilities.*;
|
2014-04-19 02:30:29 -04:00
|
|
|
import static net.filebot.util.XPathUtilities.*;
|
|
|
|
import static net.filebot.web.EpisodeUtilities.*;
|
|
|
|
import static net.filebot.web.WebRequest.*;
|
2009-01-04 13:28:28 -05:00
|
|
|
|
2008-06-21 15:24:18 -04:00
|
|
|
import java.net.URI;
|
2009-10-28 11:09:47 -04:00
|
|
|
import java.net.URISyntaxException;
|
2007-12-23 14:28:04 -05:00
|
|
|
import java.net.URL;
|
2014-12-20 13:46:54 -05:00
|
|
|
import java.util.AbstractMap.SimpleImmutableEntry;
|
2007-12-23 14:28:04 -05:00
|
|
|
import java.util.ArrayList;
|
2013-12-27 17:49:56 -05:00
|
|
|
import java.util.Collections;
|
|
|
|
import java.util.Comparator;
|
2009-10-28 11:09:47 -04:00
|
|
|
import java.util.HashMap;
|
2007-12-23 14:28:04 -05:00
|
|
|
import java.util.List;
|
2010-11-11 09:23:59 -05:00
|
|
|
import java.util.Locale;
|
2009-10-28 11:09:47 -04:00
|
|
|
import java.util.Map;
|
2012-01-03 04:18:32 -05:00
|
|
|
import java.util.Map.Entry;
|
2009-10-28 11:09:47 -04:00
|
|
|
import java.util.Scanner;
|
2011-11-04 01:23:23 -04:00
|
|
|
import java.util.Set;
|
2013-03-29 23:44:24 -04:00
|
|
|
import java.util.concurrent.TimeUnit;
|
2013-12-27 13:16:08 -05:00
|
|
|
import java.util.logging.Level;
|
|
|
|
import java.util.logging.Logger;
|
2009-07-13 08:40:27 -04:00
|
|
|
import java.util.regex.Matcher;
|
|
|
|
import java.util.regex.Pattern;
|
2014-12-20 13:46:54 -05:00
|
|
|
import java.util.stream.Collectors;
|
2009-10-28 11:09:47 -04:00
|
|
|
import java.util.zip.GZIPInputStream;
|
2007-12-23 14:28:04 -05:00
|
|
|
|
2008-07-13 13:59:05 -04:00
|
|
|
import javax.swing.Icon;
|
|
|
|
|
2016-03-07 01:38:23 -05:00
|
|
|
import net.filebot.Cache;
|
2016-03-06 13:11:30 -05:00
|
|
|
import net.filebot.CacheType;
|
2014-04-19 02:30:29 -04:00
|
|
|
import net.filebot.ResourceManager;
|
2012-07-25 00:34:20 -04:00
|
|
|
|
2013-12-27 17:49:56 -05:00
|
|
|
import org.jsoup.Jsoup;
|
2007-12-23 14:28:04 -05:00
|
|
|
import org.w3c.dom.Document;
|
|
|
|
import org.w3c.dom.Node;
|
|
|
|
|
2011-08-08 13:37:45 -04:00
|
|
|
public class AnidbClient extends AbstractEpisodeListProvider {
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2014-11-09 10:30:00 -05:00
|
|
|
private static final FloodLimit REQUEST_LIMIT = new FloodLimit(2, 5, TimeUnit.SECONDS); // no more than 2 requests within a 5 second window
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2011-11-13 13:22:50 -05:00
|
|
|
private final String host = "anidb.net";
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2011-01-06 22:51:08 -05:00
|
|
|
private final String client;
|
|
|
|
private final int clientver;
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2011-01-06 22:51:08 -05:00
|
|
|
public AnidbClient(String client, int clientver) {
|
|
|
|
this.client = client;
|
|
|
|
this.clientver = clientver;
|
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2008-07-13 13:59:05 -04:00
|
|
|
@Override
|
|
|
|
public String getName() {
|
|
|
|
return "AniDB";
|
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2008-07-13 13:59:05 -04:00
|
|
|
@Override
|
|
|
|
public Icon getIcon() {
|
|
|
|
return ResourceManager.getIcon("search.anidb");
|
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2007-12-23 14:28:04 -05:00
|
|
|
@Override
|
2014-12-10 13:53:58 -05:00
|
|
|
public boolean hasSeasonSupport() {
|
2011-08-08 13:37:45 -04:00
|
|
|
return false;
|
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2011-08-08 13:37:45 -04:00
|
|
|
@Override
|
2014-12-10 13:53:58 -05:00
|
|
|
protected SortOrder vetoRequestParameter(SortOrder order) {
|
|
|
|
return SortOrder.Absolute;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected Locale vetoRequestParameter(Locale language) {
|
|
|
|
return language != null ? language : Locale.ENGLISH;
|
2011-08-08 13:37:45 -04:00
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2011-11-26 04:44:49 -05:00
|
|
|
@Override
|
|
|
|
public ResultCache getCache() {
|
2016-03-07 01:38:23 -05:00
|
|
|
return new ResultCache(getName(), Cache.getCache(getName(), CacheType.Weekly));
|
2011-11-26 04:44:49 -05:00
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2011-08-08 13:37:45 -04:00
|
|
|
@Override
|
2014-08-06 10:07:17 -04:00
|
|
|
public List<SearchResult> search(String query, Locale locale) throws Exception {
|
2011-11-26 04:44:49 -05:00
|
|
|
// bypass automatic caching since search is based on locally cached data anyway
|
|
|
|
return fetchSearchResult(query, locale);
|
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2011-11-26 04:44:49 -05:00
|
|
|
@Override
|
2014-08-06 10:07:17 -04:00
|
|
|
public List<SearchResult> fetchSearchResult(String query, Locale locale) throws Exception {
|
2013-09-07 11:48:24 -04:00
|
|
|
LocalSearch<SearchResult> index = new LocalSearch<SearchResult>(getAnimeTitles()) {
|
|
|
|
|
2009-10-28 11:09:47 -04:00
|
|
|
@Override
|
2013-09-07 11:48:24 -04:00
|
|
|
protected Set<String> getFields(SearchResult it) {
|
2013-10-13 10:50:45 -04:00
|
|
|
return set(it.getEffectiveNames());
|
2009-10-28 11:09:47 -04:00
|
|
|
}
|
|
|
|
};
|
2011-11-04 01:23:23 -04:00
|
|
|
return new ArrayList<SearchResult>(index.search(query));
|
2009-05-25 16:13:30 -04:00
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2009-07-13 08:40:27 -04:00
|
|
|
@Override
|
2014-12-10 13:53:58 -05:00
|
|
|
protected SeriesData fetchSeriesData(SearchResult searchResult, SortOrder sortOrder, Locale locale) throws Exception {
|
2011-08-08 13:37:45 -04:00
|
|
|
AnidbSearchResult anime = (AnidbSearchResult) searchResult;
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2011-01-06 22:51:08 -05:00
|
|
|
// e.g. http://api.anidb.net:9001/httpapi?request=anime&client=filebot&clientver=1&protover=1&aid=4521
|
|
|
|
URL url = new URL("http", "api." + host, 9001, "/httpapi?request=anime&client=" + client + "&clientver=" + clientver + "&protover=1&aid=" + anime.getAnimeId());
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2013-03-29 23:44:24 -04:00
|
|
|
// respect flood protection limits
|
|
|
|
REQUEST_LIMIT.acquirePermit();
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2009-07-13 08:40:27 -04:00
|
|
|
// get anime page as xml
|
2011-01-06 22:51:08 -05:00
|
|
|
Document dom = getDocument(url);
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2014-12-10 13:53:58 -05:00
|
|
|
// parse series info
|
|
|
|
SeriesInfo seriesInfo = new SeriesInfo(getName(), sortOrder, locale, anime.getId());
|
|
|
|
seriesInfo.setAliasNames(searchResult.getEffectiveNames());
|
|
|
|
|
2014-06-13 12:42:14 -04:00
|
|
|
// AniDB types: Movie, Music Video, Other, OVA, TV Series, TV Special, Web, unknown
|
|
|
|
String animeType = selectString("//type", dom);
|
2016-01-23 11:20:03 -05:00
|
|
|
if (animeType != null && animeType.matches("(?i:music.video|unkown)")) {
|
2014-12-10 13:53:58 -05:00
|
|
|
return new SeriesData(seriesInfo, emptyList());
|
2014-06-13 12:42:14 -04:00
|
|
|
}
|
|
|
|
|
2014-12-10 13:53:58 -05:00
|
|
|
seriesInfo.setName(selectString("anime/titles/title[@type='main']", dom));
|
2014-12-23 01:38:29 -05:00
|
|
|
seriesInfo.setRating(getDecimal(selectString("anime/ratings/permanent", dom)));
|
2016-01-09 23:54:35 -05:00
|
|
|
seriesInfo.setRatingCount(matchInteger(getTextContent("anime/ratings/permanent/@count", dom)));
|
2016-02-10 06:47:13 -05:00
|
|
|
seriesInfo.setStartDate(SimpleDate.parse(selectString("anime/startdate", dom)));
|
2014-12-10 13:53:58 -05:00
|
|
|
|
2014-12-20 13:46:54 -05:00
|
|
|
// add categories ordered by weight as genres
|
|
|
|
// * only use categories with weight >= 400
|
|
|
|
// * sort by weight (descending)
|
|
|
|
// * limit to 5 genres
|
2016-03-09 00:58:36 -05:00
|
|
|
seriesInfo.setGenres(streamNodes("anime/categories/category", dom).map(categoryNode -> {
|
2014-12-20 13:46:54 -05:00
|
|
|
String name = getTextContent("name", categoryNode);
|
2016-01-09 23:54:35 -05:00
|
|
|
Integer weight = matchInteger(getAttribute("weight", categoryNode));
|
2014-12-20 13:46:54 -05:00
|
|
|
return new SimpleImmutableEntry<String, Integer>(name, weight);
|
|
|
|
}).filter(nw -> {
|
|
|
|
return nw.getKey() != null && nw.getValue() != null && nw.getKey().length() > 0 && nw.getValue() >= 400;
|
|
|
|
}).sorted((a, b) -> {
|
|
|
|
return b.getValue().compareTo(a.getValue());
|
|
|
|
}).map(it -> it.getKey()).limit(5).collect(Collectors.toList()));
|
|
|
|
|
2014-12-10 13:53:58 -05:00
|
|
|
// parse episode data
|
|
|
|
String animeTitle = selectString("anime/titles/title[@type='official' and @lang='" + locale.getLanguage() + "']", dom);
|
|
|
|
if (animeTitle == null || animeTitle.length() == 0) {
|
|
|
|
animeTitle = seriesInfo.getName();
|
2011-08-08 13:37:45 -04:00
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2011-11-26 04:44:49 -05:00
|
|
|
List<Episode> episodes = new ArrayList<Episode>(25);
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2014-12-10 13:53:58 -05:00
|
|
|
for (Node node : selectNodes("anime/episodes/episode", dom)) {
|
2013-05-06 04:21:20 -04:00
|
|
|
Node epno = getChild("epno", node);
|
|
|
|
int number = Integer.parseInt(getTextContent(epno).replaceAll("\\D", ""));
|
|
|
|
int type = Integer.parseInt(getAttribute("type", epno));
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2013-05-06 04:21:20 -04:00
|
|
|
if (type == 1 || type == 2) {
|
2016-02-10 06:47:13 -05:00
|
|
|
SimpleDate airdate = SimpleDate.parse(getTextContent("airdate", node));
|
2014-07-24 07:31:03 -04:00
|
|
|
String title = selectString(".//title[@lang='" + locale.getLanguage() + "']", node);
|
2011-08-08 13:37:45 -04:00
|
|
|
if (title.isEmpty()) { // English language fall-back
|
|
|
|
title = selectString(".//title[@lang='en']", node);
|
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2013-05-06 04:21:20 -04:00
|
|
|
if (type == 1) {
|
2014-12-10 13:53:58 -05:00
|
|
|
episodes.add(new Episode(animeTitle, null, number, title, number, null, airdate, new SeriesInfo(seriesInfo))); // normal episode, no seasons for anime
|
2013-05-06 04:21:20 -04:00
|
|
|
} else {
|
2014-12-10 13:53:58 -05:00
|
|
|
episodes.add(new Episode(animeTitle, null, null, title, null, number, airdate, new SeriesInfo(seriesInfo))); // special episode
|
2013-05-06 04:21:20 -04:00
|
|
|
}
|
2009-07-13 08:40:27 -04:00
|
|
|
}
|
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2011-01-06 22:51:08 -05:00
|
|
|
// make sure episodes are in ordered correctly
|
2015-01-18 03:57:37 -05:00
|
|
|
sort(episodes, episodeComparator());
|
2013-09-07 11:48:24 -04:00
|
|
|
|
|
|
|
// sanity check
|
2011-11-26 04:44:49 -05:00
|
|
|
if (episodes.isEmpty()) {
|
2009-07-13 08:40:27 -04:00
|
|
|
// anime page xml doesn't work sometimes
|
2014-12-23 01:38:29 -05:00
|
|
|
Logger.getLogger(AnidbClient.class.getName()).log(Level.WARNING, String.format("Unable to parse episode data: %s (%d): %s", anime, anime.getAnimeId(), getXmlString(dom, false).split("\n", 2)[0].trim()));
|
2009-07-13 08:40:27 -04:00
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2014-12-10 13:53:58 -05:00
|
|
|
return new SeriesData(seriesInfo, episodes);
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
|
|
protected SearchResult createSearchResult(int id) {
|
|
|
|
return new AnidbSearchResult(id, null, new String[0]);
|
2007-12-23 14:28:04 -05:00
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2008-07-05 23:17:23 -04:00
|
|
|
@Override
|
|
|
|
public URI getEpisodeListLink(SearchResult searchResult) {
|
2009-10-28 11:09:47 -04:00
|
|
|
try {
|
2012-02-13 04:54:57 -05:00
|
|
|
return new URI("http", host, "/a" + ((AnidbSearchResult) searchResult).getAnimeId(), null);
|
2009-10-28 11:09:47 -04:00
|
|
|
} catch (URISyntaxException e) {
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
}
|
2008-07-05 23:17:23 -04:00
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2014-01-08 04:34:59 -05:00
|
|
|
/**
|
2014-01-09 13:26:25 -05:00
|
|
|
* This method is (and must be!) overridden by WebServices.AnidbClientWithLocalSearch to use our own anime index from sourceforge (as to not abuse anidb servers)
|
2014-01-08 04:34:59 -05:00
|
|
|
*/
|
2012-02-11 09:03:54 -05:00
|
|
|
public synchronized List<AnidbSearchResult> getAnimeTitles() throws Exception {
|
2013-03-17 10:19:11 -04:00
|
|
|
URL url = new URL("http", host, "/api/anime-titles.dat.gz");
|
2011-11-26 04:44:49 -05:00
|
|
|
ResultCache cache = getCache();
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2012-01-03 04:18:32 -05:00
|
|
|
@SuppressWarnings("unchecked")
|
2011-11-26 04:44:49 -05:00
|
|
|
List<AnidbSearchResult> anime = (List) cache.getSearchResult(null, Locale.ROOT);
|
|
|
|
if (anime != null) {
|
2010-11-11 09:23:59 -05:00
|
|
|
return anime;
|
2011-11-26 04:44:49 -05:00
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2009-10-28 11:09:47 -04:00
|
|
|
// <aid>|<type>|<language>|<title>
|
|
|
|
// type: 1=primary title (one per anime), 2=synonyms (multiple per anime), 3=shorttitles (multiple per anime), 4=official title (one per language)
|
|
|
|
Pattern pattern = Pattern.compile("^(?!#)(\\d+)[|](\\d)[|]([\\w-]+)[|](.+)$");
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2013-12-27 17:49:56 -05:00
|
|
|
List<String> languageOrder = new ArrayList<String>();
|
|
|
|
languageOrder.add("x-jat");
|
|
|
|
languageOrder.add("en");
|
|
|
|
languageOrder.add("ja");
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2014-01-09 13:26:25 -05:00
|
|
|
List<String> typeOrder = new ArrayList<String>();
|
|
|
|
typeOrder.add("1");
|
|
|
|
typeOrder.add("4");
|
|
|
|
typeOrder.add("2");
|
2014-01-09 15:44:02 -05:00
|
|
|
typeOrder.add("3");
|
2014-01-09 13:26:25 -05:00
|
|
|
|
2009-10-28 11:09:47 -04:00
|
|
|
// fetch data
|
2013-12-27 17:49:56 -05:00
|
|
|
Map<Integer, List<Object[]>> entriesByAnime = new HashMap<Integer, List<Object[]>>(65536);
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2013-12-27 17:49:56 -05:00
|
|
|
Scanner scanner = new Scanner(new GZIPInputStream(url.openStream()), "UTF-8");
|
2009-10-28 11:09:47 -04:00
|
|
|
try {
|
|
|
|
while (scanner.hasNextLine()) {
|
|
|
|
Matcher matcher = pattern.matcher(scanner.nextLine());
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2009-10-28 11:09:47 -04:00
|
|
|
if (matcher.matches()) {
|
2011-11-04 01:23:23 -04:00
|
|
|
int aid = Integer.parseInt(matcher.group(1));
|
|
|
|
String type = matcher.group(2);
|
|
|
|
String language = matcher.group(3);
|
|
|
|
String title = matcher.group(4);
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2014-01-09 13:26:25 -05:00
|
|
|
if (aid > 0 && title.length() > 0 && typeOrder.contains(type) && languageOrder.contains(language)) {
|
2014-01-09 15:44:02 -05:00
|
|
|
// resolve HTML entities
|
|
|
|
title = Jsoup.parse(title).text();
|
|
|
|
|
|
|
|
if (type.equals("3") && (title.length() < 5 || !Character.isUpperCase(title.charAt(0)) || Character.isUpperCase(title.charAt(title.length() - 1)))) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-12-27 17:49:56 -05:00
|
|
|
List<Object[]> names = entriesByAnime.get(aid);
|
|
|
|
if (names == null) {
|
|
|
|
names = new ArrayList<Object[]>();
|
|
|
|
entriesByAnime.put(aid, names);
|
2011-11-04 01:23:23 -04:00
|
|
|
}
|
2014-01-09 13:26:25 -05:00
|
|
|
names.add(new Object[] { typeOrder.indexOf(type), languageOrder.indexOf(language), title });
|
2009-10-28 11:09:47 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
scanner.close();
|
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2011-11-04 01:23:23 -04:00
|
|
|
// build up a list of all possible AniDB search results
|
2013-12-27 17:49:56 -05:00
|
|
|
anime = new ArrayList<AnidbSearchResult>(entriesByAnime.size());
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2013-12-27 17:49:56 -05:00
|
|
|
for (Entry<Integer, List<Object[]>> entry : entriesByAnime.entrySet()) {
|
|
|
|
int aid = entry.getKey();
|
|
|
|
List<Object[]> triples = entry.getValue();
|
|
|
|
|
|
|
|
Collections.sort(triples, new Comparator<Object[]>() {
|
|
|
|
|
|
|
|
@SuppressWarnings({ "unchecked", "rawtypes" })
|
|
|
|
@Override
|
|
|
|
public int compare(Object[] a, Object[] b) {
|
|
|
|
for (int i = 0; i < a.length; i++) {
|
|
|
|
if (!a[i].equals(b[i]))
|
|
|
|
return ((Comparable) a[i]).compareTo(b[i]);
|
|
|
|
}
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
List<String> names = new ArrayList<String>(triples.size());
|
|
|
|
for (Object[] it : triples) {
|
|
|
|
names.add((String) it[2]);
|
2012-03-19 13:16:27 -04:00
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2013-12-27 17:49:56 -05:00
|
|
|
String primaryTitle = names.get(0);
|
|
|
|
String[] aliasNames = names.subList(1, names.size()).toArray(new String[0]);
|
|
|
|
anime.add(new AnidbSearchResult(aid, primaryTitle, aliasNames));
|
2009-10-28 11:09:47 -04:00
|
|
|
}
|
2013-09-07 11:48:24 -04:00
|
|
|
|
2009-10-28 11:09:47 -04:00
|
|
|
// populate cache
|
2011-11-26 04:44:49 -05:00
|
|
|
return cache.putSearchResult(null, Locale.ROOT, anime);
|
2009-10-28 11:09:47 -04:00
|
|
|
}
|
2007-12-23 14:28:04 -05:00
|
|
|
}
|