Загрузка данных


/*
 * cdp-clicker — минималистичный кроссплатформенный кликер (C99)
 * Windows (MinGW/GCC) и Linux (GCC)
 *
 * Сборка:
 *   Windows: gcc -O2 -s -o clicker.exe main.c
 *   Linux:   gcc -O2 -s -o clicker_linux main.c
 *
 * Требования: curl в PATH, curl 7.86+ (WebSocket-клиент)
 */

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

#ifdef _WIN32
#  include <windows.h>
#  include <io.h>
#  define access _access
#  define F_OK 0
#else
#  include <unistd.h>
#endif

#define DEFAULT_QUERY  "\xd0\xbc\xd0\xb5\xd0\xb3\xd0\xb0\xd0\xb1\xd0\xb0\xd0\xb9\xd1\x82 \xd0\xbf\xd0\xbb\xd1\x8e\xd1\x81"
#define DEFAULT_DOMAIN "megabyteplus.ru"
#define CDP_PORT       9222
#define TMP_JSON       "cdp_cmd.json"
#define TMP_LIST       "cdp_list.json"

static int g_cdp_id = 1;

/* ---------- утилиты ---------- */

static void sleep_ms(int ms)
{
#ifdef _WIN32
    Sleep((DWORD)ms);
#else
    usleep((useconds_t)ms * 1000);
#endif
}

static int file_exists(const char *path)
{
    return access(path, F_OK) == 0;
}

static void json_escape(const char *src, char *dst, size_t dst_size)
{
    size_t j = 0;
    if (dst_size == 0)
        return;
    for (; *src && j + 2 < dst_size; src++) {
        char c = *src;
        if (c == '\\' || c == '"') {
            if (j + 3 >= dst_size)
                break;
            dst[j++] = '\\';
            dst[j++] = c;
        } else if ((unsigned char)c < 0x20) {
            if (j + 6 >= dst_size)
                break;
            j += (size_t)snprintf(dst + j, dst_size - j, "\\u%04x", (unsigned char)c);
        } else {
            dst[j++] = c;
        }
    }
    dst[j] = '\0';
}

static int write_text_file(const char *path, const char *text)
{
    FILE *f = fopen(path, "wb");
    if (!f)
        return -1;
    if (fputs(text, f) == EOF) {
        fclose(f);
        return -1;
    }
    fclose(f);
    return 0;
}

static int run_curl_capture(const char *args, char *out, size_t out_size)
{
    char cmd[4096];
#ifdef _WIN32
    snprintf(cmd, sizeof(cmd), "curl -s %s > \"%s\" 2>nul", args, TMP_LIST);
#else
    snprintf(cmd, sizeof(cmd), "curl -s %s > '%s' 2>/dev/null", args, TMP_LIST);
#endif
    if (system(cmd) != 0)
        return -1;

    FILE *f = fopen(TMP_LIST, "rb");
    if (!f)
        return -1;

    size_t n = fread(out, 1, out_size - 1, f);
    out[n] = '\0';
    fclose(f);
    return (int)n;
}

static int cdp_send_ws(const char *ws_url, const char *json_body)
{
    if (write_text_file(TMP_JSON, json_body) != 0)
        return -1;

    char cmd[8192];
#ifdef _WIN32
    snprintf(cmd, sizeof(cmd),
             "curl -s --max-time 10 --no-buffer --websocket \"%s\" -d @\"%s\" >nul 2>nul",
             ws_url, TMP_JSON);
#else
    snprintf(cmd, sizeof(cmd),
             "curl -s --max-time 10 --no-buffer --websocket '%s' -d @'%s' >/dev/null 2>&1",
             ws_url, TMP_JSON);
#endif
    return system(cmd);
}

static int extract_ws_url(const char *json, char *ws_url, size_t ws_size)
{
    const char *key = "\"webSocketDebuggerUrl\":\"";
    const char *p = strstr(json, key);
    if (!p)
        return -1;
    p += strlen(key);
    const char *end = strchr(p, '"');
    if (!end || (size_t)(end - p) >= ws_size)
        return -1;
    memcpy(ws_url, p, (size_t)(end - p));
    ws_url[end - p] = '\0';
    return 0;
}

static int cdp_get_ws_url(char *ws_url, size_t ws_size)
{
    char list[65536];
    char url[128];
    snprintf(url, sizeof(url), "http://127.0.0.1:%d/json/list", CDP_PORT);

    /* несколько попыток — браузер может стартовать медленно */
    for (int attempt = 0; attempt < 15; attempt++) {
        if (run_curl_capture(url, list, sizeof(list)) > 0) {
            if (extract_ws_url(list, ws_url, ws_size) == 0)
                return 0;
        }
        sleep_ms(500);
    }
    return -1;
}

static int cdp_evaluate(const char *ws_url, const char *expression)
{
    char escaped[8192];
    char json[9216];
    json_escape(expression, escaped, sizeof(escaped));
    snprintf(json, sizeof(json),
             "{\"id\":%d,\"method\":\"Runtime.evaluate\",\"params\":{\"expression\":\"%s\"}}",
             g_cdp_id++, escaped);
    return cdp_send_ws(ws_url, json);
}

static int cdp_key_char(const char *ws_url, const char *text)
{
    char escaped[64];
    char json[512];
    json_escape(text, escaped, sizeof(escaped));
    snprintf(json, sizeof(json),
             "{\"id\":%d,\"method\":\"Input.dispatchKeyEvent\",\"params\":{\"type\":\"char\",\"text\":\"%s\"}}",
             g_cdp_id++, escaped);
    return cdp_send_ws(ws_url, json);
}

