<commit_before>/**
 * WarfaceBot, a blind XMPP client for Warface (FPS)
 * Copyright (C) 2015, 2016 <NAME> <<EMAIL>>
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <wb_session.h>
#include <wb_xmpp_wf.h>
#include <wb_xmpp.h>
#include <wb_tools.h>
#include <wb_stream.h>
#include <wb_shop.h>
#include <wb_log.h>
#include <wb_list.h>
#include <wb_item.h>

#include <stdlib.h>
#include <string.h>

static unsigned int random_box_items_init = 0;
static struct random_box {
    const char *const name;
    const char *const shop_name;
    const char *const token_name;
    const unsigned int max_tokens;
    unsigned int shop_id;
    unsigned int box_id;
} random_box_items[] = {
    { "bullpup", "random_box_01", NULL, 0, 0, 0 },
    { "exarh", "random_box_02", "box_token_cry_money_34", 1000, 0, 0 },
    { "kksmg", "random_box_03", NULL, 0, 0, 0 },
    { "bt50", "random_box_04", NULL, 0, 0, 0 },
    { "eagle", "random_box_05", "box_token_cry_money_01", 1000, 0, 0 },
    { "us12", "random_box_06", NULL, 0, 0, 0 },
    { "calico", "random_box_07", NULL, 0, 0, 0 },
    { "ccr", "random_box_08", NULL, 0, 0, 0 },
    { "ump", "random_box_09", NULL, 0, 0, 0 },
    { "ak47", "random_box_10", "box_token_cry_money_10", 1000, 0, 0 },
    { "rk14", "random_box_11", NULL, 0, 0, 0 },
    { "fas12", "random_box_12", NULL, 0, 0, 0 },
    { "t27", "random_box_13", NULL, 0, 0, 0 },
    { "twm", "random_box_14", "box_token_cry_money_14", 1000, 0, 0 },
    { "ac7smg", "random_box_15", NULL, 0, 0, 0 },
    { "gu7", "random_box_16", NULL, 0, 0, 0 },
    { "coin", "random_box_17", NULL, 0, 0, 0 },
    { "msg", "random_box_18", NULL, 0, 0, 0 },
    { "machete", "random_box_19", NULL, 0, 0, 0 },
    { "s18g", "random_box_20", NULL, 0, 0, 0 },
    { "miller", "random_box_21", NULL, 0, 0, 0 },
    { "katana", "random_box_22", NULL, 0, 0, 0 },
    { "lmg3", "random_box_23", NULL, 0, 0, 0 },
    { "seven", "random_box_24", NULL, 0, 0, 0 },
    { "pink", "random_box_25", NULL, 0, 0, 0 },
    { "shark", "random_box_26", NULL, 0, 0, 0 },
    { "870", "random_box_27", NULL, 0, 0, 0 },
    { "ccrcqb", "random_box_28", NULL, 0, 0, 0 },
    { "x308", "random_box_29", "box_token_cry_money_29", 1000, 0, 0 },
    { "para", "random_box_30", "box_token_cry_money_30", 1000, 0, 0 },
    { "metasoma", "random_box_31", NULL, 0, 0, 0 },
    { "ay226", "random_box_32", NULL, 0, 0, 0 },
    { "rba", "random_box_33", NULL, 0, 0, 0 },
    { "sat", "random_box_34", "box_token_cry_money_02", 1000, 0, 0 },
    { "exarl", "random_box_35", "box_token_cry_money_35", 1000, 0, 0 },
    { "r16a4", "random_box_36", "box_token_cry_money_35", 1000, 0, 0 },
    { "axe", "random_box_37", "box_token_cry_money_37", 1000, 0, 0 },
    { "s22", "random_box_38", "box_token_cry_money_38", 1000, 0, 0 },
    { "volcano", "random_box_39", NULL, 0, 0, 0 },
    { "atf", "random_box_40", "box_token_cry_money_40", 1000, 0, 0 },
    { "scout", "random_box_41", "box_token_cry_money_41", 1000, 0, 0 },
    { "honey", "random_box_42", "box_token_cry_money_42", 1000, 0, 0 },
    { "volcano2", "random_box_43", NULL, 0, 0, 0 },
    { "europe", "random_box_44", NULL, 0, 0, 0 },
    { "es-msg", "random_box_45", NULL, 0, 0, 0 },
    { "es-exarl", "random_box_46", NULL, 0, 0, 0 },
    { "es-x308", "random_box_47", NULL, 0, 0, 0 },
    { "es-ay226", "random_box_48", NULL, 0, 0, 0 },
    { "es-katana", "random_box_49", NULL, 0, 0, 0 },
    { "es-ccr", "random_box_50", NULL, 0, 0, 0 },
    { "dogsoldier", "random_box_51", NULL, 0, 0, 0 },
    { "dogengineer", "random_box_52", NULL, 0, 0, 0 },
    { "dogmedic", "random_box_53", NULL, 0, 0, 0 },
    { "dogsniper", "random_box_54", NULL, 0, 0, 0 },
    { "ltr6", "random_box_55", "box_token_cry_money_55", 1000, 0, 0 },
    { "r65e4", "random_box_56", "box_token_cry_money_56", 1000, 0, 0 },

    { "kommando", "random_box_58", NULL, 0, 0, 0 },
    { "wx86", "random_box_59", "box_token_cry_money_59", 1000, 0, 0 },
    { "dragunov", "random_box_61", "box_token_cry_money_61", 1000, 0, 0 },
};

struct cb_args
{
    int count;
    const struct random_box *box;
};

static void _randombox_cb(const char *msg,
                          enum xmpp_msg_type type,
                          void *args)
{
    /* Answer :
       <iq to='masterserver@warface/pve_2' type='get'>
        <query xmlns='urn:cryonline:k01'>
         <shop_buy_multiple_offer error_status="0">
          <purchased_item>
           <exp name="exp_item_01" added="50" total="40014" offerId="9870"/>
           <profile_item name="flashbang" profile_item_id="xxxxx"
                         offerId="9870" added_expiration="1 day"
                         added_quantity="0" error_status="0">
            <item id="xxxxx" name="flashbang" attached_to="0"
                                config="dm=0;material=;pocket_index=3246082"
                                slot="0" equipped="0" default="0"
                                permanent="0" expired_confirmed="0"
                                buy_time_utc="1429646487"
                                expiration_time_utc="1449778407"
                                seconds_left="172628"/>
           </profile_item>
           <exp name="exp_item_01" added="50" total="xxxx" offerId="9871"/>
           <crown_money name='crown_money_item_01' added='100' total='xxxx'/>
            ...
          </purchased_item>
          <money game_money="AAA" cry_money="BBB" crown_money="CCC"/>
         </shop_buy_multiple_offer>
        </query>
       </iq>
     */

    struct cb_args *a = (struct cb_args *) args;

    if (type & XMPP_TYPE_ERROR)
    {
        xprintf("Error while purshasing items\n");
        free(a);
        return;
    }

    char *data = wf_get_query_content(msg);

    if (data != NULL)
    {
        unsigned int error_code = get_info_int(data, "error_status='", "'", NULL);
        unsigned int game_money_left = get_info_int(data, "game_money='", "'", NULL);
        unsigned int crown_money_left = get_info_int(data, "crown_money='", "'", NULL);
        unsigned int cry_money_left = get_info_int(data, "cry_money='", "'", NULL);

        const char *m = strstr(data, "<shop_buy_multiple_offer");
        int need_resync = 0;

        if (m != NULL && (error_code == 1 || error_code == 0))
        {
            unsigned total_xp = 0;
            unsigned total_crown = 0;

            m += sizeof ("<shop_buy_multiple_offer");

            do {

                const char *exp_s = strstr(m, "<exp");
                const char *crown_s = strstr(m, "<crown_money");
                const char *profile_item_s = strstr(m, "<profile_item");

                if (exp_s != NULL
                    && (profile_item_s == NULL || exp_s < profile_item_s)
                    && (crown_s == NULL || exp_s < crown_s))

                {
                    m = exp_s + sizeof ("<exp");

                    total_xp += get_info_int(m, "added='", "'", NULL);
                }
                else if (crown_s != NULL
                         && (profile_item_s == NULL || crown_s < profile_item_s)
                         && (exp_s == NULL || crown_s < exp_s))
                {
                    m = crown_s + sizeof ("<crown_money");

                    total_crown += get_info_int(m, "added='", "'", NULL);
                }
                else if (profile_item_s != NULL)
                {
                    m = profile_item_s + sizeof ("<profile_item");

                    char *name = get_info(m, "name='", "'", NULL);
                    char *expir = get_info(m, "added_expiration='", "'", NULL);
                    char *quant = get_info(m, "added_quantity='", "'", NULL);
                    int perm = get_info_int(m, "permanent='", "'", NULL);

                    const char *s;
                    const char *c;

                    if (expir && expir[0] != '0')
                    {
                        s = expir;
                        c = "";
                    }
                    else if (quant && quant[0] != '0')
                    {
                        s = quant;
                        c = "";
                    }
                    else if (perm)
                    {
                        s = "100%";
                        c = "\033[32;1m";
                    }
                    else
                    {
                        s = "Permanent";
                        c = "\033[33;1m";
                        need_resync = 1;
                    }

                    xprintf("RB Item: %s%9s\033[0m %s\n", c, s, name);

                    free(quant);
                    free(expir);
                    free(name);
                }
                else
                {
                    break;
                }

            } while (1);

            if (total_xp != 0)
                xprintf("RB Item: %9u XP\n",
                        total_xp);

            if (total_crown != 0)
                xprintf("RB Item: %9u crown\n",
                        total_crown);

            session.profile.experience += total_xp;
            session.profile.money.game = game_money_left;
            session.profile.money.crown = crown_money_left;
            session.profile.money.cry = cry_money_left;

            if (error_code == 1)
            {
                xprintf("Error: Not enough money!\n");
                need_resync = 1;
            }
            else
            {
                struct game_item *it = NULL;

                if (a->box->token_name != NULL)
                {
                    it = item_list_get(a->box->token_name);

                    if (it != NULL)
                    {
                        it->quantity += a->count;
                    }
                    else
                    {
                        need_resync = 1;
                    }
                }
            }

            xprintf("Money left: %9d WFD - %9d Crowns - %9d K\n",
                    game_money_left,
                    crown_money_left,
                    cry_money_left);

            if (need_resync)
            {
                item_list_update(NULL, NULL);
            }
        }
        else
        {
            switch (error_code)
            {
                case 1:
                    xprintf("Not enough money\n");
                    break;
                case 2:
                    xprintf("Restricted purshase\n");
                    break;
                case 3:
                    xprintf("Out of store\n");
                    break;
                case 4:
                    xprintf("Limit reached\n");
                    break;
                case 5:
                    xprintf("Item not available\n");
                    break;
                default:
                    xprintf("Error (code: %d)\n", error_code);
                    break;
            }
        }
    }

    free(data);
    free(a);
}

