<?php
/**
 * NetStats Node Agent for netstats.ir
 * Upload to your public web root. Set the constants below after admin approval.
 *
 * Endpoints:
 *   ?action=health
 *   ?action=run   (POST JSON, Authorization: Bearer <NODE_TOKEN>)
 */
declare(strict_types=1);

// ===================== CONFIG =====================
const HUB_URL = 'https://netstats.ir';
const NODE_TOKEN = '';          // paste after approval
const HUB_HMAC_SECRET = '';     // paste after approval
const NODE_NAME = 'node';
const ALLOW_PRIVATE_TARGETS = false;
const AGENT_VERSION = '1.0.0';
const MAX_BODY = 65536;
const DEFAULT_TIMEOUT = 10;
// ==================================================

header('X-Content-Type-Options: nosniff');
header('X-Frame-Options: DENY');
header('Referrer-Policy: no-referrer');

$action = strtolower((string) ($_GET['action'] ?? 'health'));

if ($action === 'health') {
    ns_agent_json([
        'ok' => 1,
        'version' => AGENT_VERSION,
        'name' => NODE_NAME,
    ]);
}

if ($action !== 'run') {
    ns_agent_json(['ok' => 0, 'error' => 'Unknown action'], 404);
}

if (($_SERVER['REQUEST_METHOD'] ?? '') !== 'POST') {
    ns_agent_json(['ok' => 0, 'error' => 'POST required'], 405);
}

if (NODE_TOKEN === '' || HUB_HMAC_SECRET === '') {
    ns_agent_json(['ok' => 0, 'error' => 'Agent not configured'], 503);
}

$auth = $_SERVER['HTTP_AUTHORIZATION'] ?? ($_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? '');
if (!preg_match('/^Bearer\s+(\S+)$/i', $auth, $m) || !hash_equals(NODE_TOKEN, $m[1])) {
    ns_agent_json(['ok' => 0, 'error' => 'Unauthorized'], 401);
}

$len = (int) ($_SERVER['CONTENT_LENGTH'] ?? 0);
if ($len > MAX_BODY) {
    ns_agent_json(['ok' => 0, 'error' => 'Payload too large'], 413);
}
$raw = file_get_contents('php://input', false, null, 0, MAX_BODY + 1);
if ($raw === false || strlen($raw) > MAX_BODY) {
    ns_agent_json(['ok' => 0, 'error' => 'Payload too large'], 413);
}
$job = json_decode($raw, true);
if (!is_array($job)) {
    ns_agent_json(['ok' => 0, 'error' => 'Invalid JSON'], 400);
}

$requestId = (string) ($job['request_id'] ?? '');
$nodeId = (string) ($job['node_id'] ?? '');
$type = strtolower((string) ($job['type'] ?? ''));
$host = (string) ($job['host'] ?? '');
$timeout = (int) ($job['timeout'] ?? DEFAULT_TIMEOUT);
$exp = (int) ($job['exp'] ?? 0);
$nonce = (string) ($job['nonce'] ?? '');
$sig = (string) ($job['sig'] ?? '');
$callback = (string) ($job['callback'] ?? '');
$allowPrivate = !empty($job['allow_private']) || ALLOW_PRIVATE_TARGETS;

if ($exp < time()) {
    ns_agent_json(['ok' => 0, 'error' => 'Job expired'], 403);
}
if (!preg_match('/^[a-zA-Z0-9_-]{8,64}$/', $requestId) || $nonce === '' || $sig === '') {
    ns_agent_json(['ok' => 0, 'error' => 'Bad job'], 400);
}

$signFields = [
    'request_id' => $requestId,
    'node_id' => $nodeId,
    'type' => $type,
    'host' => $host,
    'timeout' => $timeout,
    'exp' => $exp,
    'nonce' => $nonce,
    'allow_private' => !empty($job['allow_private']) ? 1 : 0,
];
if (!ns_agent_hmac_verify($signFields, $sig, HUB_HMAC_SECRET)) {
    ns_agent_json(['ok' => 0, 'error' => 'Bad signature'], 403);
}

if (!in_array($type, ['ping', 'http', 'tcp', 'dns', 'udp'], true)) {
    ns_agent_json(['ok' => 0, 'error' => 'Unsupported type'], 400);
}

