false, 'message' => 'Invalid date.']); if ($train === '' || $from === '' || $to === '') avail_out(['status' => false, 'message' => 'Missing parameters.']); if (!in_array($cls, ['SL', '3A', '2A', '1A', 'CC', 'EC', '2S', '3E', 'FC'], true)) $cls = 'SL'; $pdo = db(); $pdo->exec("CREATE TABLE IF NOT EXISTS avail_cache (ckey CHAR(40) PRIMARY KEY, payload MEDIUMTEXT, created_at INT)"); $pdo->exec("CREATE TABLE IF NOT EXISTS rapidapi_usage (ym CHAR(6) PRIMARY KEY, calls INT NOT NULL DEFAULT 0)"); $pdo->exec("CREATE TABLE IF NOT EXISTS train_classes (train_no VARCHAR(10), class VARCHAR(4), valid TINYINT, updated_at INT, PRIMARY KEY (train_no, class))"); /* ---- analytics: count this availability check (cached/short-circuited ones count too) ---- */ $__an = __DIR__ . '/app/analytics.php'; if (is_file($__an)) { require_once $__an; rr_track('availability', ['train_no' => $train, 'from_code' => $from, 'to_code' => $to, 'jdate' => $date, 'note' => $cls . '/' . $quota]); } /* ---- short-circuit: we already learned this train has no such class ---- */ $kc = $pdo->prepare("SELECT valid FROM train_classes WHERE train_no = ? AND class = ?"); $kc->execute([$train, $cls]); $known = $kc->fetchColumn(); if ($known !== false && (int) $known === 0) { avail_out(['status' => false, 'noclass' => true, 'message' => "This train doesn't have $cls class."]); } /* ---- cache (success or cached rejection), per exact query ---- */ $ckey = sha1("$train|$from|$to|$cls|$quota|$date"); $cr = $pdo->prepare("SELECT payload, created_at FROM avail_cache WHERE ckey = ?"); $cr->execute([$ckey]); $c = $cr->fetch(PDO::FETCH_ASSOC); if ($c && (time() - (int) $c['created_at'] < AVAIL_CACHE_TTL)) { $p = json_decode($c['payload'], true); if (!empty($p['ok'])) avail_out(['status' => true, 'cached' => true, 'class' => $cls, 'data' => $p['data']]); avail_out(['status' => false, 'cached' => true, 'apierror' => true, 'message' => $p['err'] ?? 'This train/segment can\'t be checked here.']); } /* ---- monthly budget guard ---- */ $ym = date('Ym'); $uq = $pdo->prepare("SELECT calls FROM rapidapi_usage WHERE ym = ?"); $uq->execute([$ym]); $used = (int) ($uq->fetchColumn() ?: 0); if ($used >= AVAIL_MONTHLY_BUDGET) { avail_out(['status' => false, 'budget' => true, 'message' => 'Live availability is paused for this month. Please check on the RailOne app or IRCTC.']); } $apiKey = defined('RAPIDAPI_KEY') ? RAPIDAPI_KEY : (getenv('RAPIDAPI_KEY') ?: ''); if ($apiKey === '') avail_out(['status' => false, 'message' => 'Availability service not configured.']); $qs = http_build_query([ 'classType' => $cls, 'fromStationCode' => $from, 'quota' => $quota, 'toStationCode' => $to, 'trainNo' => $train, 'date' => $date, ]); $ch = curl_init(AVAIL_ENDPOINT . '?' . $qs); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15, CURLOPT_HTTPHEADER => ['x-rapidapi-key: ' . $apiKey, 'x-rapidapi-host: ' . AVAIL_HOST], ]); $resp = curl_exec($ch); $code = (int) curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($resp === false) { avail_out(['status' => false, 'message' => 'Could not reach the availability service. Try again, or check on RailOne / IRCTC.']); } if ($code === 429) { avail_out(['status' => false, 'budget' => true, 'message' => 'Availability is busy right now. Please check on the RailOne app or IRCTC.']); } $json = json_decode($resp, true); if ($code >= 400 || !is_array($json)) { avail_out(['status' => false, 'http' => $code, 'message' => 'Could not fetch availability right now. Try again, or check on RailOne / IRCTC.']); } /* A JSON body came back (HTTP 200) — this spends one call. Count it. */ $pdo->prepare("INSERT INTO rapidapi_usage (ym, calls) VALUES (?, 1) ON DUPLICATE KEY UPDATE calls = calls + 1")->execute([$ym]); if (empty($json['status'])) { $raw = (string) ($json['message'] ?? ''); /* Learn "class does not exist" so we never spend another credit on it. */ if (stripos($raw, 'class') !== false && (stripos($raw, 'exist') !== false || stripos($raw, 'not available') !== false)) { $pdo->prepare("REPLACE INTO train_classes (train_no, class, valid, updated_at) VALUES (?, ?, 0, ?)") ->execute([$train, $cls, time()]); avail_out(['status' => false, 'noclass' => true, 'message' => "This train doesn't have $cls class."]); } $msg = (stripos($raw, 'valid train') !== false || $raw === '') ? 'Live availability isn\'t available for this train/segment here. You can check on IRCTC or the RailOne app.' : $raw; $pdo->prepare("REPLACE INTO avail_cache (ckey, payload, created_at) VALUES (?, ?, ?)") ->execute([$ckey, json_encode(['ok' => false, 'err' => $msg]), time()]); avail_out(['status' => false, 'apierror' => true, 'message' => $msg]); } /* Success — remember this class is valid for the train. */ $pdo->prepare("REPLACE INTO train_classes (train_no, class, valid, updated_at) VALUES (?, ?, 1, ?)") ->execute([$train, $cls, time()]); $data = $json['data'] ?? []; $pdo->prepare("REPLACE INTO avail_cache (ckey, payload, created_at) VALUES (?, ?, ?)") ->execute([$ckey, json_encode(['ok' => true, 'data' => $data]), time()]); avail_out(['status' => true, 'cached' => false, 'class' => $cls, 'data' => $data]);