static void init_rb_items(void)
{
    if (random_box_items_init == 0)
    {
        if (session.wf.shop.offers != NULL)
        {
            unsigned int i = 0;
            for (; i < sizeof (random_box_items) / sizeof (random_box_items[0]);
                 ++i)
            {
                const struct shop_offer *o =
                    list_get(session.wf.shop.offers, random_box_items[i].shop_name);

                if (o == NULL)
                    continue;

                random_box_items[i].shop_id = o->id;
            }

            random_box_items_init = 1;
        }
        else
        {
            eprintf("Shop offers not fetched yet\n");
            return;
        }
    }
}

void cmd_randombox(const char *name, unsigned int count)
{
    init_rb_items();

    if (name == NULL)
    {
        xprintf("Random boxes available:\n");

       unsigned int i = 0;
       for (; i < sizeof (random_box_items) / sizeof (random_box_items[0]);
            ++i)
       {
           if (random_box_items[i].shop_id != 0)
           {
                const struct shop_offer *o =
                    list_get(session.wf.shop.offers, random_box_items[i].shop_name);

                if (o == NULL)
                    continue;

                const char *currency = "";
                unsigned int price = 0;
                unsigned int tokens = 0;
                unsigned int max_tokens = 0;
                char enough = 0;

                struct game_item *it = NULL;

                if (random_box_items[i].token_name != NULL)
                {
                    max_tokens = random_box_items[i].max_tokens;
                    it = item_list_get(random_box_items[i].token_name);

                    if (it != NULL)
                    {
                        tokens = it->quantity;
                    }
                }

                if (o->price.cry.curr != 0)
                {
                    currency = "K";
                    price = o->price.cry.curr;
                    enough = price <= session.profile.money.cry;
                }
                else if (o->price.crown.curr != 0)
                {
                    currency = "crown";
                    price = o->price.crown.curr;
                    enough = price <= session.profile.money.crown;
                }
                else if (o->price.game.curr != 0)
                {
                    currency = "wfd";
                    price = o->price.game.curr;
                    enough = price <= session.profile.money.game;
                }
                else if (o->price.key.curr != 0)
                {
                    currency = "key";
                    price = o->price.key.curr;
                }

                xprintf(random_box_items[i].token_name != NULL
                        ? " - %11s \033[%d;1m%u\033[0m %s (%u/%u)\n"
                        : " - %11s \033[%d;1m%u\033[0m %s\n",
                        random_box_items[i].name,
                        enough ? 32 : 31,
                        price,
                        currency,
                        tokens,
                        max_tokens);
           }
       }

       return;
    }

    if (count <= 0 || count > 5)
    {
        eprintf("Invalid amount\n");
        return;
    }

    struct random_box *box = NULL;
    unsigned int i = 0;

    for (; i < sizeof (random_box_items) / sizeof (random_box_items[0]);
         ++i)
    {
        if (strcmp(name, random_box_items[i].name) == 0
            || strcmp(name, random_box_items[i].shop_name) == 0)
        {
            box = &random_box_items[i];
            break;
        }
    }

    if (box == NULL)
    {
        eprintf("Unknown randombox\n");
        return;
    }

    {
        char *offers = NULL;
        unsigned int i = 0;

        if (box->box_id + count > 5)
            box->box_id = 0;

        for (; i < count; ++i)
        {
            char *s;
            FORMAT(s, "%s<offer id='%d'/>",
                   offers ? offers : "",
                   box->shop_id + box->box_id);
            free(offers);
            offers = s;

            box->box_id = box->box_id + 1;
        }

        struct cb_args *a = calloc(1, sizeof (struct cb_args));
        a->count = count;
        a->box = box;

        xmpp_send_iq_get(
            JID_MS(session.online.channel),
            _randombox_cb, a,
            "<query xmlns='urn:cryonline:k01'>"
            "<shop_buy_multiple_offer hash='%s' supplier_id='1'>"
            "%s"
            "</shop_buy_multiple_offer>"
            "</query>",
            session.wf.shop.hash,
            offers);

        free(offers);
    }
}