$timeout = max(2, min(20, $timeout));

try {
    $target = ns_agent_parse_target($host, $type, $allowPrivate);
    $result = ns_agent_run_check($type, $target, $timeout);
} catch (Throwable $e) {
    $result = ['error' => $e->getMessage()];
}

// Optional callback to hub (best-effort); also return synchronously
if ($callback !== '' && preg_match('#^https://#i', $callback)) {
    ns_agent_callback($callback, $requestId, $nodeId, $result);
}

ns_agent_json(['ok' => 1, 'result' => $result]);

// ----------------- helpers -----------------

function ns_agent_json(array $data, int $status = 200): void
{
    http_response_code($status);
    header('Content-Type: application/json; charset=utf-8');
    echo json_encode($data, JSON_UNESCAPED_SLASHES);
    exit;
}

function ns_agent_hmac_canonical(array $payload): string
{
    ksort($payload);
    $parts = [];
    foreach ($payload as $k => $v) {
        if (is_bool($v)) {
            $v = $v ? '1' : '0';
        }
        $parts[] = $k . '=' . (string) $v;
    }
    return implode('&', $parts);
}

function ns_agent_hmac_verify(array $payload, string $signature, string $secret): bool
{
    $expected = hash_hmac('sha256', ns_agent_hmac_canonical($payload), $secret);
    return hash_equals($expected, $signature);
}

function ns_agent_normalize_ip(string $ip): ?string
{
    $bin = @inet_pton(trim($ip, '[]'));
    if ($bin === false) {
        return null;
    }
    $out = @inet_ntop($bin);
    return $out === false ? null : $out;
}

function ns_agent_is_ipv6(string $ip): bool
{
    return (bool) filter_var(trim($ip, '[]'), FILTER_VALIDATE_IP, FILTER_FLAG_IPV6);
}

function ns_agent_is_ipv4(string $ip): bool
{
    return (bool) filter_var(trim($ip, '[]'), FILTER_VALIDATE_IP, FILTER_FLAG_IPV4);
}

function ns_agent_host_for_url(string $host): string
{
    $host = trim($host, '[]');
    return ns_agent_is_ipv6($host) ? '[' . $host . ']' : $host;
}

function ns_agent_host_for_socket(string $ip): string
{
    $ip = trim($ip, '[]');
    return ns_agent_is_ipv6($ip) ? '[' . $ip . ']' : $ip;
}

function ns_agent_ip_in_cidr(string $ip, string $cidr): bool
{
    $ipNorm = ns_agent_normalize_ip($ip);
    if ($ipNorm === null) {
        return false;
    }
    if (!str_contains($cidr, '/')) {
        $entry = ns_agent_normalize_ip($cidr);
        return $entry !== null && $entry === $ipNorm;
    }
    [$subnet, $mask] = array_pad(explode('/', $cidr, 2), 2, '');
    $mask = (int) $mask;
    $ipBin = @inet_pton($ipNorm);
    $subnetBin = @inet_pton(trim($subnet, '[]'));
    if ($ipBin === false || $subnetBin === false || strlen($ipBin) !== strlen($subnetBin)) {
        return false;
    }
    $maxBits = strlen($ipBin) * 8;
    if ($mask < 0 || $mask > $maxBits) {
        return false;
    }
    $bytes = intdiv($mask, 8);
    $bits = $mask % 8;
    if ($bytes > 0 && substr($ipBin, 0, $bytes) !== substr($subnetBin, 0, $bytes)) {
        return false;
    }
    if ($bits === 0) {
        return true;
    }
    $maskByte = (~((1 << (8 - $bits)) - 1)) & 0xFF;
    return (ord($ipBin[$bytes]) & $maskByte) === (ord($subnetBin[$bytes]) & $maskByte);
}

function ns_agent_mapped_ipv4(string $ip): ?string
{
    $bin = @inet_pton(trim($ip, '[]'));
    if ($bin === false || strlen($bin) !== 16) {
        return null;
    }
    if (substr($bin, 0, 10) === str_repeat("\0", 10) && substr($bin, 10, 2) === "\xff\xff") {
        return inet_ntop(substr($bin, 12)) ?: null;
    }
    return null;
}

