class DerpiRipper extends AbstractJSONRipper {

    private URL currUrl;
    private Integer currPage;

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

    private String apiUrl;

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

    @Override
    public String getDomain() {
        return "derpibooru.org";
    }

    @Override
    public URL sanitizeURL(URL url) throws MalformedURLException {<FILL_FUNCTION_BODY>}

    @Override
    public String getGID(URL url) throws MalformedURLException {
        currUrl = url;
        currPage = 1;

        // search
        Pattern p = Pattern.compile("^https?://derpibooru\\.org/search\\.json\\?q=([^&]+).*?$");
        Matcher m = p.matcher(url.toExternalForm());
        if (m.matches()) {
            return "search_" + m.group(1);
        }

        // tags
        p = Pattern.compile("^https?://derpibooru\\.org/tags/([^.]+)\\.json.*?$");
        m = p.matcher(url.toExternalForm());
        if (m.matches()) {
            return "tags_" + m.group(1);
        }

        // galleries
        p = Pattern.compile("^https?://derpibooru\\.org/galleries/([^/]+)/(\\d+)\\.json.*?$");
        m = p.matcher(url.toExternalForm());
        if (m.matches()) {
            return "galleries_" + m.group(1) + "_" + m.group(2);
        }

        // single image
        p = Pattern.compile("^https?://derpibooru\\.org/(\\d+)\\.json.*?$");
        m = p.matcher(url.toExternalForm());
        if (m.matches()) {
            return "image_" + m.group(1);
        }

        throw new MalformedURLException("Unable to find image in " + url);
    }

    @Override
    public JSONObject getFirstPage() throws IOException {
        return Http.url(url).getJSON();
    }

    @Override
    public JSONObject getNextPage(JSONObject doc) throws IOException {
        currPage++;
        String u = currUrl.toExternalForm() + "&page=" + Integer.toString(currPage);
        JSONObject json = Http.url(new URL(u)).getJSON();
        JSONArray arr;
        if (json.has("images")) {
            arr = json.getJSONArray("images");
        } else if (json.has("search")) {
            arr = json.getJSONArray("search");
        } else {
            throw new IOException("No more images");
        }
        if (arr.length() == 0) {
            throw new IOException("No more images");
        }
        return json;
    }

    private String getImageUrlFromJson(JSONObject json) {
        return "https:" + json.getJSONObject("representations").getString("full");
    }

    @Override
    public List<String> getURLsFromJSON(JSONObject json) {
        List<String> imageURLs = new ArrayList<>();

        JSONArray arr = null;
        if (json.has("images")) {
            arr = json.getJSONArray("images");
        } else if (json.has("search")) {
            arr = json.getJSONArray("search");
        }
        if (arr != null) {
            for (int i = 0; i < arr.length(); i++){
                imageURLs.add(this.getImageUrlFromJson(arr.getJSONObject(i)));
            }
        } else {
            imageURLs.add(this.getImageUrlFromJson(json));
        }
        return imageURLs;
    }

    @Override
    public void downloadURL(URL url, int index) {
        // we don't set an index prefix here as derpibooru already prefixes their images with their unique IDs
        addURLToDownload(url, "");
    }
}

        String u = url.toExternalForm();
        String[] uu = u.split("\\?", 2);
        String newU = uu[0];
        if (newU.substring(newU.length() - 1).equals("/")) {
            newU = newU.substring(0, newU.length() - 1);
        }
        newU += ".json?";
        if (uu.length > 1) {
            newU += uu[1];
        }

        String key = Utils.getConfigString("derpi.key", "");
        if (!key.equals("")) {
            newU += "&key=" + key;
        }

        return new URL(newU);
    
<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 java.lang.String getStatusText() ,public void rip() throws java.io.IOException,public java.net.URL sanitizeURL(java.net.URL) throws java.net.MalformedURLException,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