void cmd_randombox_wrapper(const char *name,
                           const char *count_str)
{
    if (name == NULL)
        cmd_randombox(NULL, 0);
    else if (count_str != NULL)
        cmd_randombox(name,
                      strtol(count_str, NULL, 10));
    else
        xprintf("Box count required\n");
}

int cmd_randombox_completions(struct list *l, int arg_index)
{
    switch (arg_index)
    {
        case 1:
        {
            init_rb_items();

            unsigned int i = 0;
            for (; i < sizeof (random_box_items) / sizeof (random_box_items[0]);
                 ++i)
            {
                if (random_box_items[i].shop_id != 0)
                {
                    list_add(l, strdup(random_box_items[i].name));
                }
            }

            break;
        }

        case 2:
            list_add(l, strdup("1"));
            list_add(l, strdup("2"));
            list_add(l, strdup("3"));
            list_add(l, strdup("4"));
            list_add(l, strdup("5"));
            break;

        default:
            break;
    }

    return 1;
}
<commit_msg>Add kapow and new zsd random boxes<commit_after>/**
 * WarfaceBot, a blind XMPP client for Warface (FPS)
 * Copyright (C) 2015, 2016 <NAME> <<EMAIL>>
 *
 * This program is free software: you can redistribute it and/or modify it
 * under the terms of the GNU Affero General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or (at your
 * option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU Affero General Public License
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 */