function ns_agent_is_blocked_ip(string $ip, bool $allowPrivate): bool
{
    if ($allowPrivate) {
        return false;
    }
    $norm = ns_agent_normalize_ip($ip);
    if ($norm === null) {
        return true;
    }
    if (ns_agent_is_ipv4($norm)) {
        $long = ip2long($norm);
        if ($long === false) {
            return true;
        }
        $ranges = [
            ['0.0.0.0', '0.255.255.255'],
            ['10.0.0.0', '10.255.255.255'],
            ['100.64.0.0', '100.127.255.255'],
            ['127.0.0.0', '127.255.255.255'],
            ['169.254.0.0', '169.254.255.255'],
            ['172.16.0.0', '172.31.255.255'],
            ['192.0.0.0', '192.0.0.255'],
            ['192.168.0.0', '192.168.255.255'],
            ['198.18.0.0', '198.19.255.255'],
            ['224.0.0.0', '255.255.255.255'],
        ];
        foreach ($ranges as [$a, $b]) {
            if ($long >= ip2long($a) && $long <= ip2long($b)) {
                return true;
            }
        }
        return false;
    }
    $mapped = ns_agent_mapped_ipv4($norm);
    if ($mapped !== null) {
        return ns_agent_is_blocked_ip($mapped, $allowPrivate);
    }
    foreach (['::1/128', '::/128', '100::/64', '2001:db8::/32', 'fc00::/7', 'fe80::/10', 'ff00::/8'] as $cidr) {
        if (ns_agent_ip_in_cidr($norm, $cidr)) {
            return true;
        }
    }
    return false;
}

function ns_agent_sort_ips(array $ips): array
{
    $v6 = [];
    $v4 = [];
    foreach ($ips as $ip) {
        $n = ns_agent_normalize_ip((string) $ip);
        if ($n === null) {
            continue;
        }
        if (ns_agent_is_ipv6($n)) {
            $v6[] = $n;
        } else {
            $v4[] = $n;
        }
    }
    return array_values(array_unique(array_merge($v6, $v4)));
}

function ns_agent_resolve(string $host, bool $allowPrivate): array
{
    $host = trim($host, '[]');
    if (filter_var($host, FILTER_VALIDATE_IP)) {
        $norm = ns_agent_normalize_ip($host);
        if ($norm === null || ns_agent_is_blocked_ip($norm, $allowPrivate)) {
            throw new InvalidArgumentException('Target IP not allowed');
        }
        return [$norm];
    }
    $ips = [];
    $recs = @dns_get_record($host, DNS_A + DNS_AAAA);
    if (is_array($recs)) {
        foreach ($recs as $r) {
            if (!empty($r['ipv6'])) {
                $ips[] = $r['ipv6'];
            }
            if (!empty($r['ip'])) {
                $ips[] = $r['ip'];
            }
        }
    }
    if (!$ips) {
        $aaaa = @dns_get_record($host, DNS_AAAA);
        if (is_array($aaaa)) {
            foreach ($aaaa as $r) {
                if (!empty($r['ipv6'])) {
                    $ips[] = $r['ipv6'];
                }
            }
        }
    }
    if (!$ips) {
        $a = @gethostbynamel($host);
        if (is_array($a)) {
            $ips = $a;
        }
    }
    $public = [];
    foreach ($ips as $ip) {
        $norm = ns_agent_normalize_ip((string) $ip);
        if ($norm === null || ns_agent_is_blocked_ip($norm, $allowPrivate)) {
            continue;
        }
        $public[] = $norm;
    }
    $public = ns_agent_sort_ips($public);
    if (!$public) {
        throw new InvalidArgumentException('Host could not be resolved');
    }
    return $public;
}

