Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ public ComicVineIssue execute() throws MetadataException {
try {
result = request.block();
} catch (Exception error) {
throw new MetadataException("failed to get issue details", error);
throw new MetadataException("failed to get story details", error);
}

if (result == null) throw new MetadataException("No response received");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
/*
* ComiXed - A digital comic book library management application.
* Copyright (C) 2025, The ComiXed Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses>
*/

package org.comixedproject.metadata.comicvine.actions;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import org.comixedproject.metadata.MetadataException;
import org.comixedproject.metadata.comicvine.model.ComicVineGetStoryListResponse;
import org.comixedproject.metadata.model.StoryMetadata;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

/**
* <code>ComicVineGetStoriesAction</code> defines an action that loads a list of candidates when
* scraping a story arc.
*
* @author Darryl L. Pierce
*/
@Log4j2
public class ComicVineGetStoriesAction
extends AbstractComicVineScrapingAction<List<StoryMetadata>> {
@Getter @Setter private String storyName;
@Getter @Setter private Integer maxRecords;
private int page;

@Override
public List<StoryMetadata> execute() throws MetadataException {
this.doCheckSetup();

this.addFilter(NAME_FILTER, this.storyName);

this.addField("id");
this.addField("name");
this.addField("publisher");
this.addField("image");

this.addParameter(RESOURCES_PARAMETER, "volume");
this.addParameter(QUERY_PARAMETER, this.storyName);
if (maxRecords > 0) this.addParameter(RESULT_LIMIT_PARAMETER, String.valueOf(this.maxRecords));

List<StoryMetadata> result = new ArrayList<>();
boolean done = false;

while (!done) {
this.doIncrementPage();

log.debug(
"Creating url for: API key=****{} story name={}", this.getMaskedApiKey(), this.storyName);
final String url = this.createUrl(this.baseUrl, "story_arcs");
final WebClient client = this.createWebClient(url);
final Mono<ComicVineGetStoryListResponse> request =
client.get().uri(url).retrieve().bodyToMono(ComicVineGetStoryListResponse.class);
ComicVineGetStoryListResponse response = null;

try {
response = request.block();
} catch (Exception error) {
throw new MetadataException("Failed to get response", error);
}

if (response == null) {
throw new MetadataException("Failed to receive a response");
}
log.debug("Received: {} stories", response.getResults().size());

Integer totalRecords = maxRecords;
if (totalRecords == 0 || totalRecords >= response.getResults().size())
totalRecords = response.getResults().size();

response
.getResults()
.subList(0, totalRecords)
.forEach(
storyListEntry -> {
log.trace(
"Processing story record: {} name={}",
storyListEntry.getReferenceId(),
storyListEntry.getName());
final StoryMetadata entry = new StoryMetadata();
entry.setReferenceId(storyListEntry.getReferenceId());
if (Objects.nonNull(storyListEntry.getPublisher())) {
entry.setPublisher(storyListEntry.getPublisher().getName());
}
entry.setName(storyListEntry.getName());
entry.setImageUrl(storyListEntry.getImages().get("original_url"));
result.add(entry);
});

done =
(hitMaxRecordLimit(result))
|| (response.getOffset() + response.getNumberOfPageResults())
>= response.getNumberOfTotalResults();

if (!done) {
log.trace("Sleeping for {}s", this.getDelay());
try {
Thread.sleep(this.getDelay() * 1000L);
} catch (InterruptedException error) {
log.error("ComicVine get volumes action interrupted", error);
throw error;
}
}
}

return result;
}

private void doIncrementPage() {
log.trace("Incremented page value: {}", this.page);
this.page++;
if (this.page > 1) {
log.trace("Setting page: {}", this.page);
this.addParameter(PAGE_PARAMETER, String.valueOf(this.page));
}
}

private void doCheckSetup() throws MetadataException {
if (!StringUtils.hasLength(this.getApiKey())) throw new MetadataException("Missing API key");
if (!StringUtils.hasLength(this.storyName)) throw new MetadataException("Missing story name");
if (maxRecords == null) throw new MetadataException("Missing maximum records");
}

private boolean hitMaxRecordLimit(final List<StoryMetadata> records) {
return this.maxRecords > 0 && records.size() == this.maxRecords;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*
* ComiXed - A digital comic book library management application.
* Copyright (C) 2025, The ComiXed Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses>
*/

package org.comixedproject.metadata.comicvine.actions;

import java.util.Objects;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.log4j.Log4j2;
import org.comixedproject.metadata.MetadataException;
import org.comixedproject.metadata.comicvine.model.ComicVineGetStoryDetailResponse;
import org.comixedproject.metadata.comicvine.model.ComicVineIssue;
import org.comixedproject.metadata.model.IssueDetailsMetadata;
import org.comixedproject.metadata.model.StoryDetailMetadata;
import org.comixedproject.metadata.model.StoryIssueMetadata;
import org.springframework.util.StringUtils;
import org.springframework.web.reactive.function.client.WebClient;
import reactor.core.publisher.Mono;

/**
* <code>ComicVineGetStoryDetailAction</code> fetches the metadata for a single story arc from
* ComicVine.
*
* @author Darryl L. Pierce
*/
@NoArgsConstructor
@Log4j2
public class ComicVineGetStoryDetailAction
extends AbstractComicVineScrapingAction<StoryDetailMetadata> {
@Getter @Setter private String referenceId;

ComicVineGetIssueDetailsAction getIssueDetailsAction = new ComicVineGetIssueDetailsAction();

@Override
public StoryDetailMetadata execute() throws MetadataException {
if (!StringUtils.hasLength(this.getApiKey())) throw new MetadataException("Missing API key");
if (this.referenceId == null) throw new MetadataException("Missing reference id");

this.addField("id");
this.addField("publisher");
this.addField("name");
this.addField("description");
this.addField("issues");

final StoryDetailMetadata result = new StoryDetailMetadata();
log.debug(
"Creating url for: API key=****{} reference id={}",
this.getMaskedApiKey(),
this.referenceId);
final String url =
this.createUrl(this.baseUrl, String.format("story_arc/4045-%s", this.referenceId));
final WebClient client = this.createWebClient(url);

final Mono<ComicVineGetStoryDetailResponse> request =
client.get().uri(url).retrieve().bodyToMono(ComicVineGetStoryDetailResponse.class);

ComicVineGetStoryDetailResponse response = null;

try {
response = request.block();
} catch (Exception error) {
throw new MetadataException("Failed to get response", error);
}

if (response == null) throw new MetadataException("No response received");

log.debug("Received response with {} issue(s)", response.getResults().getIssues().size());

result.setReferenceId(this.referenceId);
if (Objects.nonNull(response.getResults().getPublisher())) {
result.setPublisher(response.getResults().getPublisher().getName().trim());
}
result.setName(response.getResults().getName());
result.setDescription(response.getResults().getDescription());
this.getIssueDetailsAction.setBaseUrl(this.baseUrl);
this.getIssueDetailsAction.setApiKey(this.getApiKey());

for (int index = 0; index < response.getResults().getIssues().size(); index++) {
final ComicVineIssue entry = response.getResults().getIssues().get(index);
this.getIssueDetailsAction.setIssueId(entry.getId());

final IssueDetailsMetadata issue = this.getIssueDetailsAction.execute();
final StoryIssueMetadata issueMetadata = new StoryIssueMetadata();
issueMetadata.setReadingOrder(index + 1);
issueMetadata.setName(issue.getSeries());
issueMetadata.setVolume(issue.getVolume());
issueMetadata.setIssueNumber(issue.getIssueNumber());
issueMetadata.setCoverDate(issue.getCoverDate());
result.getIssues().add(issueMetadata);
}

return result;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,8 @@
import org.comixedproject.metadata.MetadataException;
import org.comixedproject.metadata.adaptors.AbstractMetadataAdaptor;
import org.comixedproject.metadata.adaptors.MetadataAdaptor;
import org.comixedproject.metadata.comicvine.actions.ComicVineGetAllIssuesAction;
import org.comixedproject.metadata.comicvine.actions.ComicVineGetIssueAction;
import org.comixedproject.metadata.comicvine.actions.ComicVineGetIssueDetailsAction;
import org.comixedproject.metadata.comicvine.actions.ComicVineGetVolumesAction;
import org.comixedproject.metadata.model.IssueDetailsMetadata;
import org.comixedproject.metadata.model.IssueMetadata;
import org.comixedproject.metadata.model.VolumeMetadata;
import org.comixedproject.metadata.comicvine.actions.*;
import org.comixedproject.metadata.model.*;
import org.comixedproject.model.metadata.MetadataSource;

/**
Expand All @@ -57,6 +52,44 @@ public ComicVineMetadataAdaptor() {
super("ComiXed ComicVine Scraper", PROVIDER_NAME);
}

@Override
public List<StoryMetadata> getStories(
final String storyName, final Integer maxRecords, final MetadataSource metadataSource)
throws MetadataException {
log.debug("Fetching stories from ComicVine: storyName={}", storyName);
final ComicVineGetStoriesAction action = new ComicVineGetStoriesAction();
action.setBaseUrl(BASE_URL);
action.setApiKey(
this.getSourcePropertyByName(metadataSource.getProperties(), PROPERTY_API_KEY, true));
action.setDelay(this.doGetDelayValue(metadataSource));
action.setStoryName(storyName);
action.setMaxRecords(maxRecords);

log.debug("Executing action");
final List<StoryMetadata> result = action.execute();

log.debug("Returning {} stories", result.size());
return result;
}

@Override
public StoryDetailMetadata getStory(final String referenceId, final MetadataSource metadataSource)
throws MetadataException {
log.debug("Fetching story details: referenceId={}", referenceId);
final ComicVineGetStoryDetailAction action = new ComicVineGetStoryDetailAction();
action.setBaseUrl(BASE_URL);
action.setApiKey(
this.getSourcePropertyByName(metadataSource.getProperties(), PROPERTY_API_KEY, true));
action.setDelay(this.doGetDelayValue(metadataSource));
action.setReferenceId(referenceId);

log.debug("Executing action");
final StoryDetailMetadata result = action.execute();

log.debug("Returning one story with {} issues", result.getIssues().size());
return result;
}

@Override
public List<VolumeMetadata> getVolumes(
final String seriesName, final Integer maxRecords, final MetadataSource metadataSource)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.comixedproject.metadata.comicvine.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor
public class ComicVineGetStoryDetailResponse extends AbstractComicVineQueryResponse {
@JsonProperty("results")
@Getter
private ComicVineStoryDetailResponse results;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/*
* ComiXed - A digital comic book library management application.
* Copyright (C) 2025, The ComiXed Project
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses>
*/

package org.comixedproject.metadata.comicvine.model;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.List;
import lombok.Getter;

/**
* <code>ComicVineGetStoryListResponse</code> represents the paylaod returned when scraping a list
* of stories.
*
* @author Darryl L. Pierce
*/
public class ComicVineGetStoryListResponse extends AbstractComicVineQueryResponse {
@JsonProperty("results")
@Getter
private List<ComicVineStoryListEntry> results;
}
Loading