#include <wb_session.h>
#include <wb_xmpp_wf.h>
#include <wb_xmpp.h>
#include <wb_tools.h>
#include <wb_stream.h>
#include <wb_shop.h>
#include <wb_log.h>
#include <wb_list.h>
#include <wb_item.h>

#include <stdlib.h>
#include <string.h>

static unsigned int random_box_items_init = 0;
static struct random_box {
    const char *const name;
    const char *const shop_name;
    const char *const token_name;
    const unsigned int max_tokens;
    unsigned int shop_id;
    unsigned int box_id;
} random_box_items[] = {
    { "bullpup", "random_box_01", NULL, 0, 0, 0 },
    { "exarh", "random_box_02", "box_token_cry_money_34", 1000, 0, 0 },
    { "kksmg", "random_box_03", NULL, 0, 0, 0 },
    { "bt50", "random_box_04", NULL, 0, 0, 0 },
    { "eagle", "random_box_05", "box_token_cry_money_01", 1000, 0, 0 },
    { "us12", "random_box_06", NULL, 0, 0, 0 },
    { "calico", "random_box_07", NULL, 0, 0, 0 },
    { "ccr", "random_box_08", NULL, 0, 0, 0 },
    { "ump", "random_box_09", NULL, 0, 0, 0 },
    { "ak47", "random_box_10", "box_token_cry_money_10", 1000, 0, 0 },
    { "rk14", "random_box_11", NULL, 0, 0, 0 },
    { "fas12", "random_box_12", NULL, 0, 0, 0 },
    { "t27", "random_box_13", NULL, 0, 0, 0 },
    { "twm", "random_box_14", "box_token_cry_money_14", 1000, 0, 0 },
    { "ac7smg", "random_box_15", NULL, 0, 0, 0 },
    { "gu7", "random_box_16", NULL, 0, 0, 0 },
    { "coin", "random_box_17", NULL, 0, 0, 0 },
    { "msg", "random_box_18", NULL, 0, 0, 0 },
    { "machete", "random_box_19", NULL, 0, 0, 0 },
    { "s18g", "random_box_20", NULL, 0, 0, 0 },
    { "miller", "random_box_21", NULL, 0, 0, 0 },
    { "katana", "random_box_22", NULL, 0, 0, 0 },
    { "lmg3", "random_box_23", NULL, 0, 0, 0 },
    { "seven", "random_box_24", NULL, 0, 0, 0 },
    { "pink", "random_box_25", NULL, 0, 0, 0 },
    { "shark", "random_box_26", NULL, 0, 0, 0 },
    { "870", "random_box_27", NULL, 0, 0, 0 },
    { "ccrcqb", "random_box_28", NULL, 0, 0, 0 },
    { "x308", "random_box_29", "box_token_cry_money_29", 1000, 0, 0 },
    { "para", "random_box_30", "box_token_cry_money_30", 1000, 0, 0 },
    { "metasoma", "random_box_31", NULL, 0, 0, 0 },
    { "ay226", "random_box_32", NULL, 0, 0, 0 },
    { "rba", "random_box_33", NULL, 0, 0, 0 },
    { "sat", "random_box_34", "box_token_cry_money_02", 1000, 0, 0 },
    { "exarl", "random_box_35", "box_token_cry_money_35", 1000, 0, 0 },
    { "r16a4", "random_box_36", "box_token_cry_money_35", 1000, 0, 0 },
    { "axe", "random_box_37", "box_token_cry_money_37", 1000, 0, 0 },
    { "s22", "random_box_38", "box_token_cry_money_38", 1000, 0, 0 },
    { "volcano", "random_box_39", NULL, 0, 0, 0 },
    { "atf", "random_box_40", "box_token_cry_money_40", 1000, 0, 0 },
    { "scout", "random_box_41", "box_token_cry_money_41", 1000, 0, 0 },
    { "honey", "random_box_42", "box_token_cry_money_42", 1000, 0, 0 },
    { "volcano2", "random_box_43", NULL, 0, 0, 0 },
    { "europe", "random_box_44", NULL, 0, 0, 0 },
    { "es-msg", "random_box_45", NULL, 0, 0, 0 },
    { "es-exarl", "random_box_46", NULL, 0, 0, 0 },
    { "es-x308", "random_box_47", NULL, 0, 0, 0 },
    { "es-ay226", "random_box_48", NULL, 0, 0, 0 },
    { "es-katana", "random_box_49", NULL, 0, 0, 0 },
    { "es-ccr", "random_box_50", NULL, 0, 0, 0 },
    { "dogsoldier", "random_box_51", NULL, 0, 0, 0 },
    { "dogengineer", "random_box_52", NULL, 0, 0, 0 },
    { "dogmedic", "random_box_53", NULL, 0, 0, 0 },
    { "dogsniper", "random_box_54", NULL, 0, 0, 0 },
    { "ltr6", "random_box_55", "box_token_cry_money_55", 1000, 0, 0 },
    { "r65e4", "random_box_56", "box_token_cry_money_56", 1000, 0, 0 },

    { "kommando", "random_box_58", NULL, 0, 0, 0 },
    { "wx86", "random_box_59", "box_token_cry_money_59", 1000, 0, 0 },
    { "jade", "random_box_60", NULL, 0, 0, 0 },
    { "dragunov", "random_box_61", "box_token_cry_money_61", 1000, 0, 0 },
    { "scarlet", "random_box_62", NULL, 0, 0, 0 },

    { "zsd-exarh", "random_box_65", NULL, 0, 0, 0 },
    { "zsd-atf", "random_box_66", NULL, 0, 0, 0 },
    { "zsd-ac7smg", "random_box_67", NULL, 0, 0, 0 },
    { "zsd-s18g", "random_box_68", NULL, 0, 0, 0 },
    { "zsd-axe", "random_box_69", NULL, 0, 0, 0 },
    { "zsd-scout", "random_box_70", NULL, 0, 0, 0 },

    { "kapow", "random_box_72", "box_token_cry_money_72", 1000, 0, 0 },

    { "zsd-bullpup", "random_box_75", NULL, 0, 0, 0 },
};