function ns_agent_parse_target(string $raw, string $type, bool $allowPrivate): array
{
    $raw = trim($raw);
    if ($raw === '' || strlen($raw) > 512 || preg_match('/\s/', $raw) || str_contains($raw, '@')) {
        throw new InvalidArgumentException('Invalid host');
    }
    $scheme = null;
    $host = $raw;
    $port = null;
    $path = '/';

    if (preg_match('#^(https?)://#i', $raw, $m)) {
        $scheme = strtolower($m[1]);
        $parts = parse_url($raw);
        if ($parts === false || empty($parts['host']) || !empty($parts['user'])) {
            throw new InvalidArgumentException('Invalid URL');
        }
        $host = (string) $parts['host'];
        $port = isset($parts['port']) ? (int) $parts['port'] : ($scheme === 'https' ? 443 : 80);
        $path = ($parts['path'] ?? '/') . (isset($parts['query']) ? '?' . $parts['query'] : '');
    } elseif (preg_match('/^\[([^\]]+)\](?::(\d+))?$/', $raw, $m)) {
        $host = $m[1];
        $port = isset($m[2]) ? (int) $m[2] : null;
        if (!ns_agent_is_ipv6($host)) {
            throw new InvalidArgumentException('Invalid IPv6 address');
        }
    } elseif (filter_var($raw, FILTER_VALIDATE_IP)) {
        $host = $raw;
    } elseif (preg_match('/^(\d{1,3}(?:\.\d{1,3}){3}):(\d+)$/', $raw, $m)) {
        $host = $m[1];
        $port = (int) $m[2];
    } elseif (preg_match('/^([^:\/\[\]]+):(\d+)$/', $raw, $m)) {
        $host = $m[1];
        $port = (int) $m[2];
    }

    $host = trim($host, '[]');
    if (ns_agent_is_ipv4($host) || ns_agent_is_ipv6($host)) {
        $norm = ns_agent_normalize_ip($host);
        if ($norm === null) {
            throw new InvalidArgumentException('Invalid IP');
        }
        $host = $norm;
    } else {
        $host = strtolower($host);
    }

    if (in_array($host, ['localhost', 'ip6-localhost', 'ip6-loopback', 'metadata.google.internal', 'metadata'], true)) {
        throw new InvalidArgumentException('Host not allowed');
    }
    if ($port !== null && ($port < 1 || $port > 65535)) {
        throw new InvalidArgumentException('Invalid port');
    }
    if ($type === 'http') {
        $scheme = $scheme ?? 'http';
        $port = $port ?? ($scheme === 'https' ? 443 : 80);
    } elseif (in_array($type, ['tcp', 'udp'], true) && $port === null) {
        throw new InvalidArgumentException('Port required (for IPv6 use [addr]:port)');
    }

    $ips = ns_agent_resolve($host, $allowPrivate);
    return compact('raw', 'host', 'port', 'scheme', 'path', 'ips');
}

function ns_agent_run_check(string $type, array $target, int $timeout)
{
    return match ($type) {
        'ping' => ns_agent_ping($target, $timeout),
        'http' => ns_agent_http($target, $timeout),
        'tcp' => ns_agent_tcp($target, $timeout),
        'dns' => ns_agent_dns($target),
        'udp' => ns_agent_udp($target, $timeout),
        default => ['error' => 'Unsupported'],
    };
}

function ns_agent_ping(array $target, int $timeout): array
{
    $ip = $target['ips'][0];
    $count = 4;
    $isWin = strtoupper(substr(PHP_OS, 0, 3)) === 'WIN';
    $isV6 = ns_agent_is_ipv6($ip);

    if ($isWin) {
        $args = $isV6
            ? ['ping', '-6', '-n', (string) $count, '-w', (string) ($timeout * 1000), $ip]
            : ['ping', '-n', (string) $count, '-w', (string) ($timeout * 1000), $ip];
        $out = ns_agent_proc($args, $timeout + 2);
    } else {
        $args = $isV6
            ? ['ping', '-6', '-c', (string) $count, '-W', (string) $timeout, $ip]
            : ['ping', '-c', (string) $count, '-W', (string) $timeout, $ip];
        $out = ns_agent_proc($args, $timeout + 2);
        if ($isV6 && !$out['ok']) {
            $out = ns_agent_proc(['ping6', '-c', (string) $count, '-W', (string) $timeout, $ip], $timeout + 2);
        }
    }
    $samples = [];
    if ($out['ok']) {
        foreach (preg_split('/\r\n|\n|\r/', $out['stdout']) as $line) {
            if (preg_match('/time[=<]([\d.]+)\s*ms/i', $line, $m)) {
                $samples[] = ['OK', ((float) $m[1]) / 1000.0, $ip];
            } elseif (preg_match('/timed out|100% packet loss|Destination Host Unreachable|Network is unreachable/i', $line)) {
                $samples[] = ['TIMEOUT', (float) $timeout];
            }
        }
    }

    if (!$samples) {
        $t0 = microtime(true);
        $errno = 0;
        $errstr = '';
        $fp = @fsockopen(ns_agent_host_for_socket($ip), 80, $errno, $errstr, min(3, $timeout));
        $dt = microtime(true) - $t0;
        if ($fp) {
            fclose($fp);
            $samples[] = ['OK', round($dt, 4), $ip];
        } else {
            $samples[] = ['TIMEOUT', round($dt, 4)];
        }
    }

    while (count($samples) < $count) {
        $samples[] = ['TIMEOUT', (float) $timeout];
    }
    return [array_slice($samples, 0, $count)];
}

