<commit_before>function parseSearch(search: string): { query: string, purity: string } {
    let query: string = "";
    let purity: string = "111";
    for (const tag of search.split(" ")) {
        if (tag.indexOf("rating:") === 0) {
            const val = tag.substr(7);
            purity = val === "s" || val === "safe" ? "100" : (val === "e" || val === "explicit" ? "001" : "010");
        } else if (tag.indexOf("-rating:") === 0) {
            const val = tag.substr(8);
            purity = val === "s" || val === "safe" ? "011" : (val === "e" || val === "explicit" ? "110" : "101");
        } else {
            query += (query ? " " : "") + tag;
        }
    }
    return { query, purity }
}

export const source: ISource = {
    name: "WallHaven",
    modifiers: ["rating:s", "rating:safe", "rating:q", "rating:questionable", "rating:e", "rating:explicit"],
    forcedTokens: ["tags"],
    auth: {
        url: {
            type: "url",
            fields: [
                {
                    id: "apiKey",
                    key: "apikey",
                },
            ],
        },
    },
    apis: {
        json: {
            name: "JSON",
            auth: [],
            forcedLimit: 24,
            search: {
                url: (query: ISearchQuery): string => {
                    const search = parseSearch(query.search);
                    return "/api/v1/search?q=" + encodeURIComponent(search.query) + "&purity=" + search.purity + "&page=" + query.page;
                },
                parse: (src: string): IParsedSearch => {
                    const map = {
                        "id": "id",
                        // "page_url": "url",
                        "source": "source",
                        "width": "dimension_x",
                        "height": "dimension_y",
                        "file_size": "file_size",
                        "created_at": "created_at",
                        "file_url": "path",
                        "preview_url": "thumbs.original",
                    };

                    const data = JSON.parse(src);

                    const images: IImage[] = [];
                    for (const image of data["data"]) {
                        const img: IImage = Grabber.mapFields(image, map);
                        img.page_url = "/api/v1/w/" + img.id;
                        img.rating = image.purity === "sfw" ? "safe" : (image.purity === "nsfw" ? "explicit" : "questionable");
                        img.ext = image.file_type === "image/png" ? "png" : (image.file_type === "image/jpeg" ? "jpg" : undefined);
                        images.push(img);
                    }
<commit_msg>Add support for sorting on Wallhaven (issue #1886)<commit_after>function parseSearch(search: string): { query: string, purity: string, order: string, sort: string } {
    let query: string = "";
    let purity: string = "111";
    let order: string = "date_added";
    let sort: string = "desc";
    for (const tag of search.split(" ")) {
        if (tag.indexOf("rating:") === 0) {
            const val = tag.substr(7);
            purity = val === "s" || val === "safe" ? "100" : (val === "e" || val === "explicit" ? "001" : "010");
        } else if (tag.indexOf("-rating:") === 0) {
            const val = tag.substr(8);
            purity = val === "s" || val === "safe" ? "011" : (val === "e" || val === "explicit" ? "110" : "101");
        }  else if (tag.indexOf("order:") === 0) {
            const val = tag.substr(6);
            if (val.substr(-5) === "_desc") {
                order =  val.substr(0, val.length - 5);
                sort = "desc";
            } else if (val.substr(-4) === "_asc") {
                order =  val.substr(0, val.length - 4);
                sort = "asc";
            } else {
                order = val;
            }
        } else {
            query += (query ? " " : "") + tag;
        }
    }
    return { query, purity, order, sort }
}

export const source: ISource = {
    name: "WallHaven",
    modifiers: ["rating:s", "rating:safe", "rating:q", "rating:questionable", "rating:e", "rating:explicit", "order:relevance", "order:random", "order:date_added", "order:views", "order:favorites",  "order:toplist", "order:hot"],
    forcedTokens: ["tags"],
    auth: {
        url: {
            type: "url",
            fields: [
                {
                    id: "apiKey",
                    key: "apikey",
                },
            ],
        },
    },
    apis: {
        json: {
            name: "JSON",
            auth: [],
            forcedLimit: 24,
            search: {
                url: (query: ISearchQuery): string => {
                    const search = parseSearch(query.search);
                    return "/api/v1/search?q=" + encodeURIComponent(search.query) + "&purity=" + search.purity + "&page=" + query.page + "&sorting=" + search.order + "&order=" + search.sort;
                },
                parse: (src: string): IParsedSearch => {
                    const map = {
                        "id": "id",
                        // "page_url": "url",
                        "source": "source",
                        "width": "dimension_x",
                        "height": "dimension_y",
                        "file_size": "file_size",
                        "created_at": "created_at",
                        "file_url": "path",
                        "preview_url": "thumbs.original",
                    };

                    const data = JSON.parse(src);

                    const images: IImage[] = [];
                    for (const image of data["data"]) {
                        const img: IImage = Grabber.mapFields(image, map);
                        img.page_url = "/api/v1/w/" + img.id;
                        img.rating = image.purity === "sfw" ? "safe" : (image.purity === "nsfw" ? "explicit" : "questionable");
                        img.ext = image.file_type === "image/png" ? "png" : (image.file_type === "image/jpeg" ? "jpg" : undefined);
                        images.push(img);
                    }

{'fraction_non_alphanumeric': 0.16955526487900588, 'fraction_numerical': 0.01144538914323087, 'mean_word_length': 1.3572254335260117, 'pattern_counts': {'":': 18, '<': 3, '<?xml version=': 0, '>': 7, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4712090', 'n_tokens_mistral': 1830, 'n_tokens_neox': 1706, 'n_words': 399}