struct cb_args
{
    int count;
    const struct random_box *box;
};

static void _randombox_cb(const char *msg,
                          enum xmpp_msg_type type,
                          void *args)
{
    /* Answer :
       <iq to='masterserver@warface/pve_2' type='get'>
        <query xmlns='urn:cryonline:k01'>
         <shop_buy_multiple_offer error_status="0">
          <purchased_item>
           <exp name="exp_item_01" added="50" total="40014" offerId="9870"/>
           <profile_item name="flashbang" profile_item_id="xxxxx"
                         offerId="9870" added_expiration="1 day"
                         added_quantity="0" error_status="0">
            <item id="xxxxx" name="flashbang" attached_to="0"
                                config="dm=0;material=;pocket_index=3246082"
                                slot="0" equipped="0" default="0"
                                permanent="0" expired_confirmed="0"
                                buy_time_utc="1429646487"
                                expiration_time_utc="1449778407"
                                seconds_left="172628"/>
           </profile_item>
           <exp name="exp_item_01" added="50" total="xxxx" offerId="9871"/>
           <crown_money name='crown_money_item_01' added='100' total='xxxx'/>
            ...
          </purchased_item>
          <money game_money="AAA" cry_money="BBB" crown_money="CCC"/>
         </shop_buy_multiple_offer>
        </query>
       </iq>
     */

    struct cb_args *a = (struct cb_args *) args;

    if (type & XMPP_TYPE_ERROR)
    {
        xprintf("Error while purshasing items\n");
        free(a);
        return;
    }

    char *data = wf_get_query_content(msg);

    if (data != NULL)
    {
        unsigned int error_code = get_info_int(data, "error_status='", "'", NULL);
        unsigned int game_money_left = get_info_int(data, "game_money='", "'", NULL);
        unsigned int crown_money_left = get_info_int(data, "crown_money='", "'", NULL);
        unsigned int cry_money_left = get_info_int(data, "cry_money='", "'", NULL);

        const char *m = strstr(data, "<shop_buy_multiple_offer");
        int need_resync = 0;

        if (m != NULL && (error_code == 1 || error_code == 0))
        {
            unsigned total_xp = 0;
            unsigned total_crown = 0;

            m += sizeof ("<shop_buy_multiple_offer");

            do {

                const char *exp_s = strstr(m, "<exp");
                const char *crown_s = strstr(m, "<crown_money");
                const char *profile_item_s = strstr(m, "<profile_item");

                if (exp_s != NULL
                    && (profile_item_s == NULL || exp_s < profile_item_s)
                    && (crown_s == NULL || exp_s < crown_s))

                {
                    m = exp_s + sizeof ("<exp");

                    total_xp += get_info_int(m, "added='", "'", NULL);
                }
                else if (crown_s != NULL
                         && (profile_item_s == NULL || crown_s < profile_item_s)
                         && (exp_s == NULL || crown_s < exp_s))
                {
                    m = crown_s + sizeof ("<crown_money");

                    total_crown += get_info_int(m, "added='", "'", NULL);
                }
                else if (profile_item_s != NULL)
                {
                    m = profile_item_s + sizeof ("<profile_item");

                    char *name = get_info(m, "name='", "'", NULL);
                    char *expir = get_info(m, "added_expiration='", "'", NULL);
                    char *quant = get_info(m, "added_quantity='", "'", NULL);
                    int perm = get_info_int(m, "permanent='", "'", NULL);

                    const char *s;
                    const char *c;

                    if (expir && expir[0] != '0')
                    {
                        s = expir;
                        c = "";
                    }
                    else if (quant && quant[0] != '0')
                    {
                        s = quant;
                        c = "";
                    }
                    else if (perm)
                    {
                        s = "100%";
                        c = "\033[32;1m";
                    }
                    else
                    {
                        s = "Permanent";
                        c = "\033[33;1m";
                        need_resync = 1;
                    }

                    xprintf("RB Item: %s%9s\033[0m %s\n", c, s, name);

                    free(quant);
                    free(expir);
                    free(name);
                }
                else
                {
                    break;
                }

            } while (1);

            if (total_xp != 0)
                xprintf("RB Item: %9u XP\n",
                        total_xp);

            if (total_crown != 0)
                xprintf("RB Item: %9u crown\n",
                        total_crown);

            session.profile.experience += total_xp;
            session.profile.money.game = game_money_left;
            session.profile.money.crown = crown_money_left;
            session.profile.money.cry = cry_money_left;

            if (error_code == 1)
            {
                xprintf("Error: Not enough money!\n");
                need_resync = 1;
            }
            else
            {
                struct game_item *it = NULL;

                if (a->box->token_name != NULL)
                {
                    it = item_list_get(a->box->token_name);

                    if (it != NULL)
                    {
                        it->quantity += a->count;
                    }
                    else
                    {
                        need_resync = 1;
                    }
                }
            }

            xprintf("Money left: %9d WFD - %9d Crowns - %9d K\n",
                    game_money_left,
                    crown_money_left,
                    cry_money_left);

            if (need_resync)
            {
                item_list_update(NULL, NULL);
            }
        }
        else
        {
            switch (error_code)
            {
                case 1:
                    xprintf("Not enough money\n");
                    break;
                case 2:
                    xprintf("Restricted purshase\n");
                    break;
                case 3:
                    xprintf("Out of store\n");
                    break;
                case 4:
                    xprintf("Limit reached\n");
                    break;
                case 5:
                    xprintf("Item not available\n");
                    break;
                default:
                    xprintf("Error (code: %d)\n", error_code);
                    break;
            }
        }
    }

    free(data);
    free(a);
}