static int cdp_key_enter(const char *ws_url)
{
    char json_down[512];
    char json_up[512];
    snprintf(json_down, sizeof(json_down),
             "{\"id\":%d,\"method\":\"Input.dispatchKeyEvent\",\"params\":"
             "{\"type\":\"keyDown\",\"windowsVirtualKeyCode\":13,\"nativeVirtualKeyCode\":13,"
             "\"code\":\"Enter\",\"key\":\"Enter\"}}",
             g_cdp_id++);
    snprintf(json_up, sizeof(json_up),
             "{\"id\":%d,\"method\":\"Input.dispatchKeyEvent\",\"params\":"
             "{\"type\":\"keyUp\",\"windowsVirtualKeyCode\":13,\"nativeVirtualKeyCode\":13,"
             "\"code\":\"Enter\",\"key\":\"Enter\"}}",
             g_cdp_id++);
    cdp_send_ws(ws_url, json_down);
    return cdp_send_ws(ws_url, json_up);
}

/* ---------- UTF-8: следующий символ ---------- */

static const char *next_utf8(const char *s, char *buf, size_t buf_size)
{
    unsigned char c;
    int len;

    if (!s || !*s)
        return NULL;

    c = (unsigned char)*s;
    if (c < 0x80)
        len = 1;
    else if ((c & 0xE0) == 0xC0)
        len = 2;
    else if ((c & 0xF0) == 0xE0)
        len = 3;
    else if ((c & 0xF8) == 0xF0)
        len = 4;
    else
        len = 1;

    if ((int)buf_size <= len)
        len = 1;

    memcpy(buf, s, (size_t)len);
    buf[len] = '\0';
    return s + len;
}

/* ---------- поиск браузера ---------- */

static int find_browser(char *path, size_t path_size)
{
#ifdef _WIN32
    static const char *candidates[] = {
        "C:\\Program Files (x86)\\Microsoft\\Edge\\Application\\msedge.exe",
        "C:\\Program Files\\Microsoft\\Edge\\Application\\msedge.exe",
        "C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe",
        "C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe",
        NULL
    };
    int i;

    for (i = 0; candidates[i]; i++) {
        if (file_exists(candidates[i])) {
            strncpy(path, candidates[i], path_size - 1);
            path[path_size - 1] = '\0';
            return 0;
        }
    }

    {
        const char *local = getenv("LOCALAPPDATA");
        if (local) {
            snprintf(path, path_size,
                     "%s\\Yandex\\YandexBrowser\\Application\\browser.exe", local);
            if (file_exists(path))
                return 0;
        }
    }
#else
    static const char *candidates[] = {
        "/usr/bin/google-chrome",
        "/usr/bin/chromium",
        "/usr/bin/chromium-browser",
        "/usr/bin/microsoft-edge",
        "/usr/bin/yandex-browser",
        NULL
    };
    int i;

    for (i = 0; candidates[i]; i++) {
        if (file_exists(candidates[i])) {
            strncpy(path, candidates[i], path_size - 1);
            path[path_size - 1] = '\0';
            return 0;
        }
    }
#endif
    return -1;
}

static int launch_browser(const char *browser_path)
{
    char cmd[2048];
#ifdef _WIN32
    snprintf(cmd, sizeof(cmd),
             "start \"\" \"%s\" --remote-debugging-port=%d \"https://ya.ru\"",
             browser_path, CDP_PORT);
#else
    snprintf(cmd, sizeof(cmd),
             "\"%s\" --remote-debugging-port=%d \"https://ya.ru\" >/dev/null 2>&1 &",
             browser_path, CDP_PORT);
#endif
    return system(cmd);
}

/* ---------- основной сценарий ---------- */

static int click_target_link(const char *ws_url, const char *domain)
{
    char js[4096];
    char escaped_domain[256];

    json_escape(domain, escaped_domain, sizeof(escaped_domain));
    snprintf(js, sizeof(js),
             "(function(){"
             "var links=document.querySelectorAll('a');"
             "for(var i=0;i<links.length;i++){"
             "if(links[i].href&&links[i].href.indexOf('%s')!==-1){"
             "links[i].scrollIntoView({behavior:'smooth',block:'center'});"
             "links[i].click();"
             "return true;"
             "}"
             "}"
             "return false;"
             "})()",
             escaped_domain);
    return cdp_evaluate(ws_url, js);
}

int main(int argc, char *argv[])
{
    const char *query  = (argc > 1 && argv[1][0]) ? argv[1] : DEFAULT_QUERY;
    const char *domain = (argc > 2 && argv[2][0]) ? argv[2] : DEFAULT_DOMAIN;

    char browser[1024];
    char ws_url[512];
    char ch[8];

    srand((unsigned)time(NULL));

    if (find_browser(browser, sizeof(browser)) != 0) {
        fprintf(stderr, "Browser not found.\n");
        return 1;
    }

    if (launch_browser(browser) != 0) {
        fprintf(stderr, "Failed to launch browser.\n");
        return 1;
    }

    sleep_ms(3000);

    if (cdp_get_ws_url(ws_url, sizeof(ws_url)) != 0) {
        fprintf(stderr, "CDP endpoint not available on port %d.\n", CDP_PORT);
        return 1;
    }

    cdp_evaluate(ws_url, "document.querySelector('input[name=\"text\"]').focus()");

    for (const char *p = query; p && *p; ) {
        p = next_utf8(p, ch, sizeof(ch));
        if (!p && !ch[0])
            break;
        cdp_key_char(ws_url, ch);
        sleep_ms(100 + (rand() % 100));
    }

    cdp_key_enter(ws_url);
    sleep_ms(4000);

    click_target_link(ws_url, domain);
    sleep_ms(5000);

    cdp_evaluate(ws_url, "window.scrollBy(0, 350)");
    sleep_ms(2000);
    cdp_evaluate(ws_url, "window.scrollBy(0, 350)");

    return 0;
}