function ns_agent_http(array $target, int $timeout): array
{
    $scheme = $target['scheme'] ?? 'http';
    $host = $target['host'];
    $url = $scheme . '://' . ns_agent_host_for_url($host);
    if (!empty($target['port']) && !(($scheme === 'http' && $target['port'] === 80) || ($scheme === 'https' && $target['port'] === 443))) {
        $url .= ':' . $target['port'];
    }
    $url .= $target['path'] ?? '/';

    $ip = $target['ips'][0];
    $port = (int) ($target['port'] ?: ($scheme === 'https' ? 443 : 80));
    $resolveIp = ns_agent_is_ipv6($ip) ? '[' . $ip . ']' : $ip;

    $opts = [
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_FOLLOWLOCATION => true,
        CURLOPT_MAXREDIRS => 3,
        CURLOPT_TIMEOUT => $timeout,
        CURLOPT_CONNECTTIMEOUT => min(5, $timeout),
        CURLOPT_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
        CURLOPT_REDIR_PROTOCOLS => CURLPROTO_HTTP | CURLPROTO_HTTPS,
        CURLOPT_USERAGENT => 'NetStats-Node/' . AGENT_VERSION,
        CURLOPT_HEADER => true,
        CURLOPT_NOBODY => false,
    ];
    if (defined('CURL_IPRESOLVE_V6')) {
        $opts[CURLOPT_IPRESOLVE] = ns_agent_is_ipv6($ip) ? CURL_IPRESOLVE_V6 : CURL_IPRESOLVE_V4;
    }
    if (!filter_var($host, FILTER_VALIDATE_IP)) {
        $opts[CURLOPT_RESOLVE] = [$host . ':' . $port . ':' . $resolveIp];
    }

    $ch = curl_init($url);
    curl_setopt_array($ch, $opts);

    curl_setopt($ch, CURLOPT_HEADERFUNCTION, static function ($ch, $header) {
        return strlen($header);
    });

    $body = curl_exec($ch);
    $errno = curl_errno($ch);
    $err = curl_error($ch);
    $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE);
    $total = (float) curl_getinfo($ch, CURLINFO_TOTAL_TIME);
    $primary = (string) curl_getinfo($ch, CURLINFO_PRIMARY_IP);
    curl_close($ch);

    if ($errno) {
        return [[0, round($total, 4), $err ?: 'error', null, null]];
    }
    if ($primary && ns_agent_is_blocked_ip($primary, ALLOW_PRIVATE_TARGETS)) {
        return [[0, round($total, 4), 'Blocked address', null, null]];
    }
    $ok = $code >= 200 && $code < 400 ? 1 : 0;
    $text = $ok ? 'OK' : 'Error';
    return [[$ok, round($total, 4), $text, (string) $code, $primary ?: $ip]];
}

function ns_agent_tcp(array $target, int $timeout): array
{
    $ip = $target['ips'][0];
    $port = (int) $target['port'];
    $t0 = microtime(true);
    $errno = 0;
    $errstr = '';
    $fp = @fsockopen(ns_agent_host_for_socket($ip), $port, $errno, $errstr, $timeout);
    $dt = round(microtime(true) - $t0, 4);
    if ($fp) {
        fclose($fp);
        return ['time' => $dt, 'address' => $ip];
    }
    return ['error' => $errstr !== '' ? $errstr : ('Connection failed #' . $errno)];
}