static void init_rb_items(void)
{
    if (random_box_items_init == 0)
    {
        if (session.wf.shop.offers != NULL)
        {
            unsigned int i = 0;
            for (; i < sizeof (random_box_items) / sizeof (random_box_items[0]);
                 ++i)
            {
                const struct shop_offer *o =
                    list_get(session.wf.shop.offers, random_box_items[i].shop_name);

                if (o == NULL)
                    continue;

                random_box_items[i].shop_id = o->id;
            }

            random_box_items_init = 1;
        }
        else
        {
            eprintf("Shop offers not fetched yet\n");
            return;
        }
    }
}

void cmd_randombox(const char *name, unsigned int count)
{
    init_rb_items();

    if (name == NULL)
    {
        xprintf("Random boxes available:\n");

       unsigned int i = 0;
       for (; i < sizeof (random_box_items) / sizeof (random_box_items[0]);
            ++i)
       {
           if (random_box_items[i].shop_id != 0)
           {
                const struct shop_offer *o =
                    list_get(session.wf.shop.offers, random_box_items[i].shop_name);

                if (o == NULL)
                    continue;

                const char *currency = "";
                unsigned int price = 0;
                unsigned int tokens = 0;
                unsigned int max_tokens = 0;
                char enough = 0;

                struct game_item *it = NULL;

                if (random_box_items[i].token_name != NULL)
                {
                    max_tokens = random_box_items[i].max_tokens;
                    it = item_list_get(random_box_items[i].token_name);

                    if (it != NULL)
                    {
                        tokens = it->quantity;
                    }
                }

                if (o->price.cry.curr != 0)
                {
                    currency = "K";
                    price = o->price.cry.curr;
                    enough = price <= session.profile.money.cry;
                }
                else if (o->price.crown.curr != 0)
                {
                    currency = "crown";
                    price = o->price.crown.curr;
                    enough = price <= session.profile.money.crown;
                }
                else if (o->price.game.curr != 0)
                {
                    currency = "wfd";
                    price = o->price.game.curr;
                    enough = price <= session.profile.money.game;
                }
                else if (o->price.key.curr != 0)
                {
                    currency = "key";
                    price = o->price.key.curr;
                }

                xprintf(random_box_items[i].token_name != NULL
                        ? " - %11s \033[%d;1m%u\033[0m %s (%u/%u)\n"
                        : " - %11s \033[%d;1m%u\033[0m %s\n",
                        random_box_items[i].name,
                        enough ? 32 : 31,
                        price,
                        currency,
                        tokens,
                        max_tokens);
           }
       }

       return;
    }

    if (count <= 0 || count > 5)
    {
        eprintf("Invalid amount\n");
        return;
    }

    struct random_box *box = NULL;
    unsigned int i = 0;

    for (; i < sizeof (random_box_items) / sizeof (random_box_items[0]);
         ++i)
    {
        if (strcmp(name, random_box_items[i].name) == 0
            || strcmp(name, random_box_items[i].shop_name) == 0)
        {
            box = &random_box_items[i];
            break;
        }
    }

    if (box == NULL)
    {
        eprintf("Unknown randombox\n");
        return;
    }

    {
        char *offers = NULL;
        unsigned int i = 0;

        if (box->box_id + count > 5)
            box->box_id = 0;

        for (; i < count; ++i)
        {
            char *s;
            FORMAT(s, "%s<offer id='%d'/>",
                   offers ? offers : "",
                   box->shop_id + box->box_id);
            free(offers);
            offers = s;

            box->box_id = box->box_id + 1;
        }

        struct cb_args *a = calloc(1, sizeof (struct cb_args));
        a->count = count;
        a->box = box;

        xmpp_send_iq_get(
            JID_MS(session.online.channel),
            _randombox_cb, a,
            "<query xmlns='urn:cryonline:k01'>"
            "<shop_buy_multiple_offer hash='%s' supplier_id='1'>"
            "%s"
            "</shop_buy_multiple_offer>"
            "</query>",
            session.wf.shop.hash,
            offers);

        free(offers);
    }
}

