-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- boilerplate for communitation - WIP: fetch all - WIP: parse response - stacked on nanoseconds issue - Added api models
- Loading branch information
Showing
12 changed files
with
487 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
37 changes: 37 additions & 0 deletions
37
src/main/java/eu/cybershu/pocketstats/reader/api/Category.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package eu.cybershu.pocketstats.reader.api; | ||
|
||
import com.fasterxml.jackson.annotation.JsonCreator; | ||
import com.fasterxml.jackson.annotation.JsonValue; | ||
|
||
public enum Category { | ||
ARTICLE("article"), | ||
EMAIL("email"), | ||
RSS("rss"), | ||
HIGHLIGHT("highlight"), | ||
NOTE("note"), | ||
PDF("pdf"), | ||
EPUB("epub"), | ||
TWEET("tweet"), | ||
VIDEO("video"); | ||
|
||
private final String value; | ||
|
||
Category(String value) { | ||
this.value = value; | ||
} | ||
|
||
@JsonValue | ||
public String getValue() { | ||
return value; | ||
} | ||
|
||
@JsonCreator | ||
public static Category forValue(String value) { | ||
for (Category category : Category.values()) { | ||
if (category.getValue().equals(value)) { | ||
return category; | ||
} | ||
} | ||
throw new IllegalArgumentException("Unknown category: " + value); | ||
} | ||
} |
34 changes: 34 additions & 0 deletions
34
src/main/java/eu/cybershu/pocketstats/reader/api/Location.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
package eu.cybershu.pocketstats.reader.api; | ||
|
||
import com.fasterxml.jackson.annotation.JsonCreator; | ||
import com.fasterxml.jackson.annotation.JsonValue; | ||
|
||
//values: new, later, shortlist, archive, feed | ||
public enum Location { | ||
NEW("new"), | ||
LATER("later"), | ||
SHORTLIST("shortlist"), | ||
ARCHIVE("archive"), | ||
FEED("feed"); | ||
|
||
private final String value; | ||
|
||
Location(String value) { | ||
this.value = value; | ||
} | ||
|
||
@JsonValue | ||
public String getValue() { | ||
return value; | ||
} | ||
|
||
@JsonCreator | ||
public static Location forValue(String value) { | ||
for (Location location : Location.values()) { | ||
if (location.getValue().equals(value)) { | ||
return location; | ||
} | ||
} | ||
throw new IllegalArgumentException("Unknown location: " + value); | ||
} | ||
} |
127 changes: 127 additions & 0 deletions
127
src/main/java/eu/cybershu/pocketstats/reader/api/ReaderApiService.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
package eu.cybershu.pocketstats.reader.api; | ||
|
||
import com.fasterxml.jackson.annotation.JsonInclude; | ||
import com.fasterxml.jackson.databind.DeserializationFeature; | ||
import com.fasterxml.jackson.databind.ObjectMapper; | ||
import com.fasterxml.jackson.databind.SerializationFeature; | ||
import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; | ||
import eu.cybershu.pocketstats.pocket.api.ApiXHeaders; | ||
import eu.cybershu.pocketstats.utils.RequestUtils; | ||
import lombok.extern.slf4j.Slf4j; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.util.StringUtils; | ||
|
||
import java.io.IOException; | ||
import java.net.URI; | ||
import java.net.http.HttpClient; | ||
import java.net.http.HttpRequest; | ||
import java.net.http.HttpResponse; | ||
import java.time.Duration; | ||
import java.util.LinkedList; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
||
/** | ||
* Service for interacting with the Readwise Reader API: https://readwise.io/reader_api | ||
*/ | ||
@Slf4j | ||
@Service | ||
public class ReaderApiService { | ||
private final String readewiseReaderListUrl = "https://readwise.io/api/v3/list/?"; | ||
|
||
private final HttpClient client; | ||
|
||
private final ObjectMapper mapper; | ||
|
||
public ReaderApiService() { | ||
this.client = HttpClient | ||
.newBuilder() | ||
.followRedirects(HttpClient.Redirect.NORMAL) | ||
.connectTimeout(Duration.ofSeconds(60)) | ||
.build(); | ||
this.mapper = new ObjectMapper(); | ||
this.mapper | ||
.registerModule(new JavaTimeModule()) | ||
.configure(SerializationFeature.WRITE_DATE_TIMESTAMPS_AS_NANOSECONDS, false) | ||
.configure(DeserializationFeature.READ_DATE_TIMESTAMPS_AS_NANOSECONDS, false) | ||
.setSerializationInclusion(JsonInclude.Include.NON_NULL); | ||
} | ||
|
||
/** | ||
* Key Type Description Required | ||
* id string The document's unique id. Using this parameter it will return just one document, if found. no | ||
* updatedAfter string (formatted as ISO 8601 date) Fetch only documents updated after this date no | ||
* location string The document's location, could be one of: new, later, shortlist, archive, feed no | ||
* category string The document's category, could be one of: article, email, rss, highlight, note, pdf, epub, tweet, video no | ||
* pageCursor string A string returned by a previous request to this endpoint. Use it to get the next page of documents if there are too many for one request. no | ||
*/ | ||
public List<ReaderItem> fetchList(String accessToken, ReadwiseFetchParams params) throws IOException, InterruptedException { | ||
log.info("Fetching Readwise list with params: {}", params); | ||
|
||
List<ReaderItem> items = new LinkedList<>(); | ||
ReadwiseFetchPaginationParams pageParams = ReadwiseFetchPaginationParams | ||
.builder() | ||
.category(params.category()) | ||
.location(params.location()) | ||
.pageCursor(null) | ||
.build(); | ||
|
||
do { | ||
ReaderListResponse response = fetchPage(accessToken, pageParams); | ||
items.addAll(response.results()); | ||
|
||
pageParams = ReadwiseFetchPaginationParams | ||
.builder() | ||
.category(params.category()) | ||
.location(params.location()) | ||
.pageCursor(response.nextPageCursor()) | ||
.build(); | ||
} while (pageParams.pageCursor() != null); | ||
|
||
return items; | ||
} | ||
|
||
private ReaderListResponse fetchPage(String accessToken, ReadwiseFetchPaginationParams params) throws IOException, InterruptedException { | ||
log.info("Fetching Readwise list with params: {}", params); | ||
|
||
String url = readewiseReaderListUrl + params.toQueryParams(); | ||
log.debug("url: {}", url); | ||
|
||
HttpRequest request = HttpRequest.newBuilder() | ||
.GET() | ||
.uri(URI.create(url)) | ||
.header("Authorization", "Token " + accessToken) | ||
.build(); | ||
|
||
HttpResponse<String> response = client.send(request, HttpResponse.BodyHandlers.ofString()); | ||
|
||
logResponse(response); | ||
|
||
switch (response.statusCode()) { | ||
case 200: | ||
String body = response.body(); | ||
|
||
return mapper.readValue(body, ReaderListResponse.class); | ||
case 401: | ||
break; | ||
case 400: | ||
case 403: | ||
case 500: | ||
case 504: | ||
default: | ||
throw new IllegalStateException("Not expected http response code " + response.statusCode()); | ||
} | ||
|
||
throw new IllegalStateException("Not expected http response code " + response.statusCode()); | ||
} | ||
|
||
private void logResponse(HttpResponse<String> response) { | ||
log.debug("status: {}", response.statusCode()); | ||
|
||
// print response body | ||
if (response.statusCode() != HttpStatus.OK.value()) { | ||
log.debug("response: {}", response.body()); | ||
} | ||
} | ||
} |
56 changes: 56 additions & 0 deletions
56
src/main/java/eu/cybershu/pocketstats/reader/api/ReaderItem.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package eu.cybershu.pocketstats.reader.api; | ||
|
||
import com.fasterxml.jackson.annotation.JsonIgnoreProperties; | ||
import com.fasterxml.jackson.annotation.JsonInclude; | ||
import com.fasterxml.jackson.annotation.JsonProperty; | ||
import lombok.AllArgsConstructor; | ||
import lombok.Data; | ||
|
||
import java.time.Instant; | ||
import java.util.List; | ||
|
||
/** | ||
{ | ||
* "id": "01gkqt8nbms4t698abcdvcswvf", | ||
* "url": "https://readwise.io/new/read/01gkqt8nbms4t698abcdvcswvf", | ||
* "source_url": "https://www.vanityfair.com/news/2022/10/covid-origins-investigation-wuhan-lab", | ||
* "title": "COVID-19 Origins: Investigating a “Complex and Grave Situation” Inside a Wuhan Lab", | ||
* "author": "Condé Nast", | ||
* "source": "Reader add from import URL", | ||
* "category": "article", | ||
* "location": "new", | ||
* "tags": {}, | ||
* "site_name": "Vanity Fair", | ||
* "word_count": 9601, | ||
* "created_at": "2022-12-08T02:50:35.662027+00:00", | ||
* "updated_at": "2023-03-22T13:29:41.827456+00:00", | ||
* "published_date": "2022-10-28", | ||
* "notes": "", | ||
* "summary": "The Wuhan Institute of Virology, the cutting-edge ...", | ||
* "image_url": "https://media.vanityfair.com/photos/63599642578d980751943b65/16:9/w_1280,c_limit/vf-1022-covid-trackers-site-story.jpg", | ||
* "parent_id": null, | ||
* "reading_progress": 0, | ||
* } | ||
*/ | ||
@JsonInclude(JsonInclude.Include.NON_NULL) | ||
@JsonIgnoreProperties(ignoreUnknown = true) | ||
public record ReaderItem( | ||
@JsonProperty String id, | ||
@JsonProperty String url, | ||
@JsonProperty("source_url") String sourceUrl, | ||
@JsonProperty String title, | ||
@JsonProperty String author, | ||
@JsonProperty String source, | ||
@JsonProperty Category category, | ||
@JsonProperty Location location, | ||
//@JsonProperty List<String> tags, | ||
@JsonProperty("site_name") String siteName, | ||
@JsonProperty("word_count") int wordCount, | ||
@JsonProperty Instant created_at, | ||
@JsonProperty Instant updated_at, | ||
@JsonProperty Instant published_date, | ||
@JsonProperty String notes, | ||
@JsonProperty String summary, | ||
@JsonProperty("image_url") String imageUrl, | ||
@JsonProperty double reading_progress | ||
) {} |
Oops, something went wrong.