From 391b3696920d78479cd46dab94dfbba3f7fbf42c Mon Sep 17 00:00:00 2001 From: jpoulton Date: Sun, 29 Oct 2023 07:20:20 +0000 Subject: [PATCH] Support coomer.party --- .../ripper/rippers/CoomerPartyRipper.java | 152 ++++++++++++++++++ .../ripper/rippers/CoomerPartyRipperTest.java | 38 +++++ 2 files changed, 190 insertions(+) create mode 100644 src/main/java/com/rarchives/ripme/ripper/rippers/CoomerPartyRipper.java create mode 100644 src/test/java/com/rarchives/ripme/tst/ripper/rippers/CoomerPartyRipperTest.java diff --git a/src/main/java/com/rarchives/ripme/ripper/rippers/CoomerPartyRipper.java b/src/main/java/com/rarchives/ripme/ripper/rippers/CoomerPartyRipper.java new file mode 100644 index 00000000..ca885da1 --- /dev/null +++ b/src/main/java/com/rarchives/ripme/ripper/rippers/CoomerPartyRipper.java @@ -0,0 +1,152 @@ +package com.rarchives.ripme.ripper.rippers; + +import com.rarchives.ripme.ripper.AbstractJSONRipper; +import com.rarchives.ripme.utils.Http; +import com.rarchives.ripme.utils.Utils; + +import org.apache.log4j.Logger; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.stream.Collectors; + +/** + * See this link for the API schema. + */ +public class CoomerPartyRipper extends AbstractJSONRipper { + private static final Logger LOGGER = Logger.getLogger(CoomerPartyRipper.class); + private static final String IMG_URL_BASE = "https://c3.coomer.su/data"; + private static final String VID_URL_BASE = "https://c1.coomer.su/data"; + private static final Pattern IMG_PATTERN = Pattern.compile("^.*\\.(jpg|jpeg|png|gif|apng|webp|tif|tiff)$", Pattern.CASE_INSENSITIVE); + private static final Pattern VID_PATTERN = Pattern.compile("^.*\\.(webm|mp4|m4v)$", Pattern.CASE_INSENSITIVE); + + // just so we can return a JSONObject from getFirstPage + private static final String KEY_WRAPPER_JSON_ARRAY = "array"; + + private static final String KEY_FILE = "file"; + private static final String KEY_PATH = "path"; + private static final String KEY_ATTACHMENTS = "attachments"; + + private final String service; + private final String user; + + public CoomerPartyRipper(URL url) throws IOException { + super(url); + List pathElements = Arrays.stream(url.getPath().split("/")) + .filter(element -> !element.isBlank()) + .collect(Collectors.toList()); + + service = pathElements.get(0); + user = pathElements.get(2); + + if (service == null || user == null || service.isBlank() || user.isBlank()) { + LOGGER.warn("service=" + service + ", user=" + user); + throw new MalformedURLException("Invalid coomer.party URL: " + url); + } + LOGGER.debug("Parsed service=" + service + " and user=" + user + " from " + url); + } + + @Override + protected String getDomain() { + return "coomer.party"; + } + + @Override + public String getHost() { + return "coomer.party"; + } + + @Override + public boolean canRip(URL url) { + String host = url.getHost(); + return host.endsWith("coomer.party") || host.endsWith("coomer.su"); + } + + @Override + public String getGID(URL url) { + return Utils.filesystemSafe(String.format("%s_%s", service, user)); + } + + @Override + protected JSONObject getFirstPage() throws IOException { + String apiUrl = String.format("https://coomer.su/api/v1/%s/user/%s", service, user); + String jsonArrayString = Http.url(apiUrl) + .ignoreContentType() + .response() + .body(); + JSONArray jsonArray = new JSONArray(jsonArrayString); + + // Ideally we'd just return the JSONArray from here, but we have to wrap it in a JSONObject + JSONObject wrapperObject = new JSONObject(); + wrapperObject.put(KEY_WRAPPER_JSON_ARRAY, jsonArray); + return wrapperObject; + } + + @Override + protected List getURLsFromJSON(JSONObject json) { + // extract the array from our wrapper JSONObject + JSONArray posts = json.getJSONArray(KEY_WRAPPER_JSON_ARRAY); + ArrayList urls = new ArrayList<>(); + for (int i = 0; i < posts.length(); i++) { + JSONObject post = posts.getJSONObject(i); + pullFileUrl(post, urls); + pullAttachmentUrls(post, urls); + } + LOGGER.debug("Pulled " + urls.size() + " URLs from " + posts.length() + " posts"); + return urls; + } + + @Override + protected void downloadURL(URL url, int index) { + addURLToDownload(url, getPrefix(index)); + } + + private void pullFileUrl(JSONObject post, ArrayList results) { + try { + JSONObject file = post.getJSONObject(KEY_FILE); + String path = file.getString(KEY_PATH); + if (isImage(path)) { + String url = IMG_URL_BASE + path; + results.add(url); + } else if (isVideo(path)) { + String url = VID_URL_BASE + path; + results.add(url); + } else { + LOGGER.error("Unknown extension for coomer.su path: " + path); + } + } catch (JSONException e) { + /* No-op */ + } + } + + private void pullAttachmentUrls(JSONObject post, ArrayList results) { + try { + JSONArray attachments = post.getJSONArray(KEY_ATTACHMENTS); + for (int i = 0; i < attachments.length(); i++) { + JSONObject attachment = attachments.getJSONObject(0); + pullFileUrl(attachment, results); + } + } catch (JSONException e) { + /* No-op */ + } + } + + private boolean isImage(String path) { + Matcher matcher = IMG_PATTERN.matcher(path); + return matcher.matches(); + } + + private boolean isVideo(String path) { + Matcher matcher = VID_PATTERN.matcher(path); + return matcher.matches(); + } +} diff --git a/src/test/java/com/rarchives/ripme/tst/ripper/rippers/CoomerPartyRipperTest.java b/src/test/java/com/rarchives/ripme/tst/ripper/rippers/CoomerPartyRipperTest.java new file mode 100644 index 00000000..ccc26a76 --- /dev/null +++ b/src/test/java/com/rarchives/ripme/tst/ripper/rippers/CoomerPartyRipperTest.java @@ -0,0 +1,38 @@ +package com.rarchives.ripme.tst.ripper.rippers; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import com.rarchives.ripme.ripper.rippers.CoomerPartyRipper; + +import org.junit.jupiter.api.Test; + +import java.io.IOException; +import java.net.URL; + +public class CoomerPartyRipperTest extends RippersTest { + @Test + public void testRip() throws IOException { + URL url = new URL("https://coomer.su/onlyfans/user/soogsx"); + CoomerPartyRipper ripper = new CoomerPartyRipper(url); + testRipper(ripper); + } + + @Test + public void testUrlParsing() throws IOException { + String expectedGid = "onlyfans_soogsx"; + String[] urls = new String[]{ + "https://coomer.su/onlyfans/user/soogsx", // normal url + "http://coomer.su/onlyfans/user/soogsx", // http, not https + "https://coomer.su/onlyfans/user/soogsx/", // with slash at the end + "https://coomer.su/onlyfans/user/soogsx?whatever=abc", // with url params + "https://coomer.party/onlyfans/user/soogsx", // alternate domain + }; + for (String stringUrl : urls) { + URL url = new URL(stringUrl); + CoomerPartyRipper ripper = new CoomerPartyRipper(url); + assertTrue(ripper.canRip(url)); + assertEquals(expectedGid, ripper.getGID(url)); + } + } +} \ No newline at end of file