void cmd_randombox_wrapper(const char *name,
                           const char *count_str)
{
    if (name == NULL)
        cmd_randombox(NULL, 0);
    else if (count_str != NULL)
        cmd_randombox(name,
                      strtol(count_str, NULL, 10));
    else
        xprintf("Box count required\n");
}

int cmd_randombox_completions(struct list *l, int arg_index)
{
    switch (arg_index)
    {
        case 1:
        {
            init_rb_items();

            unsigned int i = 0;
            for (; i < sizeof (random_box_items) / sizeof (random_box_items[0]);
                 ++i)
            {
                if (random_box_items[i].shop_id != 0)
                {
                    list_add(l, strdup(random_box_items[i].name));
                }
            }

            break;
        }

        case 2:
            list_add(l, strdup("1"));
            list_add(l, strdup("2"));
            list_add(l, strdup("3"));
            list_add(l, strdup("4"));
            list_add(l, strdup("5"));
            break;

        default:
            break;
    }

    return 1;
}

{'fraction_non_alphanumeric': 0.13688547882921284, 'fraction_numerical': 0.03739698778061949, 'mean_word_length': 1.4426320538627055, 'pattern_counts': {'":': 0, '<': 115, '<?xml version=': 0, '>': 125, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 4}, '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': '3707960', 'n_tokens_mistral': 12629, 'n_tokens_neox': 11021, 'n_words': 2540}