function ns_agent_udp(array $target, int $timeout): array
{
    $ip = $target['ips'][0];
    $port = (int) $target['port'];
    $t0 = microtime(true);
    $sock = @fsockopen('udp://' . ns_agent_host_for_socket($ip), $port, $errno, $errstr, $timeout);
    if (!$sock) {
        return ['ok' => 0, 'error' => $errstr !== '' ? $errstr : 'UDP open failed'];
    }
    stream_set_timeout($sock, $timeout);
    @fwrite($sock, "\x00");
    $dt = round(microtime(true) - $t0, 4);
    fclose($sock);
    return ['ok' => 1, 'time' => $dt, 'address' => $ip];
}

function ns_agent_dns(array $target): array
{
    $host = $target['host'];
    $a = [];
    $aaaa = [];
    $ttl = null;
    $recs = @dns_get_record($host, DNS_A + DNS_AAAA);
    if (is_array($recs)) {
        foreach ($recs as $r) {
            if (($r['type'] ?? '') === 'A' && !empty($r['ip'])) {
                $a[] = $r['ip'];
                if (isset($r['ttl'])) {
                    $ttl = (int) $r['ttl'];
                }
            }
            if (($r['type'] ?? '') === 'AAAA' && !empty($r['ipv6'])) {
                $aaaa[] = $r['ipv6'];
                if (isset($r['ttl'])) {
                    $ttl = (int) $r['ttl'];
                }
            }
        }
    }
    if (!$a && !$aaaa) {
        $a = @gethostbynamel($host) ?: [];
    }
    return ['A' => array_values(array_unique($a)), 'AAAA' => array_values(array_unique($aaaa)), 'TTL' => $ttl];
}

function ns_agent_proc(array $args, int $timeout): array
{
    $descriptors = [
        0 => ['pipe', 'r'],
        1 => ['pipe', 'w'],
        2 => ['pipe', 'w'],
    ];
    $proc = @proc_open($args, $descriptors, $pipes, null, null, ['bypass_shell' => true]);
    if (!is_resource($proc)) {
        return ['ok' => false, 'stdout' => '', 'stderr' => 'proc_open failed'];
    }
    fclose($pipes[0]);
    stream_set_blocking($pipes[1], false);
    stream_set_blocking($pipes[2], false);
    $stdout = '';
    $stderr = '';
    $start = time();
    do {
        $stdout .= stream_get_contents($pipes[1]) ?: '';
        $stderr .= stream_get_contents($pipes[2]) ?: '';
        $status = proc_get_status($proc);
        if (!$status['running']) {
            break;
        }
        usleep(50000);
    } while ((time() - $start) < $timeout);

    $status = proc_get_status($proc);
    if ($status['running']) {
        proc_terminate($proc);
    }
    $stdout .= stream_get_contents($pipes[1]) ?: '';
    $stderr .= stream_get_contents($pipes[2]) ?: '';
    fclose($pipes[1]);
    fclose($pipes[2]);
    proc_close($proc);
    return ['ok' => true, 'stdout' => $stdout, 'stderr' => $stderr];
}

function ns_agent_callback(string $url, string $requestId, string $nodeId, $result): void
{
    $exp = time() + 60;
    $nonce = bin2hex(random_bytes(12));
    $resultHash = hash('sha256', json_encode($result, JSON_UNESCAPED_SLASHES));
    $signFields = [
        'request_id' => $requestId,
        'node_id' => $nodeId,
        'exp' => $exp,
        'nonce' => $nonce,
        'result' => $resultHash,
    ];
    $payload = [
        'request_id' => $requestId,
        'node_id' => $nodeId,
        'exp' => $exp,
        'nonce' => $nonce,
        'result' => $result,
        'sig' => hash_hmac('sha256', ns_agent_hmac_canonical($signFields), HUB_HMAC_SECRET),
    ];
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_POST => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => 8,
        CURLOPT_HTTPHEADER => [
            'Content-Type: application/json',
            'Authorization: Bearer ' . NODE_TOKEN,
        ],
        CURLOPT_POSTFIELDS => json_encode($payload, JSON_UNESCAPED_SLASHES),
        CURLOPT_PROTOCOLS => CURLPROTO_HTTPS,
    ]);
    curl_exec($ch);
    curl_close($ch);
}
