class MastodonRipper extends AbstractHTMLRipper {
    private Map<String, String> itemIDs = Collections.synchronizedMap(new HashMap<String, String>());

    public MastodonRipper(URL url) throws IOException {
        super(url);
    }

    @Override
    public String getHost() {
        return "mastodon";
    }

    @Override
    public String getDomain() {
        return "mastodon.social";
    }

    @Override
    public String getGID(URL url) throws MalformedURLException {
        Pattern p = Pattern.compile("^https?://(" + getDomain() + ")/@([a-zA-Z0-9_-]+)(/media/?)?$");
        Matcher m = p.matcher(url.toExternalForm());
        if (m.matches()) {
            // Return the text contained between () in the regex
            return m.group(1) + "@" + m.group(2);
        }
        throw new MalformedURLException(
                "Expected " + getDomain() + " URL format: " +
                getDomain() + "/@username - got " + url + " instead");
    }

    @Override
    public Document getFirstPage() throws IOException {
        Pattern p = Pattern.compile("^/@[a-zA-Z0-9_-]+/media/?$");
        Matcher m = p.matcher(url.getPath());
        if (m.matches()) {
            return Http.url(url).get();
        }
        return Http.url(url.toExternalForm().replaceAll("/$", "") + "/media").get();
    }

    @Override
    public Document getNextPage(Document doc) throws IOException {
        Elements hrefs = doc.select(".h-entry + .entry > a.load-more.load-gap");
        if (hrefs.isEmpty()) {
            throw new IOException("No more pages");
        }
        String nextUrl = hrefs.last().attr("href");
        sleep(500);
        return Http.url(nextUrl).get();
    }

    @Override
    public List<String> getURLsFromPage(Document doc) {<FILL_FUNCTION_BODY>}

    @Override
    public void downloadURL(URL url, int index) {
        addURLToDownload(url, itemIDs.get(url.toString()) + "_");
    }
}

        List<String> result = new ArrayList<String>();
        for (Element el : doc.select("[data-component=\"MediaGallery\"]")) {
            String props = el.attr("data-props");
            JSONObject obj = new JSONObject(props);
            JSONArray arr = obj.getJSONArray("media");
            for (int i = 0; i < arr.length(); i++) {
                String url = arr.getJSONObject(i).getString("url");
                result.add(url);
                String id = arr.getJSONObject(i).getString("id");
                itemIDs.put(url, id);
            }
        }
        return result;
    
<methods>public boolean addURLToDownload(java.net.URL, java.io.File, java.lang.String, Map<java.lang.String,java.lang.String>, java.lang.Boolean) ,public boolean addURLToDownload(java.net.URL, java.io.File) ,public boolean canRip(java.net.URL) ,public void downloadCompleted(java.net.URL, java.io.File) ,public void downloadErrored(java.net.URL, java.lang.String) ,public void downloadExists(java.net.URL, java.io.File) ,public int getCompletionPercentage() ,public int getCount() ,public abstract java.lang.String getHost() ,public Document getNextPage(Document) throws java.io.IOException,public java.lang.String getStatusText() ,public void rip() throws java.io.IOException,public java.net.URL sanitizeURL(java.net.URL) throws java.net.MalformedURLException,public boolean saveText(java.net.URL, java.lang.String, java.lang.String, int) ,public void setWorkingDir(java.net.URL) throws java.io.IOException<variables>private Map<java.net.URL,java.io.File> itemsCompleted,private Map<java.net.URL,java.lang.String> itemsErrored,private Map<java.net.URL,java.io.File> itemsPending