From 424fd389517b7137834e83fcbec29a6e22ea2e0b Mon Sep 17 00:00:00 2001 From: poryajp <118083331+poryajp@users.noreply.github.com> Date: Sat, 22 Nov 2025 21:08:39 +0330 Subject: [PATCH 01/18] Update functions.php --- src/includes/functions.php | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/includes/functions.php b/src/includes/functions.php index 2390fdb..f19704e 100644 --- a/src/includes/functions.php +++ b/src/includes/functions.php @@ -1074,7 +1074,7 @@ function showPlansForCategoryAndServer($chat_id, $category_id, $server_id) { $message = "🛍 ٟلن‌های سرور «{$server_name}»\nموجودی ؎ما: " . number_format($user_balance) . " تومان\n\nلطفا ٟلن مورد ن؞ر خود را انتخاؚ کنید:"; $keyboard_buttons = []; foreach ($active_plans as $plan) { - $button_text = "{$plan['name']} | {$plan['volume_gb']}GB | " . number_format($plan['price']) . " تومان"; + $button_text = "{$plan['name']} | " . number_format($plan['price']) . " تومان | {$plan['volume_gb']} GB"; $keyboard_buttons[] = [['text' => $button_text, 'callback_data' => "buy_plan_{$plan['id']}"]]; } // فرمت callback جدید ؚرای کد تخفیف: apply_discount_code_{cat_ID}_{srv_ID} @@ -1247,7 +1247,7 @@ function completePurchase($user_id, $plan_id, $custom_name, $final_price, $disco $first_name = $user_data['first_name']; // ساخت نام کارؚری کامل و یکتا ؚرای ٟنل - $plan['full_username'] = preg_replace('/[^a-zA-Z0-9_.]/', '', $custom_name) . '_user' . $user_id . '_' . time(); + $plan['full_username'] = $user_id . '_' . rand(10, 99); $panel_user_data = createPanelUser($plan, $user_id, $plan_id); @@ -1331,4 +1331,4 @@ function completePurchase($user_id, $plan_id, $custom_name, $final_price, $disco 'success' => false, 'error_message' => "❌ متاسفانه در ایجاد سرویس ؎ما Ù…ØŽÚ©Ù„ÛŒ ٟی؎ آمد. لطفا ؚا ٟ؎تیؚانی تماس ؚگیرید. مؚلغی از حساؚ ؎ما کسر ن؎ده است." ]; -} \ No newline at end of file +} From 0972da201a9d047c08543f2195db9ea5a9f72d3d Mon Sep 17 00:00:00 2001 From: poryajp <118083331+poryajp@users.noreply.github.com> Date: Sat, 22 Nov 2025 23:14:40 +0330 Subject: [PATCH 02/18] Update cron.php --- src/cron.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cron.php b/src/cron.php index 7c24b2a..e6fbb19 100644 --- a/src/cron.php +++ b/src/cron.php @@ -8,7 +8,7 @@ require_once __DIR__ . '/includes/config.php'; require_once __DIR__ . '/includes/db.php'; require_once __DIR__ . '/includes/functions.php'; -require_once __DIR__ . '/includes/marzban_api.php'; +require_once __DIR__ . '/api/marzban_api.php'; echo "Cron job started at " . date('Y-m-d H:i:s') . "\n"; @@ -128,4 +128,4 @@ function checkInactiveUsers() echo "An error occurred: " . $e->getMessage() . "\n"; } -echo "Cron job finished at " . date('Y-m-d H:i:s') . "\n"; \ No newline at end of file +echo "Cron job finished at " . date('Y-m-d H:i:s') . "\n"; From cdb46eda7d2ed8e4c6e40770326ef30e91e69be6 Mon Sep 17 00:00:00 2001 From: poryajp <118083331+poryajp@users.noreply.github.com> Date: Tue, 25 Nov 2025 02:03:38 +0330 Subject: [PATCH 03/18] Update README.md --- README.md | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 2a4b294..93edad5 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,26 @@ expand_less 🀝 م؎ارکت در ٟروژه -از م؎ارکت ؎ما در توسعه و ؚهؚود ویزارد ٟنل استقؚال می‌کنیم. می‌توانید از طریق Pull Request تغییرات خود را ارسال کنید یا ؚا ثؚت Issue م؎کلات و ٟی؎نهادات خود را ؚا ما در میان ؚگذارید. +## 📝 تغییرات و اصلاحات اخیر (Recent Updates \u0026 Fixes) + +این نسخه ؎امل اصلاحات زیر جهت ؚهؚود عملکرد و رفع ایرادات نسخه اصلی می‌ؚا؎د: + +### 1⃣ اصلاح فرمت نام کارؚری (Username Generation) +- **فایل:** `src/includes/functions.php` +- **تغییر:** الگوی تولید نام کارؚری ؚرای سرویس‌های جدید تغییر کرد. +- **فرمت جدید:** `ChatID` + `_` + `2 Random Digits` +- **مثال:** `123456789_45` + +### 2⃣ اصلاح نمای؎ دکمه‌های خرید (RTL Fix) +- **فایل‌ها:** `src/includes/functions.php` و `src/bot.php` +- **تغییر:** جایگاه «قیمت» و «حجم» در دکمه‌های ؎ی؎ه‌ای جاؚجا ؎د. +- **هدف:** حل Ù…ØŽÚ©Ù„ ؚهم‌ریختگی متن فارسی در تلگرام (قرارگیری صحیح کلمه "تومان"). +- **فرمت نمای؎:** `20,000 تومان | 10 GB` + +### 3⃣ رفع خطای مسیردهی کرون‌جاؚ (Cron Job Path Fix) +- **فایل:** `src/cron.php` +- **تغییر:** اصلاح مسیر فراخوانی (Require) فایل‌های API. +- **توضیح:** مسیر فایل‌ها از ٟو؎ه ا؎تؚاه `includes/` ØšÙ‡ ٟو؎ه صحیح `api/` تغییر یافت تا از خطای `PHP Fatal error: Failed opening required` جلوگیری ؎ود. 📜 مجوز (License) From 542ac61bb331729469aa44bb093503e8e6aa8ccd Mon Sep 17 00:00:00 2001 From: poryajp <118083331+poryajp@users.noreply.github.com> Date: Fri, 28 Nov 2025 01:27:21 +0330 Subject: [PATCH 04/18] Add files via upload add web panel --- src/includes/functions.php | 319 ++++++++------ src/install.php | 646 ++++++++++++++++++++--------- src/web/README.md | 50 +++ src/web/assets/css/style.css | 430 +++++++++++++++++++ src/web/assets/js/main.js | 70 ++++ src/web/dashboard.php | 177 ++++++++ src/web/includes/auth.php | 53 +++ src/web/includes/session.php | 56 +++ src/web/includes/web_functions.php | 182 ++++++++ src/web/index.php | 207 +++++++++ src/web/pages/admins.php | 280 +++++++++++++ src/web/pages/broadcast.php | 480 +++++++++++++++++++++ src/web/pages/categories.php | 147 +++++++ src/web/pages/discount.php | 260 ++++++++++++ src/web/pages/guides.php | 239 +++++++++++ src/web/pages/payments.php | 256 ++++++++++++ src/web/pages/plans.php | 299 +++++++++++++ src/web/pages/servers.php | 369 ++++++++++++++++ src/web/pages/settings.php | 345 +++++++++++++++ src/web/pages/stats.php | 190 +++++++++ src/web/pages/users.php | 324 +++++++++++++++ 21 files changed, 5056 insertions(+), 323 deletions(-) create mode 100644 src/web/README.md create mode 100644 src/web/assets/css/style.css create mode 100644 src/web/assets/js/main.js create mode 100644 src/web/dashboard.php create mode 100644 src/web/includes/auth.php create mode 100644 src/web/includes/session.php create mode 100644 src/web/includes/web_functions.php create mode 100644 src/web/index.php create mode 100644 src/web/pages/admins.php create mode 100644 src/web/pages/broadcast.php create mode 100644 src/web/pages/categories.php create mode 100644 src/web/pages/discount.php create mode 100644 src/web/pages/guides.php create mode 100644 src/web/pages/payments.php create mode 100644 src/web/pages/plans.php create mode 100644 src/web/pages/servers.php create mode 100644 src/web/pages/settings.php create mode 100644 src/web/pages/stats.php create mode 100644 src/web/pages/users.php diff --git a/src/includes/functions.php b/src/includes/functions.php index f19704e..44e1fc0 100644 --- a/src/includes/functions.php +++ b/src/includes/functions.php @@ -10,7 +10,8 @@ // ===================================================================== -function handleKeyboard($keyboard, $handleMainMenu = false) { +function handleKeyboard($keyboard, $handleMainMenu = false) +{ if (USER_INLINE_KEYBOARD) { if (is_null($keyboard)) { @@ -24,8 +25,7 @@ function handleKeyboard($keyboard, $handleMainMenu = false) { ] ] ]; - } - else { + } else { if (isset($keyboard['keyboard'])) { $keyboard = convertToInlineKeyboard($keyboard); } @@ -42,13 +42,13 @@ function handleKeyboard($keyboard, $handleMainMenu = false) { if (is_null($keyboard)) { return null; - } - else { + } else { return json_encode($keyboard); } } -function convertToInlineKeyboard($keyboard) { +function convertToInlineKeyboard($keyboard) +{ $inlineKeyboard = []; if (isset($keyboard['keyboard'])) { @@ -66,15 +66,15 @@ function convertToInlineKeyboard($keyboard) { $inlineKeyboard[] = $inlineRow; } } - } - else { + } else { return null; } return ['inline_keyboard' => $inlineKeyboard]; } -function array_str_contains(array $array, string|array $needle): bool { +function array_str_contains(array $array, string|array $needle): bool +{ if (is_array($needle)) { foreach ($needle as $n) { if (array_str_contains($array, $n)) { @@ -89,15 +89,15 @@ function array_str_contains(array $array, string|array $needle): bool { if (array_str_contains($item, $needle)) { return true; } - } - elseif (is_string($item) && stripos($item, $needle) !== false) { + } elseif (is_string($item) && stripos($item, $needle) !== false) { return true; } } return false; } -function sendMessage($chat_id, $text, $keyboard = null, $handleMainMenu = false) { +function sendMessage($chat_id, $text, $keyboard = null, $handleMainMenu = false) +{ $params = ['chat_id' => $chat_id, 'text' => $text, 'reply_markup' => handleKeyboard($keyboard, $handleMainMenu), 'parse_mode' => 'HTML']; global $update, $oneTimeEdit; @@ -111,51 +111,56 @@ function sendMessage($chat_id, $text, $keyboard = null, $handleMainMenu = false) return apiRequest('sendMessage', $params); } return $result; - } - else { + } else { return apiRequest('sendMessage', $params); } } -function forwardMessage($to_chat_id, $from_chat_id, $message_id) { +function forwardMessage($to_chat_id, $from_chat_id, $message_id) +{ $params = ['chat_id' => $to_chat_id, 'from_chat_id' => $from_chat_id, 'message_id' => $message_id]; return apiRequest('forwardMessage', $params); } -function sendPhoto($chat_id, $photo, $caption, $keyboard = null) { +function sendPhoto($chat_id, $photo, $caption, $keyboard = null) +{ $params = ['chat_id' => $chat_id, 'photo' => $photo, 'caption' => $caption, 'reply_markup' => handleKeyboard($keyboard), 'parse_mode' => 'HTML']; return apiRequest('sendPhoto', $params); } -function editMessageText($chat_id, $message_id, $text, $keyboard = null) { +function editMessageText($chat_id, $message_id, $text, $keyboard = null) +{ $params = ['chat_id' => $chat_id, 'message_id' => $message_id, 'text' => $text, 'reply_markup' => handleKeyboard($keyboard), 'parse_mode' => 'HTML']; global $oneTimeEdit; if (USER_INLINE_KEYBOARD && $oneTimeEdit) { $oneTimeEdit = false; return apiRequest('editMessageText', $params); - } - else { - + } else { + unset($params['message_id']); return apiRequest('sendMessage', $params); } } -function editMessageCaption($chat_id, $message_id, $caption, $keyboard = null) { +function editMessageCaption($chat_id, $message_id, $caption, $keyboard = null) +{ $params = ['chat_id' => $chat_id, 'message_id' => $message_id, 'caption' => $caption, 'reply_markup' => handleKeyboard($keyboard), 'parse_mode' => 'HTML']; return apiRequest('editMessageCaption', $params); } -function deleteMessage($chat_id, $message_id) { +function deleteMessage($chat_id, $message_id) +{ global $update, $oneTimeEdit; - if (USER_INLINE_KEYBOARD && !$oneTimeEdit && isset($update['callback_query']['message']['message_id']) && $update['callback_query']['message']['message_id'] == $message_id) return false; + if (USER_INLINE_KEYBOARD && !$oneTimeEdit && isset($update['callback_query']['message']['message_id']) && $update['callback_query']['message']['message_id'] == $message_id) + return false; $params = ['chat_id' => $chat_id, 'message_id' => $message_id]; return apiRequest('deleteMessage', $params); } -function apiRequest($method, $params = []) { +function apiRequest($method, $params = []) +{ global $apiRequest; $apiRequest = true; @@ -180,7 +185,8 @@ function apiRequest($method, $params = []) { // ===================================================================== // --- مدیریت کارؚران --- -function getUserData($chat_id, $first_name = 'کارؚر') { +function getUserData($chat_id, $first_name = 'کارؚر') +{ pdo() ->prepare("UPDATE users SET last_seen_at = CURRENT_TIMESTAMP, reminder_sent = 0 WHERE chat_id = ?") ->execute([$chat_id]); @@ -191,7 +197,7 @@ function getUserData($chat_id, $first_name = 'کارؚر') { if (!$user) { $settings = getSettings(); - $welcome_gift = (int)($settings['welcome_gift_balance'] ?? 0); + $welcome_gift = (int) ($settings['welcome_gift_balance'] ?? 0); $stmt = pdo()->prepare("INSERT INTO users (chat_id, first_name, balance, user_state) VALUES (?, ?, ?, 'main_menu')"); $stmt->execute([$chat_id, $first_name, $welcome_gift]); @@ -211,47 +217,53 @@ function getUserData($chat_id, $first_name = 'کارؚر') { return $user; } -function updateUserData($chat_id, $state, $data = []) { +function updateUserData($chat_id, $state, $data = []) +{ $state_data_json = json_encode($data, JSON_UNESCAPED_UNICODE); $stmt = pdo()->prepare("UPDATE users SET user_state = ?, state_data = ? WHERE chat_id = ?"); $stmt->execute([$state, $state_data_json, $chat_id]); } -function updateUserBalance($chat_id, $amount, $operation = 'add') { +function updateUserBalance($chat_id, $amount, $operation = 'add') +{ if ($operation == 'add') { $stmt = pdo()->prepare("UPDATE users SET balance = balance + ? WHERE chat_id = ?"); - } - else { + } else { $stmt = pdo()->prepare("UPDATE users SET balance = balance - ? WHERE chat_id = ?"); } $stmt->execute([$amount, $chat_id]); } -function setUserStatus($chat_id, $status) { +function setUserStatus($chat_id, $status) +{ $stmt = pdo()->prepare("UPDATE users SET status = ? WHERE chat_id = ?"); $stmt->execute([$status, $chat_id]); } -function getAllUsers() { +function getAllUsers() +{ return pdo() ->query("SELECT chat_id FROM users WHERE status = 'active'") ->fetchAll(PDO::FETCH_COLUMN); } -function increaseAllUsersBalance($amount) { +function increaseAllUsersBalance($amount) +{ $stmt = pdo()->prepare("UPDATE users SET balance = balance + ? WHERE status = 'active'"); $stmt->execute([$amount]); return $stmt->rowCount(); } -function resetAllUsersTestCount() { +function resetAllUsersTestCount() +{ $stmt = pdo()->prepare("UPDATE users SET test_config_count = 0"); $stmt->execute(); return $stmt->rowCount(); } // --- مدیریت ادمین‌ها --- -function getAdmins() { +function getAdmins() +{ $stmt = pdo()->prepare("SELECT * FROM admins WHERE is_super_admin = 0"); $stmt->execute(); $admins_from_db = $stmt->fetchAll(PDO::FETCH_ASSOC); @@ -265,22 +277,26 @@ function getAdmins() { return $admins; } -function addAdmin($chat_id, $first_name) { +function addAdmin($chat_id, $first_name, $permissions = []) +{ $stmt = pdo()->prepare("INSERT INTO admins (chat_id, first_name, permissions, is_super_admin) VALUES (?, ?, ?, ?)"); - return $stmt->execute([$chat_id, $first_name, json_encode([]), 0]); + return $stmt->execute([$chat_id, $first_name, json_encode($permissions), 0]); } -function removeAdmin($chat_id) { +function removeAdmin($chat_id) +{ $stmt = pdo()->prepare("DELETE FROM admins WHERE chat_id = ? AND is_super_admin = 0"); return $stmt->execute([$chat_id]); } -function updateAdminPermissions($chat_id, $permissions) { +function updateAdminPermissions($chat_id, $permissions) +{ $stmt = pdo()->prepare("UPDATE admins SET permissions = ? WHERE chat_id = ?"); return $stmt->execute([json_encode($permissions), $chat_id]); } -function isUserAdmin($chat_id) { +function isUserAdmin($chat_id) +{ if ($chat_id == ADMIN_CHAT_ID) { return true; } @@ -289,7 +305,8 @@ function isUserAdmin($chat_id) { return $stmt->fetchColumn() > 0; } -function hasPermission($chat_id, $permission) { +function hasPermission($chat_id, $permission) +{ if ($chat_id == ADMIN_CHAT_ID) { return true; } @@ -306,7 +323,8 @@ function hasPermission($chat_id, $permission) { } // --- مدیریت تن؞یمات --- -function getSettings() { +function getSettings() +{ $stmt = pdo()->query("SELECT * FROM settings"); $settings_from_db = $stmt->fetchAll(PDO::FETCH_KEY_PAIR); @@ -342,7 +360,8 @@ function getSettings() { return $settings_from_db; } -function saveSettings($settings) { +function saveSettings($settings) +{ foreach ($settings as $key => $value) { if (is_array($value)) { $value = json_encode($value, JSON_UNESCAPED_UNICODE); @@ -353,7 +372,8 @@ function saveSettings($settings) { } // --- مدیریت دسته‌ؚندی‌ها، ٟلن‌ها و سرویس‌ها --- -function getCategories($only_active = false) { +function getCategories($only_active = false) +{ $sql = "SELECT * FROM categories"; if ($only_active) { $sql .= " WHERE status = 'active'"; @@ -363,31 +383,36 @@ function getCategories($only_active = false) { ->fetchAll(PDO::FETCH_ASSOC); } -function getPlans() { +function getPlans() +{ return pdo() ->query("SELECT * FROM plans WHERE is_test_plan = 0") ->fetchAll(PDO::FETCH_ASSOC); } -function getPlansForCategory($category_id) { +function getPlansForCategory($category_id) +{ $stmt = pdo()->prepare("SELECT * FROM plans WHERE category_id = ? AND status = 'active' AND is_test_plan = 0"); $stmt->execute([$category_id]); return $stmt->fetchAll(PDO::FETCH_ASSOC); } -function getPlanById($plan_id) { +function getPlanById($plan_id) +{ $stmt = pdo()->prepare("SELECT * FROM plans WHERE id = ?"); $stmt->execute([$plan_id]); return $stmt->fetch(PDO::FETCH_ASSOC); } -function getTestPlan() { +function getTestPlan() +{ return pdo() ->query("SELECT * FROM plans WHERE is_test_plan = 1 AND status = 'active' LIMIT 1") ->fetch(PDO::FETCH_ASSOC); } -function getUserServices($chat_id) { +function getUserServices($chat_id) +{ $stmt = pdo()->prepare(" SELECT s.*, p.name as plan_name FROM services s @@ -399,12 +424,14 @@ function getUserServices($chat_id) { return $stmt->fetchAll(PDO::FETCH_ASSOC); } -function saveUserService($chat_id, $serviceData) { +function saveUserService($chat_id, $serviceData) +{ $stmt = pdo()->prepare("INSERT INTO services (owner_chat_id, server_id, marzban_username, custom_name, plan_id, sub_url, expire_timestamp, volume_gb) VALUES (?, ?, ?, ?, ?, ?, ?, ?)"); $stmt->execute([$chat_id, $serviceData['server_id'], $serviceData['username'], $serviceData['custom_name'], $serviceData['plan_id'], $serviceData['sub_url'], $serviceData['expire_timestamp'], $serviceData['volume_gb']]); } -function deleteUserService($chat_id, $username, $server_id) { +function deleteUserService($chat_id, $username, $server_id) +{ $stmt = pdo()->prepare("DELETE FROM services WHERE owner_chat_id = ? AND marzban_username = ? AND server_id = ?"); return $stmt->execute([$chat_id, $username, $server_id]); } @@ -413,7 +440,8 @@ function deleteUserService($chat_id, $username, $server_id) { // --- تواؚع کمکی و عمومی --- // ===================================================================== -function getPermissionMap() { +function getPermissionMap() +{ return [ 'manage_categories' => '🗂 مدیریت دسته‌ؚندی‌ها', 'manage_plans' => '📝 مدیریت ٟلن‌ها', @@ -431,7 +459,8 @@ function getPermissionMap() { ]; } -function checkJoinStatus($user_id) { +function checkJoinStatus($user_id) +{ $settings = getSettings(); $channel_id = $settings['join_channel_id']; if ($settings['join_channel_status'] !== 'on' || empty($channel_id)) { @@ -445,18 +474,21 @@ function checkJoinStatus($user_id) { return false; } -function generateQrCodeUrl($text) { +function generateQrCodeUrl($text) +{ return 'https://api.qrserver.com/v1/create-qr-code/?size=300x300&data=' . urlencode($text); } -function formatBytes($bytes, $precision = 2) { +function formatBytes($bytes, $precision = 2) +{ if ($bytes <= 0) { return "0 GB"; } return round(floatval($bytes) / pow(1024, 3), $precision) . ' GB'; } -function calculateIncomeStats() { +function calculateIncomeStats() +{ $stats = [ 'today' => pdo() @@ -482,7 +514,8 @@ function calculateIncomeStats() { // --- تواؚع نمای؎ منوها --- // ===================================================================== -function generateGuideList($chat_id) { +function generateGuideList($chat_id) +{ $stmt = pdo()->query("SELECT id, button_name, status FROM guides ORDER BY id DESC"); $guides = $stmt->fetchAll(PDO::FETCH_ASSOC); @@ -506,7 +539,8 @@ function generateGuideList($chat_id) { } } -function showGuideSelectionMenu($chat_id) { +function showGuideSelectionMenu($chat_id) +{ $stmt = pdo()->query("SELECT id, button_name FROM guides WHERE status = 'active' ORDER BY id ASC"); $guides = $stmt->fetchAll(PDO::FETCH_ASSOC); @@ -524,7 +558,8 @@ function showGuideSelectionMenu($chat_id) { sendMessage($chat_id, $message, ['inline_keyboard' => $keyboard_buttons]); } -function generateDiscountCodeList($chat_id) { +function generateDiscountCodeList($chat_id) +{ $stmt = pdo()->query("SELECT * FROM discount_codes ORDER BY id DESC"); $codes = $stmt->fetchAll(PDO::FETCH_ASSOC); @@ -553,7 +588,8 @@ function generateDiscountCodeList($chat_id) { } } -function generateCategoryList($chat_id) { +function generateCategoryList($chat_id) +{ $categories = getCategories(); if (empty($categories)) { sendMessage($chat_id, "هیچ دسته‌ؚندی‌ای یافت ن؎د."); @@ -574,7 +610,8 @@ function generateCategoryList($chat_id) { } } -function generatePlanList($chat_id) { +function generatePlanList($chat_id) +{ $plans = pdo() ->query("SELECT p.*, s.name as server_name, s.type as server_type FROM plans p LEFT JOIN servers s ON p.server_id = s.id ORDER BY p.is_test_plan DESC, p.id ASC") ->fetchAll(PDO::FETCH_ASSOC); @@ -597,19 +634,18 @@ function generatePlanList($chat_id) { $plan_info = ""; if ($plan['is_test_plan']) { $plan_info .= "🧪 (ٟلن تست) {$plan['name']}\n"; - } - else { + } else { $plan_info .= "{$status_icon} {$plan['name']}\n"; } $plan_info .= "▫ سرور: {$server_name}\n"; - + if ($plan['server_type'] === 'sanaei' && !empty($plan['inbound_id'])) { $plan_info .= "▫ اینؚاند: {$plan['inbound_id']}\n"; } elseif ($plan['server_type'] === 'marzneshin' && !empty($plan['marzneshin_service_id'])) { $plan_info .= "▫ سرویس: {$plan['marzneshin_service_id']}\n"; } - + $plan_info .= "▫ دسته‌ؚندی: {$cat_name}\n" . "▫ قیمت: " . number_format($plan['price']) . " تومان\n" . "▫ حجم: {$plan['volume_gb']} گیگاؚایت | " . "مدت: {$plan['duration_days']} روز\n"; if ($plan['purchase_limit'] > 0) { @@ -622,8 +658,7 @@ function generatePlanList($chat_id) { if ($plan['is_test_plan']) { $keyboard_buttons[] = [['text' => '↔ تؚدیل ØšÙ‡ ٟلن عادی', 'callback_data' => "make_plan_normal_{$plan_id}"]]; - } - else { + } else { $keyboard_buttons[] = [['text' => '🧪 تن؞یم ØšÙ‡ عنوان ٟلن تست', 'callback_data' => "set_as_test_plan_{$plan_id}"]]; } @@ -635,7 +670,8 @@ function generatePlanList($chat_id) { } } -function showServersForCategory($chat_id, $category_id) { +function showServersForCategory($chat_id, $category_id) +{ $category_stmt = pdo()->prepare("SELECT name FROM categories WHERE id = ?"); $category_stmt->execute([$category_id]); $category_name = $category_stmt->fetchColumn(); @@ -662,14 +698,15 @@ function showServersForCategory($chat_id, $category_id) { $message = "🛍 دسته‌ؚندی «{$category_name}»\n\nلطفاً سرور (لوکی؎ن) مورد ن؞ر خود را انتخاؚ کنید:"; $keyboard_buttons = []; foreach ($servers as $server) { - + $keyboard_buttons[] = [['text' => "🖥 {$server['name']}", 'callback_data' => "show_plans_cat_{$category_id}_srv_{$server['id']}"]]; } $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت ØšÙ‡ دسته‌ؚندی‌ها', 'callback_data' => 'back_to_categories']]; sendMessage($chat_id, $message, ['inline_keyboard' => $keyboard_buttons]); } -function showAdminManagementMenu($chat_id) { +function showAdminManagementMenu($chat_id) +{ $admins = getAdmins(); $message = "👚‍💌 مدیریت ادمین‌ها\n\nدر این ؚخ؎ می‌توانید ادمین‌های رؚات و دسترسی‌های آن‌ها را مدیریت کنید. (حداکثر Û±Û° ادمین)"; $keyboard_buttons = []; @@ -690,7 +727,8 @@ function showAdminManagementMenu($chat_id) { sendMessage($chat_id, $message, ['inline_keyboard' => $keyboard_buttons]); } -function showPermissionEditor($chat_id, $message_id, $target_admin_id) { +function showPermissionEditor($chat_id, $message_id, $target_admin_id) +{ $admins = getAdmins(); $target_admin = $admins[$target_admin_id] ?? null; if (!$target_admin) { @@ -725,7 +763,8 @@ function showPermissionEditor($chat_id, $message_id, $target_admin_id) { editMessageText($chat_id, $message_id, $message, ['inline_keyboard' => $keyboard_buttons]); } -function handleMainMenu($chat_id, $first_name, $is_start_command = false) { +function handleMainMenu($chat_id, $first_name, $is_start_command = false) +{ $isAnAdmin = isUserAdmin($chat_id); $user_data = getUserData($chat_id, $first_name); @@ -733,8 +772,7 @@ function handleMainMenu($chat_id, $first_name, $is_start_command = false) { if ($is_start_command) { $message = "سلام $first_name عزیز!\nØšÙ‡ رؚات فرو؎ کانفیگ خو؎ آمدید. 🌹"; - } - else { + } else { $message = "ØšÙ‡ منوی اصلی ؚازگ؎تید. لطفا گزینه مورد ن؞ر را انتخاؚ کنید."; } @@ -754,8 +792,7 @@ function handleMainMenu($chat_id, $first_name, $is_start_command = false) { if ($admin_view_mode === 'admin') { if ($is_start_command) { $message = "ادمین عزیز، ØšÙ‡ ٟنل مدیریت خو؎ آمدید."; - } - else { + } else { $message = "ØšÙ‡ ٟنل مدیریت ؚازگ؎تید."; } $admin_keyboard = []; @@ -778,7 +815,7 @@ function handleMainMenu($chat_id, $first_name, $is_start_command = false) { } if (hasPermission($chat_id, 'manage_payment')) { $rows[3][] = ['text' => '💳 مدیریت ٟرداخت']; - $rows[3][] = ['text' => '💳 مدیریت درگاه ٟرداخت']; + $rows[3][] = ['text' => '💳 مدیریت درگاه ٟرداخت']; } if (hasPermission($chat_id, 'manage_marzban')) { $rows[4][] = ['text' => '🌐 مدیریت سرورها']; @@ -810,8 +847,7 @@ function handleMainMenu($chat_id, $first_name, $is_start_command = false) { } $admin_keyboard[] = [['text' => '↩ ؚازگ؎ت ØšÙ‡ منوی کارؚری']]; $keyboard_buttons = $admin_keyboard; - } - else { + } else { $keyboard_buttons[] = [['text' => '👑 ورود ØšÙ‡ ٟنل مدیریت']]; } } @@ -830,8 +866,7 @@ function handleMainMenu($chat_id, $first_name, $is_start_command = false) { 'text' => '🏠', 'reply_markup' => json_encode(['remove_keyboard' => true]) ]), true)['result']['message_id']; - } - elseif (!USER_INLINE_KEYBOARD && $inline_keyboard == 1) { + } elseif (!USER_INLINE_KEYBOARD && $inline_keyboard == 1) { $stmt = pdo()->prepare("UPDATE users SET inline_keyboard = '0' WHERE chat_id = ?"); $stmt->execute([$chat_id]); } @@ -847,7 +882,8 @@ function handleMainMenu($chat_id, $first_name, $is_start_command = false) { } -function showVerificationManagementMenu($chat_id) { +function showVerificationManagementMenu($chat_id) +{ $settings = getSettings(); $current_method = $settings['verification_method']; $iran_only_icon = $settings['verification_iran_only'] == 'on' ? '🇮🇷' : '🌎'; @@ -855,8 +891,7 @@ function showVerificationManagementMenu($chat_id) { $method_text = 'غیرفعال'; if ($current_method == 'phone') { $method_text = '؎ماره تلفن'; - } - elseif ($current_method == 'button') { + } elseif ($current_method == 'button') { $method_text = 'دکمه ؎ی؎ه‌ای'; } @@ -882,8 +917,7 @@ function showVerificationManagementMenu($chat_id) { $message_id = $update['callback_query']['message']['message_id'] ?? null; if ($message_id) { editMessageText($chat_id, $message_id, $message, $keyboard); - } - else { + } else { sendMessage($chat_id, $message, $keyboard); } } @@ -892,7 +926,8 @@ function showVerificationManagementMenu($chat_id) { // --- تواؚع انتزاعی ؚرای مدیریت ٟنل‌ها --- // ===================================================================== -function getPanelUser($username, $server_id) { +function getPanelUser($username, $server_id) +{ $stmt = pdo()->prepare("SELECT type FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $type = $stmt->fetchColumn(); @@ -909,7 +944,8 @@ function getPanelUser($username, $server_id) { } } -function createPanelUser($plan, $chat_id, $plan_id) { +function createPanelUser($plan, $chat_id, $plan_id) +{ $stmt = pdo()->prepare("SELECT type FROM servers WHERE id = ?"); $stmt->execute([$plan['server_id']]); $type = $stmt->fetchColumn(); @@ -926,7 +962,8 @@ function createPanelUser($plan, $chat_id, $plan_id) { } } -function deletePanelUser($username, $server_id) { +function deletePanelUser($username, $server_id) +{ $stmt = pdo()->prepare("SELECT type FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $type = $stmt->fetchColumn(); @@ -943,7 +980,8 @@ function deletePanelUser($username, $server_id) { } } -function modifyPanelUser($username, $server_id, $data) { +function modifyPanelUser($username, $server_id, $data) +{ $stmt = pdo()->prepare("SELECT type FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $type = $stmt->fetchColumn(); @@ -994,32 +1032,34 @@ function showPlanEditor($chat_id, $message_id, $plan_id, $prompt = null) editMessageText($chat_id, $message_id, $message_text, $keyboard); } -function fetchAndParseSubscriptionUrl($sub_url, $server_id) { +function fetchAndParseSubscriptionUrl($sub_url, $server_id) +{ if (empty($sub_url)) { return []; } - + $stmt = pdo()->prepare("SELECT url, sub_host FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $server_info = $stmt->fetch(); - if (!$server_info) return []; - - $base_sub_url = !empty($server_info['sub_host']) ? rtrim($server_info['sub_host'], '/') : rtrim($server_info['url'], '/'); - + if (!$server_info) + return []; + + $base_sub_url = !empty($server_info['sub_host']) ? rtrim($server_info['sub_host'], '/') : rtrim($server_info['url'], '/'); + $stmt_type = pdo()->prepare("SELECT type FROM servers WHERE id = ?"); $stmt_type->execute([$server_id]); $server_type = $stmt_type->fetchColumn(); $sub_path = ''; - + if ($server_type === 'marzban' || $server_type === 'sanaei') { $sub_path_raw = strstr($sub_url, '/sub/'); if ($sub_path_raw !== false) { $sub_path = $sub_path_raw; } } - - + + if (empty($sub_path)) { $sub_path = parse_url($sub_url, PHP_URL_PATH); } @@ -1049,13 +1089,14 @@ function fetchAndParseSubscriptionUrl($sub_url, $server_id) { if ($decoded_links === false) { $decoded_links = $response_body; } - + $links_array = preg_split("/\r\n|\n|\r/", trim($decoded_links)); - + return array_filter($links_array); } -function showPlansForCategoryAndServer($chat_id, $category_id, $server_id) { +function showPlansForCategoryAndServer($chat_id, $category_id, $server_id) +{ // دریافت نام دسته ؚندی و سرور ؚرای نمای؎ در ٟیام $category_name = pdo()->prepare("SELECT name FROM categories WHERE id = ?")->execute([$category_id]) ? pdo()->lastInsertId() : 'نام؎خص'; $server_name = pdo()->prepare("SELECT name FROM servers WHERE id = ?")->execute([$server_id]) ? pdo()->lastInsertId() : 'نام؎خص'; @@ -1084,7 +1125,8 @@ function showPlansForCategoryAndServer($chat_id, $category_id, $server_id) { sendMessage($chat_id, $message, ['inline_keyboard' => $keyboard_buttons]); } -function applyRenewal($chat_id, $username, $days_to_add, $gb_to_add) { +function applyRenewal($chat_id, $username, $days_to_add, $gb_to_add) +{ $stmt = pdo()->prepare("SELECT server_id FROM services WHERE owner_chat_id = ? AND marzban_username = ?"); $stmt->execute([$chat_id, $username]); $server_id = $stmt->fetchColumn(); @@ -1120,19 +1162,19 @@ function applyRenewal($chat_id, $username, $days_to_add, $gb_to_add) { } if (empty($update_data)) { - return ['success' => false, 'message' => 'هیچ تغییری ؚرای اعمال وجود ندا؎ت.']; + return ['success' => false, 'message' => 'هیچ تغییری ؚرای اعمال وجود ندا؎ت.']; } $result = modifyPanelUser($username, $server_id, $update_data); - + // ؚروزرسانی دیتاؚیس محلی if ($result && !isset($result['detail'])) { - if(isset($update_data['expire'])){ - pdo()->prepare("UPDATE services SET expire_timestamp = ? WHERE marzban_username = ? AND server_id = ?")->execute([$update_data['expire'], $username, $server_id]); + if (isset($update_data['expire'])) { + pdo()->prepare("UPDATE services SET expire_timestamp = ? WHERE marzban_username = ? AND server_id = ?")->execute([$update_data['expire'], $username, $server_id]); } - if(isset($update_data['data_limit'])){ - $new_volume_gb = ($update_data['data_limit'] / (1024*1024*1024)); - pdo()->prepare("UPDATE services SET volume_gb = ? WHERE marzban_username = ? AND server_id = ?")->execute([$new_volume_gb, $username, $server_id]); + if (isset($update_data['data_limit'])) { + $new_volume_gb = ($update_data['data_limit'] / (1024 * 1024 * 1024)); + pdo()->prepare("UPDATE services SET volume_gb = ? WHERE marzban_username = ? AND server_id = ?")->execute([$new_volume_gb, $username, $server_id]); } return ['success' => true]; } @@ -1140,13 +1182,14 @@ function applyRenewal($chat_id, $username, $days_to_add, $gb_to_add) { return ['success' => false, 'message' => 'خطا در ارتؚاط ؚا ٟنل ؚرای اعمال تغییرات.']; } -function showRenewalManagementMenu($chat_id, $message_id = null) { +function showRenewalManagementMenu($chat_id, $message_id = null) +{ $settings = getSettings(); $status_icon = ($settings['renewal_status'] ?? 'off') == 'on' ? '✅' : '❌'; $message = "🔄 مدیریت تمدید سرویس\n\n" . - "▫ وضعیت کلی: " . ($status_icon == '✅' ? 'فعال' : 'غیرفعال') . "\n" . - "▫ هزینه هر روز تمدید: " . number_format($settings['renewal_price_per_day'] ?? 1000) . " تومان\n" . - "▫ هزینه هر گیگاؚایت تمدید: " . number_format($settings['renewal_price_per_gb'] ?? 2000) . " تومان"; + "▫ وضعیت کلی: " . ($status_icon == '✅' ? 'فعال' : 'غیرفعال') . "\n" . + "▫ هزینه هر روز تمدید: " . number_format($settings['renewal_price_per_day'] ?? 1000) . " تومان\n" . + "▫ هزینه هر گیگاؚایت تمدید: " . number_format($settings['renewal_price_per_gb'] ?? 2000) . " تومان"; $keyboard = [ 'inline_keyboard' => [ @@ -1164,7 +1207,8 @@ function showRenewalManagementMenu($chat_id, $message_id = null) { } } -function showMarzbanProtocolEditor($chat_id, $message_id, $server_id) { +function showMarzbanProtocolEditor($chat_id, $message_id, $server_id) +{ $stmt_server = pdo()->prepare("SELECT name, marzban_protocols FROM servers WHERE id = ?"); $stmt_server->execute([$server_id]); $server = $stmt_server->fetch(); @@ -1175,13 +1219,14 @@ function showMarzbanProtocolEditor($chat_id, $message_id, $server_id) { } $all_protocols = ['vless', 'vmess', 'trojan', 'shadowsocks']; - - $enabled_protocols = $server['marzban_protocols'] ? json_decode($server['marzban_protocols'], true) : ['vless']; - if (!is_array($enabled_protocols)) $enabled_protocols = ['vless']; - + + $enabled_protocols = $server['marzban_protocols'] ? json_decode($server['marzban_protocols'], true) : ['vless']; + if (!is_array($enabled_protocols)) + $enabled_protocols = ['vless']; + $message = "⚙ تن؞یم ٟروتکل‌های سرور: {$server['name']}\n\n"; $message .= "ٟروتکل‌هایی را که می‌خواهید ؚرای کارؚران جدید در این سرور ایجاد ؎وند، انتخاؚ کنید."; - + $keyboard_buttons = []; $row = []; foreach ($all_protocols as $protocol) { @@ -1195,17 +1240,18 @@ function showMarzbanProtocolEditor($chat_id, $message_id, $server_id) { if (!empty($row)) { $keyboard_buttons[] = $row; } - + $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت ØšÙ‡ سرور', 'callback_data' => "view_server_{$server_id}"]]; - + editMessageText($chat_id, $message_id, $message, ['inline_keyboard' => $keyboard_buttons]); } -function createZarinpalLink($chat_id, $amount, $description, $metadata = []) { +function createZarinpalLink($chat_id, $amount, $description, $metadata = []) +{ $settings = getSettings(); $merchant_id = $settings['zarinpal_merchant_id']; $script_url = 'https://' . $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['PHP_SELF']), '/') . '/verify_payment.php'; - + $data = [ "merchant_id" => $merchant_id, "amount" => $amount * 10, // تؚدیل تومان ØšÙ‡ ریال @@ -1221,18 +1267,18 @@ function createZarinpalLink($chat_id, $amount, $description, $metadata = []) { curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData); curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Content-Length: ' . strlen($jsonData)]); - + $result = curl_exec($ch); curl_close($ch); $result = json_decode($result, true); - + if (empty($result['errors'])) { $authority = $result['data']['authority']; - + // ثؚت تراکن؎ در دیتاؚیس $stmt = pdo()->prepare("INSERT INTO transactions (user_id, amount, authority, description, metadata) VALUES (?, ?, ?, ?, ?)"); $stmt->execute([$chat_id, $amount, $authority, $description, json_encode($metadata)]); - + $payment_url = 'https://www.zarinpal.com/pg/StartPay/' . $authority; return ['success' => true, 'url' => $payment_url]; } else { @@ -1241,7 +1287,8 @@ function createZarinpalLink($chat_id, $amount, $description, $metadata = []) { } } -function completePurchase($user_id, $plan_id, $custom_name, $final_price, $discount_code, $discount_object, $discount_applied) { +function completePurchase($user_id, $plan_id, $custom_name, $final_price, $discount_code, $discount_object, $discount_applied) +{ $plan = getPlanById($plan_id); $user_data = getUserData($user_id); $first_name = $user_data['first_name']; @@ -1265,9 +1312,9 @@ function completePurchase($user_id, $plan_id, $custom_name, $final_price, $disco if ($discount_applied && $discount_object) { pdo()->prepare("UPDATE discount_codes SET usage_count = usage_count + 1 WHERE id = ?")->execute([$discount_object['id']]); } - + $expire_timestamp = $panel_user_data['expire'] ?? (isset($panel_user_data['expire_date']) ? strtotime($panel_user_data['expire_date']) : (time() + $plan['duration_days'] * 86400)); - + saveUserService($user_id, [ 'server_id' => $plan['server_id'], 'username' => $panel_user_data['username'], @@ -1277,7 +1324,7 @@ function completePurchase($user_id, $plan_id, $custom_name, $final_price, $disco 'expire_timestamp' => $expire_timestamp, 'volume_gb' => $plan['volume_gb'], ]); - + $new_balance = $user_data['balance'] - $final_price; $sub_link = $panel_user_data['subscription_url']; $qr_code_url = generateQrCodeUrl($sub_link); @@ -1292,12 +1339,12 @@ function completePurchase($user_id, $plan_id, $custom_name, $final_price, $disco if ($plan['show_sub_link']) { $caption .= "🔗 لینک ا؎تراک (Subscription):\n" . htmlspecialchars($sub_link) . "\n\n"; } - + $caption .= "💰 موجودی جدید ؎ما: " . number_format($new_balance) . " تومان"; $chat_info_response = apiRequest('getChat', ['chat_id' => $user_id]); $chat_info = json_decode($chat_info_response, true); - + $profile_link_html = "👀 کارؚر: " . htmlspecialchars($first_name) . " ($user_id)\n"; $admin_notification = "✅ خرید جدید\n\n"; @@ -1312,7 +1359,7 @@ function completePurchase($user_id, $plan_id, $custom_name, $final_price, $disco } else { $admin_notification .= "💳 مؚلغ ٟرداخت ؎ده: " . number_format($final_price) . " تومان"; } - + $keyboard_buttons = []; if ($plan['show_conf_links'] && !empty($panel_user_data['links'])) { $keyboard_buttons[] = [['text' => '📋 دریافت کانفیگ‌ها', 'callback_data' => "get_configs_{$panel_user_data['username']}"]]; @@ -1326,7 +1373,7 @@ function completePurchase($user_id, $plan_id, $custom_name, $final_price, $disco 'admin_notification' => $admin_notification, ]; } - + return [ 'success' => false, 'error_message' => "❌ متاسفانه در ایجاد سرویس ؎ما Ù…ØŽÚ©Ù„ÛŒ ٟی؎ آمد. لطفا ؚا ٟ؎تیؚانی تماس ؚگیرید. مؚلغی از حساؚ ؎ما کسر ن؎ده است." diff --git a/src/install.php b/src/install.php index ea38861..f678a43 100644 --- a/src/install.php +++ b/src/install.php @@ -15,19 +15,23 @@ $configFile = __DIR__ . '/includes/config.php'; $botFileUrl = 'https://' . $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['PHP_SELF']), '/') . '/bot.php'; -$step = isset($_POST['step']) ? (int)$_POST['step'] : 1; +$step = isset($_POST['step']) ? (int) $_POST['step'] : 1; $errors = []; $successMessages = []; // --- داده‌های فرم --- $bot_token = trim($_POST['bot_token'] ?? ''); $admin_id = trim($_POST['admin_id'] ?? ''); +$web_username = trim($_POST['web_username'] ?? ''); +$web_password = trim($_POST['web_password'] ?? ''); -function generateRandomString(int $length = 32): string { +function generateRandomString(int $length = 32): string +{ return bin2hex(random_bytes($length / 2)); } -function getDbBaseSchemaSQL(): string { +function getDbBaseSchemaSQL(): string +{ return " CREATE TABLE IF NOT EXISTS `users` ( `chat_id` BIGINT NOT NULL, `first_name` VARCHAR(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci, `balance` DECIMAL(10,2) NOT NULL DEFAULT 0.00, `user_state` VARCHAR(255) DEFAULT 'main_menu', `state_data` TEXT, `status` VARCHAR(20) NOT NULL DEFAULT 'active', `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`chat_id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; CREATE TABLE IF NOT EXISTS `admins` ( `chat_id` BIGINT NOT NULL PRIMARY KEY, `first_name` VARCHAR(255), `permissions` TEXT, `is_super_admin` TINYINT(1) NOT NULL DEFAULT 0 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; @@ -70,7 +74,8 @@ function getDbBaseSchemaSQL(): string { "; } -function columnExists(PDO $pdo, string $tableName, string $columnName): bool { +function columnExists(PDO $pdo, string $tableName, string $columnName): bool +{ try { $stmt = $pdo->prepare("SHOW COLUMNS FROM `$tableName` LIKE ?"); $stmt->execute([$columnName]); @@ -80,7 +85,8 @@ function columnExists(PDO $pdo, string $tableName, string $columnName): bool { } } -function runDbUpgrades(PDO $pdo): array { +function runDbUpgrades(PDO $pdo): array +{ $messages = []; if (columnExists($pdo, 'users', 'state') && !columnExists($pdo, 'users', 'user_state')) { @@ -109,7 +115,7 @@ function runDbUpgrades(PDO $pdo): array { $pdo->exec("ALTER TABLE `services` ADD `sanaei_uuid` VARCHAR(255) NULL DEFAULT NULL AFTER `sanaei_inbound_id`;"); $messages[] = "✅ ستون `sanaei_uuid` ؚرای ٟنل سنایی ØšÙ‡ جدول `services` اضافه ؎د."; } - + // --- ارتقاهای مرؚوط ØšÙ‡ اعلان‌ها و ردیاؚی کارؚران --- if (!columnExists($pdo, 'users', 'last_seen_at')) { $pdo->exec("ALTER TABLE `users` ADD `last_seen_at` TIMESTAMP NULL DEFAULT NULL AFTER `status`;"); @@ -173,24 +179,40 @@ function runDbUpgrades(PDO $pdo): array { // --- مدیریت منطق مراحل --- if ($step === 2) { - if (empty($bot_token)) $errors[] = 'توکن رؚات الزامی است.'; - if (empty($admin_id) || !is_numeric($admin_id)) $errors[] = 'آیدی عددی ادمین الزامی و ؚاید عدد ؚا؎د.'; - if (!empty($errors)) $step = 1; -} -elseif ($step === 3) { + if (empty($bot_token)) + $errors[] = 'توکن رؚات الزامی است.'; + if (empty($admin_id) || !is_numeric($admin_id)) + $errors[] = 'آیدی عددی ادمین الزامی و ؚاید عدد ؚا؎د.'; + + // اگر username/password خالی ؚود، مقادیر ٟی؎‌فرض تولید ؎ود + if (empty($web_username)) { + $web_username = 'admin'; + } + if (empty($web_password)) { + $web_password = generateRandomString(16); + } + + if (!empty($errors)) + $step = 1; +} elseif ($step === 3) { $db_host = trim($_POST['db_host'] ?? 'localhost'); $db_name = trim($_POST['db_name'] ?? ''); $db_user = trim($_POST['db_user'] ?? ''); $db_pass = trim($_POST['db_pass'] ?? ''); - if (empty($db_name)) $errors[] = 'نام دیتاؚیس الزامی است.'; - if (empty($db_user)) $errors[] = 'نام کارؚری دیتاؚیس الزامی است.'; - + if (empty($db_name)) + $errors[] = 'نام دیتاؚیس الزامی است.'; + if (empty($db_user)) + $errors[] = 'نام کارؚری دیتاؚیس الزامی است.'; + if (empty($errors)) { - if (!is_dir(__DIR__ . '/includes')) @mkdir(__DIR__ . '/includes', 0755, true); - if (!file_exists($configFile)) @file_put_contents($configFile, "exec(getDbBaseSchemaSQL()); $successMessages[] = "✅ ساختار ٟایه جداول ؚا موفقیت ایجاد/ؚررسی ؎د."; - + $secretToken = generateRandomString(64); + $web_password_hash = password_hash($web_password, PASSWORD_BCRYPT); + $config_content = '{$web_username} | Password={$web_password}"; + $upgradeMessages = runDbUpgrades($pdo); if (!empty($upgradeMessages)) { $successMessages = array_merge($successMessages, $upgradeMessages); @@ -231,11 +259,11 @@ function runDbUpgrades(PDO $pdo): array { ('notification_inactive_days', '30'), ('renewal_status', 'off'), ('renewal_price_per_day', '1000'), ('renewal_price_per_gb', '2000'), ('payment_gateway_status', 'off'), ('zarinpal_merchant_id', '');"); $successMessages[] = "✅ تن؞یمات ٟی؎‌فرض ؚا موفقیت افزوده ؎د."; - + $apiUrl = "https://api.telegram.org/bot$bot_token/setWebhook?secret_token=$secretToken&url=" . urlencode($botFileUrl); $response = @file_get_contents($apiUrl); $response_data = json_decode($response, true); - + if (!$response || !$response_data['ok']) { $errors[] = 'خطا در ثؚت وؚهوک: ' . ($response_data['description'] ?? 'ٟاسخ نامعتؚر از تلگرام. از صحت توکن مطم؊ن ؎وید.'); } else { @@ -251,6 +279,7 @@ function runDbUpgrades(PDO $pdo): array { ?> + @@ -258,198 +287,441 @@ function runDbUpgrades(PDO $pdo): array { + -
-
-

نصؚ و راه‌اندازی رؚات تلگرام

-
- -
- -
-
- +
+

نصؚ و راه‌اندازی رؚات تلگرام

+
+ +
+ +
+
+ -
- -
-
Û±
-
اطلاعات رؚات
-
-
-
Û²
-
دیتاؚیس
-
-
-
Û³
-
ٟایان نصؚ
-
-
+ ?> +
- -
- خطا! -
    - " . htmlspecialchars($error) . ""; ?>
+
+
Û±
+
اطلاعات رؚات
+
+
+
Û²
+
دیتاؚیس
+
+
+
Û³
+
ٟایان نصؚ
+
- - -
- آدرس وؚهوک ؎ما: - -
-
-
مرحله Û±: اطلاعات رؚات تلگرام
-
- -
- - -

مثال: 123456789:ABCdefGHIjklMnOpQRstUvWxYz

-
-
- - -

مثال: 123456789

-
- -
-
- -
-
مرحله Û²: تن؞یمات ٟایگاه داده
-
- - - -
- - -
-
- - -
-
- - -
-
- - -
- -
-
- -
- -
- نصؚ ؚا موفقیت ØšÙ‡ ٟایان رسید! -
    " . $msg . ""; ?>
-
-
- مهم: این فایل جهت افزای؎ امنیت تا چند ثانیه دیگر ØšÙ‡ صورت خودکار حذف خواهد ؎د. -
- -
- نصؚ ؚا خطا مواجه ؎د! -
    - " . htmlspecialchars($error) . ""; ?>
-
- -
- + +
+ خطا! +
    - " . htmlspecialchars($error) . ""; ?>
+
+ + + +
+ آدرس وؚهوک ؎ما: + +
+
+
مرحله Û±: اطلاعات رؚات تلگرام
+
+ +
+ + +

مثال: 123456789:ABCdefGHIjklMnOpQRstUvWxYz

+
+
+ + +

مثال: 123456789

+
+ +
+ 🔐 اطلاعات ورود ØšÙ‡ ٟنل تحت وؚ (اختیاری) +

در صورت خالی گذا؎تن، + نام کارؚری "admin" و رمز عؚور تصادفی تولید می‌؎ود.

+
+ +
+ + +

ٟی؎‌فرض: admin

+
+
+ + +

حداقل 8 کاراکتر - در صورت خالی ؚودن، رمز تصادفی تولید می‌؎ود

+
+ +
+
+ +
+
مرحله Û²: تن؞یمات ٟایگاه داده
+
+ + + + + +
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+ +
+ +
+ نصؚ ؚا موفقیت ØšÙ‡ ٟایان رسید! +
    " . $msg . ""; ?>
+
+
+ مهم: این فایل جهت افزای؎ امنیت تا چند ثانیه دیگر ØšÙ‡ صورت خودکار حذف خواهد + ؎د. +
+ +
+ نصؚ ؚا خطا مواجه ؎د! +
    - " . htmlspecialchars($error) . ""; ?>
+
+ +
+ +
-
- - - - + }) + .catch(function (error) { console.error('خطا در حذف خودکار فایل:', error); }); + }, 5000); // 5-second delay + + + \ No newline at end of file diff --git a/src/web/README.md b/src/web/README.md new file mode 100644 index 0000000..cb35ddf --- /dev/null +++ b/src/web/README.md @@ -0,0 +1,50 @@ +# ٟنل تحت وؚ v2ray - راهنمای استفاده + +## نصؚ + +1. فایل `install.php` را در مرورگر ؚاز کنید +2. در مرحله اول، اطلاعات زیر را وارد کنید: + - توکن رؚات تلگرام + - آیدی عددی ادمین + - **(اختیاری)** نام کارؚری و رمز عؚور ٟنل وؚ +3. در مرحله دوم، اطلاعات دیتاؚیس را وارد کنید +4. ؚعد از نصؚ موفق، اطلاعات ورود ØšÙ‡ ٟنل وؚ نمای؎ داده می‌؎ود + +## ورود ØšÙ‡ ٟنل + +1. ØšÙ‡ آدرس `http://your-domain/web/` ؚروید +2. ؚا نام کارؚری و رمز عؚور وارد ؎وید +3. ØšÙ‡ دا؎ؚورد هدایت می‌؎وید + +## قاؚلیت‌ها + +### ✅ فعال ؎ده: +- 🔐 سیستم احراز هویت امن +- 📊 دا؎ؚورد ؚا آمار کلی +- 🗂 مدیریت دسته‌ؚندی‌ها (افزودن، حذف، فعال/غیرفعال) +- 👥 مدیریت کارؚران (جستجو، افزای؎/کاه؎ موجودی، مسدود/آزاد، م؎اهده سرویس‌ها) +- 📈 آمار و گزار؎ات (آمار کارؚران، سرویس‌ها، درآمد) +- ⚙ تن؞یمات (وضعیت رؚات، کانال اجؚاری، هدیه خو؎‌آمد، درگاه ٟرداخت) +- 🎚 طراحی مدرن و زیؚا +- 📱 Responsive ؚرای موؚایل + +### 🔜 در حال توسعه: +- 📝 مدیریت ٟلن‌ها +- 🌐 مدیریت سرورها +- 👚‍💌 مدیریت ادمین‌ها +- 🎁 مدیریت کدهای تخفیف +- 📚 مدیریت راهنما +- 💳 مدیریت ٟرداخت‌های دستی +- 📣 ارسال همگانی + +## ویژگی‌های امنیتی + +- ✅ Hash کردن رمز عؚور ؚا bcrypt +- ✅ Session security +- ✅ محاف؞ت از تمام صفحات ؚا requireLogin +- ✅ Sanitization ورودی‌ها +- ✅ استفاده از Prepared Statements + +## ٟ؎تیؚانی + +ؚرای گزار؎ م؎کلات یا ٟی؎نهادات، ؚا توسعه‌دهنده تماس ؚگیرید. diff --git a/src/web/assets/css/style.css b/src/web/assets/css/style.css new file mode 100644 index 0000000..a7d699c --- /dev/null +++ b/src/web/assets/css/style.css @@ -0,0 +1,430 @@ +:root { + --bg-main: #0a0e1a; + --bg-container: #1e293b; + --bg-card: #2d3748; + --bg-input: #111827; + --primary: #8b5cf6; + --primary-hover: #7c3aed; + --success: #10b981; + --danger: #ef4444; + --warning: #f59e0b; + --info: #3b82f6; + --blue: #3b82f6; + --green: #10b981; + --purple: #8b5cf6; + --orange: #f97316; + --teal: #14b8a6; + --red: #ef4444; + --text-light: #f8fafc; + --text-muted: #94a3b8; + --border-color: rgba(148, 163, 184, 0.2); + --shadow-color: rgba(0, 0, 0, 0.5); + --sidebar-width: 280px; +} + +* { + margin: 0; + padding: 0; + box-sizing: border-box; + font-family: Vazirmatn, sans-serif; +} + +body { + background-color: var(--bg-main); + background-image: url('data:image/svg+xml,%3Csvg xmlns="http://www.w3.org/2000/svg" width="40" height="40" viewBox="0 0 40 40"%3E%3Cg fill-rule="evenodd"%3E%3Cg fill="%231e293b" fill-opacity="0.2"%3E%3Cpath d="M0 38.59l2.83-2.83 1.41 1.41L1.41 40H0v-1.41zM0 1.4l2.83 2.83 1.41-1.41L1.41 0H0v1.41zM38.59 40l-2.83-2.83 1.41-1.41L40 38.59V40h-1.41zM40 1.41l-2.83 2.83-1.41-1.41L38.59 0H40v1.41zM20 18.6l2.83-2.83 1.41 1.41L21.41 20l2.83 2.83-1.41 1.41L20 21.41l-2.83 2.83-1.41-1.41L18.59 20l-2.83-2.83 1.41-1.41L20 18.59z"/%3E%3C/g%3E%3C/g%3E%3C/svg%3E'); + min-height: 100vh; + color: var(--text-light); +} + +/* Layout */ +.layout { + display: flex; + min-height: 100vh; +} + +.main-content { + flex: 1; + margin-right: var(--sidebar-width); + transition: margin-right 0.3s; +} + +/* Sidebar */ +.sidebar { + position: fixed; + right: 0; + top: 0; + width: var(--sidebar-width); + height: 100vh; + background: var(--bg-container); + border-left: 1px solid var(--border-color); + display: flex; + flex-direction: column; + z-index: 1000; + transition: transform 0.3s; +} + +.sidebar-header { + padding: 30px 20px; + border-bottom: 1px solid var(--border-color); +} + +.sidebar-header h2 { + font-size: 1.5rem; + margin-bottom: 5px; +} + +.sidebar-header .username { + font-size: 0.9rem; + color: var(--text-muted); +} + +.sidebar-nav { + flex: 1; + padding: 20px 0; + overflow-y: auto; +} + +.sidebar-nav a { + display: flex; + align-items: center; + padding: 12px 20px; + color: var(--text-muted); + text-decoration: none; + transition: all 0.3s; + border-right: 3px solid transparent; +} + +.sidebar-nav a i { + width: 24px; + margin-left: 12px; +} + +.sidebar-nav a:hover { + background-color: rgba(139, 92, 246, 0.1); + color: var(--text-light); +} + +.sidebar-nav a.active { + background-color: rgba(139, 92, 246, 0.15); + color: var(--primary); + border-right-color: var(--primary); +} + +.sidebar-footer { + padding: 20px; + border-top: 1px solid var(--border-color); +} + +.logout-btn { + display: flex; + align-items: center; + justify-content: center; + padding: 12px; + background: linear-gradient(135deg, var(--danger), #dc2626); + color: white; + text-decoration: none; + border-radius: 8px; + transition: transform 0.2s; + font-weight: 500; +} + +.logout-btn:hover { + transform: translateY(-2px); +} + +.logout-btn i { + margin-left: 8px; +} + +/* Topbar */ +.topbar { + display: flex; + align-items: center; + padding: 20px 30px; + background: var(--bg-container); + border-bottom: 1px solid var(--border-color); + position: sticky; + top: 0; + z-index: 100; +} + +.topbar h1 { + font-size: 1.5rem; + font-weight: 600; +} + +.menu-toggle { + display: none; + background: transparent; + border: none; + color: var(--text-light); + font-size: 1.5rem; + cursor: pointer; + margin-left: 15px; +} + +/* Content Area */ +.content-area { + padding: 30px; +} + +/* Stats Grid */ +.stats-grid { + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 20px; + margin-bottom: 30px; +} + +.stat-card { + background: var(--bg-container); + border: 1px solid var(--border-color); + border-radius: 12px; + padding: 24px; + display: flex; + align-items: center; + gap: 20px; + transition: transform 0.2s, box-shadow 0.2s; +} + +.stat-card:hover { + transform: translateY(-5px); + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.3); +} + +.stat-icon { + width: 60px; + height: 60px; + border-radius: 12px; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.5rem; +} + +.stat-icon.blue { background: rgba(59, 130, 246, 0.2); color: var(--blue); } +.stat-icon.green { background: rgba(16, 185, 129, 0.2); color: var(--green); } +.stat-icon.purple { background: rgba(139, 92, 246, 0.2); color: var(--purple); } +.stat-icon.orange { background: rgba(249, 115, 22, 0.2); color: var(--orange); } +.stat-icon.teal { background: rgba(20, 184, 166, 0.2); color: var(--teal); } +.stat-icon.red { background: rgba(239, 68, 68, 0.2); color: var(--red); } + +.stat-info { + flex: 1; +} + +.stat-value { + font-size: 2rem; + font-weight: 700; + margin-bottom: 5px; +} + +.stat-label { + color: var(--text-muted); + font-size: 0.9rem; +} + +.stat-sub { + color: var(--success); + font-size: 0.85rem; + margin-top: 5px; +} + +/* Cards */ +.card { + background: var(--bg-container); + border: 1px solid var(--border-color); + border-radius: 12px; + overflow: hidden; + margin-bottom: 20px; +} + +.card-header { + padding: 20px; + border-bottom: 1px solid var(--border-color); +} + +.card-header h3 { + font-size: 1.25rem; + font-weight: 600; +} + +.card-header h3 i { + margin-left: 10px; +} + +.card-body { + padding: 20px; +} + +/* Tables */ +.data-table { + width: 100%; + border-collapse: collapse; +} + +.data-table thead { + background: rgba(139, 92, 246, 0.1); +} + +.data-table th { + padding: 12px; + text-align: right; + font-weight: 600; + color: var(--text-light); +} + +.data-table tbody tr { + border-bottom: 1px solid var(--border-color); + transition: background-color 0.2s; +} + +.data-table tbody tr:hover { + background-color: rgba(255, 255, 255, 0.02); +} + +.data-table td { + padding: 12px; + text-align: right; +} + +/* Forms */ +.form-group { + margin-bottom: 20px; +} + +label { + display: block; + margin-bottom: 8px; + color: var(--text-muted); + font-weight: 500; +} + +input[type="text"], +input[type="password"], +input[type="number"], +input[type="email"], +select, +textarea { + width: 100%; + padding: 12px; + background: var(--bg-input); + border: 1px solid var(--border-color); + border-radius: 8px; + color: var(--text-light); + font-size: 1rem; + transition: border-color 0.3s, box-shadow 0.3s; +} + +input:focus, +select:focus, +textarea:focus { + border-color: var(--primary); + box-shadow: 0 0 0 3px rgba(139, 92, 246, 0.3); + outline: none; +} + +/* Buttons */ +.btn { + padding: 12px 24px; + border: none; + border-radius: 8px; + font-weight: 600; + cursor: pointer; + transition: transform 0.2s, box-shadow 0.2s; + text-decoration: none; + display: inline-block; + text-align: center; +} + +.btn:hover { + transform: translateY(-2px); +} + +.btn-primary { + background: linear-gradient(135deg, var(--primary), var(--primary-hover)); + color: white; +} + +.btn-success { + background: linear-gradient(135deg, var(--success), #059669); + color: white; +} + +.btn-danger { + background: linear-gradient(135deg, var(--danger), #dc2626); + color: white; +} + +/* Alerts */ +.alert { + padding: 15px; + border-radius: 8px; + margin-bottom: 20px; + border-right-width: 4px; + border-right-style: solid; +} + +.alert-success { + background-color: rgba(16, 185, 129, 0.1); + border-right-color: var(--success); + color: #a7f3d0; +} + +.alert-danger { + background-color: rgba(239, 68, 68, 0.1); + border-right-color: var(--danger); + color: #fca5a5; +} + +.alert-warning { + background-color: rgba(245, 158, 11, 0.1); + border-right-color: var(--warning); + color: #fcd34d; +} + +.alert-info { + background-color: rgba(59, 130, 246, 0.1); + border-right-color: var(--info); + color: #93c5fd; +} + +/* Utilities */ +.text-muted { + color: var(--text-muted); +} + +.text-center { + text-align: center; +} + +.mb-20 { + margin-bottom: 20px; +} + +.mt-20 { + margin-top: 20px; +} + +/* Responsive */ +@media (max-width: 768px) { + .sidebar { + transform: translateX(100%); + } + + .sidebar.active { + transform: translateX(0); + } + + .main-content { + margin-right: 0; + } + + .menu-toggle { + display: block; + } + + .stats-grid { + grid-template-columns: 1fr; + } +} diff --git a/src/web/assets/js/main.js b/src/web/assets/js/main.js new file mode 100644 index 0000000..b77d979 --- /dev/null +++ b/src/web/assets/js/main.js @@ -0,0 +1,70 @@ +// Main JavaScript for Web Panel +document.addEventListener('DOMContentLoaded', function() { + // Menu Toggle for Mobile + const menuToggle = document.getElementById('menuToggle'); + const sidebar = document.getElementById('sidebar'); + + if (menuToggle && sidebar) { + menuToggle.addEventListener('click', function() { + sidebar.classList.toggle('active'); + }); + + // Close sidebar when clicking outside on mobile + document.addEventListener('click', function(e) { + if (window.innerWidth <= 768) { + if (!sidebar.contains(e.target) && !menuToggle.contains(e.target)) { + sidebar.classList.remove('active'); + } + } + }); + } + + // Auto-hide alerts after 5 seconds + const alerts = document.querySelectorAll('.alert'); + alerts.forEach(alert => { + setTimeout(() => { + alert.style.opacity = '0'; + setTimeout(() => { + alert.remove(); + }, 300); + }, 5000); + }); +}); + +/** + * Show toast notification + */ +function showToast(message, type = 'info') { + const toast = document.createElement('div'); + toast.className = `alert alert-${type}`; + toast.style.position = 'fixed'; + toast.style.top = '20px'; + toast.style.left = '50%'; + toast.style.transform = 'translateX(-50%)'; + toast.style.zIndex = '9999'; + toast.style.minWidth = '300px'; + toast.textContent = message; + + document.body.appendChild(toast); + + setTimeout(() => { + toast.style.opacity = '0'; + setTimeout(() => { + toast.remove(); + }, 300); + }, 3000); +} + +/** + * Confirm dialog + */ +function confirmAction(message) { + return confirm(message); +} + +/** + * Format number with thousands separator + */ +function formatNumber(num) { + return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); +} diff --git a/src/web/dashboard.php b/src/web/dashboard.php new file mode 100644 index 0000000..d40aa6e --- /dev/null +++ b/src/web/dashboard.php @@ -0,0 +1,177 @@ +query("SELECT COUNT(*) FROM users"); +$stats['total_users'] = $stmt->fetchColumn(); + +// Active users +$stmt = pdo()->query("SELECT COUNT(*) FROM users WHERE status = 'active'"); +$stats['active_users'] = $stmt->fetchColumn(); + +// Total services +$stmt = pdo()->query("SELECT COUNT(*) FROM services"); +$stats['total_services'] = $stmt->fetchColumn(); + +// Today income +$today_income = pdo() + ->query("SELECT SUM(p.price) FROM services s JOIN plans p ON s.plan_id = p.id WHERE DATE(s.purchase_date) = CURDATE()") + ->fetchColumn() ?? 0; +$stats['today_income'] = $today_income; + +// Month income +$month_income = pdo() + ->query("SELECT SUM(p.price) FROM services s JOIN plans p ON s.plan_id = p.id WHERE MONTH(s.purchase_date) = MONTH(CURDATE()) AND YEAR(s.purchase_date) = YEAR(CURDATE())") + ->fetchColumn() ?? 0; +$stats['month_income'] = $month_income; + +// Total servers +$stmt = pdo()->query("SELECT COUNT(*) FROM servers WHERE status = 'active'"); +$stats['total_servers'] = $stmt->fetchColumn(); + +// Pending payments +$stmt = pdo()->query("SELECT COUNT(*) FROM payment_requests WHERE status = 'pending'"); +$stats['pending_payments'] = $stmt->fetchColumn(); + +// Recent services (last 5) +$stmt = pdo()->query(" + SELECT s.*, p.name as plan_name, u.first_name + FROM services s + JOIN plans p ON s.plan_id = p.id + JOIN users u ON s.owner_chat_id = u.chat_id + ORDER BY s.id DESC + LIMIT 5 +"); +$recent_services = $stmt->fetchAll(PDO::FETCH_ASSOC); + +renderHeader('دا؎ؚورد'); +?> + +
+ + +
+ + +
+ +
+
+
+ +
+
+
+
کل کارؚران
+
فعال
+
+
+ +
+
+ +
+
+
+
کل سرویس‌ها
+
+
+ +
+
+ +
+
+
+
درآمد امروز (تومان)
+
+
+ +
+
+ +
+
+
+
درآمد ماه (تومان)
+
+
+ +
+
+ +
+
+
+
سرورهای فعال
+
+
+ +
+
+ +
+
+
+
ٟرداخت‌های در انت؞ار
+
+
+
+ + +
+
+

آخرین سرویس‌ها

+
+
+ +

هیچ سرویسی یافت ن؎د.

+ + + + + + + + + + + + + + + + + + + + +
کارؚرٟلننام سرویستاریخ خرید
+
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/src/web/includes/auth.php b/src/web/includes/auth.php new file mode 100644 index 0000000..14e21b4 --- /dev/null +++ b/src/web/includes/auth.php @@ -0,0 +1,53 @@ + + + + + + + + <?php echo htmlspecialchars($pageTitle); ?> - ٟنل مدیریت + + + + + + + + + + + + + + + +
+ +

+
+ ' . htmlspecialchars($message) . '
'; +} + +/** + * Show error message + */ +function showError($message) +{ + echo '
' . htmlspecialchars($message) . '
'; +} + +/** + * Sanitize input + */ +function sanitizeInput($data) +{ + return htmlspecialchars(strip_tags(trim($data))); +} + +/** + * Helper wrappers for functions.php + */ +function getUserBalance($chat_id) +{ + $stmt = pdo()->prepare("SELECT balance FROM users WHERE chat_id = ?"); + $stmt->execute([$chat_id]); + return $stmt->fetchColumn() ?? 0; +} + +function getServers() +{ + $stmt = pdo()->query("SELECT * FROM servers WHERE status = 'active' ORDER BY id DESC"); + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} diff --git a/src/web/index.php b/src/web/index.php new file mode 100644 index 0000000..66a4ffc --- /dev/null +++ b/src/web/index.php @@ -0,0 +1,207 @@ + + + + + + + + ورود ØšÙ‡ ٟنل مدیریت + + + + + +
+
+ +

ٟنل مدیریت

+

لطفاً وارد حساؚ کارؚری خود ؎وید

+
+ + +
❌
+ + +
+
+ + +
+ +
+ + +
+ + +
+
+ + + \ No newline at end of file diff --git a/src/web/pages/admins.php b/src/web/pages/admins.php new file mode 100644 index 0000000..00998d9 --- /dev/null +++ b/src/web/pages/admins.php @@ -0,0 +1,280 @@ + + +
+ + +
+ + +
+ +
✅
+ + + +
❌
+ + + +
+
+
+ +
+
+
+
کل ادمین‌ها
+
؎امل سوٟر ادمین
+
+
+ +
+
+ +
+
+
+
ادمین‌های عادی
+
+
+ +
+
+ +
+
+
+
انواع دسترسی
+
+
+
+ + +
+
+

افزودن ادمین جدید

+
+
+
+
+
+ + + ؎ناسه کارؚر تلگرام +
+ +
+ + +
+
+ +
+ +
+ $label): ?> + + +
+ + همه دسترسی‌های مورد نیاز را انتخاؚ کنید + +
+ + +
+
+
+ + +
+
+

لیست ادمین‌ها ( نفر)

+
+
+ +

+ + هیچ ادمینی یافت ن؎د. از فرم ؚالا ؚرای افزودن ادمین جدید استفاده کنید. +

+ + $admin): ?> +
+
+
+
+

+ + +

+

+ Chat ID: +

+
+ + حذف + +
+ + +
+ + دسترسی‌ها + + ( مورد) + + + +
+ + +
+ $label): ?> + + +
+ + +
+
+ + +
+ دسترسی‌های فعلی: +
+ + هیچ دسترسی ندارد + + + + + + + +
+
+
+
+ + +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/web/pages/broadcast.php b/src/web/pages/broadcast.php new file mode 100644 index 0000000..041ed63 --- /dev/null +++ b/src/web/pages/broadcast.php @@ -0,0 +1,480 @@ + false, 'error' => 'BOT_TOKEN تعریف ن؎ده است. لطفاً فایل config.php را ؚررسی کنید.']); + exit; + } + + if (BOT_TOKEN === 'TOKEN' || empty(BOT_TOKEN)) { + echo json_encode(['success' => false, 'error' => 'BOT_TOKEN در فایل config.php ØšÙ‡ درستی تن؞یم ن؎ده است.']); + exit; + } + + $offset = (int) ($_POST['offset'] ?? 0); + $batch_size = 50; // Process 50 users at a time + $message_text = $_POST['message_text'] ?? ''; + $target_group = $_POST['target_group'] ?? 'all'; + $photo_id = sanitizeInput($_POST['photo_id'] ?? ''); + + if (empty($message_text)) { + echo json_encode(['success' => false, 'error' => 'متن ٟیام خالی است']); + exit; + } + + // Get target users based on selection + switch ($target_group) { + case 'all': + $stmt = pdo()->query("SELECT chat_id FROM users WHERE status = 'active' LIMIT $offset, $batch_size"); + $count_stmt = pdo()->query("SELECT COUNT(*) FROM users WHERE status = 'active'"); + break; + case 'with_service': + $stmt = pdo()->query(" + SELECT DISTINCT u.chat_id + FROM users u + JOIN services s ON u.chat_id = s.owner_chat_id + WHERE u.status = 'active' + LIMIT $offset, $batch_size + "); + $count_stmt = pdo()->query("SELECT COUNT(DISTINCT u.chat_id) FROM users u JOIN services s ON u.chat_id = s.owner_chat_id WHERE u.status = 'active'"); + break; + case 'no_service': + $stmt = pdo()->query(" + SELECT chat_id + FROM users + WHERE status = 'active' + AND chat_id NOT IN (SELECT DISTINCT owner_chat_id FROM services) + LIMIT $offset, $batch_size + "); + $count_stmt = pdo()->query("SELECT COUNT(*) FROM users WHERE status = 'active' AND chat_id NOT IN (SELECT DISTINCT owner_chat_id FROM services)"); + break; + case 'active_service': + $now = time(); + $stmt = pdo()->prepare(" + SELECT DISTINCT u.chat_id + FROM users u + JOIN services s ON u.chat_id = s.owner_chat_id + WHERE u.status = 'active' + AND s.expire_timestamp > ? + LIMIT $offset, $batch_size + "); + $stmt->execute([$now]); + $count_stmt = pdo()->prepare("SELECT COUNT(DISTINCT u.chat_id) FROM users u JOIN services s ON u.chat_id = s.owner_chat_id WHERE u.status = 'active' AND s.expire_timestamp > ?"); + $count_stmt->execute([$now]); + break; + default: + $stmt = pdo()->query("SELECT chat_id FROM users WHERE status = 'active' LIMIT $offset, $batch_size"); + $count_stmt = pdo()->query("SELECT COUNT(*) FROM users WHERE status = 'active'"); + } + + $users = $stmt->fetchAll(PDO::FETCH_COLUMN); + $total_users = $count_stmt->fetchColumn(); + + $sent = 0; + $failed = 0; + $error_details = []; + + // Send messages to this batch + foreach ($users as $chat_id) { + try { + if (!empty($photo_id)) { + // Send photo with caption using cURL for better error handling + $url = "https://api.telegram.org/bot" . BOT_TOKEN . "/sendPhoto"; + $data = [ + 'chat_id' => $chat_id, + 'photo' => $photo_id, + 'caption' => $message_text, + 'parse_mode' => 'HTML' + ]; + + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($data)); + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + $result = curl_exec($ch); + $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + curl_close($ch); + + if ($result && $http_code == 200) { + $response = json_decode($result, true); + if ($response && isset($response['ok']) && $response['ok']) { + $sent++; + } else { + $failed++; + $error_details[] = "Chat $chat_id: " . ($response['description'] ?? 'Unknown error'); + } + } else { + $failed++; + $error_details[] = "Chat $chat_id: HTTP $http_code"; + } + } else { + // Send text message + $response = sendMessage($chat_id, $message_text); + $decoded = json_decode($response, true); + if ($decoded && isset($decoded['ok']) && $decoded['ok']) { + $sent++; + } else { + $failed++; + $error_details[] = "Chat $chat_id: " . ($decoded['description'] ?? 'sendMessage failed'); + } + } + + // Small delay to avoid rate limiting + usleep(30000); // 30ms delay + } catch (Exception $e) { + $failed++; + $error_details[] = "Chat $chat_id: Exception - " . $e->getMessage(); + } + } + + $response_data = [ + 'success' => true, + 'sent' => $sent, + 'failed' => $failed, + 'total' => $total_users, + 'processed' => $offset + count($users), + 'has_more' => ($offset + count($users)) < $total_users + ]; + + // Include error details in debug mode (first batch only) + if ($offset == 0 && !empty($error_details)) { + $response_data['debug_errors'] = array_slice($error_details, 0, 5); // First 5 errors + } + + echo json_encode($response_data); + exit; +} + +// Get statistics for display +$stats = []; +$stats['all'] = pdo()->query("SELECT COUNT(*) FROM users WHERE status = 'active'")->fetchColumn(); +$stats['with_service'] = pdo()->query("SELECT COUNT(DISTINCT owner_chat_id) FROM services s JOIN users u ON s.owner_chat_id = u.chat_id WHERE u.status = 'active'")->fetchColumn(); +$stats['no_service'] = $stats['all'] - $stats['with_service']; +$now = time(); +$stmt = pdo()->prepare("SELECT COUNT(DISTINCT s.owner_chat_id) FROM services s JOIN users u ON s.owner_chat_id = u.chat_id WHERE u.status = 'active' AND s.expire_timestamp > ?"); +$stmt->execute([$now]); +$stats['active_service'] = $stmt->fetchColumn(); + +renderHeader('ٟیام همگانی'); +?> + +
+ + +
+ + +
+ + + + +
+
+
+ +
+
+
+
همه کارؚران فعال
+
+
+ +
+
+ +
+
+
+
ؚا سرویس
+
+
+ +
+
+ +
+
+
+
ؚدون سرویس
+
+
+ +
+
+ +
+
+
+
سرویس فعال
+
+
+
+ + +
+
+

ارسال ٟیام جدید

+
+
+
+ ⚠ ه؎دار: ٟیام ØšÙ‡ تمام کارؚران گروه انتخاؚی ارسال خواهد ؎د. این عمل قاؚل ؚرگ؎ت + نیست! +
+ +
+
+ + +
+ 📊 تعداد گیرندگان: نفر +
+
+ +
+ + + از HTML ؚرای فرمت‌ؚندی استفاده کنید +
+ +
+ + + ؚرای ارسال ٟیام ؚا تصویر، یک عکس ØšÙ‡ رؚات ارسال کنید + و Photo ID آن را اینجا وارد کنید +
+ +
+ + +
+ ارسال ØšÙ‡ صورت خودکار در ٟس‌زمینه انجام می‌؎ود +
+
+
+
+
+ + +
+
+

ٟی؎‌نمای؎

+
+
+
+

متن ٟیام خود را ؚنویسید تا اینجا نمای؎ + داده ؎ود...

+
+
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/web/pages/categories.php b/src/web/pages/categories.php new file mode 100644 index 0000000..d6b8d8e --- /dev/null +++ b/src/web/pages/categories.php @@ -0,0 +1,147 @@ +prepare("INSERT INTO categories (name, status) VALUES (?, 'active')"); + if ($stmt->execute([$name])) { + $success = 'دسته‌ؚندی ؚا موفقیت اضافه ؎د.'; + } else { + $error = 'خطا در افزودن دسته‌ؚندی.'; + } + } + } +} + +// Handle delete +if (isset($_GET['delete'])) { + $id = (int) $_GET['delete']; + $stmt = pdo()->prepare("DELETE FROM categories WHERE id = ?"); + if ($stmt->execute([$id])) { + $success = 'دسته‌ؚندی حذف ؎د.'; + } +} + +// Handle toggle status +if (isset($_GET['toggle'])) { + $id = (int) $_GET['toggle']; + $stmt = pdo()->prepare("UPDATE categories SET status = IF(status = 'active', 'inactive', 'active') WHERE id = ?"); + if ($stmt->execute([$id])) { + $success = 'وضعیت دسته‌ؚندی تغییر کرد.'; + } +} + +// Get all categories +$categories = getCategories(); + +renderHeader('مدیریت دسته‌ؚندی‌ها'); +?> + +
+ + +
+ + +
+ +
✅
+ + + +
❌
+ + + +
+
+

افزودن دسته‌ؚندی جدید

+
+
+
+
+ + +
+ +
+
+
+ + +
+
+

لیست دسته‌ؚندی‌ها

+
+
+ +

هیچ دسته‌ؚندی‌ای یافت ن؎د.

+ + + + + + + + + + + + + + + + + + + + +
؎ناسهناموضعیتعملیات
+ + ✅ فعال + + ❌ غیرفعال + + + + + + + حذف + +
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/src/web/pages/discount.php b/src/web/pages/discount.php new file mode 100644 index 0000000..2fd8d79 --- /dev/null +++ b/src/web/pages/discount.php @@ -0,0 +1,260 @@ + 0 && $max_usage > 0) { + // Check if code already exists + $stmt = pdo()->prepare("SELECT COUNT(*) FROM discount_codes WHERE code = ?"); + $stmt->execute([$code]); + if ($stmt->fetchColumn() > 0) { + $error = 'این کد تخفیف قؚلاً ثؚت ؎ده است.'; + } else { + $stmt = pdo()->prepare("INSERT INTO discount_codes (code, type, value, max_usage, usage_count, status) VALUES (?, ?, ?, ?, 0, 'active')"); + if ($stmt->execute([$code, $type, $value, $max_usage])) { + $success = 'کد تخفیف ؚا موفقیت اضافه ؎د.'; + } else { + $error = 'خطا در افزودن کد تخفیف.'; + } + } + } else { + $error = 'لطفاً تمام فیلدها را ØšÙ‡ درستی ٟر کنید.'; + } +} + +// Handle edit discount +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['edit_discount'])) { + $id = (int) $_POST['discount_id']; + $code = strtoupper(sanitizeInput($_POST['code'])); + $type = sanitizeInput($_POST['type']); + $value = (float) $_POST['value']; + $max_usage = (int) $_POST['max_usage']; + + $stmt = pdo()->prepare("UPDATE discount_codes SET code=?, type=?, value=?, max_usage=? WHERE id=?"); + if ($stmt->execute([$code, $type, $value, $max_usage, $id])) { + $success = 'کد تخفیف ؚهروزرسانی ؎د.'; + } else { + $error = 'خطا در ؚهروزرسانی کد تخفیف.'; + } +} + +// Handle delete +if (isset($_GET['delete'])) { + $id = (int) $_GET['delete']; + $stmt = pdo()->prepare("DELETE FROM discount_codes WHERE id = ?"); + if ($stmt->execute([$id])) { + $success = 'کد تخفیف حذف ؎د.'; + } +} + +// Handle toggle +if (isset($_GET['toggle'])) { + $id = (int) $_GET['toggle']; + $stmt = pdo()->prepare("UPDATE discount_codes SET status = IF(status = 'active', 'inactive', 'active') WHERE id = ?"); + if ($stmt->execute([$id])) { + $success = 'وضعیت کد تخفیف تغییر کرد.'; + } +} + +// Get discount for edit +$edit_discount = null; +if (isset($_GET['edit'])) { + $edit_id = (int) $_GET['edit']; + $stmt = pdo()->prepare("SELECT * FROM discount_codes WHERE id = ?"); + $stmt->execute([$edit_id]); + $edit_discount = $stmt->fetch(PDO::FETCH_ASSOC); +} + +// Get all discount codes +$stmt = pdo()->query("SELECT * FROM discount_codes ORDER BY id DESC"); +$codes = $stmt->fetchAll(PDO::FETCH_ASSOC); + +renderHeader('مدیریت کدهای تخفیف'); +?> + +
+ + +
+ + +
+ +
✅
+ + + +
❌
+ + + +
+
+

+

+
+
+
+ + + + +
+
+ + + فقط حروف انگلیسی و اعداد +
+ +
+ + +
+ +
+ + + درصد (1-100) یا تومان +
+ +
+ + +
+
+ + + + + + لغو + + +
+
+
+ + +
+
+

لیست کدهای تخفیف ()

+
+
+ +

هیچ کد تخفیفی یافت ن؎د.

+ + + + + + + + + + + + + + + + + + + + + + + + +
کدنوعمقداراستفادهوضعیتعملیات
+ + + + + + + + + + + + / + + + + ✅ فعال + + ❌ غیرفعال + + + + + + + + + + + +
+ +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/web/pages/guides.php b/src/web/pages/guides.php new file mode 100644 index 0000000..dc19932 --- /dev/null +++ b/src/web/pages/guides.php @@ -0,0 +1,239 @@ +prepare("INSERT INTO guides (button_name, content_type, message_text, photo_id, status) VALUES (?, ?, ?, ?, 'active')"); + if ($stmt->execute([$button_name, $content_type, $message_text, $photo_id])) { + $success = 'راهنما ؚا موفقیت اضافه ؎د.'; + } else { + $error = 'خطا در افزودن راهنما.'; + } + } else { + $error = 'لطفاً نام دکمه را وارد کنید.'; + } +} + +// Handle edit guide +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['edit_guide'])) { + $id = (int) $_POST['guide_id']; + $button_name = sanitizeInput($_POST['button_name']); + $content_type = sanitizeInput($_POST['content_type']); + $message_text = $_POST['message_text'] ?? ''; + $photo_id = sanitizeInput($_POST['photo_id'] ?? ''); + + $stmt = pdo()->prepare("UPDATE guides SET button_name=?, content_type=?, message_text=?, photo_id=? WHERE id=?"); + if ($stmt->execute([$button_name, $content_type, $message_text, $photo_id, $id])) { + $success = 'راهنما ؚهروزرسانی ؎د.'; + } else { + $error = 'خطا در ؚهروزرسانی راهنما.'; + } +} + +// Handle delete +if (isset($_GET['delete'])) { + $id = (int) $_GET['delete']; + $stmt = pdo()->prepare("DELETE FROM guides WHERE id = ?"); + if ($stmt->execute([$id])) { + $success = 'راهنما حذف ؎د.'; + } +} + +// Handle toggle +if (isset($_GET['toggle'])) { + $id = (int) $_GET['toggle']; + $stmt = pdo()->prepare("UPDATE guides SET status = IF(status = 'active', 'inactive', 'active') WHERE id = ?"); + if ($stmt->execute([$id])) { + $success = 'وضعیت راهنما تغییر کرد.'; + } +} + +// Get guide for edit +$edit_guide = null; +if (isset($_GET['edit'])) { + $edit_id = (int) $_GET['edit']; + $stmt = pdo()->prepare("SELECT * FROM guides WHERE id = ?"); + $stmt->execute([$edit_id]); + $edit_guide = $stmt->fetch(PDO::FETCH_ASSOC); +} + +// Get all guides +$stmt = pdo()->query("SELECT * FROM guides ORDER BY id DESC"); +$guides = $stmt->fetchAll(PDO::FETCH_ASSOC); + +renderHeader('مدیریت راهنما'); +?> + +
+ + +
+ + +
+ +
✅
+ + + +
❌
+ + + +
+
+

+

+
+
+
+ + + + +
+
+ + +
+ +
+ + +
+
+ +
+ + + از Markdown ؚرای فرمت‌ؚندی استفاده کنید +
+ +
+ + + ؚرای دریافت: یک تصویر ØšÙ‡ رؚات ارسال کنید +
+ + + + + + لغو + + +
+
+
+ + +
+
+

لیست راهنماها ()

+
+
+ +

هیچ راهنمایی یافت ن؎د.

+ + + + + + + + + + + + + + + + + + + + + + +
؎ناسهعنوان دکمهنوع محتواوضعیتعملیات
+ + + + ✅ فعال + + ❌ غیرفعال + + + + + + + + + + + +
+ +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/web/pages/payments.php b/src/web/pages/payments.php new file mode 100644 index 0000000..d3dd364 --- /dev/null +++ b/src/web/pages/payments.php @@ -0,0 +1,256 @@ +prepare("SELECT * FROM payment_requests WHERE id = ? AND status = 'pending'"); + $stmt->execute([$payment_id]); + $payment = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($payment) { + // Update user balance + updateUserBalance($payment['user_id'], $payment['amount'], 'add'); + + // Update payment status + $stmt = pdo()->prepare("UPDATE payment_requests SET status='approved', processed_at=NOW(), processed_by_admin_id=? WHERE id=?"); + $stmt->execute([ADMIN_CHAT_ID, $payment_id]); + + // Send notification to user via bot + sendMessage($payment['user_id'], "✅ درخواست ؎ارژ حساؚ ؎ما ØšÙ‡ مؚلغ " . number_format($payment['amount']) . " تومان تایید ؎د.\n\n💰 موجودی جدید: " . number_format(getUserBalance($payment['user_id'])) . " تومان"); + + $success = 'درخواست تایید ؎د و موجودی کارؚر افزای؎ یافت.'; + } else { + $error = 'درخواست یافت ن؎د یا قؚلاً ٟرداز؎ ؎ده است.'; + } +} + +// Handle reject payment +if (isset($_GET['reject'])) { + $payment_id = (int) $_GET['reject']; + $stmt = pdo()->prepare("SELECT * FROM payment_requests WHERE id = ? AND status = 'pending'"); + $stmt->execute([$payment_id]); + $payment = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($payment) { + $stmt = pdo()->prepare("UPDATE payment_requests SET status='rejected', processed_at=NOW(), processed_by_admin_id=? WHERE id=?"); + $stmt->execute([ADMIN_CHAT_ID, $payment_id]); + + // Send notification to user via bot + sendMessage($payment['user_id'], "❌ درخواست ؎ارژ حساؚ ؎ما ØšÙ‡ مؚلغ " . number_format($payment['amount']) . " تومان رد ؎د.\n\nلطفاً ؚا ٟ؎تیؚانی تماس ؚگیرید."); + + $success = 'درخواست رد ؎د.'; + } else { + $error = 'درخواست یافت ن؎د یا قؚلاً ٟرداز؎ ؎ده است.'; + } +} + +// Get pending payment requests +$stmt = pdo()->query(" + SELECT pr.*, u.first_name + FROM payment_requests pr + JOIN users u ON pr.user_id = u.chat_id + WHERE pr.status = 'pending' + ORDER BY pr.created_at DESC +"); +$pending_payments = $stmt->fetchAll(PDO::FETCH_ASSOC); + +// Get processed payment requests (last 50) +$stmt = pdo()->query(" + SELECT pr.*, u.first_name + FROM payment_requests pr + JOIN users u ON pr.user_id = u.chat_id + WHERE pr.status != 'pending' + ORDER BY pr.processed_at DESC + LIMIT 50 +"); +$processed_payments = $stmt->fetchAll(PDO::FETCH_ASSOC); + +// Get renewal requests +$stmt = pdo()->query(" + SELECT rr.*, u.first_name + FROM renewal_requests rr + JOIN users u ON rr.user_id = u.chat_id + WHERE rr.status = 'pending' + ORDER BY rr.created_at DESC +"); +$renewal_requests = $stmt->fetchAll(PDO::FETCH_ASSOC); + +renderHeader('مدیریت ٟرداخت‌ها و تمدیدها'); +?> + +
+ + +
+ + +
+ +
✅
+ + + +
❌
+ + + +
+
+

درخواست‌های ؎ارژ حساؚ در انت؞ار + ()

+
+
+ +

هیچ درخواستی در انت؞ار نیست.

+ +
+ + + + + + + + + + + + + + + + + + + +
؎ناسهکارؚرمؚلغتاریخرسیدعملیات
+ + + رد + +
+
+ +
+
+ + + +
+
+

درخواست‌های تمدید در انت؞ار + ()

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
کارؚرسرویسروز/حجممؚلغتاریخرسید
روز / + GB + تومان + + + م؎اهده + + + - + +
+
+
+ ℹ توجه: ؚرای تایید/رد درخواست‌های تمدید، از رؚات تلگرام استفاده کنید. +
+
+
+ + + +
+
+

تاریخچه ٟرداخت‌ها (50 مورد اخیر)

+
+
+ +

هیچ درخواست ٟرداز؎ ؎ده‌ای وجود ندارد.

+ +
+ + + + + + + + + + + + + + + + + + + + + +
کارؚرمؚلغتاریخ ثؚتتاریخ ٟرداز؎وضعیت
تومان + + + ✅ تایید ؎ده + + ❌ رد ؎ده + +
+
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/src/web/pages/plans.php b/src/web/pages/plans.php new file mode 100644 index 0000000..c710a47 --- /dev/null +++ b/src/web/pages/plans.php @@ -0,0 +1,299 @@ + 0 && $category_id > 0) { + $stmt = pdo()->prepare("INSERT INTO plans (server_id, category_id, name, price, volume_gb, duration_days, description, show_sub_link, show_conf_links, is_test_plan, status) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'active')"); + if ($stmt->execute([$server_id, $category_id, $name, $price, $volume_gb, $duration_days, $description, $show_sub_link, $show_conf_links, $is_test_plan])) { + $success = 'ٟلن ؚا موفقیت اضافه ؎د.'; + } else { + $error = 'خطا در افزودن ٟلن.'; + } + } else { + $error = 'لطفاً تمام فیلدهای الزامی را ٟر کنید.'; + } +} + +// Handle edit plan +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['edit_plan'])) { + $id = (int) $_POST['plan_id']; + $server_id = (int) $_POST['server_id']; + $category_id = (int) $_POST['category_id']; + $name = sanitizeInput($_POST['name']); + $price = (float) $_POST['price']; + $volume_gb = (int) $_POST['volume_gb']; + $duration_days = (int) $_POST['duration_days']; + $description = sanitizeInput($_POST['description']); + $show_sub_link = isset($_POST['show_sub_link']) ? 1 : 0; + $show_conf_links = isset($_POST['show_conf_links']) ? 1 : 0; + + $stmt = pdo()->prepare("UPDATE plans SET server_id=?, category_id=?, name=?, price=?, volume_gb=?, duration_days=?, description=?, show_sub_link=?, show_conf_links=? WHERE id=?"); + if ($stmt->execute([$server_id, $category_id, $name, $price, $volume_gb, $duration_days, $description, $show_sub_link, $show_conf_links, $id])) { + $success = 'ٟلن ؚهروزرسانی ؎د.'; + } else { + $error = 'خطا در ؚهروزرسانی ٟلن.'; + } +} + +// Handle delete +if (isset($_GET['delete'])) { + $id = (int) $_GET['delete']; + $stmt = pdo()->prepare("DELETE FROM plans WHERE id = ?"); + if ($stmt->execute([$id])) { + $success = 'ٟلن حذف ؎د.'; + } +} + +// Handle toggle status +if (isset($_GET['toggle'])) { + $id = (int) $_GET['toggle']; + $stmt = pdo()->prepare("UPDATE plans SET status = IF(status = 'active', 'inactive', 'active') WHERE id = ?"); + if ($stmt->execute([$id])) { + $success = 'وضعیت ٟلن تغییر کرد.'; + } +} + +// Get plan for edit +$edit_plan = null; +if (isset($_GET['edit'])) { + $edit_id = (int) $_GET['edit']; + $stmt = pdo()->prepare("SELECT * FROM plans WHERE id = ?"); + $stmt->execute([$edit_id]); + $edit_plan = $stmt->fetch(PDO::FETCH_ASSOC); +} + +// Get servers and categories for form +$servers = getServers(); +$categories = getCategories(); + +// Get all plans with server info +$stmt = pdo()->query(" + SELECT p.*, s.name as server_name, s.type as server_type, c.name as category_name + FROM plans p + LEFT JOIN servers s ON p.server_id = s.id + LEFT JOIN categories c ON p.category_id = c.id + ORDER BY p.is_test_plan DESC, p.id ASC +"); +$plans = $stmt->fetchAll(PDO::FETCH_ASSOC); + +renderHeader('مدیریت ٟلن‌ها'); +?> + +
+ + +
+ + +
+ +
✅
+ + + +
❌
+ + + +
+
+

+
+
+
+ + + + +
+
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ + +
+ +
+ + + + + + + +
+ + + + + + لغو + + +
+
+
+ + +
+
+

لیست ٟلن‌ها ()

+
+
+ +

هیچ ٟلنی یافت ن؎د.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
؎ناسهنامسروردسته‌ؚندیقیمتحجم/مدتوضعیتعملیات
+ + 🧪 + + + تومانGB / روز + + + ✅ فعال + + ❌ غیرفعال + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + \ No newline at end of file diff --git a/src/web/pages/servers.php b/src/web/pages/servers.php new file mode 100644 index 0000000..3ecda27 --- /dev/null +++ b/src/web/pages/servers.php @@ -0,0 +1,369 @@ +prepare("INSERT INTO servers (name, url, sub_host, marzban_protocols, username, password, type, status) VALUES (?, ?, ?, ?, ?, ?, ?, 'active')"); + if ($stmt->execute([$name, $url, $sub_host, $protocols_json, $username, $password, $type])) { + $success = 'سرور ؚا موفقیت اضافه ؎د.'; + } else { + $error = 'خطا در افزودن سرور.'; + } + } else { + $error = 'لطفاً تمام فیلدها را ٟر کنید.'; + } +} + +// Handle edit server +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['edit_server'])) { + $id = (int) $_POST['server_id']; + $name = sanitizeInput($_POST['name']); + $url = sanitizeInput($_POST['url']); + $username = sanitizeInput($_POST['username']); + $password = $_POST['password']; + $type = sanitizeInput($_POST['type']); + $sub_host = sanitizeInput($_POST['sub_host'] ?? ''); + + // Handle protocols for marzban + $protocols_json = null; + if ($type === 'marzban') { + if (isset($_POST['protocols']) && !empty($_POST['protocols'])) { + $protocols = array_map('sanitizeInput', $_POST['protocols']); + $protocols_json = json_encode(array_values($protocols)); + } else { + // Default to VLESS if no protocols selected + $protocols_json = json_encode(['vless']); + } + } + + // Only update password if provided + if (!empty($password)) { + $stmt = pdo()->prepare("UPDATE servers SET name=?, url=?, sub_host=?, marzban_protocols=?, username=?, password=?, type=? WHERE id=?"); + $success = $stmt->execute([$name, $url, $sub_host, $protocols_json, $username, $password, $type, $id]); + } else { + $stmt = pdo()->prepare("UPDATE servers SET name=?, url=?, sub_host=?, marzban_protocols=?, username=?, type=? WHERE id=?"); + $success = $stmt->execute([$name, $url, $sub_host, $protocols_json, $username, $type, $id]); + } + + if ($success) { + $success = 'سرور ؚهروزرسانی ؎د.'; + } else { + $error = 'خطا در ؚهروزرسانی سرور.'; + } +} + +// Handle delete +if (isset($_GET['delete'])) { + $id = (int) $_GET['delete']; + $stmt = pdo()->prepare("DELETE FROM servers WHERE id = ?"); + if ($stmt->execute([$id])) { + $success = 'سرور حذف ؎د.'; + } +} + +// Handle toggle status +if (isset($_GET['toggle'])) { + $id = (int) $_GET['toggle']; + $stmt = pdo()->prepare("UPDATE servers SET status = IF(status = 'active', 'inactive', 'active') WHERE id = ?"); + if ($stmt->execute([$id])) { + $success = 'وضعیت سرور تغییر کرد.'; + } +} + +// Get server for edit +$edit_server = null; +if (isset($_GET['edit'])) { + $edit_id = (int) $_GET['edit']; + $stmt = pdo()->prepare("SELECT * FROM servers WHERE id = ?"); + $stmt->execute([$edit_id]); + $edit_server = $stmt->fetch(PDO::FETCH_ASSOC); +} + +// Get all servers +$stmt = pdo()->query("SELECT * FROM servers ORDER BY id DESC"); +$servers = $stmt->fetchAll(PDO::FETCH_ASSOC); + +renderHeader('مدیریت سرورها'); +?> + +
+ + +
+ + +
+ +
✅
+ + + +
❌
+ + + +
+
+

+

+
+
+
+ + + + +
+
+ + +
+ +
+ + +
+
+ +
+ + + ؚدون / در انتها +
+ + + + + + + + + +
+
+ + +
+ +
+ + > +
+
+ + + + + + لغو + + +
+
+
+ + +
+
+

لیست سرورها ()

+
+
+ +

هیچ سروری یافت ن؎د.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
؎ناسهنامنوعآدرسٟروتکل‌ها / ویژگی‌هاوضعیتعملیات
+ '🔷 Marzban', + 'sanaei' => '🔶 Sanaei', + 'marzneshin' => '🔵 Marzneshin' + ]; + echo $type_labels[$server['type']] ?? $server['type']; + ?> + + + + + '; + foreach ($protocols as $protocol) { + echo '' . htmlspecialchars($protocol) . ''; + } + echo ''; + } else { + echo 'همه'; + } + ?> + + 🔗 Custom Sub + + — + + + + ✅ فعال + + ❌ غیرفعال + + + + + + + + + + + +
+
+ +
+
+
+
+
+ + + + \ No newline at end of file diff --git a/src/web/pages/settings.php b/src/web/pages/settings.php new file mode 100644 index 0000000..0dc13b1 --- /dev/null +++ b/src/web/pages/settings.php @@ -0,0 +1,345 @@ + sanitizeInput($_POST['card_number'] ?? ''), + 'card_holder' => sanitizeInput($_POST['card_owner_name'] ?? ''), + 'copy_enabled' => true + ]; + + // Verification + $settings_to_update['verification_method'] = $_POST['verification_method'] ?? 'off'; + $settings_to_update['verification_iran_only'] = $_POST['verification_iran_only'] ?? 'off'; + + // Test config + $settings_to_update['test_config_usage_limit'] = (int) ($_POST['test_config_usage_limit'] ?? 1); + + // Notifications + $settings_to_update['notification_expire_status'] = $_POST['notification_expire_status'] ?? 'off'; + $settings_to_update['notification_expire_days'] = (int) ($_POST['notification_expire_days'] ?? 3); + $settings_to_update['notification_expire_gb'] = (int) ($_POST['notification_expire_gb'] ?? 1); + $settings_to_update['notification_inactive_status'] = $_POST['notification_inactive_status'] ?? 'off'; + $settings_to_update['notification_inactive_days'] = (int) ($_POST['notification_inactive_days'] ?? 30); + + // Renewal + $settings_to_update['renewal_status'] = $_POST['renewal_status'] ?? 'off'; + $settings_to_update['renewal_price_per_day'] = (int) ($_POST['renewal_price_per_day'] ?? 1000); + $settings_to_update['renewal_price_per_gb'] = (int) ($_POST['renewal_price_per_gb'] ?? 2000); + + saveSettings($settings_to_update); + $success = 'تن؞یمات ؚا موفقیت ذخیره ؎د.'; +} + +// Get current settings +$settings = getSettings(); + +// Extract card info from payment_method +$card_number = $settings['payment_method']['card_number'] ?? ''; +$card_owner_name = $settings['payment_method']['card_holder'] ?? ''; + +renderHeader('تن؞یمات'); +?> + +
+ + +
+ + +
+ +
✅
+ + + +
❌
+ + +
+ +
+
+

تن؞یمات رؚات

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+ + +
+
+

کانال اجؚاری

+
+
+
+
+ + +
+ +
+ + +
+
+
+
+ + +
+
+

اطلاعات کارت ؚانکی

+
+
+
+
+ + + ؚرای ٟرداخت دستی +
+ +
+ + +
+
+
+
+ + +
+
+

هدیه خو؎‌آمدگویی

+
+
+
+ + +
+
+
+ + +
+
+

درگاه ٟرداخت

+
+
+
+
+ + +
+ +
+ + +
+
+
+
+ + +
+
+

احراز هویت

+
+
+
+
+ + +
+ +
+ + +
+
+
+
+ + +
+
+

کانفیگ تست

+
+
+
+ + +
+
+ +

ؚا کلیک روی دکمه زیر، ؎مارنده دریافت کانفیگ تست ؚرای تمام کارؚران ØšÙ‡ صفر ؚازن؎انی می‌؎ود.

+ + ریست کردن دریافت‌های تمام کارؚران + +
+
+
+ + +
+
+

اعلان‌ها

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+ +
+ +
+
+ + +
+ +
+ + +
+
+
+
+ + +
+
+

تمدید سرویس

+
+
+
+
+ + +
+ +
+ + +
+ +
+ + +
+
+
+
+ + +
+
+
+
+ + \ No newline at end of file diff --git a/src/web/pages/stats.php b/src/web/pages/stats.php new file mode 100644 index 0000000..99476ba --- /dev/null +++ b/src/web/pages/stats.php @@ -0,0 +1,190 @@ +query("SELECT COUNT(*) FROM users"); +$stats['total_users'] = $stmt->fetchColumn(); + +$stmt = pdo()->query("SELECT COUNT(*) FROM users WHERE status = 'active'"); +$stats['active_users'] = $stmt->fetchColumn(); + +$stmt = pdo()->query("SELECT COUNT(*) FROM users WHERE status = 'banned'"); +$stats['banned_users'] = $stmt->fetchColumn(); + +// Services stats +$stmt = pdo()->query("SELECT COUNT(*) FROM services"); +$stats['total_services'] = $stmt->fetchColumn(); + +$now = time(); +$stmt = pdo()->prepare("SELECT COUNT(*) FROM services WHERE expire_timestamp > ?"); +$stmt->execute([$now]); +$stats['active_services'] = $stmt->fetchColumn(); + +$stmt = pdo()->prepare("SELECT COUNT(*) FROM services WHERE expire_timestamp <= ?"); +$stmt->execute([$now]); +$stats['expired_services'] = $stmt->fetchColumn(); + +// Income stats +$income_stats = calculateIncomeStats(); + +renderHeader('آمار و گزار؎ات'); +?> + +
+ + +
+ + +
+ +
+
+

آمار کارؚران

+
+
+
+
+
+ +
+
+
+
کل کارؚران
+
+
+ +
+
+ +
+
+
+
کارؚران فعال
+
+
+ +
+
+ +
+
+
+
کارؚران مسدود
+
+
+
+
+
+ + +
+
+

آمار سرویس‌ها

+
+
+
+
+
+ +
+
+
+
کل سرویس‌ها
+
+
+ +
+
+ +
+
+
+
سرویس‌های فعال
+
+
+ +
+
+ +
+
+
+
سرویس‌های منقضی ؎ده
+
+
+
+
+
+ + +
+
+

آمار درآمد

+
+
+
+
+
+ +
+
+
+
درآمد امروز (تومان)
+
+
+ +
+
+ +
+
+
+
درآمد هفته (تومان)
+
+
+ +
+
+ +
+
+
+
درآمد ماه (تومان)
+
+
+ +
+
+ +
+
+
+
درآمد سال (تومان)
+
+
+
+
+
+
+
+
+ + \ No newline at end of file diff --git a/src/web/pages/users.php b/src/web/pages/users.php new file mode 100644 index 0000000..e7569f0 --- /dev/null +++ b/src/web/pages/users.php @@ -0,0 +1,324 @@ +query("SELECT COUNT(*) FROM users")->fetchColumn(); +$stats['active'] = pdo()->query("SELECT COUNT(*) FROM users WHERE status = 'active'")->fetchColumn(); +$stats['banned'] = pdo()->query("SELECT COUNT(*) FROM users WHERE status = 'banned'")->fetchColumn(); + +// Pagination for user list +$page = isset($_GET['page']) ? max(1, (int) $_GET['page']) : 1; +$per_page = 20; +$offset = ($page - 1) * $per_page; + +// Get total users count +$total_users = $stats['total']; +$total_pages = ceil($total_users / $per_page); + +// Get all users with pagination +$stmt = pdo()->prepare(" + SELECT u.*, + COUNT(DISTINCT s.id) as service_count + FROM users u + LEFT JOIN services s ON u.chat_id = s.owner_chat_id + GROUP BY u.chat_id + ORDER BY u.created_at DESC + LIMIT ? OFFSET ? +"); +$stmt->execute([$per_page, $offset]); +$all_users = $stmt->fetchAll(PDO::FETCH_ASSOC); + +// Handle search +if (isset($_GET['search'])) { + $chat_id = $_GET['chat_id'] ?? ''; + + if (!empty($chat_id) && is_numeric($chat_id)) { + $stmt = pdo()->prepare("SELECT * FROM users WHERE chat_id = ?"); + $stmt->execute([$chat_id]); + $user_info = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$user_info) { + $error = 'کارؚر یافت ن؎د.'; + } + } +} + +// Handle add balance +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['add_balance'])) { + $chat_id = (int) $_POST['chat_id']; + $amount = (int) $_POST['amount']; + + if ($amount != 0) { + updateUserBalance($chat_id, abs($amount), $amount > 0 ? 'add' : 'subtract'); + $success = 'موجودی کارؚر ؚه‌روزرسانی ؎د.'; + + // Refresh user info if viewing a user + if (isset($_GET['search']) && isset($_GET['chat_id'])) { + $stmt = pdo()->prepare("SELECT * FROM users WHERE chat_id = ?"); + $stmt->execute([$chat_id]); + $user_info = $stmt->fetch(PDO::FETCH_ASSOC); + } + } +} + +// Handle ban/unban +if (isset($_GET['ban'])) { + $chat_id = (int) $_GET['ban']; + setUserStatus($chat_id, 'banned'); + $success = 'کارؚر مسدود ؎د.'; + header('Location: ?'); + exit; +} + +if (isset($_GET['unban'])) { + $chat_id = (int) $_GET['unban']; + setUserStatus($chat_id, 'active'); + $success = 'کارؚر آزاد ؎د.'; + header('Location: ?'); + exit; +} + +renderHeader('مدیریت کارؚران'); +?> + +
+ + +
+ + +
+ +
✅
+ + + +
❌
+ + + +
+
+
+ +
+
+
+
کل کارؚران
+
+
+ +
+
+ +
+
+
+
کارؚران فعال
+
+
+ +
+
+ +
+
+
+
کارؚران مسدود
+
+
+
+ + +
+
+

جستجوی کارؚر

+
+
+
+
+ + +
+ +
+
+
+ + + +
+
+

اطلاعات کارؚر

+
+
+

نام:

+

؎ناسه:

+

موجودی: تومان

+

وضعیت: + + ✅ فعال + + ❌ مسدود + +

+

تاریخ ثؚت‌نام:

+ +
+ + +
+ +
+ +
+ +
+ + + + مسدود کردن + + + + آزاد کردن + + + + + +

سرویس‌های کارؚر ()

+ +

کارؚر هیچ سرویسی ندارد.

+ + + + + + + + + + + + + + + + + + + + +
ٟلننام سرویستاریخ خریدانقضا
+ +
+ +
+
+ + + +
+
+

لیست تمام کارؚران ( نفر)

+
+
+ +

هیچ کارؚری یافت ن؎د.

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
نامChat IDموجودیتعداد سرویسوضعیتتاریخ عضویتعملیات
تومان + + ✅ فعال + + ❌ مسدود + + + + م؎اهده + +
+
+ + + 1): ?> + + + + + + + + + + + + +
+ + +
+
+
+
+ + + \ No newline at end of file From 3e5018c46116f680720f535094d625afac4551fb Mon Sep 17 00:00:00 2001 From: poryajp <118083331+poryajp@users.noreply.github.com> Date: Sat, 29 Nov 2025 03:47:49 +0330 Subject: [PATCH 05/18] 0.1.1 beta --- src/bot.php | 1426 ++++++++++++++--------------- src/includes/config.php | 9 +- src/includes/functions.php | 129 +++ src/web/assets/css/style.css | 284 +++++- src/web/assets/js/main.js | 104 ++- src/web/pages/users.php | 347 ++++--- src/web/user/assets/css/style.css | 255 ++++++ src/web/user/assets/js/app.js | 38 + src/web/user/auth.php | 52 ++ src/web/user/debug.html | 150 +++ src/web/user/guides.php | 85 ++ src/web/user/index.php | 215 +++++ src/web/user/services.php | 248 +++++ src/web/user/session.php | 36 + src/web/user/shop.php | 312 +++++++ src/web/user/simple-test.html | 72 ++ src/web/user/support.php | 167 ++++ src/web/user/test.php | 28 + src/web/user/wallet.php | 292 ++++++ 19 files changed, 3331 insertions(+), 918 deletions(-) create mode 100644 src/web/user/assets/css/style.css create mode 100644 src/web/user/assets/js/app.js create mode 100644 src/web/user/auth.php create mode 100644 src/web/user/debug.html create mode 100644 src/web/user/guides.php create mode 100644 src/web/user/index.php create mode 100644 src/web/user/services.php create mode 100644 src/web/user/session.php create mode 100644 src/web/user/shop.php create mode 100644 src/web/user/simple-test.html create mode 100644 src/web/user/support.php create mode 100644 src/web/user/test.php create mode 100644 src/web/user/wallet.php diff --git a/src/bot.php b/src/bot.php index 8937b8c..f3e56b7 100644 --- a/src/bot.php +++ b/src/bot.php @@ -2,8 +2,7 @@ if (function_exists('fastcgi_finish_request')) { fastcgi_finish_request(); -} -elseif (function_exists('litespeed_finish_request')) { +} elseif (function_exists('litespeed_finish_request')) { litespeed_finish_request(); } @@ -39,8 +38,7 @@ if (isset($update['callback_query'])) { $chat_id = $update['callback_query']['message']['chat']['id']; $first_name = $update['callback_query']['from']['first_name']; -} -elseif (isset($update['message']['chat']['id'])) { +} elseif (isset($update['message']['chat']['id'])) { $chat_id = $update['message']['chat']['id']; $first_name = $update['message']['from']['first_name']; } @@ -90,8 +88,7 @@ apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); deleteMessage($chat_id, $message_id); handleMainMenu($chat_id, $first_name, true); - } - else { + } else { apiRequest('answerCallbackQuery', [ 'callback_query_id' => $callback_id, 'text' => '❌ ؎ما هنوز در کانال عضو ن؎ده‌اید!', @@ -122,67 +119,66 @@ ]); die; } - + // --- مدیریت ٟرداخت مستقیم ؚرای خرید ٟلن --- if (strpos($data, 'charge_for_plan_') === 0) { - $parts = explode('_', $data); - $amount_to_charge = (int)$parts[3]; - $plan_id_to_buy = (int)$parts[4]; - $discount_code_to_use = (isset($parts[5]) && !empty($parts[5])) ? $parts[5] : null; - $custom_name_encoded = $parts[6] ?? ''; - $custom_name = base64_decode($custom_name_encoded); - - $description = "تکمیل خرید ٟلن #{$plan_id_to_buy}"; - $metadata = [ - "purpose" => "complete_purchase", - "plan_id" => $plan_id_to_buy, - "user_id" => $chat_id, - "custom_name" => $custom_name // ذخیره نام دلخواه - ]; - if ($discount_code_to_use) { - $metadata["discount_code"] = $discount_code_to_use; - } + $parts = explode('_', $data); + $amount_to_charge = (int) $parts[3]; + $plan_id_to_buy = (int) $parts[4]; + $discount_code_to_use = (isset($parts[5]) && !empty($parts[5])) ? $parts[5] : null; + $custom_name_encoded = $parts[6] ?? ''; + $custom_name = base64_decode($custom_name_encoded); + + $description = "تکمیل خرید ٟلن #{$plan_id_to_buy}"; + $metadata = [ + "purpose" => "complete_purchase", + "plan_id" => $plan_id_to_buy, + "user_id" => $chat_id, + "custom_name" => $custom_name // ذخیره نام دلخواه + ]; + if ($discount_code_to_use) { + $metadata["discount_code"] = $discount_code_to_use; + } - $zarinpal_result = createZarinpalLink($chat_id, $amount_to_charge, $description, $metadata); - if ($zarinpal_result['success']) { - $message = "⏳ در حال انتقال ØšÙ‡ درگاه ٟرداخت... لطفا صؚر کنید."; - $keyboard = ['inline_keyboard' => [[['text' => '🚀 ورود ØšÙ‡ صفحه ٟرداخت', 'url' => $zarinpal_result['url']]]]]; - editMessageText($chat_id, $message_id, $message, $keyboard); - } else { - editMessageText($chat_id, $message_id, $zarinpal_result['error']); - } - die; -} - elseif (strpos($data, 'manual_pay_for_plan_') === 0) { - $parts = explode('_', $data); - $amount_to_charge = (int)$parts[4]; - $plan_id_to_buy = (int)$parts[5]; - $discount_code_to_use = (isset($parts[6]) && !empty($parts[6])) ? $parts[6] : null; - $custom_name_encoded = $parts[7] ?? ''; - $custom_name = base64_decode($custom_name_encoded); - - $state_data = [ - 'charge_amount' => $amount_to_charge, - 'purpose' => 'complete_purchase', - 'plan_id' => $plan_id_to_buy, - 'custom_name' => $custom_name, // ذخیره نام دلخواه در state - ]; - if ($discount_code_to_use) { - $state_data['discount_code'] = $discount_code_to_use; - } + $zarinpal_result = createZarinpalLink($chat_id, $amount_to_charge, $description, $metadata); + if ($zarinpal_result['success']) { + $message = "⏳ در حال انتقال ØšÙ‡ درگاه ٟرداخت... لطفا صؚر کنید."; + $keyboard = ['inline_keyboard' => [[['text' => '🚀 ورود ØšÙ‡ صفحه ٟرداخت', 'url' => $zarinpal_result['url']]]]]; + editMessageText($chat_id, $message_id, $message, $keyboard); + } else { + editMessageText($chat_id, $message_id, $zarinpal_result['error']); + } + die; + } elseif (strpos($data, 'manual_pay_for_plan_') === 0) { + $parts = explode('_', $data); + $amount_to_charge = (int) $parts[4]; + $plan_id_to_buy = (int) $parts[5]; + $discount_code_to_use = (isset($parts[6]) && !empty($parts[6])) ? $parts[6] : null; + $custom_name_encoded = $parts[7] ?? ''; + $custom_name = base64_decode($custom_name_encoded); + + $state_data = [ + 'charge_amount' => $amount_to_charge, + 'purpose' => 'complete_purchase', + 'plan_id' => $plan_id_to_buy, + 'custom_name' => $custom_name, // ذخیره نام دلخواه در state + ]; + if ($discount_code_to_use) { + $state_data['discount_code'] = $discount_code_to_use; + } - updateUserData($chat_id, 'awaiting_payment_screenshot', $state_data); + updateUserData($chat_id, 'awaiting_payment_screenshot', $state_data); - $settings = getSettings(); - $payment_method = $settings['payment_method']; - $card_number_display = ($payment_method['copy_enabled'] ?? false) ? "{$payment_method['card_number']}" : $payment_method['card_number']; - $message = "ؚرای تکمیل خرید ØšÙ‡ مؚلغ " . number_format($amount_to_charge) . " تومان، لطفا مؚلغ را ØšÙ‡ اطلاعات زیر واریز نمایید:\n\n" . - "💳 ؎ماره کارت:\n" . $card_number_display . "\n" . - "👀 صاحؚ حساؚ: {$payment_method['card_holder']}\n\n" . - "ٟس از واریز، لطفا از رسید ٟرداخت خود اسکرین‌؎ات گرفته و در همینجا ارسال کنید. ٟس از تایید، سرویس ؎ما ØšÙ‡ صورت خودکار ایجاد خواهد ؎د."; - editMessageText($chat_id, $message_id, $message); - die; -} + $settings = getSettings(); + $payment_method = $settings['payment_method']; + $card_number_display = ($payment_method['copy_enabled'] ?? false) ? "{$payment_method['card_number']}" : $payment_method['card_number']; + $message = "ؚرای تکمیل خرید ØšÙ‡ مؚلغ " . number_format($amount_to_charge) . " تومان، لطفا مؚلغ را ØšÙ‡ اطلاعات زیر واریز نمایید:\n\n" . + "💳 ؎ماره کارت:\n" . $card_number_display . "\n" . + "👀 صاحؚ حساؚ: {$payment_method['card_holder']}\n\n" . + "ٟس از واریز، لطفا از رسید ٟرداخت خود اسکرین‌؎ات گرفته و در همینجا ارسال کنید. ٟس از تایید، سرویس ؎ما ØšÙ‡ صورت خودکار ایجاد خواهد ؎د."; + editMessageText($chat_id, $message_id, $message); + die; + } // --- دکمه‌های مخصوص ادمین‌ها --- if ($isAnAdmin) { @@ -193,14 +189,13 @@ sendMessage($chat_id, "لطفا مؚلغی که می‌خواهید ØšÙ‡ موجودی کارؚر $target_id اضافه کنید را ØšÙ‡ تومان وارد کنید:", $cancelKeyboard); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); die; - } - elseif (strpos($data, 'show_user_services_') === 0 && hasPermission($chat_id, 'manage_users')) { + } elseif (strpos($data, 'show_user_services_') === 0 && hasPermission($chat_id, 'manage_users')) { $target_id = str_replace('show_user_services_', '', $data); $services = getUserServices($target_id); - + $target_user_info = getUserData($target_id); $target_user_name = htmlspecialchars($target_user_info['first_name'] ?? "کارؚر $target_id"); - + if (empty($services)) { apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => "کارؚر {$target_user_name} هیچ سرویسی ندارد.", 'show_alert' => true]); } else { @@ -213,28 +208,25 @@ $message_text .= "▫ نام کارؚری ٟنل: {$service['marzban_username']}\n"; $message_text .= "▫ تاریخ انقضا: {$expire_date}\n---\n"; } - + // ٟیام را در یک ٟیام جدید ارسال می‌کنیم تا منوی مدیریت اصلی حف؞ ؎ود sendMessage($chat_id, $message_text); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); } die; - } - elseif (strpos($data, 'deduct_balance_') === 0 && hasPermission($chat_id, 'manage_users')) { + } elseif (strpos($data, 'deduct_balance_') === 0 && hasPermission($chat_id, 'manage_users')) { $target_id = str_replace('deduct_balance_', '', $data); updateUserData($chat_id, 'admin_awaiting_amount_for_deduct_balance', ['target_user_id' => $target_id, 'admin_view' => 'admin']); sendMessage($chat_id, "لطفا مؚلغی که می‌خواهید از موجودی کارؚر $target_id کسر کنید را ØšÙ‡ تومان وارد کنید:", $cancelKeyboard); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); die; - } - elseif (strpos($data, 'message_user_') === 0 && hasPermission($chat_id, 'manage_users')) { + } elseif (strpos($data, 'message_user_') === 0 && hasPermission($chat_id, 'manage_users')) { $target_id = str_replace('message_user_', '', $data); updateUserData($chat_id, 'admin_awaiting_message_for_user', ['target_user_id' => $target_id, 'admin_view' => 'admin']); sendMessage($chat_id, "ٟیام خود را ؚرای ارسال ØšÙ‡ کارؚر $target_id وارد کنید:", $cancelKeyboard); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); die; - } - elseif (strpos($data, 'ban_user_') === 0 && hasPermission($chat_id, 'manage_users')) { + } elseif (strpos($data, 'ban_user_') === 0 && hasPermission($chat_id, 'manage_users')) { $target_id = str_replace('ban_user_', '', $data); if ($target_id == ADMIN_CHAT_ID) { apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '❌ ؎ما نمی‌توانید خودتان را مسدود کنید!', 'show_alert' => true]); @@ -245,16 +237,14 @@ apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => 'کارؚر مسدود ؎د']); } die; - } - elseif (strpos($data, 'unban_user_') === 0 && hasPermission($chat_id, 'manage_users')) { + } elseif (strpos($data, 'unban_user_') === 0 && hasPermission($chat_id, 'manage_users')) { $target_id = str_replace('unban_user_', '', $data); setUserStatus($target_id, 'active'); sendMessage($target_id, "✅ ؎ما توسط ادمین از حالت مسدودیت خارج ؎دید."); editMessageText($chat_id, $message_id, $update['callback_query']['message']['text'] . "\n\n---\n✅ کارؚر ؚا موفقیت آزاد ؎د."); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => 'کارؚر آزاد ؎د']); die; - } - elseif ($data === 'search_another_user' && hasPermission($chat_id, 'manage_users')) { + } elseif ($data === 'search_another_user' && hasPermission($chat_id, 'manage_users')) { deleteMessage($chat_id, $message_id); updateUserData($chat_id, 'admin_awaiting_user_search', ['admin_view' => 'admin']); sendMessage($chat_id, "لطفاً ؎ناسه عددی (Chat ID) کارؚر ؚعدی را وارد کنید:", $cancelKeyboard); @@ -262,7 +252,7 @@ die; } // end - + if (strpos($data, 'delete_cat_') === 0 && hasPermission($chat_id, 'manage_categories')) { $cat_id = str_replace('delete_cat_', '', $data); pdo() @@ -271,82 +261,75 @@ apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ حذف ؎د']); deleteMessage($chat_id, $message_id); generateCategoryList($chat_id); - } - elseif (strpos($data, 'charge_zarinpal_') === 0) { - $amount = (int)str_replace('charge_zarinpal_', '', $data); - $settings = getSettings(); - $merchant_id = $settings['zarinpal_merchant_id']; - - $script_url = 'https://' . $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['PHP_SELF']), '/') . '/verify_payment.php'; - - $data = [ - "merchant_id" => $merchant_id, - "amount" => $amount * 10, // تؚدیل تومان ØšÙ‡ ریال - "callback_url" => $script_url, - "description" => "؎ارژ حساؚ کارؚری - " . $chat_id, - "metadata" => ["order_id" => "user_{$chat_id}_" . time()] - ]; - $jsonData = json_encode($data); - - $ch = curl_init('https://api.zarinpal.com/pg/v4/payment/request.json'); - curl_setopt($ch, CURLOPT_USERAGENT, 'ZarinPal Rest Api v4'); - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); - curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Content-Length: ' . strlen($jsonData)]); - - $result = curl_exec($ch); - curl_close($ch); - $result = json_decode($result, true); - - if (empty($result['errors'])) { - $authority = $result['data']['authority']; - - // ثؚت تراکن؎ در دیتاؚیس - $stmt = pdo()->prepare("INSERT INTO transactions (user_id, amount, authority, description) VALUES (?, ?, ?, ?)"); - $stmt->execute([$chat_id, $amount, $authority, "؎ارژ حساؚ"]); - - $payment_url = 'https://www.zarinpal.com/pg/StartPay/' . $authority; - - $message = "⏳ در حال انتقال ØšÙ‡ درگاه ٟرداخت... لطفا صؚر کنید."; - $keyboard = ['inline_keyboard' => [[['text' => '🚀 ورود ØšÙ‡ صفحه ٟرداخت', 'url' => $payment_url]]]]; - editMessageText($chat_id, $message_id, $message, $keyboard); - - } else { - $error_code = $result['errors']['code']; - editMessageText($chat_id, $message_id, "❌ خطا در اتصال ØšÙ‡ درگاه ٟرداخت. کد خطا: {$error_code}"); - } - } - elseif ($data === 'toggle_gateway_status') { + } elseif (strpos($data, 'charge_zarinpal_') === 0) { + $amount = (int) str_replace('charge_zarinpal_', '', $data); + $settings = getSettings(); + $merchant_id = $settings['zarinpal_merchant_id']; + + $script_url = 'https://' . $_SERVER['HTTP_HOST'] . rtrim(dirname($_SERVER['PHP_SELF']), '/') . '/verify_payment.php'; + + $data = [ + "merchant_id" => $merchant_id, + "amount" => $amount * 10, // تؚدیل تومان ØšÙ‡ ریال + "callback_url" => $script_url, + "description" => "؎ارژ حساؚ کارؚری - " . $chat_id, + "metadata" => ["order_id" => "user_{$chat_id}_" . time()] + ]; + $jsonData = json_encode($data); + + $ch = curl_init('https://api.zarinpal.com/pg/v4/payment/request.json'); + curl_setopt($ch, CURLOPT_USERAGENT, 'ZarinPal Rest Api v4'); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'POST'); + curl_setopt($ch, CURLOPT_POSTFIELDS, $jsonData); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json', 'Content-Length: ' . strlen($jsonData)]); + + $result = curl_exec($ch); + curl_close($ch); + $result = json_decode($result, true); + + if (empty($result['errors'])) { + $authority = $result['data']['authority']; + + // ثؚت تراکن؎ در دیتاؚیس + $stmt = pdo()->prepare("INSERT INTO transactions (user_id, amount, authority, description) VALUES (?, ?, ?, ?)"); + $stmt->execute([$chat_id, $amount, $authority, "؎ارژ حساؚ"]); + + $payment_url = 'https://www.zarinpal.com/pg/StartPay/' . $authority; + + $message = "⏳ در حال انتقال ØšÙ‡ درگاه ٟرداخت... لطفا صؚر کنید."; + $keyboard = ['inline_keyboard' => [[['text' => '🚀 ورود ØšÙ‡ صفحه ٟرداخت', 'url' => $payment_url]]]]; + editMessageText($chat_id, $message_id, $message, $keyboard); + + } else { + $error_code = $result['errors']['code']; + editMessageText($chat_id, $message_id, "❌ خطا در اتصال ØšÙ‡ درگاه ٟرداخت. کد خطا: {$error_code}"); + } + } elseif ($data === 'toggle_gateway_status') { $settings = getSettings(); $settings['payment_gateway_status'] = ($settings['payment_gateway_status'] ?? 'off') == 'on' ? 'off' : 'on'; saveSettings($settings); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ وضعیت تغییر کرد.']); - - } - elseif ($data === 'set_zarinpal_merchant_id') { + + } elseif ($data === 'set_zarinpal_merchant_id') { updateUserData($chat_id, 'admin_awaiting_merchant_id'); editMessageText($chat_id, $message_id, "لطفا مرچنت کد Û³Û¶ کاراکتری زرین‌ٟال خود را وارد کنید:"); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif ($data === 'toggle_renewal_status') { - $settings = getSettings(); - $settings['renewal_status'] = ($settings['renewal_status'] ?? 'off') == 'on' ? 'off' : 'on'; - saveSettings($settings); - apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ وضعیت تغییر کرد.']); - showRenewalManagementMenu($chat_id, $message_id); -} - elseif ($data === 'set_renewal_price_day') { + } elseif ($data === 'toggle_renewal_status') { + $settings = getSettings(); + $settings['renewal_status'] = ($settings['renewal_status'] ?? 'off') == 'on' ? 'off' : 'on'; + saveSettings($settings); + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ وضعیت تغییر کرد.']); + showRenewalManagementMenu($chat_id, $message_id); + } elseif ($data === 'set_renewal_price_day') { updateUserData($chat_id, 'admin_awaiting_renewal_price_day'); editMessageText($chat_id, $message_id, "لطفا هزینه تمدید ØšÙ‡ ازای هر **روز** را ØšÙ‡ تومان وارد کنید (فقط عدد):"); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif ($data === 'set_renewal_price_gb') { + } elseif ($data === 'set_renewal_price_gb') { updateUserData($chat_id, 'admin_awaiting_renewal_price_gb'); editMessageText($chat_id, $message_id, "لطفا هزینه تمدید ØšÙ‡ ازای هر **گیگاؚایت** را ØšÙ‡ تومان وارد کنید (فقط عدد):"); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif (strpos($data, 'toggle_cat_') === 0 && hasPermission($chat_id, 'manage_categories')) { + } elseif (strpos($data, 'toggle_cat_') === 0 && hasPermission($chat_id, 'manage_categories')) { $cat_id = str_replace('toggle_cat_', '', $data); pdo() ->prepare("UPDATE categories SET status = IF(status = 'active', 'inactive', 'active') WHERE id = ?") @@ -354,16 +337,14 @@ apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ وضعیت تغییر کرد']); deleteMessage($chat_id, $message_id); generateCategoryList($chat_id); - } - elseif (strpos($data, 'delete_plan_') === 0 && hasPermission($chat_id, 'manage_plans')) { + } elseif (strpos($data, 'delete_plan_') === 0 && hasPermission($chat_id, 'manage_plans')) { $plan_id = str_replace('delete_plan_', '', $data); pdo() ->prepare("DELETE FROM plans WHERE id = ?") ->execute([$plan_id]); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ ٟلن حذف ؎د']); deleteMessage($chat_id, $message_id); - } - elseif (strpos($data, 'toggle_plan_') === 0 && hasPermission($chat_id, 'manage_plans')) { + } elseif (strpos($data, 'toggle_plan_') === 0 && hasPermission($chat_id, 'manage_plans')) { $plan_id = str_replace('toggle_plan_', '', $data); pdo() ->prepare("UPDATE plans SET status = IF(status = 'active', 'inactive', 'active') WHERE id = ?") @@ -371,23 +352,20 @@ apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ وضعیت تغییر کرد']); deleteMessage($chat_id, $message_id); generatePlanList($chat_id); - } - elseif ($data === 'back_to_plan_list' && hasPermission($chat_id, 'manage_plans')) { + } elseif ($data === 'back_to_plan_list' && hasPermission($chat_id, 'manage_plans')) { updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); deleteMessage($chat_id, $message_id); generatePlanList($chat_id); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif (strpos($data, 'open_plan_editor_') === 0 && hasPermission($chat_id, 'manage_plans')) { + } elseif (strpos($data, 'open_plan_editor_') === 0 && hasPermission($chat_id, 'manage_plans')) { $plan_id = str_replace('open_plan_editor_', '', $data); showPlanEditor($chat_id, $message_id, $plan_id); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif (strpos($data, 'edit_plan_field_') === 0 && hasPermission($chat_id, 'manage_plans')) { + } elseif (strpos($data, 'edit_plan_field_') === 0 && hasPermission($chat_id, 'manage_plans')) { preg_match('/edit_plan_field_(\d+)_(\w+)/', $data, $matches); $plan_id = $matches[1]; $field = $matches[2]; - + $field_map = [ 'name' => ['prompt' => '👇 لطفا نام جدید ٟلن را وارد کنید:', 'column' => 'name', 'validation' => 'text'], 'price' => ['prompt' => '👇 لطفا قیمت جدید را ØšÙ‡ تومان وارد کنید (فقط عدد):', 'column' => 'price', 'validation' => 'numeric'], @@ -401,18 +379,16 @@ $state_data = [ 'editing_plan_id' => $plan_id, 'editing_field_info' => $field_info, - 'editor_message_id' => $message_id + 'editor_message_id' => $message_id ]; updateUserData($chat_id, 'admin_awaiting_plan_edit_input', $state_data); showPlanEditor($chat_id, $message_id, $plan_id, $field_info['prompt']); } apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif (strpos($data, 'back_to_plan_view_') === 0 && hasPermission($chat_id, 'manage_plans')) { + } elseif (strpos($data, 'back_to_plan_view_') === 0 && hasPermission($chat_id, 'manage_plans')) { deleteMessage($chat_id, $message_id); generatePlanList($chat_id); - } - elseif (strpos($data, 'edit_plan_field_') === 0 && hasPermission($chat_id, 'manage_plans')) { + } elseif (strpos($data, 'edit_plan_field_') === 0 && hasPermission($chat_id, 'manage_plans')) { preg_match('/edit_plan_field_(\d+)_(\w+)/', $data, $matches); $plan_id = $matches[1]; $field = $matches[2]; @@ -470,11 +446,10 @@ if ($field !== 'category' && $field !== 'server') { deleteMessage($chat_id, $message_id); } - + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); die; - } - elseif (strpos($data, 'set_plan_category_') === 0 && hasPermission($chat_id, 'manage_plans')) { + } elseif (strpos($data, 'set_plan_category_') === 0 && hasPermission($chat_id, 'manage_plans')) { preg_match('/set_plan_category_(\d+)_(\d+)/', $data, $matches); $plan_id = $matches[1]; $category_id = $matches[2]; @@ -484,8 +459,7 @@ apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ دسته‌ؚندی ٟلن ؚا موفقیت تغییر کرد.']); deleteMessage($chat_id, $message_id); generatePlanList($chat_id); - } - elseif (strpos($data, 'set_plan_server_') === 0 && hasPermission($chat_id, 'manage_plans')) { + } elseif (strpos($data, 'set_plan_server_') === 0 && hasPermission($chat_id, 'manage_plans')) { preg_match('/set_plan_server_(\d+)_(\d+)/', $data, $matches); $plan_id = $matches[1]; $server_id = $matches[2]; @@ -495,8 +469,7 @@ apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ سرور ٟلن ؚا موفقیت تغییر کرد.']); deleteMessage($chat_id, $message_id); generatePlanList($chat_id); - } - elseif (strpos($data, 'p_cat_') === 0 && hasPermission($chat_id, 'manage_plans')) { + } elseif (strpos($data, 'p_cat_') === 0 && hasPermission($chat_id, 'manage_plans')) { $category_id = str_replace('p_cat_', '', $data); $servers = pdo() ->query("SELECT id, name FROM servers WHERE status = 'active'") @@ -511,12 +484,11 @@ $keyboard_buttons[] = [['text' => $server['name'], 'callback_data' => "p_server_{$server['id']}_cat_{$category_id}"]]; } editMessageText($chat_id, $message_id, "این ٟلن روی کدام سرور ساخته ؎ود؟", ['inline_keyboard' => $keyboard_buttons]); - } - elseif (strpos($data, 'p_server_') === 0 && hasPermission($chat_id, 'manage_plans')) { + } elseif (strpos($data, 'p_server_') === 0 && hasPermission($chat_id, 'manage_plans')) { preg_match('/p_server_(\d+)_cat_(\d+)/', $data, $matches); $server_id = $matches[1]; $category_id = $matches[2]; - + $stmt = pdo()->prepare("SELECT type FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $server_type = $stmt->fetchColumn(); @@ -535,7 +507,7 @@ editMessageText($chat_id, $message_id, "این ٟلن ØšÙ‡ کدام اینؚاند اضافه ؎ود؟", ['inline_keyboard' => $keyboard_buttons]); } elseif ($server_type === 'marzneshin') { $services = getMarzneshinServices($server_id); - if (empty($services)) { + if (empty($services)) { editMessageText($chat_id, $message_id, "❌ هیچ سرویسی روی این سرور مرزن؎ین یافت ن؎د. لطفا اؚتدا یک سرویس در ٟنل خود ؚسازید."); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); die; @@ -554,8 +526,7 @@ sendMessage($chat_id, "1/6 - لطفا نام ٟلن را وارد کنید:", $cancelKeyboard); deleteMessage($chat_id, $message_id); } - } - elseif (strpos($data, 'p_inbound_') === 0 && hasPermission($chat_id, 'manage_plans')) { + } elseif (strpos($data, 'p_inbound_') === 0 && hasPermission($chat_id, 'manage_plans')) { preg_match('/p_inbound_(\d+)_server_(\d+)_cat_(\d+)/', $data, $matches); $inbound_id = $matches[1]; $server_id = $matches[2]; @@ -569,8 +540,7 @@ updateUserData($chat_id, 'awaiting_plan_name', $state_data); sendMessage($chat_id, "1/6 - لطفا نام ٟلن را وارد کنید:", $cancelKeyboard); deleteMessage($chat_id, $message_id); - } - elseif (strpos($data, 'p_service_') === 0 && hasPermission($chat_id, 'manage_plans')) { + } elseif (strpos($data, 'p_service_') === 0 && hasPermission($chat_id, 'manage_plans')) { preg_match('/p_service_(\d+)_server_(\d+)_cat_(\d+)/', $data, $matches); $service_id = $matches[1]; $server_id = $matches[2]; @@ -584,8 +554,7 @@ updateUserData($chat_id, 'awaiting_plan_name', $state_data); sendMessage($chat_id, "1/6 - لطفا نام ٟلن را وارد کنید:", $cancelKeyboard); deleteMessage($chat_id, $message_id); - } - elseif (strpos($data, 'copy_toggle_') === 0 && hasPermission($chat_id, 'manage_payment')) { + } elseif (strpos($data, 'copy_toggle_') === 0 && hasPermission($chat_id, 'manage_payment')) { $toggle = str_replace('copy_toggle_', '', $data) === 'yes'; $settings = getSettings(); $settings['payment_method'] = ['card_number' => $user_data['state_data']['temp_card_number'], 'card_holder' => $user_data['state_data']['temp_card_holder'], 'copy_enabled' => $toggle]; @@ -594,8 +563,7 @@ apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ تن؞یمات ذخیره ؎د']); editMessageText($chat_id, $message_id, "✅ تن؞یمات رو؎ ٟرداخت ؚا موفقیت ذخیره ؎د."); handleMainMenu($chat_id, $first_name); - } - elseif (strpos($data, 'approve_') === 0 || strpos($data, 'reject_') === 0) { + } elseif (strpos($data, 'approve_') === 0 || strpos($data, 'reject_') === 0) { list($action, $request_id) = explode('_', $data); $stmt = pdo()->prepare("SELECT * FROM payment_requests WHERE id = ?"); @@ -632,9 +600,9 @@ // این ٟرداخت ؚرای تکمیل خرید یک ٟلن $plan_id = $metadata['plan_id']; $discount_code = $metadata['discount_code'] ?? null; - + $plan = getPlanById($plan_id); - $final_price = (float)$plan['price']; + $final_price = (float) $plan['price']; $discount_applied = false; $discount_object = null; @@ -643,7 +611,7 @@ $stmt_discount->execute([$discount_code]); $discount_object = $stmt_discount->fetch(); if ($discount_object) { - if ($discount_object['type'] == 'percent') { + if ($discount_object['type'] == 'percent') { $final_price = $plan['price'] - ($plan['price'] * $discount_object['value']) / 100; } else { $final_price = $plan['price'] - $discount_object['value']; @@ -652,19 +620,19 @@ $discount_applied = true; } } - + // ؎ارژ موقت حساؚ کارؚر ؚا مؚلغ ٟرداختی updateUserBalance($user_id_to_charge, $amount_to_charge, 'add'); - $custom_name = $metadata['custom_name'] ?? 'سرویس'; -$purchase_result = completePurchase($user_id_to_charge, $plan_id, $custom_name, $final_price, $discount_code, $discount_object, $discount_applied); + $custom_name = $metadata['custom_name'] ?? 'سرویس'; + $purchase_result = completePurchase($user_id_to_charge, $plan_id, $custom_name, $final_price, $discount_code, $discount_object, $discount_applied); if ($purchase_result['success']) { sendPhoto($user_id_to_charge, $purchase_result['qr_code_url'], $purchase_result['caption']); sendMessage(ADMIN_CHAT_ID, $purchase_result['admin_notification']); sendMessage($user_id_to_charge, "✅ ٟرداخت ؎ما تایید و سرویس ؚا موفقیت ایجاد ؎د."); } else { - sendMessage($user_id_to_charge, "❌ ٟرداخت ؎ما تایید ؎د اما در ایجاد سرویس خطایی رخ داد. مؚلغ ٟرداخت ؎ده ØšÙ‡ موجودی ؎ما اضافه ؎د. لطفاً ؚا ٟ؎تیؚانی تماس ؚگیرید."); + sendMessage($user_id_to_charge, "❌ ٟرداخت ؎ما تایید ؎د اما در ایجاد سرویس خطایی رخ داد. مؚلغ ٟرداخت ؎ده ØšÙ‡ موجودی ؎ما اضافه ؎د. لطفاً ؚا ٟ؎تیؚانی تماس ؚگیرید."); } updateUserData($user_id_to_charge, 'main_menu'); @@ -678,8 +646,7 @@ editMessageCaption($chat_id, $message_id, $update['callback_query']['message']['caption'] . "\n\n✅ توسط ؎ما تایید ؎د.", null); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ درخواست تایید ؎د']); - } - elseif ($action == 'reject') { + } elseif ($action == 'reject') { pdo()->prepare("UPDATE payment_requests SET status = 'rejected', processed_by_admin_id = ?, processed_at = NOW() WHERE id = ?")->execute([$admin_who_processed, $request_id]); sendMessage($user_id_to_charge, "❌ درخواست ؎ارژ حساؚ ؎ما ØšÙ‡ مؚلغ " . number_format($amount_to_charge) . " تومان توسط ادمین رد ؎د."); @@ -687,8 +654,7 @@ editMessageCaption($chat_id, $message_id, $update['callback_query']['message']['caption'] . "\n\n❌ توسط ؎ما رد ؎د.", null); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '❌ درخواست رد ؎د']); } - } - elseif ($data === 'manage_servers' && hasPermission($chat_id, 'manage_marzban')) { + } elseif ($data === 'manage_servers' && hasPermission($chat_id, 'manage_marzban')) { $servers = pdo() ->query("SELECT id, name FROM servers") ->fetchAll(PDO::FETCH_ASSOC); @@ -699,117 +665,112 @@ $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت ØšÙ‡ ٟنل', 'callback_data' => 'back_to_admin_panel']]; editMessageText($chat_id, $message_id, "🌐 مدیریت سرورها\n\nسرور مورد ن؞ر را ؚرای م؎اهده یا حذف انتخاؚ کنید، یا یک سرور جدید اضافه کنید:", ['inline_keyboard' => $keyboard_buttons]); - } - elseif ($data === 'add_server_select_type' && hasPermission($chat_id, 'manage_marzban')) { - $keyboard = ['inline_keyboard' => [ - [['text' => '🔵 مرزؚان (Marzban)', 'callback_data' => 'add_server_type_marzban']], - [['text' => '🟠 سنایی (3x-ui)', 'callback_data' => 'add_server_type_sanaei']], - [['text' => '🟢 مرزن؎ین (Marzneshin)', 'callback_data' => 'add_server_type_marzneshin']], - [['text' => '◀ ؚازگ؎ت', 'callback_data' => 'manage_servers']], - ]]; + } elseif ($data === 'add_server_select_type' && hasPermission($chat_id, 'manage_marzban')) { + $keyboard = [ + 'inline_keyboard' => [ + [['text' => '🔵 مرزؚان (Marzban)', 'callback_data' => 'add_server_type_marzban']], + [['text' => '🟠 سنایی (3x-ui)', 'callback_data' => 'add_server_type_sanaei']], + [['text' => '🟢 مرزن؎ین (Marzneshin)', 'callback_data' => 'add_server_type_marzneshin']], + [['text' => '◀ ؚازگ؎ت', 'callback_data' => 'manage_servers']], + ] + ]; editMessageText($chat_id, $message_id, "لطفا نوع ٟنل سرور را انتخاؚ کنید:", $keyboard); - } - elseif (strpos($data, 'edit_protocols_') === 0 && hasPermission($chat_id, 'manage_marzban')) { + } elseif (strpos($data, 'edit_protocols_') === 0 && hasPermission($chat_id, 'manage_marzban')) { $server_id = str_replace('edit_protocols_', '', $data); showMarzbanProtocolEditor($chat_id, $message_id, $server_id); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif (strpos($data, 'toggle_protocol_') === 0 && hasPermission($chat_id, 'manage_marzban')) { + } elseif (strpos($data, 'toggle_protocol_') === 0 && hasPermission($chat_id, 'manage_marzban')) { preg_match('/toggle_protocol_(\d+)_(\w+)/', $data, $matches); $server_id = $matches[1]; $protocol = $matches[2]; - + $stmt_get = pdo()->prepare("SELECT marzban_protocols FROM servers WHERE id = ?"); $stmt_get->execute([$server_id]); $protocols_json = $stmt_get->fetchColumn(); - + $current_protocols = $protocols_json ? json_decode($protocols_json, true) : []; - if (!is_array($current_protocols)) $current_protocols = []; + if (!is_array($current_protocols)) + $current_protocols = []; if (in_array($protocol, $current_protocols)) { $current_protocols = array_diff($current_protocols, [$protocol]); } else { $current_protocols[] = $protocol; } - + $new_protocols_json = json_encode(array_values($current_protocols)); $stmt_update = pdo()->prepare("UPDATE servers SET marzban_protocols = ? WHERE id = ?"); $stmt_update->execute([$new_protocols_json, $server_id]); - + showMarzbanProtocolEditor($chat_id, $message_id, $server_id); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif (strpos($data, 'add_server_type_') === 0 && hasPermission($chat_id, 'manage_marzban')) { + } elseif (strpos($data, 'add_server_type_') === 0 && hasPermission($chat_id, 'manage_marzban')) { deleteMessage($chat_id, $message_id); $type = str_replace('add_server_type_', '', $data); updateUserData($chat_id, 'admin_awaiting_server_name', ['selected_server_type' => $type]); sendMessage($chat_id, "مرحله Û±/ÛŽ: یک نام دلخواه ؚرای ؎ناسایی سرور وارد کنید (مثال: آلمان-هتزنر):", $cancelKeyboard); - } - elseif (strpos($data, 'view_server_') === 0 && hasPermission($chat_id, 'manage_marzban')) { + } elseif (strpos($data, 'view_server_') === 0 && hasPermission($chat_id, 'manage_marzban')) { $server_id = str_replace('view_server_', '', $data); $stmt = pdo()->prepare("SELECT * FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $server = $stmt->fetch(); if ($server) { $panel_type_text = ucfirst($server['type']); - if ($server['type'] === 'sanaei') $panel_type_text = 'سنایی (3x-ui)'; - if ($server['type'] === 'marzneshin') $panel_type_text = 'مرزن؎ین'; - + if ($server['type'] === 'sanaei') + $panel_type_text = 'سنایی (3x-ui)'; + if ($server['type'] === 'marzneshin') + $panel_type_text = 'مرزن؎ین'; + $msg = "م؎خصات سرور: {$server['name']}\n\n"; $msg .= "▫ نوع ٟنل: {$panel_type_text}\n"; $msg .= "▫ آدرس مدیریت ٟنل: {$server['url']}\n"; - + $keyboard_buttons = []; - - + + if ($server['type'] === 'sanaei' || $server['type'] === 'marzban') { $sub_host_text = !empty($server['sub_host']) ? "{$server['sub_host']}" : "ٟی؎‌فرض (مانند آدرس ٟنل)"; $msg .= "▫ آدرس لینک ا؎تراک: {$sub_host_text}\n"; $keyboard_buttons[] = [['text' => '🔗 ویرای؎ آدرس ساؚ', 'callback_data' => "edit_sub_host_{$server_id}"]]; } - + if ($server['type'] === 'marzban') { $keyboard_buttons[] = [['text' => '⚙ تن؞یم ٟروتکل‌ها', 'callback_data' => "edit_protocols_{$server_id}"]]; } - + $msg .= "▫ نام کارؚری: {$server['username']}\n"; $keyboard_buttons[] = [['text' => '🗑 حذف این سرور', 'callback_data' => "delete_server_{$server_id}"]]; $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت ØšÙ‡ لیست سرورها', 'callback_data' => 'manage_servers']]; - + $keyboard = ['inline_keyboard' => $keyboard_buttons]; editMessageText($chat_id, $message_id, $msg, $keyboard); } - } - elseif (strpos($data, 'edit_sub_host_') === 0 && hasPermission($chat_id, 'manage_marzban')) { + } elseif (strpos($data, 'edit_sub_host_') === 0 && hasPermission($chat_id, 'manage_marzban')) { $server_id = str_replace('edit_sub_host_', '', $data); updateUserData($chat_id, 'admin_awaiting_sub_host', ['editing_server_id' => $server_id]); $prompt = "لطفا آدرس کامل و عمومی که ؚرای لینک ا؎تراک استفاده می‌؎ود را وارد کنید.\nاین آدرس ؚاید ؎امل http/https و ٟورت صحیح ؚا؎د (مثال: http://your.domain.com:2096).\n\n💡 ؚرای ؚازگ؎ت ØšÙ‡ حالت ٟی؎‌فرض (استفاده از همان آدرس ٟنل)، کلمه `reset` را ارسال کنید."; editMessageText($chat_id, $message_id, $prompt); - } - elseif (strpos($data, 'delete_server_') === 0 && hasPermission($chat_id, 'manage_marzban')) { + } elseif (strpos($data, 'delete_server_') === 0 && hasPermission($chat_id, 'manage_marzban')) { $server_id = str_replace('delete_server_', '', $data); $stmt_check = pdo()->prepare("SELECT COUNT(*) FROM plans WHERE server_id = ?"); $stmt_check->execute([$server_id]); if ($stmt_check->fetchColumn() > 0) { apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '❌ نمی‌توانید این سرور را حذف کنید زیرا یک یا چند ٟلن ØšÙ‡ آن متصل هستند.', 'show_alert' => true]); - } - else { + } else { $stmt = pdo()->prepare("DELETE FROM servers WHERE id = ?"); $stmt->execute([$server_id]); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ سرور ؚا موفقیت حذف ؎د.']); - $data = 'manage_servers'; + $data = 'manage_servers'; } - } - elseif (strpos($data, 'plan_set_sub_') === 0) { + } elseif (strpos($data, 'plan_set_sub_') === 0) { $show_sub = str_replace('plan_set_sub_', '', $data) === 'yes'; $state_data = $user_data['state_data']; $state_data['temp_plan_data']['show_sub_link'] = $show_sub; updateUserData($chat_id, 'awaiting_plan_conf_link_setting', $state_data); $keyboard = ['inline_keyboard' => [[['text' => '✅ ؚله', 'callback_data' => 'plan_set_conf_yes'], ['text' => '❌ خیر', 'callback_data' => 'plan_set_conf_no']]]]; editMessageText($chat_id, $message_id, "سوال Û²/Û²: آیا لینک‌های تکی کانفیگ‌ها ØšÙ‡ کارؚر نمای؎ داده ؎ود؟\n(ٟی؎نهادی: ؚله)", $keyboard); - } - elseif (strpos($data, 'plan_set_conf_') === 0) { + } elseif (strpos($data, 'plan_set_conf_') === 0) { $show_conf = str_replace('plan_set_conf_', '', $data) === 'yes'; $final_plan_data = $user_data['state_data']['temp_plan_data'] ?? null; if ($final_plan_data) { @@ -834,14 +795,12 @@ editMessageText($chat_id, $message_id, "✅ ٟلن جدید ؚا تمام تن؞یمات ؚا موفقیت ذخیره ؎د."); updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); handleMainMenu($chat_id, $first_name); - } - else { + } else { editMessageText($chat_id, $message_id, "❌ خطا در ذخیره‌سازی ٟلن. لطفا مجددا تلا؎ کنید."); updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); handleMainMenu($chat_id, $first_name); } - } - elseif (strpos($data, 'discount_type_') === 0) { + } elseif (strpos($data, 'discount_type_') === 0) { $type = str_replace('discount_type_', '', $data); $state_data = $user_data['state_data']; $state_data['new_discount_type'] = $type; @@ -849,16 +808,14 @@ $unit = $type == 'percent' ? 'درصد' : 'تومان'; editMessageText($chat_id, $message_id, "3/4 - لطفاً مقدار تخفیف را ØšÙ‡ $unit وارد کنید (فقط عدد):"); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif (strpos($data, 'delete_discount_') === 0) { + } elseif (strpos($data, 'delete_discount_') === 0) { $code_id = str_replace('delete_discount_', '', $data); pdo() ->prepare("DELETE FROM discount_codes WHERE id = ?") ->execute([$code_id]); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ کد تخفیف حذف ؎د.']); deleteMessage($chat_id, $message_id); - } - elseif (strpos($data, 'toggle_discount_') === 0) { + } elseif (strpos($data, 'toggle_discount_') === 0) { $code_id = str_replace('toggle_discount_', '', $data); pdo() ->prepare("UPDATE discount_codes SET status = IF(status = 'active', 'inactive', 'active') WHERE id = ?") @@ -866,8 +823,7 @@ apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ وضعیت کد تخفیف تغییر کرد.']); deleteMessage($chat_id, $message_id); generateDiscountCodeList($chat_id); - } - elseif (strpos($data, 'delete_guide_') === 0 && hasPermission($chat_id, 'manage_guides')) { + } elseif (strpos($data, 'delete_guide_') === 0 && hasPermission($chat_id, 'manage_guides')) { $guide_id = str_replace('delete_guide_', '', $data); pdo() ->prepare("DELETE FROM guides WHERE id = ?") @@ -875,8 +831,7 @@ apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ راهنما حذف ؎د.']); deleteMessage($chat_id, $message_id); generateGuideList($chat_id); - } - elseif (strpos($data, 'toggle_guide_') === 0 && hasPermission($chat_id, 'manage_guides')) { + } elseif (strpos($data, 'toggle_guide_') === 0 && hasPermission($chat_id, 'manage_guides')) { $guide_id = str_replace('toggle_guide_', '', $data); pdo() ->prepare("UPDATE guides SET status = IF(status = 'active', 'inactive', 'active') WHERE id = ?") @@ -884,8 +839,7 @@ apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ وضعیت راهنما تغییر کرد.']); deleteMessage($chat_id, $message_id); generateGuideList($chat_id); - } - elseif (strpos($data, 'reset_plan_count_') === 0 && hasPermission($chat_id, 'manage_plans')) { + } elseif (strpos($data, 'reset_plan_count_') === 0 && hasPermission($chat_id, 'manage_plans')) { $plan_id = str_replace('reset_plan_count_', '', $data); pdo() ->prepare("UPDATE plans SET purchase_count = 0 WHERE id = ?") @@ -904,8 +858,7 @@ apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ این ٟلن ØšÙ‡ عنوان ٟلن تست تن؞یم ؎د.']); deleteMessage($chat_id, $message_id); generatePlanList($chat_id); - } - elseif (strpos($data, 'make_plan_normal_') === 0 && hasPermission($chat_id, 'manage_plans')) { + } elseif (strpos($data, 'make_plan_normal_') === 0 && hasPermission($chat_id, 'manage_plans')) { $plan_id = str_replace('make_plan_normal_', '', $data); pdo() ->prepare("UPDATE plans SET is_test_plan = 0 WHERE id = ?") @@ -917,8 +870,7 @@ if (strpos($data, 'admin_notifications_soon') === 0) { apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => 'این ؚخ؎ ØšÙ‡ زودی فعال خواهد ؎د.', 'show_alert' => true]); - } - elseif (($data == 'user_notifications_menu' || $data == 'config_expire_warning' || $data == 'config_inactive_reminder') && hasPermission($chat_id, 'manage_notifications')) { + } elseif (($data == 'user_notifications_menu' || $data == 'config_expire_warning' || $data == 'config_inactive_reminder') && hasPermission($chat_id, 'manage_notifications')) { $settings = getSettings(); $expire_status_icon = ($settings['notification_expire_status'] ?? 'off') == 'on' ? '✅' : '❌'; $inactive_status_icon = ($settings['notification_inactive_status'] ?? 'off') == 'on' ? '✅' : '❌'; @@ -941,8 +893,7 @@ ], ]; editMessageText($chat_id, $message_id, $message, $keyboard); - } - elseif ($data == 'config_expire_warning') { + } elseif ($data == 'config_expire_warning') { $message = "⚙ تن؞یمات ه؎دار انقضا\n\nاین ٟیام زمانی ؚرای کارؚر ارسال می‌؎ود که حجم یا زمان سرویس او رو ØšÙ‡ اتمام ؚا؎د.\n\n" . "▫وضعیت: " . @@ -959,8 +910,7 @@ ], ]; editMessageText($chat_id, $message_id, $message, $keyboard); - } - elseif ($data == 'config_inactive_reminder') { + } elseif ($data == 'config_inactive_reminder') { $message = "⚙ تن؞یمات یادآور عدم فعالیت\n\nاین ٟیام زمانی ؚرای کارؚر ارسال می‌؎ود که ؚرای مدت طولانی از رؚات استفاده نکرده ؚا؎د.\n\n" . "▫وضعیت: " . @@ -977,22 +927,19 @@ ]; editMessageText($chat_id, $message_id, $message, $keyboard); } - } - elseif (strpos($data, 'toggle_expire_notification') === 0 && hasPermission($chat_id, 'manage_notifications')) { + } elseif (strpos($data, 'toggle_expire_notification') === 0 && hasPermission($chat_id, 'manage_notifications')) { $settings = getSettings(); $settings['notification_expire_status'] = ($settings['notification_expire_status'] ?? 'off') == 'on' ? 'off' : 'on'; saveSettings($settings); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ وضعیت تغییر کرد.']); $data = 'config_expire_warning'; - } - elseif (strpos($data, 'toggle_inactive_notification') === 0 && hasPermission($chat_id, 'manage_notifications')) { + } elseif (strpos($data, 'toggle_inactive_notification') === 0 && hasPermission($chat_id, 'manage_notifications')) { $settings = getSettings(); $settings['notification_inactive_status'] = ($settings['notification_inactive_status'] ?? 'off') == 'on' ? 'off' : 'on'; saveSettings($settings); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ وضعیت تغییر کرد.']); $data = 'config_inactive_reminder'; - } - elseif (in_array($data, ['set_expire_days', 'set_expire_gb', 'edit_expire_message', 'set_inactive_days', 'edit_inactive_message']) && hasPermission($chat_id, 'manage_notifications')) { + } elseif (in_array($data, ['set_expire_days', 'set_expire_gb', 'edit_expire_message', 'set_inactive_days', 'edit_inactive_message']) && hasPermission($chat_id, 'manage_notifications')) { deleteMessage($chat_id, $message_id); switch ($data) { case 'set_expire_days': @@ -1031,8 +978,7 @@ } $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت ØšÙ‡ ٟنل', 'callback_data' => 'back_to_admin_panel']]; editMessageText($chat_id, $message_id, "🌐 مدیریت سرورها\n\nسرور مورد ن؞ر را ؚرای م؎اهده یا حذف انتخاؚ کنید، یا یک سرور جدید اضافه کنید:", ['inline_keyboard' => $keyboard_buttons]); - } - else { + } else { $menu_to_refresh = strpos($data, 'inactive') !== false || strpos($user_state, 'inactive') !== false ? 'config_inactive_reminder' : 'config_expire_warning'; $message_id = sendMessage($chat_id, "درحال ؚارگذاری مجدد منو...")['result']['message_id']; $data = $menu_to_refresh; @@ -1062,19 +1008,16 @@ $admins = getAdmins(); if (count($admins) >= 9) { apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '❌ حداکثر تعداد ادمین‌ها (Û±Û°) ثؚت ؎ده است.', 'show_alert' => true]); - } - else { + } else { updateUserData($chat_id, 'admin_awaiting_new_admin_id'); editMessageText($chat_id, $message_id, "لطفا ؎ناسه عددی (Chat ID) کارؚر مورد ن؞ر را ؚرای افزودن ØšÙ‡ لیست ادمین‌ها وارد کنید:"); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); } - } - elseif (strpos($data, 'edit_admin_permissions_') === 0) { + } elseif (strpos($data, 'edit_admin_permissions_') === 0) { $target_admin_id = str_replace('edit_admin_permissions_', '', $data); showPermissionEditor($chat_id, $message_id, $target_admin_id); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif (strpos($data, 'toggle_perm_') === 0) { + } elseif (strpos($data, 'toggle_perm_') === 0) { $payload = substr($data, strlen('toggle_perm_')); $parts = explode('_', $payload, 2); if (count($parts) === 2) { @@ -1085,8 +1028,7 @@ $current_permissions = $admins[$target_admin_id]['permissions'] ?? []; if (($key = array_search($permission_key, $current_permissions)) !== false) { unset($current_permissions[$key]); - } - else { + } else { $current_permissions[] = $permission_key; } updateAdminPermissions($target_admin_id, array_values($current_permissions)); @@ -1094,14 +1036,12 @@ } } apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif (strpos($data, 'delete_admin_confirm_') === 0) { + } elseif (strpos($data, 'delete_admin_confirm_') === 0) { $target_admin_id = str_replace('delete_admin_confirm_', '', $data); $keyboard = ['inline_keyboard' => [[['text' => '✅ ؚله، حذف کن', 'callback_data' => "delete_admin_do_{$target_admin_id}"]], [['text' => '❌ انصراف', 'callback_data' => "edit_admin_permissions_{$target_admin_id}"]]]]; editMessageText($chat_id, $message_id, "⚠ آیا از حذف این ادمین مطم؊ن هستید؟", $keyboard); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif (strpos($data, 'delete_admin_do_') === 0) { + } elseif (strpos($data, 'delete_admin_do_') === 0) { $target_admin_id = str_replace('delete_admin_do_', '', $data); $result = removeAdmin($target_admin_id); if ($result) { @@ -1118,12 +1058,10 @@ } $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت ØšÙ‡ ٟنل مدیریت', 'callback_data' => 'back_to_admin_panel']]; editMessageText($chat_id, $message_id, $message, ['inline_keyboard' => $keyboard_buttons]); - } - else { + } else { apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '❌ خطا در حذف ادمین.', 'show_alert' => true]); } - } - elseif ($data == 'back_to_admin_list') { + } elseif ($data == 'back_to_admin_list') { apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); $admins = getAdmins(); $message = "👚‍💌 مدیریت ادمین‌ها\n\nدر این ؚخ؎ می‌توانید ادمین‌های رؚات و دسترسی‌های آن‌ها را مدیریت کنید. (حداکثر Û±Û° ادمین)"; @@ -1137,8 +1075,7 @@ } $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت ØšÙ‡ ٟنل مدیریت', 'callback_data' => 'back_to_admin_panel']]; editMessageText($chat_id, $message_id, $message, ['inline_keyboard' => $keyboard_buttons]); - } - elseif ($data == 'back_to_admin_panel') { + } elseif ($data == 'back_to_admin_panel') { apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); deleteMessage($chat_id, $message_id); handleMainMenu($chat_id, $first_name); @@ -1158,51 +1095,47 @@ $ticket_status = $stmt->fetchColumn(); if (!$ticket_status || $ticket_status == 'closed') { apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => 'این تیکت ؚسته ؎ده است.', 'show_alert' => true]); - } - else { + } else { if ($isAnAdmin) { updateUserData($chat_id, 'admin_replying_to_ticket', ['replying_to_ticket' => $ticket_id]); sendMessage($chat_id, "لطفا ٟاسخ خود را ؚرای تیکت $ticket_id وارد کنید:", $cancelKeyboard); - } - else { + } else { updateUserData($chat_id, 'user_replying_to_ticket', ['replying_to_ticket' => $ticket_id]); sendMessage($chat_id, "لطفا ٟاسخ خود را ؚرای تیکت $ticket_id وارد کنید:", $cancelKeyboard); } } - } - elseif (strpos($data, 'approve_renewal_') === 0 || strpos($data, 'reject_renewal_') === 0) { - list($action, $type, $request_id) = explode('_', $data); + } elseif (strpos($data, 'approve_renewal_') === 0 || strpos($data, 'reject_renewal_') === 0) { + list($action, $type, $request_id) = explode('_', $data); - $stmt = pdo()->prepare("SELECT * FROM renewal_requests WHERE id = ?"); - $stmt->execute([$request_id]); - $request = $stmt->fetch(); + $stmt = pdo()->prepare("SELECT * FROM renewal_requests WHERE id = ?"); + $stmt->execute([$request_id]); + $request = $stmt->fetch(); - if (!$request || $request['status'] !== 'pending') { - apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => 'این درخواست قؚلا ٟرداز؎ ؎ده است.', 'show_alert' => true]); - die; - } - - $admin_who_processed = $update['callback_query']['from']['id']; + if (!$request || $request['status'] !== 'pending') { + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => 'این درخواست قؚلا ٟرداز؎ ؎ده است.', 'show_alert' => true]); + die; + } - if ($action == 'approve') { - $result = applyRenewal($request['user_id'], $request['service_username'], $request['days_to_add'], $request['gb_to_add']); - if ($result['success']) { - pdo()->prepare("UPDATE renewal_requests SET status = 'approved', processed_by_admin_id = ?, processed_at = NOW() WHERE id = ?")->execute([$admin_who_processed, $request_id]); - sendMessage($request['user_id'], "✅ درخواست تمدید ؎ما ؚرای سرویس `{$request['service_username']}` تایید و ؚا موفقیت اعمال ؎د."); - editMessageCaption($chat_id, $message_id, $update['callback_query']['message']['caption'] . "\n\n✅ توسط ؎ما تایید ؎د.", null); - apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ تمدید تایید ؎د.']); - } else { - sendMessage($chat_id, "❌ خطا در اعمال تمدید: " . $result['message']); - apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => 'خطا در اعمال تمدید.', 'show_alert' => true]); - } - } elseif ($action == 'reject') { - pdo()->prepare("UPDATE renewal_requests SET status = 'rejected', processed_by_admin_id = ?, processed_at = NOW() WHERE id = ?")->execute([$admin_who_processed, $request_id]); - sendMessage($request['user_id'], "❌ درخواست تمدید ؎ما ؚرای سرویس `{$request['service_username']}` توسط ادمین رد ؎د."); - editMessageCaption($chat_id, $message_id, $update['callback_query']['message']['caption'] . "\n\n❌ توسط ؎ما رد ؎د.", null); - apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '❌ درخواست رد ؎د.']); + $admin_who_processed = $update['callback_query']['from']['id']; + + if ($action == 'approve') { + $result = applyRenewal($request['user_id'], $request['service_username'], $request['days_to_add'], $request['gb_to_add']); + if ($result['success']) { + pdo()->prepare("UPDATE renewal_requests SET status = 'approved', processed_by_admin_id = ?, processed_at = NOW() WHERE id = ?")->execute([$admin_who_processed, $request_id]); + sendMessage($request['user_id'], "✅ درخواست تمدید ؎ما ؚرای سرویس `{$request['service_username']}` تایید و ؚا موفقیت اعمال ؎د."); + editMessageCaption($chat_id, $message_id, $update['callback_query']['message']['caption'] . "\n\n✅ توسط ؎ما تایید ؎د.", null); + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ تمدید تایید ؎د.']); + } else { + sendMessage($chat_id, "❌ خطا در اعمال تمدید: " . $result['message']); + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => 'خطا در اعمال تمدید.', 'show_alert' => true]); } + } elseif ($action == 'reject') { + pdo()->prepare("UPDATE renewal_requests SET status = 'rejected', processed_by_admin_id = ?, processed_at = NOW() WHERE id = ?")->execute([$admin_who_processed, $request_id]); + sendMessage($request['user_id'], "❌ درخواست تمدید ؎ما ؚرای سرویس `{$request['service_username']}` توسط ادمین رد ؎د."); + editMessageCaption($chat_id, $message_id, $update['callback_query']['message']['caption'] . "\n\n❌ توسط ؎ما رد ؎د.", null); + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '❌ درخواست رد ؎د.']); } - elseif (strpos($data, 'close_ticket_') === 0) { + } elseif (strpos($data, 'close_ticket_') === 0) { if ($isAnAdmin && !hasPermission($chat_id, 'view_tickets')) { apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '؎ما دسترسی لازم ؚرای ؚستن تیکت‌ها را ندارید.', 'show_alert' => true]); die; @@ -1225,8 +1158,7 @@ } editMessageText($chat_id, $message_id, $update['callback_query']['message']['text'] . "\n\n-- ➖ این تیکت ؚسته ؎د ➖ --", null); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => 'تیکت ؚا موفقیت ؚسته ؎د.']); - } - else { + } else { apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => 'خطا: تیکت یافت ن؎د.', 'show_alert' => true]); } } @@ -1234,7 +1166,7 @@ // --- دکمه‌های عمومی کارؚران --- elseif (strpos($data, 'get_configs_') === 0) { $username = str_replace('get_configs_', '', $data); - + $stmt_service = pdo()->prepare("SELECT server_id FROM services WHERE owner_chat_id = ? AND marzban_username = ?"); $stmt_service->execute([$chat_id, $username]); $server_id = $stmt_service->fetchColumn(); @@ -1245,7 +1177,7 @@ } $panel_user = getPanelUser($username, $server_id); - + if ($panel_user && !empty($panel_user['links'])) { // --- ارسال مستقیم همه کانفیگ‌ها --- $all_links_text = implode("\n\n", $panel_user['links']); @@ -1256,8 +1188,7 @@ apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '❌ هیچ لینک کانفیگی ؚرای این سرویس یافت ن؎د.', 'show_alert' => true]); } die; - } - elseif (strpos($data, 'show_guide_') === 0) { + } elseif (strpos($data, 'show_guide_') === 0) { $guide_id = str_replace('show_guide_', '', $data); $stmt = pdo()->prepare("SELECT * FROM guides WHERE id = ? AND status = 'active'"); $stmt->execute([$guide_id]); @@ -1270,17 +1201,14 @@ } if ($guide['content_type'] === 'photo' && !empty($guide['photo_id'])) { sendPhoto($chat_id, $guide['photo_id'], $guide['message_text'], $keyboard); - } - else { + } else { sendMessage($chat_id, $guide['message_text'], $keyboard); } - } - else { + } else { apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '❌ این راهنما یافت ن؎د یا غیرفعال ؎ده است.', 'show_alert' => true]); } - } - elseif (strpos($data, 'charge_manual_') === 0) { - $amount = (int)str_replace('charge_manual_', '', $data); + } elseif (strpos($data, 'charge_manual_') === 0) { + $amount = (int) str_replace('charge_manual_', '', $data); $settings = getSettings(); $payment_method = $settings['payment_method'] ?? []; $card_number = $payment_method['card_number'] ?? ''; @@ -1288,32 +1216,29 @@ $copy_enabled = $payment_method['copy_enabled'] ?? false; if (empty($card_number)) { - editMessageText($chat_id, $message_id, "❌ رو؎ ٟرداخت دستی توسط ادمین تن؞یم ن؎ده است."); - apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - die; + editMessageText($chat_id, $message_id, "❌ رو؎ ٟرداخت دستی توسط ادمین تن؞یم ن؎ده است."); + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); + die; } $card_number_display = $copy_enabled ? "{$card_number}" : $card_number; $message = "ؚرای ؎ارژ حساؚ ØšÙ‡ مؚلغ " . number_format($amount) . " تومان، لطفا مؚلغ را ØšÙ‡ اطلاعات زیر واریز نمایید:\n\n" . - "💳 ؎ماره کارت:\n" . $card_number_display . "\n" . - "👀 صاحؚ حساؚ: {$card_holder}\n\n" . - "ٟس از واریز، لطفا از رسید ٟرداخت خود اسکرین‌؎ات گرفته و در همینجا ارسال کنید."; + "💳 ؎ماره کارت:\n" . $card_number_display . "\n" . + "👀 صاحؚ حساؚ: {$card_holder}\n\n" . + "ٟس از واریز، لطفا از رسید ٟرداخت خود اسکرین‌؎ات گرفته و در همینجا ارسال کنید."; editMessageText($chat_id, $message_id, $message); updateUserData($chat_id, 'awaiting_payment_screenshot', ['charge_amount' => $amount]); - } - elseif (strpos($data, 'cat_') === 0) { + } elseif (strpos($data, 'cat_') === 0) { $categoryId = str_replace('cat_', '', $data); showServersForCategory($chat_id, $categoryId); deleteMessage($chat_id, $message_id); - } - elseif (strpos($data, 'show_plans_cat_') === 0) { + } elseif (strpos($data, 'show_plans_cat_') === 0) { preg_match('/show_plans_cat_(\d+)_srv_(\d+)/', $data, $matches); $category_id = $matches[1]; $server_id = $matches[2]; showPlansForCategoryAndServer($chat_id, $category_id, $server_id); deleteMessage($chat_id, $message_id); - } - elseif (strpos($data, 'apply_discount_code_') === 0) { + } elseif (strpos($data, 'apply_discount_code_') === 0) { $parts = explode('_', $data); $category_id = $parts[3]; $server_id = $parts[4]; // server_id اضافه ؎د @@ -1323,46 +1248,44 @@ ]); editMessageText($chat_id, $message_id, "🎁 لطفاً کد تخفیف خود را وارد کنید:"); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif (strpos($data, 'buy_plan_') === 0) { - $parts = explode('_', $data); - $plan_id = $parts[2]; - $discount_code = null; - if (isset($parts[5]) && $parts[3] == 'with' && $parts[4] == 'code') { - $discount_code = strtoupper($parts[5]); - } - - $plan = getPlanById($plan_id); - if (!$plan) { - apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '❌ خطا: ٟلن یافت ن؎د.']); - die; - } + } elseif (strpos($data, 'buy_plan_') === 0) { + $parts = explode('_', $data); + $plan_id = $parts[2]; + $discount_code = null; + if (isset($parts[5]) && $parts[3] == 'with' && $parts[4] == 'code') { + $discount_code = strtoupper($parts[5]); + } - if ($plan['purchase_limit'] > 0 && $plan['purchase_count'] >= $plan['purchase_limit']) { - apiRequest('answerCallbackQuery', [ - 'callback_query_id' => $callback_id, - 'text' => '❌ متاسفانه ؞رفیت خرید این ٟلن ØšÙ‡ اتمام رسیده است.', - 'show_alert' => true, - ]); - die; - } + $plan = getPlanById($plan_id); + if (!$plan) { + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '❌ خطا: ٟلن یافت ن؎د.']); + die; + } - // ØšÙ‡ جای خرید مستقیم، وضعیت را ؚرای دریافت نام تن؞یم می‌کنیم - $state_data = [ - 'purchasing_plan_id' => $plan_id, - 'discount_code' => $discount_code - ]; - updateUserData($chat_id, 'awaiting_service_name', $state_data); - - $message = "✅ ٟلن انتخاؚ ؎د.\n\nلطفاً یک نام دلخواه ؚرای این سرویس وارد کنید (مثلاً: سرویس ؎خصی). این نام در لیست سرویس‌های ؎ما نمای؎ داده خواهد ؎د."; - - - deleteMessage($chat_id, $message_id); - sendMessage($chat_id, $message, $cancelKeyboard); - apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - die; -} - elseif ($data === 'confirm_renewal_payment') { + if ($plan['purchase_limit'] > 0 && $plan['purchase_count'] >= $plan['purchase_limit']) { + apiRequest('answerCallbackQuery', [ + 'callback_query_id' => $callback_id, + 'text' => '❌ متاسفانه ؞رفیت خرید این ٟلن ØšÙ‡ اتمام رسیده است.', + 'show_alert' => true, + ]); + die; + } + + // ØšÙ‡ جای خرید مستقیم، وضعیت را ؚرای دریافت نام تن؞یم می‌کنیم + $state_data = [ + 'purchasing_plan_id' => $plan_id, + 'discount_code' => $discount_code + ]; + updateUserData($chat_id, 'awaiting_service_name', $state_data); + + $message = "✅ ٟلن انتخاؚ ؎د.\n\nلطفاً یک نام دلخواه ؚرای این سرویس وارد کنید (مثلاً: سرویس ؎خصی). این نام در لیست سرویس‌های ؎ما نمای؎ داده خواهد ؎د."; + + + deleteMessage($chat_id, $message_id); + sendMessage($chat_id, $message, $cancelKeyboard); + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); + die; + } elseif ($data === 'confirm_renewal_payment') { $state_data = $user_data['state_data']; $total_cost = $state_data['renewal_total_cost']; @@ -1370,52 +1293,51 @@ // ٟرداخت از موجودی editMessageText($chat_id, $message_id, "⏳ در حال تمدید سرویس ؚا استفاده از موجودی ؎ما..."); updateUserBalance($chat_id, $total_cost, 'deduct'); - + $result = applyRenewal($chat_id, $state_data['renewal_username'], $state_data['renewal_days'], $state_data['renewal_gb']); - + if ($result['success']) { $new_balance = number_format($user_data['balance'] - $total_cost); $success_msg = "✅ سرویس ؎ما ؚا موفقیت تمدید ؎د.\n\n" . - "💰 مؚلغ " . number_format($total_cost) . " تومان از حساؚ ؎ما کسر گردید.\n" . - "موجودی جدید: {$new_balance} تومان."; + "💰 مؚلغ " . number_format($total_cost) . " تومان از حساؚ ؎ما کسر گردید.\n" . + "موجودی جدید: {$new_balance} تومان."; editMessageText($chat_id, $message_id, $success_msg); } else { editMessageText($chat_id, $message_id, "❌ خطایی در تمدید سرویس رخ داد: " . $result['message']); - + updateUserBalance($chat_id, $total_cost, 'add'); } updateUserData($chat_id, 'main_menu'); } else { - + $stmt = pdo()->prepare( "INSERT INTO renewal_requests (user_id, service_username, days_to_add, gb_to_add, total_cost) VALUES (?, ?, ?, ?, ?)" ); $stmt->execute([$chat_id, $state_data['renewal_username'], $state_data['renewal_days'], $state_data['renewal_gb'], $total_cost]); $request_id = pdo()->lastInsertId(); - + $state_data['renewal_request_id'] = $request_id; updateUserData($chat_id, 'awaiting_renewal_screenshot', $state_data); - + $settings = getSettings(); $payment_method = $settings['payment_method'] ?? []; if (empty($payment_method['card_number'])) { editMessageText($chat_id, $message_id, "موجودی ؎ما کافی نیست و رو؎ ٟرداخت کارت ØšÙ‡ کارت نیز توسط ادمین تن؞یم ن؎ده است. لطفا اؚتدا حساؚ خود را ؎ارژ کنید."); } else { - $card_number = $payment_method['card_number'] ?? ''; - $card_holder = $payment_method['card_holder'] ?? ''; - $copy_enabled = $payment_method['copy_enabled'] ?? false; - $card_number_display = $copy_enabled ? "{$card_number}" : $card_number; - $message = "موجودی ؎ما کافی نیست. لطفا مؚلغ " . number_format($total_cost) . " تومان را ØšÙ‡ اطلاعات زیر واریز کرده و سٟس اسکرین‌؎ات رسید را ارسال کنید:\n\n" . - "💳 ؎ماره کارت:\n" . $card_number_display . "\n" . - "👀 صاحؚ حساؚ: {$card_holder}"; - editMessageText($chat_id, $message_id, $message); + $card_number = $payment_method['card_number'] ?? ''; + $card_holder = $payment_method['card_holder'] ?? ''; + $copy_enabled = $payment_method['copy_enabled'] ?? false; + $card_number_display = $copy_enabled ? "{$card_number}" : $card_number; + $message = "موجودی ؎ما کافی نیست. لطفا مؚلغ " . number_format($total_cost) . " تومان را ØšÙ‡ اطلاعات زیر واریز کرده و سٟس اسکرین‌؎ات رسید را ارسال کنید:\n\n" . + "💳 ؎ماره کارت:\n" . $card_number_display . "\n" . + "👀 صاحؚ حساؚ: {$card_holder}"; + editMessageText($chat_id, $message_id, $message); } } apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif ($data == 'back_to_categories') { + } elseif ($data == 'back_to_categories') { deleteMessage($chat_id, $message_id); $categories = getCategories(true); $keyboard_buttons = []; @@ -1423,96 +1345,94 @@ $keyboard_buttons[] = [['text' => '🛍 ' . $category['name'], 'callback_data' => 'cat_' . $category['id']]]; } sendMessage($chat_id, "لطفا یکی از دسته‌ؚندی‌های زیر را انتخاؚ کنید:", ['inline_keyboard' => $keyboard_buttons]); - } - elseif (strpos($data, 'service_details_') === 0) { - $username = str_replace('service_details_', '', $data); - if (isset($update['callback_query']['message']['photo'])) { - editMessageCaption($chat_id, $message_id, "⏳ در حال دریافت اطلاعات ؚه‌روز سرویس، لطفا صؚر کنید..."); - } else { - editMessageText($chat_id, $message_id, "⏳ در حال دریافت اطلاعات ؚه‌روز سرویس، لطفا صؚر کنید..."); - } + } elseif (strpos($data, 'service_details_') === 0) { + $username = str_replace('service_details_', '', $data); + if (isset($update['callback_query']['message']['photo'])) { + editMessageCaption($chat_id, $message_id, "⏳ در حال دریافت اطلاعات ؚه‌روز سرویس، لطفا صؚر کنید..."); + } else { + editMessageText($chat_id, $message_id, "⏳ در حال دریافت اطلاعات ؚه‌روز سرویس، لطفا صؚر کنید..."); + } - $stmt_local = pdo()->prepare("SELECT s.*, p.name as plan_name, p.show_sub_link, p.show_conf_links FROM services s JOIN plans p ON s.plan_id = p.id WHERE s.owner_chat_id = ? AND s.marzban_username = ?"); - $stmt_local->execute([$chat_id, $username]); - $local_service = $stmt_local->fetch(); + $stmt_local = pdo()->prepare("SELECT s.*, p.name as plan_name, p.show_sub_link, p.show_conf_links FROM services s JOIN plans p ON s.plan_id = p.id WHERE s.owner_chat_id = ? AND s.marzban_username = ?"); + $stmt_local->execute([$chat_id, $username]); + $local_service = $stmt_local->fetch(); - if ($local_service) { - $stmt_server = pdo()->prepare("SELECT * FROM servers WHERE id = ?"); - $stmt_server->execute([$local_service['server_id']]); - $server_info = $stmt_server->fetch(); + if ($local_service) { + $stmt_server = pdo()->prepare("SELECT * FROM servers WHERE id = ?"); + $stmt_server->execute([$local_service['server_id']]); + $server_info = $stmt_server->fetch(); - $dynamic_sub_url = $local_service['sub_url']; - if ($server_info) { - $base_sub_url = !empty($server_info['sub_host']) ? rtrim($server_info['sub_host'], '/') : rtrim($server_info['url'], '/'); - $sub_path = strstr($local_service['sub_url'], '/sub/'); - if ($sub_path === false) { - $sub_path = parse_url($local_service['sub_url'], PHP_URL_PATH); - } - $dynamic_sub_url = $base_sub_url . $sub_path; - } + $dynamic_sub_url = $local_service['sub_url']; + if ($server_info) { + $base_sub_url = !empty($server_info['sub_host']) ? rtrim($server_info['sub_host'], '/') : rtrim($server_info['url'], '/'); + $sub_path = strstr($local_service['sub_url'], '/sub/'); + if ($sub_path === false) { + $sub_path = parse_url($local_service['sub_url'], PHP_URL_PATH); + } + $dynamic_sub_url = $base_sub_url . $sub_path; + } - $panel_user = getPanelUser($username, $local_service['server_id']); - - if ($panel_user && !isset($panel_user['detail'])) { - $qr_code_url = generateQrCodeUrl($dynamic_sub_url); - - $total_gb_from_db = $local_service['volume_gb']; - $used_bytes_from_panel = $panel_user['used_traffic']; - - $total_text = ($total_gb_from_db > 0) ? "{$total_gb_from_db} گیگاؚایت" : 'نامحدود'; - $used_text = formatBytes($used_bytes_from_panel); - - $remaining_text = 'نامحدود'; - if ($total_gb_from_db > 0) { - $total_bytes_from_db = $total_gb_from_db * 1024 * 1024 * 1024; - $remaining_bytes = $total_bytes_from_db - $used_bytes_from_panel; - $remaining_text = formatBytes(max(0, $remaining_bytes)); - } + $panel_user = getPanelUser($username, $local_service['server_id']); - $expire_date = $panel_user['expire'] ? date('Y-m-d', $panel_user['expire']) : 'نامحدود'; - $status_text = ($panel_user['status'] === 'active' && ($panel_user['expire'] == 0 || $panel_user['expire'] > time())) ? 'فعال' : 'غیرفعال'; - - $caption = - "م؎خصات سرویس: {$local_service['plan_name']}\n" . - "➖➖➖➖➖➖➖➖➖➖\n" . - "▫ وضعیت: {$status_text}\n" . - "🗓 تاریخ انقضا: {$expire_date}\n\n" . - "📊 حجم کل: " . $total_text . "\n" . - "📈 حجم مصرفی: " . $used_text . "\n" . - "📉 حجم ؚاقی‌مانده: " . $remaining_text . "\n" . - "➖➖➖➖➖➖➖➖➖➖\n"; - - if ($local_service['show_sub_link']) { - $caption .= "\n🔗 لینک ا؎تراک (Subscription):\n" . htmlspecialchars($dynamic_sub_url) . "\n"; - } else { - $caption .= "\n🔗 لینک ا؎تراک ؚرای این ٟلن نمای؎ داده نمی‌؎ود.\n"; - } + if ($panel_user && !isset($panel_user['detail'])) { + $qr_code_url = generateQrCodeUrl($dynamic_sub_url); - - $keyboard_buttons = [ - [['text' => '♻ تمدید سرویس', 'callback_data' => "renew_service_{$username}"]], - ]; + $total_gb_from_db = $local_service['volume_gb']; + $used_bytes_from_panel = $panel_user['used_traffic']; - if ($local_service['show_conf_links'] && !empty($panel_user['links'])) { - $keyboard_buttons[0][] = ['text' => '📋 دریافت کانفیگ‌ها', 'callback_data' => "get_configs_{$username}"]; - } - - $keyboard_buttons[] = [['text' => '🗑 حذف سرویس', 'callback_data' => "delete_service_confirm_{$username}"]]; - $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت ØšÙ‡ لیست', 'callback_data' => 'back_to_services']]; - + $total_text = ($total_gb_from_db > 0) ? "{$total_gb_from_db} گیگاؚایت" : 'نامحدود'; + $used_text = formatBytes($used_bytes_from_panel); - $keyboard = ['inline_keyboard' => $keyboard_buttons]; + $remaining_text = 'نامحدود'; + if ($total_gb_from_db > 0) { + $total_bytes_from_db = $total_gb_from_db * 1024 * 1024 * 1024; + $remaining_bytes = $total_bytes_from_db - $used_bytes_from_panel; + $remaining_text = formatBytes(max(0, $remaining_bytes)); + } - deleteMessage($chat_id, $message_id); - sendPhoto($chat_id, $qr_code_url, trim($caption), $keyboard); - } else { - editMessageText($chat_id, $message_id, "❌ خطایی در دریافت اطلاعات سرویس از سرور رخ داد یا سرویس یافت ن؎د. ممکن است توسط ادمین حذف ؎ده ؚا؎د."); - } + $expire_date = $panel_user['expire'] ? date('Y-m-d', $panel_user['expire']) : 'نامحدود'; + $status_text = ($panel_user['status'] === 'active' && ($panel_user['expire'] == 0 || $panel_user['expire'] > time())) ? 'فعال' : 'غیرفعال'; + + $caption = + "م؎خصات سرویس: {$local_service['plan_name']}\n" . + "➖➖➖➖➖➖➖➖➖➖\n" . + "▫ وضعیت: {$status_text}\n" . + "🗓 تاریخ انقضا: {$expire_date}\n\n" . + "📊 حجم کل: " . $total_text . "\n" . + "📈 حجم مصرفی: " . $used_text . "\n" . + "📉 حجم ؚاقی‌مانده: " . $remaining_text . "\n" . + "➖➖➖➖➖➖➖➖➖➖\n"; + + if ($local_service['show_sub_link']) { + $caption .= "\n🔗 لینک ا؎تراک (Subscription):\n" . htmlspecialchars($dynamic_sub_url) . "\n"; } else { - editMessageText($chat_id, $message_id, "❌ سرویس در دیتاؚیس رؚات یافت ن؎د."); + $caption .= "\n🔗 لینک ا؎تراک ؚرای این ٟلن نمای؎ داده نمی‌؎ود.\n"; } + + + $keyboard_buttons = [ + [['text' => '♻ تمدید سرویس', 'callback_data' => "renew_service_{$username}"]], + ]; + + if ($local_service['show_conf_links'] && !empty($panel_user['links'])) { + $keyboard_buttons[0][] = ['text' => '📋 دریافت کانفیگ‌ها', 'callback_data' => "get_configs_{$username}"]; + } + + $keyboard_buttons[] = [['text' => '🗑 حذف سرویس', 'callback_data' => "delete_service_confirm_{$username}"]]; + $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت ØšÙ‡ لیست', 'callback_data' => 'back_to_services']]; + + + $keyboard = ['inline_keyboard' => $keyboard_buttons]; + + deleteMessage($chat_id, $message_id); + sendPhoto($chat_id, $qr_code_url, trim($caption), $keyboard); + } else { + editMessageText($chat_id, $message_id, "❌ خطایی در دریافت اطلاعات سرویس از سرور رخ داد یا سرویس یافت ن؎د. ممکن است توسط ادمین حذف ؎ده ؚا؎د."); } - elseif (strpos($data, 'renew_service_') === 0) { + } else { + editMessageText($chat_id, $message_id, "❌ سرویس در دیتاؚیس رؚات یافت ن؎د."); + } + } elseif (strpos($data, 'renew_service_') === 0) { $settings = getSettings(); if (($settings['renewal_status'] ?? 'off') !== 'on') { apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '❌ قاؚلیت تمدید سرویس در حال حاضر غیرفعال است.', 'show_alert' => true]); @@ -1521,22 +1441,20 @@ $username = str_replace('renew_service_', '', $data); updateUserData($chat_id, 'user_awaiting_renewal_days', ['renewal_username' => $username]); - + $price_day = number_format($settings['renewal_price_per_day'] ?? 1000); $message = "تمدید سرویس\n\n" . - "Û±. چند **روز** ØšÙ‡ اعتؚار سرویس ؎ما اضافه ؎ود؟\n\n" . - "▫ هزینه هر روز: {$price_day} تومان\n" . - "💡 ؚرای رد ؎دن و عدم تمدید زمان، عدد `0` را وارد کنید."; - + "Û±. چند **روز** ØšÙ‡ اعتؚار سرویس ؎ما اضافه ؎ود؟\n\n" . + "▫ هزینه هر روز: {$price_day} تومان\n" . + "💡 ؚرای رد ؎دن و عدم تمدید زمان، عدد `0` را وارد کنید."; + editMessageCaption($chat_id, $message_id, $message, null); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); - } - elseif (strpos($data, 'delete_service_confirm_') === 0) { + } elseif (strpos($data, 'delete_service_confirm_') === 0) { $username = str_replace('delete_service_confirm_', '', $data); $keyboard = ['inline_keyboard' => [[['text' => '✅ ؚله، حذف کن', 'callback_data' => "delete_service_do_{$username}"], ['text' => '❌ خیر، لغو', 'callback_data' => "service_details_{$username}"]]]]; editMessageCaption($chat_id, $message_id, "⚠ آیا از حذف این سرویس مطم؊ن هستید؟\nاین عمل غیرقاؚل ؚازگ؎ت است و تمام اطلاعات سرویس ٟاک خواهد ؎د.", $keyboard); - } - elseif (strpos($data, 'delete_service_do_') === 0) { + } elseif (strpos($data, 'delete_service_do_') === 0) { $username = str_replace('delete_service_do_', '', $data); editMessageCaption($chat_id, $message_id, "⏳ در حال حذف سرویس..."); @@ -1549,23 +1467,19 @@ deleteUserService($chat_id, $username, $server_id); if ($result_panel) { editMessageCaption($chat_id, $message_id, "✅ سرویس ؎ما ؚا موفقیت حذف ؎د."); - } - else { + } else { editMessageCaption($chat_id, $message_id, "⚠ سرویس از لیست ؎ما حذف ؎د، اما ممکن است در حذف از ٟنل اصلی Ù…ØŽÚ©Ù„ÛŒ رخ داده ؚا؎د. لطفا ØšÙ‡ ٟ؎تیؚانی اطلاع دهید."); error_log("Failed to delete panel user {$username} on server {$server_id}. Response: " . json_encode($result_panel)); } - } - else { + } else { editMessageCaption($chat_id, $message_id, "❌ خطایی در یافتن اطلاعات سرور ؚرای این سرویس رخ داد."); } - } - elseif ($data == 'back_to_services') { + } elseif ($data == 'back_to_services') { deleteMessage($chat_id, $message_id); $services = getUserServices($chat_id); if (empty($services)) { sendMessage($chat_id, "؎ما هیچ سرویس فعالی ندارید."); - } - else { + } else { $keyboard_buttons = []; $now = time(); foreach ($services as $service) { @@ -1582,8 +1496,7 @@ handleMainMenu($chat_id, $first_name, true); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); die; - } - elseif ($apiRequest) { + } elseif ($apiRequest) { apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); die; } @@ -1605,8 +1518,7 @@ $keyboard = ['keyboard' => [[['text' => '🔒 ا؎تراک‌گذاری ؎ماره تلفن', 'request_contact' => true]]], 'resize_keyboard' => true, 'one_time_keyboard' => true]; sendMessage($chat_id, $message, $keyboard); die; - } - elseif ($verification_method === 'button') { + } elseif ($verification_method === 'button') { $message = "سلام! ؚرای اطمینان از اینکه ؎ما یک کارؚر واقعی هستید، لطفاً روی دکمه زیر کلیک کنید."; $keyboard = ['inline_keyboard' => [[['text' => '✅ تایید می‌کنم', 'callback_data' => 'verify_by_button']]]]; sendMessage($chat_id, $message, $keyboard); @@ -1621,18 +1533,18 @@ $amount = $state_data['charge_amount']; $user_id = $update['message']['from']['id']; $photo_id = $update['message']['photo'][count($update['message']['photo']) - 1]['file_id']; - + // --- آماده‌سازی metadata --- $metadata_to_save = null; if (isset($state_data['purpose']) && $state_data['purpose'] === 'complete_purchase') { $metadata_to_save = json_encode([ - 'purpose' => 'complete_purchase', - 'plan_id' => $state_data['plan_id'], - 'discount_code' => $state_data['discount_code'] ?? null, - 'custom_name' => $state_data['custom_name'] ?? 'سرویس' // اضافه کردن نام دلخواه -]); -} - + 'purpose' => 'complete_purchase', + 'plan_id' => $state_data['plan_id'], + 'discount_code' => $state_data['discount_code'] ?? null, + 'custom_name' => $state_data['custom_name'] ?? 'سرویس' // اضافه کردن نام دلخواه + ]); + } + $stmt = pdo()->prepare("INSERT INTO payment_requests (user_id, amount, photo_file_id, metadata) VALUES (?, ?, ?, ?)"); $stmt->execute([$user_id, $amount, $photo_id, $metadata_to_save]); @@ -1680,8 +1592,7 @@ $stmt->execute([$phone_number, $chat_id]); sendMessage($chat_id, "✅ احراز هویت ؎ما ؚا موفقیت انجام ؎د. از همراهی ؎ما سٟاسگزاریم!"); handleMainMenu($chat_id, $first_name); - } - else { + } else { $message = "❌ متاسفانه ؎ماره ارسالی ؎ما مورد تایید نیست. این رؚات فقط ؚرای ؎ماره‌های ایران (+98) فعال است."; $keyboard = ['keyboard' => [[['text' => '🔒 ا؎تراک‌گذاری ؎ماره تلفن', 'request_contact' => true]]], 'resize_keyboard' => true, 'one_time_keyboard' => true]; sendMessage($chat_id, $message, $keyboard); @@ -1707,8 +1618,7 @@ if ($isAnAdmin && (strpos($user_state, 'admin_') === 0 || $admin_view_mode === 'admin')) { updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); handleMainMenu($chat_id, $first_name, false); - } - else { + } else { updateUserData($chat_id, 'main_menu', ['admin_view' => 'user']); handleMainMenu($chat_id, $first_name, false); } @@ -1739,95 +1649,97 @@ if ($user_state !== 'main_menu') { switch ($user_state) { - + case 'awaiting_service_name': - $custom_name = trim($text); - if (empty($custom_name) || mb_strlen($custom_name) > 50) { - sendMessage($chat_id, "❌ نام وارد ؎ده نامعتؚر است. لطفاً یک نام کوتاه‌تر (حداکثر 50 کاراکتر) وارد کنید.", $cancelKeyboard); - break; - } + $custom_name = trim($text); + if (empty($custom_name) || mb_strlen($custom_name) > 50) { + sendMessage($chat_id, "❌ نام وارد ؎ده نامعتؚر است. لطفاً یک نام کوتاه‌تر (حداکثر 50 کاراکتر) وارد کنید.", $cancelKeyboard); + break; + } - $state_data = $user_data['state_data']; - $plan_id = $state_data['purchasing_plan_id']; - $discount_code = $state_data['discount_code'] ?? null; - - $plan = getPlanById($plan_id); - if (!$plan) { - sendMessage($chat_id, "❌ خطایی رخ داد. ٟلن یافت ن؎د."); - updateUserData($chat_id, 'main_menu'); - break; - } + $state_data = $user_data['state_data']; + $plan_id = $state_data['purchasing_plan_id']; + $discount_code = $state_data['discount_code'] ?? null; - // --- Ú©ÙŸÛŒ کردن منطق ؚررسی موجودی و قیمت نهایی از کد Ù‚ØšÙ„ÛŒ --- - $final_price = (float)$plan['price']; - $discount_applied = false; - $discount_object = null; - if ($discount_code) { - $stmt = pdo()->prepare("SELECT * FROM discount_codes WHERE code = ? AND status = 'active' AND usage_count < max_usage"); - $stmt->execute([$discount_code]); - $discount = $stmt->fetch(); - if ($discount) { - if ($discount['type'] == 'percent') { - $final_price = $plan['price'] - ($plan['price'] * $discount['value']) / 100; - } else { - $final_price = $plan['price'] - $discount['value']; - } - $final_price = max(0, $final_price); - $discount_applied = true; - $discount_object = $discount; - } - } - - $user_balance = $user_data['balance']; - - if ($user_balance >= $final_price) { - sendMessage($chat_id, "⏳ نام سرویس تایید ؎د. لطفاً صؚر کنید... در حال ایجاد سرویس ؎ما هستیم."); - $purchase_result = completePurchase($chat_id, $plan_id, $custom_name, $final_price, $discount_code, $discount_object, $discount_applied); - - if ($purchase_result['success']) { - sendPhoto($chat_id, $purchase_result['qr_code_url'], $purchase_result['caption'], $purchase_result['keyboard']); - sendMessage(ADMIN_CHAT_ID, $purchase_result['admin_notification']); - } else { - sendMessage($chat_id, $purchase_result['error_message']); - sendMessage(ADMIN_CHAT_ID, "⚠ خطای ساخت سرویس\n\nکارؚر ؚا ؎ناسه $chat_id قصد خرید ٟلن '{$plan['name']}' را دا؎ت اما ارتؚاط ؚا ٟنل ناموفق ؚود."); - } - updateUserData($chat_id, 'main_menu'); - handleMainMenu($chat_id, $first_name); + $plan = getPlanById($plan_id); + if (!$plan) { + sendMessage($chat_id, "❌ خطایی رخ داد. ٟلن یافت ن؎د."); + updateUserData($chat_id, 'main_menu'); + break; + } - } else { - // کارؚر موجودی کافی ندارد، فاکتور صادر ؎ود - $needed_amount = $final_price - $user_balance; - $settings = getSettings(); - - $encoded_name = base64_encode($custom_name); - - $keyboard_buttons = []; - if (($settings['payment_gateway_status'] ?? 'off') == 'on' && !empty($settings['zarinpal_merchant_id'])) { - $callback_data_online = "charge_for_plan_{$needed_amount}_{$plan_id}"; - if ($discount_code) $callback_data_online .= "_{$discount_code}"; - $callback_data_online .= "_{$encoded_name}"; // اضافه کردن نام ØšÙ‡ انتها - $keyboard_buttons[] = [['text' => '🌐 ٟرداخت آنلاین (زرین‌ٟال)', 'callback_data' => $callback_data_online]]; - } - if (!empty($settings['payment_method']['card_number'])) { - $callback_data_manual = "manual_pay_for_plan_{$needed_amount}_{$plan_id}"; - if ($discount_code) $callback_data_manual .= "_{$discount_code}"; - $callback_data_manual .= "_{$encoded_name}"; // اضافه کردن نام ØšÙ‡ انتها - $keyboard_buttons[] = [['text' => '💳 ٟرداخت کارت ØšÙ‡ کارت', 'callback_data' => $callback_data_manual]]; - } + // --- Ú©ÙŸÛŒ کردن منطق ؚررسی موجودی و قیمت نهایی از کد Ù‚ØšÙ„ÛŒ --- + $final_price = (float) $plan['price']; + $discount_applied = false; + $discount_object = null; + if ($discount_code) { + $stmt = pdo()->prepare("SELECT * FROM discount_codes WHERE code = ? AND status = 'active' AND usage_count < max_usage"); + $stmt->execute([$discount_code]); + $discount = $stmt->fetch(); + if ($discount) { + if ($discount['type'] == 'percent') { + $final_price = $plan['price'] - ($plan['price'] * $discount['value']) / 100; + } else { + $final_price = $plan['price'] - $discount['value']; + } + $final_price = max(0, $final_price); + $discount_applied = true; + $discount_object = $discount; + } + } + + $user_balance = $user_data['balance']; + + if ($user_balance >= $final_price) { + sendMessage($chat_id, "⏳ نام سرویس تایید ؎د. لطفاً صؚر کنید... در حال ایجاد سرویس ؎ما هستیم."); + $purchase_result = completePurchase($chat_id, $plan_id, $custom_name, $final_price, $discount_code, $discount_object, $discount_applied); + + if ($purchase_result['success']) { + sendPhoto($chat_id, $purchase_result['qr_code_url'], $purchase_result['caption'], $purchase_result['keyboard']); + sendMessage(ADMIN_CHAT_ID, $purchase_result['admin_notification']); + } else { + sendMessage($chat_id, $purchase_result['error_message']); + sendMessage(ADMIN_CHAT_ID, "⚠ خطای ساخت سرویس\n\nکارؚر ؚا ؎ناسه $chat_id قصد خرید ٟلن '{$plan['name']}' را دا؎ت اما ارتؚاط ؚا ٟنل ناموفق ؚود."); + } + updateUserData($chat_id, 'main_menu'); + handleMainMenu($chat_id, $first_name); + + } else { + // کارؚر موجودی کافی ندارد، فاکتور صادر ؎ود + $needed_amount = $final_price - $user_balance; + $settings = getSettings(); + + $encoded_name = base64_encode($custom_name); + + $keyboard_buttons = []; + if (($settings['payment_gateway_status'] ?? 'off') == 'on' && !empty($settings['zarinpal_merchant_id'])) { + $callback_data_online = "charge_for_plan_{$needed_amount}_{$plan_id}"; + if ($discount_code) + $callback_data_online .= "_{$discount_code}"; + $callback_data_online .= "_{$encoded_name}"; // اضافه کردن نام ØšÙ‡ انتها + $keyboard_buttons[] = [['text' => '🌐 ٟرداخت آنلاین (زرین‌ٟال)', 'callback_data' => $callback_data_online]]; + } + if (!empty($settings['payment_method']['card_number'])) { + $callback_data_manual = "manual_pay_for_plan_{$needed_amount}_{$plan_id}"; + if ($discount_code) + $callback_data_manual .= "_{$discount_code}"; + $callback_data_manual .= "_{$encoded_name}"; // اضافه کردن نام ØšÙ‡ انتها + $keyboard_buttons[] = [['text' => '💳 ٟرداخت کارت ØšÙ‡ کارت', 'callback_data' => $callback_data_manual]]; + } + + if (empty($keyboard_buttons)) { + sendMessage($chat_id, "❌ موجودی ؎ما کافی نیست و هیچ رو؎ ٟرداختی توسط ادمین فعال ن؎ده است."); + } else { + $message = "⚠ موجودی ؎ما کافی نیست!\n\n" . + "▫ قیمت ٟلن: " . number_format($final_price) . " تومان\n" . + "▫ موجودی ؎ما: " . number_format($user_balance) . " تومان\n" . + "💰 مؚلغ مورد نیاز: " . number_format($needed_amount) . " تومان\n\n" . + "لطفاً رو؎ ٟرداخت ؚرای تکمیل خرید را انتخاؚ کنید:"; + sendMessage($chat_id, $message, ['inline_keyboard' => $keyboard_buttons]); + } + } + break; - if (empty($keyboard_buttons)) { - sendMessage($chat_id, "❌ موجودی ؎ما کافی نیست و هیچ رو؎ ٟرداختی توسط ادمین فعال ن؎ده است."); - } else { - $message = "⚠ موجودی ؎ما کافی نیست!\n\n" . - "▫ قیمت ٟلن: " . number_format($final_price) . " تومان\n" . - "▫ موجودی ؎ما: " . number_format($user_balance) . " تومان\n" . - "💰 مؚلغ مورد نیاز: " . number_format($needed_amount) . " تومان\n\n" . - "لطفاً رو؎ ٟرداخت ؚرای تکمیل خرید را انتخاؚ کنید:"; - sendMessage($chat_id, $message, ['inline_keyboard' => $keyboard_buttons]); - } - } - break; - case 'admin_awaiting_user_search': if ($isAnAdmin && hasPermission($chat_id, 'manage_users')) { if (!is_numeric($text)) { @@ -1839,10 +1751,10 @@ sendMessage($chat_id, "❌ کارؚری ؚا این ؎ناسه یافت ن؎د. لطفاً ؎ناسه را ؚررسی کرده و مجدداً تلا؎ کنید.", $cancelKeyboard); break; } - + $chat_info_response = apiRequest('getChat', ['chat_id' => $target_user['chat_id']]); $chat_info = json_decode($chat_info_response, true); - + $profile_link_html = ''; if ($chat_info['ok'] && !empty($chat_info['result']['username'])) { $username = $chat_info['result']['username']; @@ -1850,84 +1762,86 @@ } else { $profile_link_html = "👀 حساؚ کارؚری: م؎اهده ٟروفایل (ؚدون یوزرنیم)\n"; } - + // نمای؎ اطلاعات و دکمه‌های مدیریتی $balance = $target_user['balance'] ?? 0; $status_text = ($target_user['status'] ?? 'active') === 'active' ? 'فعال ✅' : 'مسدود 🚫'; $message = "اطلاعات کارؚر: " . htmlspecialchars($target_user['first_name']) . "\n\n" . - "▫ ؎ناسه: {$target_user['chat_id']}\n" . - $profile_link_html . - "💰 موجودی: " . number_format($balance) . " تومان\n" . - "▫ وضعیت: {$status_text}\n\n" . - "لطفاً عملیات مورد ن؞ر را انتخاؚ کنید:"; + "▫ ؎ناسه: {$target_user['chat_id']}\n" . + $profile_link_html . + "💰 موجودی: " . number_format($balance) . " تومان\n" . + "▫ وضعیت: {$status_text}\n\n" . + "لطفاً عملیات مورد ن؞ر را انتخاؚ کنید:"; $status_button_text = ($target_user['status'] ?? 'active') === 'active' ? '🚫 مسدود کردن' : '✅ آزاد کردن'; $status_callback = ($target_user['status'] ?? 'active') === 'active' ? "ban_user_{$target_user['chat_id']}" : "unban_user_{$target_user['chat_id']}"; - $keyboard = ['inline_keyboard' => [ - [ - ['text' => '➕ افزای؎ موجودی', 'callback_data' => "add_balance_{$target_user['chat_id']}"], - ['text' => '➖ کاه؎ موجودی', 'callback_data' => "deduct_balance_{$target_user['chat_id']}"] - ], - [ - ['text' => '✉ ارسال ٟیام', 'callback_data' => "message_user_{$target_user['chat_id']}"], - ['text' => '🔧 سرویس‌های کارؚر', 'callback_data' => "show_user_services_{$target_user['chat_id']}"] - ], - [ - ['text' => $status_button_text, 'callback_data' => $status_callback] - ], - [ - ['text' => '🔎 جستجوی کارؚر دیگر', 'callback_data' => 'search_another_user'] + $keyboard = [ + 'inline_keyboard' => [ + [ + ['text' => '➕ افزای؎ موجودی', 'callback_data' => "add_balance_{$target_user['chat_id']}"], + ['text' => '➖ کاه؎ موجودی', 'callback_data' => "deduct_balance_{$target_user['chat_id']}"] + ], + [ + ['text' => '✉ ارسال ٟیام', 'callback_data' => "message_user_{$target_user['chat_id']}"], + ['text' => '🔧 سرویس‌های کارؚر', 'callback_data' => "show_user_services_{$target_user['chat_id']}"] + ], + [ + ['text' => $status_button_text, 'callback_data' => $status_callback] + ], + [ + ['text' => '🔎 جستجوی کارؚر دیگر', 'callback_data' => 'search_another_user'] + ] ] - ]]; + ]; sendMessage($chat_id, $message, $keyboard); // وضعیت را ØšÙ‡ حالت انت؞ار ؚرای جستجوی ؚعدی ؚرمی‌گردانیم تا ادمین ؚتواند ٟ؎ت سر هم جستجو کند updateUserData($chat_id, 'admin_awaiting_user_search', ['admin_view' => 'admin']); } break; - + case 'admin_awaiting_renewal_price_day': - if ($isAnAdmin && is_numeric($text) && $text >= 0) { - $settings = getSettings(); - $settings['renewal_price_per_day'] = (int)$text; - saveSettings($settings); - sendMessage($chat_id, "✅ قیمت ؚا موفقیت تن؞یم ؎د."); - updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); - showRenewalManagementMenu($chat_id); - } else { - sendMessage($chat_id, "❌ لطفا فقط عدد وارد کنید."); - } - break; - - case 'admin_awaiting_merchant_id': + if ($isAnAdmin && is_numeric($text) && $text >= 0) { + $settings = getSettings(); + $settings['renewal_price_per_day'] = (int) $text; + saveSettings($settings); + sendMessage($chat_id, "✅ قیمت ؚا موفقیت تن؞یم ؎د."); + updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); + showRenewalManagementMenu($chat_id); + } else { + sendMessage($chat_id, "❌ لطفا فقط عدد وارد کنید."); + } + break; + + case 'admin_awaiting_merchant_id': if ($isAnAdmin && strlen($text) === 36) { $settings = getSettings(); $settings['zarinpal_merchant_id'] = $text; saveSettings($settings); sendMessage($chat_id, "✅ مرچنت کد ؚا موفقیت ذخیره ؎د."); updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); - + } else { sendMessage($chat_id, "❌ مرچنت کد نامعتؚر است. ؚاید دقیقا Û³Û¶ کاراکتر ؚا؎د."); } break; - + case 'admin_awaiting_renewal_price_gb': - if ($isAnAdmin && is_numeric($text) && $text >= 0) { - $settings = getSettings(); - $settings['renewal_price_per_gb'] = (int)$text; - saveSettings($settings); - sendMessage($chat_id, "✅ قیمت ؚا موفقیت تن؞یم ؎د."); - updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); - showRenewalManagementMenu($chat_id); - } else { - sendMessage($chat_id, "❌ لطفا فقط عدد وارد کنید."); - } - break; - + if ($isAnAdmin && is_numeric($text) && $text >= 0) { + $settings = getSettings(); + $settings['renewal_price_per_gb'] = (int) $text; + saveSettings($settings); + sendMessage($chat_id, "✅ قیمت ؚا موفقیت تن؞یم ؎د."); + updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); + showRenewalManagementMenu($chat_id); + } else { + sendMessage($chat_id, "❌ لطفا فقط عدد وارد کنید."); + } + break; + case 'admin_awaiting_category_name': if (!hasPermission($chat_id, 'manage_categories')) { break; @@ -1958,7 +1872,7 @@ break; } $state_data = $user_data['state_data']; - $state_data['new_plan_price'] = (int)$text; + $state_data['new_plan_price'] = (int) $text; updateUserData($chat_id, 'awaiting_plan_volume', $state_data); sendMessage($chat_id, "3/6 - لطفا حجم ٟلن را ØšÙ‡ گیگاؚایت (GB) وارد کنید (فقط عدد):", $cancelKeyboard); break; @@ -1972,7 +1886,7 @@ break; } $state_data = $user_data['state_data']; - $state_data['new_plan_volume'] = (int)$text; + $state_data['new_plan_volume'] = (int) $text; updateUserData($chat_id, 'awaiting_plan_duration', $state_data); sendMessage($chat_id, "4/6 - لطفا مدت زمان ٟلن را ØšÙ‡ روز وارد کنید (فقط عدد):", $cancelKeyboard); break; @@ -1986,7 +1900,7 @@ break; } $state_data = $user_data['state_data']; - $state_data['new_plan_duration'] = (int)$text; + $state_data['new_plan_duration'] = (int) $text; updateUserData($chat_id, 'awaiting_plan_description', $state_data); $keyboard = ['keyboard' => [[['text' => 'رد ؎دن'], ['text' => '◀ ؚازگ؎ت ØšÙ‡ منوی اصلی']]], 'resize_keyboard' => true]; sendMessage($chat_id, "5/6 - در صورت تمایل، توضیحات مختصری ؚرای ٟلن وارد کنید (اختیاری):", $keyboard); @@ -2006,11 +1920,11 @@ sendMessage($chat_id, "6/6 - تعداد مجاز خرید ؚرای این ٟلن را وارد کنید (فقط عدد).\n\nؚرای فرو؎ نامحدود، عدد `0` را وارد کنید.", $keyboard); break; - case 'awaiting_plan_purchase_limit': + case 'awaiting_plan_purchase_limit': if (!hasPermission($chat_id, 'manage_plans')) { break; } - if (!is_numeric($text) || (int)$text < 0) { + if (!is_numeric($text) || (int) $text < 0) { sendMessage($chat_id, "❌ لطفا فقط یک عدد صحیح (مثؚت یا صفر) وارد کنید.", $cancelKeyboard); break; } @@ -2026,7 +1940,7 @@ 'volume_gb' => $state_data['new_plan_volume'], 'duration_days' => $state_data['new_plan_duration'], 'description' => $state_data['new_plan_description'], - 'purchase_limit' => (int)$text, + 'purchase_limit' => (int) $text, ]; updateUserData($chat_id, 'awaiting_plan_sub_link_setting', ['temp_plan_data' => $new_plan_data]); @@ -2034,15 +1948,16 @@ $keyboard = ['inline_keyboard' => [[['text' => '✅ ؚله', 'callback_data' => 'plan_set_sub_yes'], ['text' => '❌ خیر', 'callback_data' => 'plan_set_sub_no']]]]; sendMessage($chat_id, "سوال Û±/Û²: آیا لینک ا؎تراک (Subscription) ØšÙ‡ کارؚر نمای؎ داده ؎ود؟\n(ٟی؎نهادی: ؚله)", $keyboard); break; - - case 'admin_awaiting_sub_host': - if (!hasPermission($chat_id, 'manage_marzban')) break; + + case 'admin_awaiting_sub_host': + if (!hasPermission($chat_id, 'manage_marzban')) + break; $state_data = $user_data['state_data']; $server_id = $state_data['editing_server_id']; $new_sub_host = null; $message_text = ""; - + if (strtolower($text) === 'reset') { $new_sub_host = null; $message_text = "✅ آدرس لینک ا؎تراک ؚا موفقیت ØšÙ‡ حالت ٟی؎‌فرض ؚازن؎انی ؎د."; @@ -2056,10 +1971,10 @@ $stmt = pdo()->prepare("UPDATE servers SET sub_host = ? WHERE id = ?"); $stmt->execute([$new_sub_host, $server_id]); - + updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); sendMessage($chat_id, $message_text); - + $servers = pdo()->query("SELECT id, name FROM servers")->fetchAll(PDO::FETCH_ASSOC); $keyboard_buttons = [[['text' => '➕ افزودن سرور جدید', 'callback_data' => 'add_server_select_type']]]; foreach ($servers as $server) { @@ -2068,7 +1983,7 @@ $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت ØšÙ‡ ٟنل', 'callback_data' => 'back_to_admin_panel']]; sendMessage($chat_id, "🌐 لیست سرورها ؚه‌روز ؎د.", ['inline_keyboard' => $keyboard_buttons]); break; - + case 'admin_awaiting_card_number': if (!hasPermission($chat_id, 'manage_payment')) { break; @@ -2155,15 +2070,16 @@ } else { $error_message = "⚠ ه؎دار: رؚات نتوانست ØšÙ‡ سرور جدید متصل ؎ود. لطفا اطلاعات وارد ؎ده را ؚررسی کرده و در صورت نیاز سرور را حذف و مجدداً اضافه کنید."; if ($connection_error) { - $error_message .= "\n\nجز؊یات خطا:\n" . htmlspecialchars($connection_error) . ""; + $error_message .= "\n\nجز؊یات خطا:\n" . htmlspecialchars($connection_error) . ""; } sendMessage($chat_id, $error_message); } handleMainMenu($chat_id, $first_name); break; - - case 'admin_awaiting_plan_edit_input': - if (!hasPermission($chat_id, 'manage_plans')) break; + + case 'admin_awaiting_plan_edit_input': + if (!hasPermission($chat_id, 'manage_plans')) + break; $state_data = $user_data['state_data']; $plan_id = $state_data['editing_plan_id']; @@ -2173,7 +2089,7 @@ $validation = $field_info['validation']; $value = $text; $user_message_id = $update['message']['message_id']; - + $is_valid = false; if ($validation === 'text' && !empty($value)) { $is_valid = true; @@ -2186,25 +2102,25 @@ deleteMessage($chat_id, $user_message_id); break; } - + $stmt = pdo()->prepare("UPDATE plans SET `{$column}` = ? WHERE id = ?"); $stmt->execute([$value, $plan_id]); - + updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); showPlanEditor($chat_id, $editor_message_id, $plan_id, "✅ مقدار ؚا موفقیت ؚه‌روز ؎د."); deleteMessage($chat_id, $user_message_id); break; - case 'awaiting_charge_amount': + case 'awaiting_charge_amount': if (!is_numeric($text) || $text <= 0) { sendMessage($chat_id, "❌ لطفا یک مؚلغ معتؚر (عدد مثؚت) ØšÙ‡ تومان وارد کنید.", $cancelKeyboard); break; } - $amount = (int)$text; + $amount = (int) $text; $settings = getSettings(); - + $keyboard_buttons = []; - + if (!empty($settings['payment_method']['card_number'])) { $keyboard_buttons[] = [['text' => '💳 ٟرداخت کارت ØšÙ‡ کارت', 'callback_data' => "charge_manual_{$amount}"]]; } @@ -2212,7 +2128,7 @@ if (($settings['payment_gateway_status'] ?? 'off') == 'on' && !empty($settings['zarinpal_merchant_id'])) { $keyboard_buttons[] = [['text' => '🌐 ٟرداخت آنلاین (زرین‌ٟال)', 'callback_data' => "charge_zarinpal_{$amount}"]]; } - + if (empty($keyboard_buttons)) { sendMessage($chat_id, "متاسفانه هیچ رو؎ ٟرداختی توسط ادمین فعال ن؎ده است."); updateUserData($chat_id, 'main_menu'); @@ -2298,8 +2214,7 @@ $user_keyboard = ['inline_keyboard' => [[['text' => '💬 ٟاسخ مجدد', 'callback_data' => "reply_ticket_{$ticket_id}"], ['text' => '✖ ؚستن تیکت', 'callback_data' => "close_ticket_{$ticket_id}"]]]]; sendMessage($target_user_id, $user_message, $user_keyboard); sendMessage($chat_id, "✅ ٟاسخ ؎ما ؚرای کارؚر ارسال ؎د."); - } - else { + } else { sendMessage($chat_id, "❌ خطایی در ارسال ٟاسخ رخ داد. تیکت یا کارؚر یافت ن؎د."); } updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); @@ -2328,7 +2243,7 @@ } $state_data = $user_data['state_data']; $target_id = $state_data['target_user_id']; - updateUserBalance($target_id, (int)$text, 'add'); + updateUserBalance($target_id, (int) $text, 'add'); $new_balance_data = getUserData($target_id, ''); sendMessage($chat_id, "✅ مؚلغ " . number_format($text) . " تومان ؚا موفقیت ØšÙ‡ موجودی کارؚر $target_id اضافه ؎د."); sendMessage($target_id, "✅ مؚلغ " . number_format($text) . " تومان توسط ادمین ØšÙ‡ موجودی ؎ما اضافه ؎د.\nموجودی جدید: " . number_format($new_balance_data['balance']) . " تومان."); @@ -2359,11 +2274,11 @@ $state_data = $user_data['state_data']; $target_id = $state_data['target_user_id']; $target_user_data = getUserData($target_id, ''); - if ($target_user_data['balance'] < (int)$text) { + if ($target_user_data['balance'] < (int) $text) { sendMessage($chat_id, "❌ موجودی کارؚر ؚرای کسر این مؚلغ کافی نیست.\nموجودی فعلی: " . number_format($target_user_data['balance']) . " تومان", $cancelKeyboard); break; } - updateUserBalance($target_id, (int)$text, 'deduct'); + updateUserBalance($target_id, (int) $text, 'deduct'); $new_balance_data = getUserData($target_id, ''); sendMessage($chat_id, "✅ مؚلغ " . number_format($text) . " تومان ؚا موفقیت از موجودی کارؚر $target_id کسر ؎د."); sendMessage($target_id, "❗ مؚلغ " . number_format($text) . " تومان توسط ادمین از موجودی ؎ما کسر ؎د.\nموجودی جدید: " . number_format($new_balance_data['balance']) . " تومان."); @@ -2394,8 +2309,7 @@ $decoded_result = json_decode($result, true); if ($decoded_result && $decoded_result['ok']) { sendMessage($chat_id, "✅ ٟیام ؎ما ؚا موفقیت ØšÙ‡ کارؚر $target_id ارسال ؎د."); - } - else { + } else { sendMessage($chat_id, "❌ ارسال ٟیام ØšÙ‡ کارؚر $target_id ناموفق ؚود. ممکن است کارؚر رؚات را ؚلاک کرده ؚا؎د."); } updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); @@ -2481,7 +2395,7 @@ break; } $settings = getSettings(); - $settings['welcome_gift_balance'] = (int)$text; + $settings['welcome_gift_balance'] = (int) $text; saveSettings($settings); sendMessage($chat_id, "✅ هدیه عضویت ؚرای کارؚران جدید روی " . number_format($text) . " تومان تن؞یم ؎د."); updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); @@ -2497,7 +2411,7 @@ break; } sendMessage($chat_id, "⏳ عملیات افزودن حجم ØšÙ‡ تمام سرویس‌ها ؎روع ؎د. این فرآیند ممکن است کمی طول ؚک؎د..."); - $data_to_add_gb = (float)$text; + $data_to_add_gb = (float) $text; $bytes_to_add = $data_to_add_gb * 1024 * 1024 * 1024; $all_services = pdo() ->query("SELECT marzban_username, server_id FROM services") @@ -2520,13 +2434,11 @@ $result = modifyPanelUser($username, $server_id, ['data_limit' => $new_limit]); if ($result && !isset($result['detail'])) { $success_count++; - } - else { + } else { $fail_count++; } } - } - else { + } else { $fail_count++; } usleep(100000); @@ -2545,7 +2457,7 @@ break; } sendMessage($chat_id, "⏳ عملیات افزودن زمان ØšÙ‡ تمام سرویس‌ها ؎روع ؎د. این فرآیند ممکن است کمی طول ؚک؎د..."); - $days_to_add = (int)$text; + $days_to_add = (int) $text; $seconds_to_add = $days_to_add * 86400; $all_services = pdo() ->query("SELECT marzban_username, server_id FROM services") @@ -2568,13 +2480,11 @@ $result = modifyPanelUser($username, $server_id, ['expire' => $new_expire]); if ($result && !isset($result['detail'])) { $success_count++; - } - else { + } else { $fail_count++; } } - } - else { + } else { $fail_count++; } usleep(100000); @@ -2592,7 +2502,7 @@ sendMessage($chat_id, "❌ ؎ناسه وارد ؎ده نامعتؚر است. لطفا فقط عدد وارد کنید.", $cancelKeyboard); break; } - $target_id = (int)$text; + $target_id = (int) $text; if ($target_id == ADMIN_CHAT_ID) { sendMessage($chat_id, "❌ ؎ما نمی‌توانید خودتان را ØšÙ‡ عنوان ادمین اضافه کنید.", $cancelKeyboard); break; @@ -2613,8 +2523,7 @@ $target_first_name = "کارؚر {$target_id}"; if ($chat_info['ok'] && isset($chat_info['result']['first_name'])) { $target_first_name = $chat_info['result']['first_name']; - } - else { + } else { sendMessage($chat_id, "⚠ نتوانستم نام کارؚر را از تلگرام دریافت کنم. ؚا نام ٟی؎‌فرض ثؚت ؎د."); } addAdmin($target_id, $target_first_name); @@ -2636,7 +2545,7 @@ break; } $state_data = $user_data['state_data']; - $state_data['new_discount_value'] = (int)$text; + $state_data['new_discount_value'] = (int) $text; updateUserData($chat_id, 'admin_awaiting_discount_usage', $state_data); sendMessage($chat_id, "4/4 - حداکثر تعداد استفاده از این کد را وارد کنید (فقط عدد):", $cancelKeyboard); break; @@ -2648,30 +2557,30 @@ } $discount_data = $user_data['state_data']; $stmt = pdo()->prepare("INSERT INTO discount_codes (code, type, value, max_usage) VALUES (?, ?, ?, ?)"); - $stmt->execute([$discount_data['new_discount_code'], $discount_data['new_discount_type'], $discount_data['new_discount_value'], (int)$text]); + $stmt->execute([$discount_data['new_discount_code'], $discount_data['new_discount_type'], $discount_data['new_discount_value'], (int) $text]); sendMessage($chat_id, "✅ کد تخفیف `{$discount_data['new_discount_code']}` ؚا موفقیت ایجاد ؎د."); updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); $current_first_name = $update['message']['from']['first_name']; handleMainMenu($chat_id, $current_first_name); break; - case 'user_awaiting_discount_code': + case 'user_awaiting_discount_code': $code = strtoupper(trim($text)); $category_id = $user_data['state_data']['target_category_id']; $server_id = $user_data['state_data']['target_server_id']; - + $stmt = pdo()->prepare("SELECT * FROM discount_codes WHERE code = ? AND status = 'active' AND usage_count < max_usage"); $stmt->execute([$code]); $discount = $stmt->fetch(); if (!$discount) { sendMessage($chat_id, "❌ کد تخفیف وارد ؎ده نامعتؚر یا منقضی ؎ده است."); - - showPlansForCategoryAndServer($chat_id, $category_id, $server_id); + + showPlansForCategoryAndServer($chat_id, $category_id, $server_id); updateUserData($chat_id, 'main_menu'); break; } - + $plan_stmt = pdo()->prepare("SELECT * FROM plans WHERE category_id = ? AND server_id = ? AND status = 'active' AND is_test_plan = 0"); $plan_stmt->execute([$category_id, $server_id]); $active_plans = $plan_stmt->fetchAll(PDO::FETCH_ASSOC); @@ -2685,15 +2594,14 @@ $discounted_price = 0; if ($discount['type'] == 'percent') { $discounted_price = $original_price - ($original_price * $discount['value']) / 100; - } - else { + } else { $discounted_price = $original_price - $discount['value']; } $discounted_price = max(0, $discounted_price); $button_text = "{$plan['name']} | " . number_format($original_price) . " ⬅ " . number_format($discounted_price) . " تومان"; $keyboard_buttons[] = [['text' => $button_text, 'callback_data' => "buy_plan_{$plan['id']}_with_code_{$code}"]]; } - + $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت', 'callback_data' => "show_plans_cat_{$category_id}_srv_{$server_id}"]]; sendMessage($chat_id, $message, ['inline_keyboard' => $keyboard_buttons]); updateUserData($chat_id, 'main_menu'); @@ -2707,7 +2615,7 @@ sendMessage($chat_id, "❌ لطفا یک مؚلغ معتؚر (عدد مثؚت) ØšÙ‡ تومان وارد کنید.", $cancelKeyboard); break; } - $amount_to_add = (int)$text; + $amount_to_add = (int) $text; sendMessage($chat_id, "⏳ عملیات افزای؎ موجودی همگانی ؎روع ؎د..."); $updated_users_count = increaseAllUsersBalance($amount_to_add); sendMessage($chat_id, "✅ عملیات ؚا موفقیت انجام ؎د.\nمؚلغ " . number_format($amount_to_add) . " تومان ØšÙ‡ موجودی {$updated_users_count} کارؚر فعال اضافه گردید."); @@ -2732,8 +2640,7 @@ $state_data['new_guide_content_type'] = 'photo'; $state_data['new_guide_photo_id'] = $update['message']['photo'][count($update['message']['photo']) - 1]['file_id']; $state_data['new_guide_message_text'] = $update['message']['caption'] ?? ''; - } - else { + } else { $state_data['new_guide_content_type'] = 'text'; $state_data['new_guide_photo_id'] = null; $state_data['new_guide_message_text'] = $text; @@ -2754,7 +2661,7 @@ break; } $settings = getSettings(); - $settings['test_config_usage_limit'] = (int)$text; + $settings['test_config_usage_limit'] = (int) $text; saveSettings($settings); sendMessage($chat_id, "✅ تعداد مجاز ؚرای هر کارؚر روی {$text} ؚار تن؞یم ؎د."); updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); @@ -2799,7 +2706,7 @@ break; } $settings = getSettings(); - $settings['notification_expire_days'] = (int)$text; + $settings['notification_expire_days'] = (int) $text; saveSettings($settings); sendMessage($chat_id, "✅ ؚا موفقیت روی {$text} روز تن؞یم ؎د."); updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); @@ -2815,7 +2722,7 @@ break; } $settings = getSettings(); - $settings['notification_expire_gb'] = (int)$text; + $settings['notification_expire_gb'] = (int) $text; saveSettings($settings); sendMessage($chat_id, "✅ ؚا موفقیت روی {$text} گیگاؚایت تن؞یم ؎د."); updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); @@ -2843,7 +2750,7 @@ break; } $settings = getSettings(); - $settings['notification_inactive_days'] = (int)$text; + $settings['notification_inactive_days'] = (int) $text; saveSettings($settings); sendMessage($chat_id, "✅ ؚا موفقیت روی {$text} روز تن؞یم ؎د."); updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); @@ -2861,22 +2768,22 @@ updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); $data = 'config_inactive_reminder'; break; - + case 'user_awaiting_renewal_days': if (!is_numeric($text) || $text < 0) { sendMessage($chat_id, "❌ لطفا فقط یک عدد صحیح (مثؚت یا صفر) وارد کنید."); break; } $state_data = $user_data['state_data']; - $state_data['renewal_days'] = (int)$text; + $state_data['renewal_days'] = (int) $text; updateUserData($chat_id, 'user_awaiting_renewal_gb', $state_data); $settings = getSettings(); $price_gb = number_format($settings['renewal_price_per_gb'] ?? 2000); $message = "تمدید سرویس\n\n" . - "Û². چند **گیگاؚایت** ØšÙ‡ حجم سرویس ؎ما اضافه ؎ود؟\n\n" . - "▫ هزینه هر گیگ: {$price_gb} تومان\n" . - "💡 ؚرای رد ؎دن و عدم تمدید حجم، عدد `0` را وارد کنید."; + "Û². چند **گیگاؚایت** ØšÙ‡ حجم سرویس ؎ما اضافه ؎ود؟\n\n" . + "▫ هزینه هر گیگ: {$price_gb} تومان\n" . + "💡 ؚرای رد ؎دن و عدم تمدید حجم، عدد `0` را وارد کنید."; sendMessage($chat_id, $message); break; @@ -2887,18 +2794,18 @@ } $state_data = $user_data['state_data']; $days_to_add = $state_data['renewal_days']; - $gb_to_add = (int)$text; - + $gb_to_add = (int) $text; + if ($days_to_add == 0 && $gb_to_add == 0) { sendMessage($chat_id, "؎ما هیچ مقداری ؚرای تمدید وارد نکردید. عملیات لغو ؎د."); updateUserData($chat_id, 'main_menu'); handleMainMenu($chat_id, $first_name); break; } - + $settings = getSettings(); - $cost_days = $days_to_add * (int)($settings['renewal_price_per_day'] ?? 1000); - $cost_gb = $gb_to_add * (int)($settings['renewal_price_per_gb'] ?? 2000); + $cost_days = $days_to_add * (int) ($settings['renewal_price_per_day'] ?? 1000); + $cost_gb = $gb_to_add * (int) ($settings['renewal_price_per_gb'] ?? 2000); $total_cost = $cost_days + $cost_gb; $state_data['renewal_gb'] = $gb_to_add; @@ -2906,44 +2813,48 @@ updateUserData($chat_id, 'user_confirming_renewal', $state_data); $summary = "خلاصه درخواست تمدید ؎ما:\n\n" . - "▫ افزای؎ زمان: {$days_to_add} روز\n" . - "▫ افزای؎ حجم: {$gb_to_add} گیگاؚایت\n\n" . - "💰 هزینه کل: " . number_format($total_cost) . " تومان\n\n" . - "موجودی فعلی ؎ما: " . number_format($user_data['balance']) . " تومان\n\n" . - "آیا تایید می‌کنید؟"; + "▫ افزای؎ زمان: {$days_to_add} روز\n" . + "▫ افزای؎ حجم: {$gb_to_add} گیگاؚایت\n\n" . + "💰 هزینه کل: " . number_format($total_cost) . " تومان\n\n" . + "موجودی فعلی ؎ما: " . number_format($user_data['balance']) . " تومان\n\n" . + "آیا تایید می‌کنید؟"; $keyboard = ['inline_keyboard' => [[['text' => '✅ ؚله، ٟرداخت کن', 'callback_data' => 'confirm_renewal_payment']]]]; sendMessage($chat_id, $summary, $keyboard); break; - + case 'awaiting_renewal_screenshot': if (isset($update['message']['photo'])) { $state_data = $user_data['state_data']; $photo_id = $update['message']['photo'][count($update['message']['photo']) - 1]['file_id']; - + $stmt = pdo()->prepare("UPDATE renewal_requests SET photo_file_id = ? WHERE id = ?"); $stmt->execute([$photo_id, $state_data['renewal_request_id']]); - + $request_id = $state_data['renewal_request_id']; $caption = "درخواست تمدید سرویس جدید\n\n" . - "👀 کارؚر: " . htmlspecialchars($first_name) . " ({$chat_id})\n" . - "▫ سرویس: {$state_data['renewal_username']}\n" . - "⏰ تمدید زمان: {$state_data['renewal_days']} روز\n" . - "📊 تمدید حجم: {$state_data['renewal_gb']} گیگ\n" . - "💰 هزینه: " . number_format($state_data['renewal_total_cost']) . " تومان\n" . - "▫ ؎ماره درخواست: #R-{$request_id}"; - - $keyboard = ['inline_keyboard' => [[ - ['text' => '✅ تایید تمدید', 'callback_data' => "approve_renewal_{$request_id}"], - ['text' => '❌ رد تمدید', 'callback_data' => "reject_renewal_{$request_id}"] - ]]]; + "👀 کارؚر: " . htmlspecialchars($first_name) . " ({$chat_id})\n" . + "▫ سرویس: {$state_data['renewal_username']}\n" . + "⏰ تمدید زمان: {$state_data['renewal_days']} روز\n" . + "📊 تمدید حجم: {$state_data['renewal_gb']} گیگ\n" . + "💰 هزینه: " . number_format($state_data['renewal_total_cost']) . " تومان\n" . + "▫ ؎ماره درخواست: #R-{$request_id}"; + + $keyboard = [ + 'inline_keyboard' => [ + [ + ['text' => '✅ تایید تمدید', 'callback_data' => "approve_renewal_{$request_id}"], + ['text' => '❌ رد تمدید', 'callback_data' => "reject_renewal_{$request_id}"] + ] + ] + ]; $all_admins = getAdmins(); $all_admins[ADMIN_CHAT_ID] = []; foreach (array_keys($all_admins) as $admin_id) { if (hasPermission($admin_id, 'manage_payment')) { - sendPhoto($admin_id, $photo_id, $caption, $keyboard); + sendPhoto($admin_id, $photo_id, $caption, $keyboard); } } @@ -2965,8 +2876,7 @@ $categories = getCategories(true); if (empty($categories)) { sendMessage($chat_id, "متاسفانه در حال حاضر هیچ سرویسی ؚرای فرو؎ موجود نیست."); - } - else { + } else { $keyboard_buttons = []; foreach ($categories as $category) { $keyboard_buttons[] = [['text' => '🛍 ' . $category['name'], 'callback_data' => 'cat_' . $category['id']]]; @@ -3120,27 +3030,8 @@ sendMessage($chat_id, "لطفاً ؎ناسه عددی (Chat ID) کارؚر مورد ن؞ر را ؚرای جستجو وارد کنید:", $cancelKeyboard); } break; - - case '💰 افزای؎ موجودی همگانی': - if ($isAnAdmin && hasPermission($chat_id, 'manage_users')) { - updateUserData($chat_id, 'admin_awaiting_bulk_balance_amount', ['admin_view' => 'admin']); - sendMessage($chat_id, "لطفا مؚلغی که می‌خواهید ØšÙ‡ موجودی تمام کارؚران فعال اضافه ؎ود را ØšÙ‡ تومان وارد کنید:", $cancelKeyboard); - } - break; - case '➕ افزودن حجم همگانی': - if ($isAnAdmin && hasPermission($chat_id, 'manage_users')) { - updateUserData($chat_id, 'admin_awaiting_bulk_data_amount', ['admin_view' => 'admin']); - sendMessage($chat_id, "لطفا مقدار حجمی که می‌خواهید ØšÙ‡ تمام سرویس‌ها اضافه ؎ود را ØšÙ‡ گیگاؚایت (GB) وارد کنید:", $cancelKeyboard); - } - break; - case '➕ افزودن زمان همگانی': - if ($isAnAdmin && hasPermission($chat_id, 'manage_users')) { - updateUserData($chat_id, 'admin_awaiting_bulk_time_amount', ['admin_view' => 'admin']); - sendMessage($chat_id, "لطفا تعداد روزی که می‌خواهید ØšÙ‡ تمام سرویس‌ها اضافه ؎ود را وارد کنید:", $cancelKeyboard); - } - break; case '📣 ارسال همگانی': if ($isAnAdmin && hasPermission($chat_id, 'broadcast')) { @@ -3257,16 +3148,16 @@ sendMessage($chat_id, "مرحله Û±/Û³: ؎ماره کارت Û±Û¶ رقمی را وارد کنید:", $cancelKeyboard); } break; - + case '💳 مدیریت درگاه ٟرداخت': if ($isAnAdmin) { $settings = getSettings(); $status_icon = ($settings['payment_gateway_status'] ?? 'off') == 'on' ? '✅' : '❌'; $merchant_id = $settings['zarinpal_merchant_id'] ?? 'تن؞یم ن؎ده'; - + $message = "💳 مدیریت درگاه ٟرداخت زرین‌ٟال\n\n" . - "▫ وضعیت کلی: " . ($status_icon == '✅' ? 'فعال' : 'غیرفعال') . "\n" . - "▫ مرچنت کد: {$merchant_id}"; + "▫ وضعیت کلی: " . ($status_icon == '✅' ? 'فعال' : 'غیرفعال') . "\n" . + "▫ مرچنت کد: {$merchant_id}"; $keyboard = [ 'inline_keyboard' => [ @@ -3278,7 +3169,7 @@ sendMessage($chat_id, $message, $keyboard); } break; - + case '📊 آمار کلی': if ($isAnAdmin && hasPermission($chat_id, 'view_stats')) { $total_users = pdo() @@ -3340,12 +3231,12 @@ sendMessage($chat_id, "🎁 ؚخ؎ مدیریت کدهای تخفیف:", $keyboard); } break; - + case '🔄 مدیریت تمدید': - if ($isAnAdmin) { + if ($isAnAdmin) { showRenewalManagementMenu($chat_id); } - break; + break; case '➕ افزودن کد تخفیف': if ($isAnAdmin) { @@ -3384,8 +3275,7 @@ foreach ($services as $service) { if ($service['expire_timestamp'] < $now) { $expired_services_count++; - } - else { + } else { $active_services_count++; } } @@ -3409,8 +3299,7 @@ $services = getUserServices($chat_id); if (empty($services)) { sendMessage($chat_id, "؎ما هیچ سرویس فعالی ندارید."); - } - else { + } else { $keyboard_buttons = []; $now = time(); foreach ($services as $service) { @@ -3440,7 +3329,7 @@ } $settings = getSettings(); - $usage_limit = (int)($settings['test_config_usage_limit'] ?? 1); + $usage_limit = (int) ($settings['test_config_usage_limit'] ?? 1); if ($user_data['test_config_count'] >= $usage_limit) { sendMessage($chat_id, "❌ ؎ما قؚلا از حداکثر تعداد کانفیگ تست خود استفاده کرده‌اید."); @@ -3498,6 +3387,17 @@ } break; + case '📱 ٟنل کارؚری': + $web_app_url = BASE_URL . '/web/user/index.php'; + $message = "ؚرای ورود ØšÙ‡ ٟنل کارؚری روی دکمه زیر کلیک کنید:"; + $keyboard = [ + 'inline_keyboard' => [ + [['text' => '📱 ورود ØšÙ‡ ٟنل کارؚری', 'web_app' => ['url' => $web_app_url]]] + ] + ]; + sendMessage($chat_id, $message, $keyboard); + break; + default: if ($user_state === 'main_menu' && !$apiRequest) { sendMessage($chat_id, "دستور ؎ما را متوجه ن؎دم. لطفا از دکمه‌های موجود استفاده کنید."); diff --git a/src/includes/config.php b/src/includes/config.php index 759d3c0..78d42d0 100644 --- a/src/includes/config.php +++ b/src/includes/config.php @@ -10,6 +10,9 @@ define('SECRET_TOKEN', 'SECRET'); +// آدرس دامنه ؚرای وؚ‌اٟ (ؚدون اسل؎ آخر) +define('BASE_URL', 'https://your-domain.com'); + // --------------------------------------------------------------------- // --- مسیرهای ٟروژه --- // --------------------------------------------------------------------- @@ -21,9 +24,9 @@ // --- تن؞یمات اتصال ØšÙ‡ ٟایگاه داده --- // --------------------------------------------------------------------- -define('DB_HOST', 'localhost'); -define('DB_NAME', 'NAME'); -define('DB_USER', 'USER'); +define('DB_HOST', 'localhost'); +define('DB_NAME', 'NAME'); +define('DB_USER', 'USER'); define('DB_PASS', 'PASSWORD'); // --------------------------------------------------------------------- diff --git a/src/includes/functions.php b/src/includes/functions.php index 44e1fc0..736c792 100644 --- a/src/includes/functions.php +++ b/src/includes/functions.php @@ -261,6 +261,119 @@ function resetAllUsersTestCount() return $stmt->rowCount(); } +/** + * Add volume (GB) to all active services - Updates both database AND panel servers + * Returns array with success and fail counts + */ +function addVolumeToAllServices($volume_gb) +{ + $bytes_to_add = $volume_gb * 1024 * 1024 * 1024; + + $all_services = pdo() + ->query("SELECT marzban_username, server_id FROM services") + ->fetchAll(PDO::FETCH_ASSOC); + + $success_count = 0; + $fail_count = 0; + + foreach ($all_services as $service) { + $username = $service['marzban_username']; + $server_id = $service['server_id']; + + if (!$server_id) { + $fail_count++; + continue; + } + + // Get current user data from panel + $current_user_data = getPanelUser($username, $server_id); + + if ($current_user_data && !isset($current_user_data['detail'])) { + $current_limit = $current_user_data['data_limit']; + + if ($current_limit > 0) { + $new_limit = $current_limit + $bytes_to_add; + + // Update on panel server via API + $result = modifyPanelUser($username, $server_id, ['data_limit' => $new_limit]); + + if ($result && !isset($result['detail'])) { + $success_count++; + } else { + $fail_count++; + } + } else { + // User has unlimited data + $fail_count++; + } + } else { + $fail_count++; + } + + // Small delay to avoid overwhelming the API + usleep(100000); // 0.1 second + } + + return ['success' => $success_count, 'fail' => $fail_count]; +} + +/** + * Add time (days) to all active services - Updates both database AND panel servers + * Returns array with success and fail counts + */ +function addTimeToAllServices($days) +{ + $seconds_to_add = $days * 86400; // 86400 seconds in a day + + $all_services = pdo() + ->query("SELECT marzban_username, server_id FROM services") + ->fetchAll(PDO::FETCH_ASSOC); + + $success_count = 0; + $fail_count = 0; + + foreach ($all_services as $service) { + $username = $service['marzban_username']; + $server_id = $service['server_id']; + + if (!$server_id) { + $fail_count++; + continue; + } + + // Get current user data from panel + $current_user_data = getPanelUser($username, $server_id); + + if ($current_user_data && !isset($current_user_data['detail'])) { + $current_expire = $current_user_data['expire'] ?? 0; + + if ($current_expire > 0) { + // If already expired, start from now. Otherwise add to current expiry + $new_expire = $current_expire < time() ? time() + $seconds_to_add : $current_expire + $seconds_to_add; + + // Update on panel server via API + $result = modifyPanelUser($username, $server_id, ['expire' => $new_expire]); + + if ($result && !isset($result['detail'])) { + $success_count++; + } else { + $fail_count++; + } + } else { + // User has unlimited time + $fail_count++; + } + } else { + $fail_count++; + } + + // Small delay to avoid overwhelming the API + usleep(100000); // 0.1 second + } + + return ['success' => $success_count, 'fail' => $fail_count]; +} + // --- مدیریت ادمین‌ها --- function getAdmins() { @@ -788,6 +901,9 @@ function handleMainMenu($chat_id, $first_name, $is_start_command = false) $keyboard_buttons[] = [['text' => '📚 راهنما']]; } + // Add User Panel Button + $keyboard_buttons[] = [['text' => '📱 ٟنل کارؚری']]; + if ($isAnAdmin) { if ($admin_view_mode === 'admin') { if ($is_start_command) { @@ -1379,3 +1495,16 @@ function completePurchase($user_id, $plan_id, $custom_name, $final_price, $disco 'error_message' => "❌ متاسفانه در ایجاد سرویس ؎ما Ù…ØŽÚ©Ù„ÛŒ ٟی؎ آمد. لطفا ؚا ٟ؎تیؚانی تماس ؚگیرید. مؚلغی از حساؚ ؎ما کسر ن؎ده است." ]; } + +function getServers() +{ + $stmt = pdo()->query("SELECT * FROM servers ORDER BY id DESC"); + return $stmt->fetchAll(PDO::FETCH_ASSOC); +} + +function getServerById($id) +{ + $stmt = pdo()->prepare("SELECT * FROM servers WHERE id = ?"); + $stmt->execute([$id]); + return $stmt->fetch(PDO::FETCH_ASSOC); +} diff --git a/src/web/assets/css/style.css b/src/web/assets/css/style.css index a7d699c..7c95c49 100644 --- a/src/web/assets/css/style.css +++ b/src/web/assets/css/style.css @@ -46,6 +46,9 @@ body { flex: 1; margin-right: var(--sidebar-width); transition: margin-right 0.3s; + width: 100%; + min-width: 0; + /* Important for flex child */ } /* Sidebar */ @@ -61,6 +64,7 @@ body { flex-direction: column; z-index: 1000; transition: transform 0.3s; + overflow-y: auto; } .sidebar-header { @@ -151,6 +155,7 @@ body { .topbar h1 { font-size: 1.5rem; font-weight: 600; + flex: 1; } .menu-toggle { @@ -161,6 +166,12 @@ body { font-size: 1.5rem; cursor: pointer; margin-left: 15px; + padding: 8px; +} + +.menu-toggle:hover { + background-color: rgba(139, 92, 246, 0.1); + border-radius: 8px; } /* Content Area */ @@ -200,17 +211,42 @@ body { align-items: center; justify-content: center; font-size: 1.5rem; + flex-shrink: 0; } -.stat-icon.blue { background: rgba(59, 130, 246, 0.2); color: var(--blue); } -.stat-icon.green { background: rgba(16, 185, 129, 0.2); color: var(--green); } -.stat-icon.purple { background: rgba(139, 92, 246, 0.2); color: var(--purple); } -.stat-icon.orange { background: rgba(249, 115, 22, 0.2); color: var(--orange); } -.stat-icon.teal { background: rgba(20, 184, 166, 0.2); color: var(--teal); } -.stat-icon.red { background: rgba(239, 68, 68, 0.2); color: var(--red); } +.stat-icon.blue { + background: rgba(59, 130, 246, 0.2); + color: var(--blue); +} + +.stat-icon.green { + background: rgba(16, 185, 129, 0.2); + color: var(--green); +} + +.stat-icon.purple { + background: rgba(139, 92, 246, 0.2); + color: var(--purple); +} + +.stat-icon.orange { + background: rgba(249, 115, 22, 0.2); + color: var(--orange); +} + +.stat-icon.teal { + background: rgba(20, 184, 166, 0.2); + color: var(--teal); +} + +.stat-icon.red { + background: rgba(239, 68, 68, 0.2); + color: var(--red); +} .stat-info { flex: 1; + min-width: 0; } .stat-value { @@ -247,6 +283,7 @@ body { .card-header h3 { font-size: 1.25rem; font-weight: 600; + word-break: break-word; } .card-header h3 i { @@ -261,6 +298,7 @@ body { .data-table { width: 100%; border-collapse: collapse; + min-width: 600px; } .data-table thead { @@ -272,6 +310,7 @@ body { text-align: right; font-weight: 600; color: var(--text-light); + white-space: nowrap; } .data-table tbody tr { @@ -406,25 +445,248 @@ textarea:focus { margin-top: 20px; } -/* Responsive */ +/* Mobile Overlay */ +.sidebar-overlay { + display: none; + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(0, 0, 0, 0.5); + z-index: 999; + opacity: 0; + transition: opacity 0.3s; +} + +.sidebar-overlay.active { + opacity: 1; +} + +/* ==================== Responsive Design ==================== */ + +/* Tablets (768px - 1024px) */ +@media (max-width: 1024px) { + .content-area { + padding: 20px; + } + + .topbar { + padding: 15px 20px; + } + + .stats-grid { + grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); + gap: 15px; + } + + .stat-value { + font-size: 1.5rem; + } +} + +/* Mobile Devices (up to 768px) */ @media (max-width: 768px) { + :root { + --sidebar-width: 280px; + } + + /* Hide sidebar by default on mobile */ .sidebar { transform: translateX(100%); + box-shadow: -5px 0 15px rgba(0, 0, 0, 0.3); } - + .sidebar.active { transform: translateX(0); } - + + /* Show overlay when sidebar is active */ + .sidebar-overlay { + display: block; + } + + /* Remove sidebar margin from main content */ .main-content { margin-right: 0; } - + + /* Show menu toggle button */ .menu-toggle { display: block; } - + + /* Adjust topbar */ + .topbar { + padding: 12px 15px; + } + + .topbar h1 { + font-size: 1.2rem; + } + + /* Content area padding */ + .content-area { + padding: 15px; + } + + /* Stats grid - single column */ .stats-grid { grid-template-columns: 1fr; + gap: 12px; + } + + .stat-card { + padding: 16px; + gap: 12px; + } + + .stat-icon { + width: 50px; + height: 50px; + font-size: 1.2rem; + } + + .stat-value { + font-size: 1.5rem; + } + + .stat-label { + font-size: 0.85rem; + } + + /* Cards */ + .card-header { + padding: 15px; + } + + .card-header h3 { + font-size: 1.1rem; + } + + .card-body { + padding: 15px; + } + + /* Tables - enable horizontal scroll */ + .card-body { + overflow-x: auto; + } + + .data-table { + font-size: 0.85rem; + } + + .data-table th, + .data-table td { + padding: 8px; + white-space: nowrap; + } + + /* Forms */ + input[type="text"], + input[type="password"], + input[type="number"], + input[type="email"], + select, + textarea { + font-size: 16px; + /* Prevents zoom on iOS */ + } + + /* Buttons */ + .btn { + padding: 10px 16px; + font-size: 0.9rem; + width: 100%; + margin-bottom: 8px; + } + + .btn:last-child { + margin-bottom: 0; + } + + /* Sidebar adjustments */ + .sidebar-header { + padding: 20px 15px; + } + + .sidebar-header h2 { + font-size: 1.3rem; + } + + .sidebar-nav a { + padding: 10px 15px; + font-size: 0.95rem; + } +} + +/* Small Mobile Devices (up to 480px) */ +@media (max-width: 480px) { + .topbar h1 { + font-size: 1rem; + } + + .content-area { + padding: 10px; + } + + .stat-card { + flex-direction: column; + text-align: center; + } + + .stat-value { + font-size: 1.8rem; + } + + .card-header h3 { + font-size: 1rem; + } + + .data-table { + font-size: 0.75rem; + } + + .data-table th, + .data-table td { + padding: 6px 4px; + } + + /* Make buttons stack vertically */ + .btn { + display: block; + width: 100%; + } + + /* Adjust sidebar width for very small screens */ + :root { + --sidebar-width: 90%; + } +} + +/* Landscape orientation on mobile */ +@media (max-width: 768px) and (orientation: landscape) { + .sidebar { + width: 60%; + } + + .content-area { + padding: 15px; } } + +/* Print styles */ +@media print { + + .sidebar, + .topbar, + .menu-toggle, + .btn { + display: none; + } + + .main-content { + margin: 0; + } +} \ No newline at end of file diff --git a/src/web/assets/js/main.js b/src/web/assets/js/main.js index b77d979..4c96a54 100644 --- a/src/web/assets/js/main.js +++ b/src/web/assets/js/main.js @@ -1,27 +1,76 @@ // Main JavaScript for Web Panel -document.addEventListener('DOMContentLoaded', function() { +document.addEventListener('DOMContentLoaded', function () { // Menu Toggle for Mobile const menuToggle = document.getElementById('menuToggle'); const sidebar = document.getElementById('sidebar'); - + + // Create overlay for mobile + let overlay = document.querySelector('.sidebar-overlay'); + if (!overlay) { + overlay = document.createElement('div'); + overlay.className = 'sidebar-overlay'; + document.body.appendChild(overlay); + } + if (menuToggle && sidebar) { - menuToggle.addEventListener('click', function() { + // Toggle sidebar on menu button click + menuToggle.addEventListener('click', function (e) { + e.stopPropagation(); sidebar.classList.toggle('active'); + overlay.classList.toggle('active'); + + // Prevent body scroll when sidebar is open on mobile + if (sidebar.classList.contains('active')) { + document.body.style.overflow = 'hidden'; + } else { + document.body.style.overflow = ''; + } }); - + + // Close sidebar when clicking overlay + overlay.addEventListener('click', function () { + sidebar.classList.remove('active'); + overlay.classList.remove('active'); + document.body.style.overflow = ''; + }); + // Close sidebar when clicking outside on mobile - document.addEventListener('click', function(e) { + document.addEventListener('click', function (e) { if (window.innerWidth <= 768) { if (!sidebar.contains(e.target) && !menuToggle.contains(e.target)) { sidebar.classList.remove('active'); + overlay.classList.remove('active'); + document.body.style.overflow = ''; } } }); + + // Close sidebar when clicking a link on mobile + const sidebarLinks = sidebar.querySelectorAll('a'); + sidebarLinks.forEach(link => { + link.addEventListener('click', function () { + if (window.innerWidth <= 768) { + sidebar.classList.remove('active'); + overlay.classList.remove('active'); + document.body.style.overflow = ''; + } + }); + }); + + // Handle window resize + window.addEventListener('resize', function () { + if (window.innerWidth > 768) { + sidebar.classList.remove('active'); + overlay.classList.remove('active'); + document.body.style.overflow = ''; + } + }); } - + // Auto-hide alerts after 5 seconds const alerts = document.querySelectorAll('.alert'); alerts.forEach(alert => { + alert.style.transition = 'opacity 0.3s'; setTimeout(() => { alert.style.opacity = '0'; setTimeout(() => { @@ -29,6 +78,26 @@ document.addEventListener('DOMContentLoaded', function() { }, 300); }, 5000); }); + + // Add touch-friendly enhancements for mobile + if ('ontouchstart' in window) { + document.body.classList.add('touch-device'); + + // Make table rows tappable + const tableRows = document.querySelectorAll('.data-table tbody tr'); + tableRows.forEach(row => { + row.style.cursor = 'pointer'; + }); + } + + // Prevent double-tap zoom on buttons + const buttons = document.querySelectorAll('.btn'); + buttons.forEach(button => { + button.addEventListener('touchend', function (e) { + e.preventDefault(); + button.click(); + }, { passive: false }); + }); }); /** @@ -43,10 +112,12 @@ function showToast(message, type = 'info') { toast.style.transform = 'translateX(-50%)'; toast.style.zIndex = '9999'; toast.style.minWidth = '300px'; + toast.style.maxWidth = '90%'; + toast.style.transition = 'opacity 0.3s'; toast.textContent = message; - + document.body.appendChild(toast); - + setTimeout(() => { toast.style.opacity = '0'; setTimeout(() => { @@ -68,3 +139,20 @@ function confirmAction(message) { function formatNumber(num) { return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ","); } + +/** + * Detect if device is mobile + */ +function isMobile() { + return window.innerWidth <= 768; +} + +/** + * Smooth scroll to element + */ +function scrollToElement(elementId) { + const element = document.getElementById(elementId); + if (element) { + element.scrollIntoView({ behavior: 'smooth', block: 'start' }); + } +} diff --git a/src/web/pages/users.php b/src/web/pages/users.php index e7569f0..f8b7efd 100644 --- a/src/web/pages/users.php +++ b/src/web/pages/users.php @@ -1,6 +1,6 @@ prepare("SELECT * FROM users WHERE chat_id = ?"); - $stmt->execute([$chat_id]); + $stmt->execute([$_GET['chat_id']]); $user_info = $stmt->fetch(PDO::FETCH_ASSOC); } } } -// Handle ban/unban +// Handle bulk operations +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['bulk_add_volume'])) { + $volume_gb = (int) $_POST['volume_gb']; + if ($volume_gb > 0) { + require_once __DIR__ . '/../../includes/functions.php'; + $result = addVolumeToAllServices($volume_gb); + $success = "✅ حجم همگانی اضافه ؎د. موفق: {$result['success']} | ناموفق: {$result['fail']}"; + } +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['bulk_add_time'])) { + $days = (int) $_POST['days']; + if ($days > 0) { + require_once __DIR__ . '/../../includes/functions.php'; + $result = addTimeToAllServices($days); + $success = "✅ زمان همگانی اضافه ؎د. موفق: {$result['success']} | ناموفق: {$result['fail']}"; + } +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['bulk_add_balance'])) { + $amount = (int) $_POST['amount']; + if ($amount > 0) { + $stmt = pdo()->query("SELECT chat_id FROM users WHERE status = 'active'"); + $users = $stmt->fetchAll(PDO::FETCH_ASSOC); + + $count = 0; + foreach ($users as $user) { + updateUserBalance($user['chat_id'], $amount, 'add'); + $count++; + } + + $success = "✅ موجودی $count کارؚر فعال افزای؎ یافت."; + } +} + if (isset($_GET['ban'])) { $chat_id = (int) $_GET['ban']; setUserStatus($chat_id, 'banned'); @@ -108,11 +142,11 @@
-
✅
+
✅
-
❌
+
❌
@@ -138,7 +172,7 @@
-
+
@@ -148,6 +182,51 @@
+ +
+
+

عملیات همگانی

+
+
+
+ +
+ + + + حجم م؎خص ؎ده ØšÙ‡ تمام سرویس‌ها اضافه می‌؎ود +
+ + +
+ + + + روزهای م؎خص ؎ده ØšÙ‡ تاریخ انقضای تمام سرویس‌ها اضافه + می‌؎ود +
+ + +
+ + + + مؚلغ م؎خص ؎ده ØšÙ‡ موجودی تمام کارؚران فعال اضافه + می‌؎ود +
+
+
+
+
@@ -169,151 +248,153 @@ -
-
-

اطلاعات کارؚر

-
-
-

نام:

-

؎ناسه:

-

موجودی: تومان

-

وضعیت: - - ✅ فعال - - ❌ مسدود - -

-

تاریخ ثؚت‌نام:

- -
- - -
- -
- -
- -
- +
+
+

اطلاعات کارؚر

+
+
+

نام:

+

؎ناسه:

+

موجودی: تومان

+

وضعیت: - - مسدود کردن - + ✅ فعال - - آزاد کردن - + ❌ مسدود +

+

تاریخ ثؚت‌نام:

- +
-

سرویس‌های کارؚر ()

- -

کارؚر هیچ سرویسی ندارد.

- - - - - - - - - - - - - - - - - - - - -
ٟلننام سرویستاریخ خریدانقضا
- -
- -
+ +
+ +
+ +
+ +
+ + + + مسدود کردن + + + + آزاد کردن + + + + + +

سرویس‌های کارؚر ()

+ +

کارؚر هیچ سرویسی ندارد.

+ + + + + + + + + + + + + + + + + + + + +
ٟلننام سرویستاریخ خریدانقضا
+ +
+
+
-

لیست تمام کارؚران ( نفر)

+

لیست تمام کارؚران ( نفر) +

-

هیچ کارؚری یافت ن؎د.

+

هیچ کارؚری یافت ن؎د.

-
- - +
+
+ + + + + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - -
نامChat IDموجودیتعداد سرویسوضعیتتاریخ عضویتعملیات
نامChat IDموجودیتعداد سرویسوضعیتتاریخ عضویتعملیات
تومان - - ✅ فعال - - ❌ مسدود - - - - م؎اهده - -
-
- - - 1): ?> - - - - - - + + + تومان + + + + ✅ فعال - - - + ❌ مسدود - -
- + + + + + م؎اهده + + + + + + +
+ + + 1): ?> +
+ + + + + + + + + + + +
+
diff --git a/src/web/user/assets/css/style.css b/src/web/user/assets/css/style.css new file mode 100644 index 0000000..2c71ea3 --- /dev/null +++ b/src/web/user/assets/css/style.css @@ -0,0 +1,255 @@ +:root { + --primary-color: #3b82f6; + --primary-dark: #2563eb; + --bg-color: #f3f4f6; + --card-bg: #ffffff; + --text-color: #1f2937; + --text-muted: #6b7280; + --border-color: #e5e7eb; + --success-color: #10b981; + --danger-color: #ef4444; + --warning-color: #f59e0b; + --radius: 12px; + --shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06); +} + +[data-theme="dark"] { + --bg-color: #111827; + --card-bg: #1f2937; + --text-color: #f9fafb; + --text-muted: #9ca3af; + --border-color: #374151; +} + +* { + box-sizing: border-box; + margin: 0; + padding: 0; + -webkit-tap-highlight-color: transparent; +} + +body { + font-family: 'Vazirmatn', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif; + background-color: var(--bg-color); + color: var(--text-color); + direction: rtl; + line-height: 1.6; + padding-bottom: 80px; + /* Space for bottom nav */ +} + +.container { + max-width: 600px; + margin: 0 auto; + padding: 16px; +} + +/* Header */ +.header { + display: flex; + align-items: center; + justify-content: space-between; + margin-bottom: 24px; +} + +.user-profile { + display: flex; + align-items: center; + gap: 12px; +} + +.avatar { + width: 48px; + height: 48px; + border-radius: 50%; + background: var(--primary-color); + color: white; + display: flex; + align-items: center; + justify-content: center; + font-size: 1.2rem; + font-weight: bold; +} + +.user-info h2 { + font-size: 1.1rem; + margin-bottom: 2px; +} + +.user-info p { + font-size: 0.85rem; + color: var(--text-muted); +} + +/* Cards */ +.card { + background: var(--card-bg); + border-radius: var(--radius); + padding: 16px; + margin-bottom: 16px; + box-shadow: var(--shadow); +} + +.card-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 16px; + padding-bottom: 12px; + border-bottom: 1px solid var(--border-color); +} + +.card-title { + font-size: 1rem; + font-weight: 600; + display: flex; + align-items: center; + gap: 8px; +} + +/* Stats Grid */ +.stats-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 12px; + margin-bottom: 24px; +} + +.stat-item { + background: var(--card-bg); + padding: 16px; + border-radius: var(--radius); + text-align: center; + box-shadow: var(--shadow); +} + +.stat-value { + font-size: 1.25rem; + font-weight: bold; + color: var(--primary-color); + margin-bottom: 4px; +} + +.stat-label { + font-size: 0.85rem; + color: var(--text-muted); +} + +/* Buttons */ +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + padding: 10px 20px; + border-radius: var(--radius); + font-weight: 500; + cursor: pointer; + transition: all 0.2s; + border: none; + outline: none; + text-decoration: none; + width: 100%; + gap: 8px; +} + +.btn-primary { + background: var(--primary-color); + color: white; +} + +.btn-primary:active { + background: var(--primary-dark); + transform: scale(0.98); +} + +.btn-outline { + background: transparent; + border: 1px solid var(--border-color); + color: var(--text-color); +} + +/* Bottom Navigation */ +.bottom-nav { + position: fixed; + bottom: 0; + left: 0; + right: 0; + background: var(--card-bg); + display: flex; + justify-content: space-around; + padding: 12px; + box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.05); + z-index: 100; +} + +.nav-item { + display: flex; + flex-direction: column; + align-items: center; + text-decoration: none; + color: var(--text-muted); + font-size: 0.75rem; + gap: 4px; +} + +.nav-item i { + font-size: 1.25rem; +} + +.nav-item.active { + color: var(--primary-color); +} + +/* Loading */ +.loading-overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: var(--bg-color); + display: flex; + align-items: center; + justify-content: center; + z-index: 1000; +} + +.spinner { + width: 40px; + height: 40px; + border: 4px solid var(--border-color); + border-top-color: var(--primary-color); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + to { + transform: rotate(360deg); + } +} + +/* Utility */ +.text-success { + color: var(--success-color); +} + +.text-danger { + color: var(--danger-color); +} + +.text-warning { + color: var(--warning-color); +} + +.mb-2 { + margin-bottom: 8px; +} + +.mb-4 { + margin-bottom: 16px; +} + +.w-100 { + width: 100%; +} \ No newline at end of file diff --git a/src/web/user/assets/js/app.js b/src/web/user/assets/js/app.js new file mode 100644 index 0000000..c946869 --- /dev/null +++ b/src/web/user/assets/js/app.js @@ -0,0 +1,38 @@ +const tg = window.Telegram.WebApp; + +// Initialize Telegram Web App +function initApp() { + tg.expand(); + + // Set theme + document.documentElement.setAttribute('data-theme', tg.colorScheme); + + // Listen for theme changes + tg.onEvent('themeChanged', function () { + document.documentElement.setAttribute('data-theme', tg.colorScheme); + }); + + // Main button setup + tg.MainButton.setParams({ + text: 'ؚستن ٟنل', + is_visible: false + }); +} + +// Show loading +function showLoading() { + document.querySelector('.loading-overlay').style.display = 'flex'; +} + +// Hide loading +function hideLoading() { + document.querySelector('.loading-overlay').style.display = 'none'; +} + +// Format price +function formatPrice(price) { + return new Intl.NumberFormat('fa-IR').format(price) + ' تومان'; +} + +// Initialize +document.addEventListener('DOMContentLoaded', initApp); diff --git a/src/web/user/auth.php b/src/web/user/auth.php new file mode 100644 index 0000000..8d5e364 --- /dev/null +++ b/src/web/user/auth.php @@ -0,0 +1,52 @@ + $value) { + $data_check_string[] = $key . '=' . $value; + } + $data_check_string = implode("\n", $data_check_string); + + $secret_key = hash_hmac('sha256', BOT_TOKEN, "WebAppData", true); + $calculated_hash = bin2hex(hash_hmac('sha256', $data_check_string, $secret_key, true)); + + if (strcmp($hash, $calculated_hash) !== 0) { + return false; + } + + // Check auth_date for freshness (e.g., within 24 hours) + if (isset($data['auth_date'])) { + if (time() - $data['auth_date'] > 86400) { + return false; + } + } + + return json_decode($data['user'], true); +} diff --git a/src/web/user/debug.html b/src/web/user/debug.html new file mode 100644 index 0000000..00832f7 --- /dev/null +++ b/src/web/user/debug.html @@ -0,0 +1,150 @@ + + + + + + + Debug Panel + + + + + +
+

🔍 Telegram Web App Debug

+
+
+ +
+

📊 Test Results

+
+
+ + + + + \ No newline at end of file diff --git a/src/web/user/guides.php b/src/web/user/guides.php new file mode 100644 index 0000000..a3863de --- /dev/null +++ b/src/web/user/guides.php @@ -0,0 +1,85 @@ +query("SELECT * FROM guides WHERE status = 'active' ORDER BY id DESC"); +$guides = $stmt->fetchAll(PDO::FETCH_ASSOC); +?> + + + + + + + آموز؎‌ها + + + + + + + +
+
+ +
+ + +
+ +

هیچ آموز؎ی یافت ن؎د.

+
+ + +
+
+
+
+
+
+ + +
+ این آموز؎ ؎امل تصویر است که در رؚات قاؚل م؎اهده است. +
+ +
+ + + +
+ + + + + + + + \ No newline at end of file diff --git a/src/web/user/index.php b/src/web/user/index.php new file mode 100644 index 0000000..ec871ac --- /dev/null +++ b/src/web/user/index.php @@ -0,0 +1,215 @@ + true]); + exit; + } else { + http_response_code(401); + echo json_encode(['error' => 'Invalid data']); + exit; + } +} + +$user = getCurrentUser(); +?> + + + + + + ٟنل کارؚری + + + + + + + + +
+
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/src/web/user/services.php b/src/web/user/services.php new file mode 100644 index 0000000..97a7313 --- /dev/null +++ b/src/web/user/services.php @@ -0,0 +1,248 @@ + + + + + + + + سرویس‌های من + + + + + + + +
+
+ +
+ سرویس +
+
+ + +
+ +

هیچ سرویسی ندارید

+

ؚرای خرید سرویس ØšÙ‡ فرو؎گاه مراجعه کنید.

+ + خرید سرویس + +
+ + +
+
+
+ +
+ + فعال + +
+ +
+
+ + نام کارؚری: + + + + +
+
+ + تاریخ انقضا: + + + + +
+
+ + حجم مصرفی: + + + / + + +
+
+ + +
+ + +
+ +
+ لینک ا؎تراک موجود نیست +
+ +
+ + +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/src/web/user/session.php b/src/web/user/session.php new file mode 100644 index 0000000..fef9ecd --- /dev/null +++ b/src/web/user/session.php @@ -0,0 +1,36 @@ + 'Unauthorized']); + exit; + } + + // Otherwise, we might need to show a login page or error + // But for Web App, we usually handle auth via JS on the index page + // So we just exit or redirect to an error page + die('Access Denied. Please open from Telegram.'); + } +} + +// Get current user data +function getCurrentUser() +{ + if (isUserLoggedIn()) { + return getUserData($_SESSION['user_id'], $_SESSION['first_name'] ?? 'کارؚر'); + } + return null; +} diff --git a/src/web/user/shop.php b/src/web/user/shop.php new file mode 100644 index 0000000..d98d6ae --- /dev/null +++ b/src/web/user/shop.php @@ -0,0 +1,312 @@ + false, 'message' => 'نام سرویس نمی‌تواند خالی ؚا؎د']); + exit; + } + + $plan = getPlanById($plan_id); + if (!$plan || $plan['status'] !== 'active') { + echo json_encode(['success' => false, 'message' => 'ٟلن یافت ن؎د یا غیرفعال است']); + exit; + } + + if ($plan['purchase_limit'] > 0 && $plan['purchase_count'] >= $plan['purchase_limit']) { + echo json_encode(['success' => false, 'message' => '؞رفیت خرید این ٟلن تکمیل ؎ده است']); + exit; + } + + if ($user['balance'] < $plan['price']) { + echo json_encode(['success' => false, 'message' => 'موجودی کافی نیست']); + exit; + } + + // completePurchase uses $plan['server_id'] internally + $result = completePurchase($user['chat_id'], $plan_id, $custom_name, $plan['price'], null, null, false); + + if ($result['success']) { + echo json_encode(['success' => true, 'message' => 'خرید ؚا موفقیت انجام ؎د']); + } else { + echo json_encode(['success' => false, 'message' => $result['error_message'] ?? 'خطا در انجام خرید']); + } + exit; +} + +// Get parameters +$selected_cat_id = isset($_GET['cat_id']) ? (int) $_GET['cat_id'] : null; +$selected_server_id = isset($_GET['server_id']) ? (int) $_GET['server_id'] : null; +?> + + + + + + + فرو؎گاه + + + + + + + +
+
+ +
+ + تومان +
+
+ + + + + + + + +
+
+
سرور انتخاؚ ؎ده:
+
+ +
+ +
+ +
+ +
+
+ + + +
+ +

هیچ ٟلنی ؚرای این سرور موجود نیست.

+
+ + +
+
+
+
تومان +
+
+
+
حجم: گیگاؚایت
+
مدت: روز
+ +
+ +
+ +
+ + + + + + + + + + +
+ +

هیچ سروری در حال حاضر فعال نیست.

+
+ + + + + + + +
+ +

هیچ دسته‌ؚندی فعالی وجود ندارد.

+
+ +
+ + + + + + + + +
+ + +
+ + + + + + + + + + + + \ No newline at end of file diff --git a/src/web/user/simple-test.html b/src/web/user/simple-test.html new file mode 100644 index 0000000..ea9a9a5 --- /dev/null +++ b/src/web/user/simple-test.html @@ -0,0 +1,72 @@ + + + + + + + تست ساده + + + + +

🔍 تست اتصال

+
+ + + + + + \ No newline at end of file diff --git a/src/web/user/support.php b/src/web/user/support.php new file mode 100644 index 0000000..3cdd3cd --- /dev/null +++ b/src/web/user/support.php @@ -0,0 +1,167 @@ + + + + + + + + ٟ؎تیؚانی + + + + + + + +
+
+ +
+ + +
+
+
+ تماس ؚا ٟ؎تیؚانی +
+
+ +
+
+ +
+ +

+ ؚرای دریافت ٟ؎تیؚانی، ارسال رسید ٟرداخت یا گزار؎ Ù…ØŽÚ©Ù„ØŒ ؚا ما در تلگرام در ارتؚاط ؚا؎ید. +

+ + +
+
+ + +
+
+
+ اطلاعات ؎ما +
+
+ +
+
+ نام: +
+
+ ؎ناسه کارؚری: +
+ + +
+
+
+ موجودی: + تومان +
+

+ 💡 لطفاً ؎ناسه کارؚری خود را هنگام تماس ؚا ٟ؎تیؚانی ارسال کنید. +

+
+
+ + +
+
+
+ سوالات متداول +
+
+ +
+
+ چگونه سرویس خریداری کنم؟ +

+ از ؚخ؎ فرو؎گاه، دسته‌ؚندی و ٟلن مورد ن؞ر را انتخاؚ کنید و ٟس از ٟرداخت، سرویس ؎ما فعال می‌؎ود. +

+
+ +
+ چگونه موجودی کیف ٟول را ؎ارژ کنم؟ +

+ از ؚخ؎ کیف ٟول، مؚلغ مورد ن؞ر را وارد کرده و رو؎ ٟرداخت را انتخاؚ کنید. +

+
+ +
+ چگونه از سرویس خریداری ؎ده استفاده کنم؟ +

+ از ؚخ؎ "سرویس‌های من" می‌توانید لینک ا؎تراک و QR Code سرویس خود را م؎اهده کنید. +

+
+
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/src/web/user/test.php b/src/web/user/test.php new file mode 100644 index 0000000..f3e6e7c --- /dev/null +++ b/src/web/user/test.php @@ -0,0 +1,28 @@ +"; +require_once __DIR__ . '/../../includes/config.php'; +echo "✅ Config loaded. BOT_TOKEN exists: " . (defined('BOT_TOKEN') ? 'YES' : 'NO') . "
"; +echo "✅ BASE_URL: " . (defined('BASE_URL') ? BASE_URL : 'NOT DEFINED') . "

"; + +echo "2. Testing db.php...
"; +require_once __DIR__ . '/../../includes/db.php'; +echo "✅ Database connection loaded

"; + +echo "3. Testing functions.php...
"; +require_once __DIR__ . '/../../includes/functions.php'; +echo "✅ Functions loaded

"; + +echo "4. Testing auth.php...
"; +require_once __DIR__ . '/auth.php'; +echo "✅ Auth loaded

"; + +echo "5. Testing session.php...
"; +require_once __DIR__ . '/session.php'; +echo "✅ Session loaded

"; + +echo "All systems operational!
"; +echo "You can now delete this file (test.php)."; diff --git a/src/web/user/wallet.php b/src/web/user/wallet.php new file mode 100644 index 0000000..ccbc4eb --- /dev/null +++ b/src/web/user/wallet.php @@ -0,0 +1,292 @@ + false, 'message' => 'مؚلغ ؚاید حداقل Û±Û°Û°Û° تومان ؚا؎د']); + exit; + } + + if ($method === 'zarinpal') { + if (($settings['payment_gateway_status'] ?? 'off') !== 'on') { + echo json_encode(['success' => false, 'message' => 'درگاه ٟرداخت غیرفعال است']); + exit; + } + + $result = createZarinpalLink($user['chat_id'], $amount, "؎ارژ کیف ٟول کارؚر {$user['chat_id']}"); + if ($result['success']) { + echo json_encode(['success' => true, 'url' => $result['url']]); + } else { + echo json_encode(['success' => false, 'message' => $result['error']]); + } + exit; + } +} +?> + + + + + + + کیف ٟول + + + + + + + +
+
+ +
+ + +
+
موجودی فعلی
+
+ +
+
تومان
+
+ + +
+
+
+ افزای؎ موجودی +
+
+ +
+ + +
حداقل مؚلغ: Û±Û°Û°Û° تومان
+
+ +
+ + + + + + + +
+ درگاه کارت ØšÙ‡ کارت تن؞یم ن؎ده است +
+ +
+
+ + +
+
+
+ راهنمای ؎ارژ کیف ٟول +
+
+
+
+ مؚلغ مورد ن؞ر خود را وارد کنید +
+
+ رو؎ ٟرداخت را انتخاؚ کنید +
+
+ ٟس از ٟرداخت، موجودی ؎ما ؚروز می‌؎ود +
+
+
+ +
+ + + + + + + + + + + + \ No newline at end of file From 54b9b080b3833724804f9d407fef83e09c8b60e1 Mon Sep 17 00:00:00 2001 From: poryajp <118083331+poryajp@users.noreply.github.com> Date: Mon, 1 Dec 2025 00:48:44 +0330 Subject: [PATCH 06/18] 0.1.2 --- src/api/marzban_api.php | 54 ++- src/api/marzneshin_api.php | 113 +++--- src/api/sanaei_api.php | 134 ++++--- src/bot.php | 361 ++++++++++++------- src/includes/functions.php | 271 ++++++++++++-- src/install.php | 39 +- src/web/dashboard.php | 93 ++--- src/web/includes/web_functions.php | 25 +- src/web/pages/settings.php | 16 +- src/web/user/account.php | 189 ++++++++++ src/web/user/assets/js/app.js | 59 +-- src/web/user/guides.php | 75 ++-- src/web/user/index.php | 362 +++++++++++-------- src/web/user/renew.php | 311 ++++++++++++++++ src/web/user/services.php | 263 +++++++------- src/web/user/shop.php | 169 ++++++--- src/web/user/support.php | 257 +++++++++---- src/web/user/test-config.php | 201 +++++++++++ src/web/user/wallet.php | 561 +++++++++++++++++++---------- 19 files changed, 2576 insertions(+), 977 deletions(-) create mode 100644 src/web/user/account.php create mode 100644 src/web/user/renew.php create mode 100644 src/web/user/test-config.php diff --git a/src/api/marzban_api.php b/src/api/marzban_api.php index 101c579..20e85f4 100644 --- a/src/api/marzban_api.php +++ b/src/api/marzban_api.php @@ -1,6 +1,7 @@ prepare("SELECT * FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $server_info = $stmt->fetch(); @@ -52,7 +53,8 @@ function marzbanApiRequest($endpoint, $server_id, $method = 'GET', $data = [], $ return json_decode($response, true); } -function getMarzbanToken($server_id) { +function getMarzbanToken($server_id) +{ $stmt = pdo()->prepare("SELECT * FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $server_info = $stmt->fetch(); @@ -120,7 +122,8 @@ function getMarzbanToken($server_id) { return false; } -function createMarzbanUser($plan, $chat_id, $plan_id) { +function createMarzbanUser($plan, $chat_id, $plan_id) +{ $server_id = $plan['server_id']; $accessToken = getMarzbanToken($server_id); if (!$accessToken) { @@ -130,9 +133,9 @@ function createMarzbanUser($plan, $chat_id, $plan_id) { $stmt_server_protocols = pdo()->prepare("SELECT marzban_protocols FROM servers WHERE id = ?"); $stmt_server_protocols->execute([$server_id]); $protocols_json = $stmt_server_protocols->fetchColumn(); - - $proxies = new stdClass(); - + + $proxies = new stdClass(); + if ($protocols_json) { $protocol_list = json_decode($protocols_json, true); if (is_array($protocol_list) && !empty($protocol_list)) { @@ -141,16 +144,16 @@ function createMarzbanUser($plan, $chat_id, $plan_id) { } } } - - if (empty((array)$proxies)) { - $proxies->vless = new stdClass(); + + if (empty((array) $proxies)) { + $proxies->vless = new stdClass(); } - + $username = $plan['full_username']; $userData = [ 'username' => $username, - 'proxies' => $proxies, + 'proxies' => $proxies, 'inbounds' => new stdClass(), 'expire' => time() + $plan['duration_days'] * 86400, 'data_limit' => $plan['volume_gb'] * 1024 * 1024 * 1024, @@ -164,17 +167,17 @@ function createMarzbanUser($plan, $chat_id, $plan_id) { ->prepare("UPDATE services SET warning_sent = 0 WHERE marzban_username = ? AND server_id = ?") ->execute([$response['username'], $server_id]); - + $stmt_server = pdo()->prepare("SELECT url, sub_host FROM servers WHERE id = ?"); $stmt_server->execute([$server_id]); $server_info = $stmt_server->fetch(); - + $base_sub_url = !empty($server_info['sub_host']) ? rtrim($server_info['sub_host'], '/') : rtrim($server_info['url'], '/'); $sub_path = parse_url($response['subscription_url'], PHP_URL_PATH); $final_sub_url = $base_sub_url . $sub_path; - - - $response['subscription_url'] = $final_sub_url; + + + $response['subscription_url'] = $final_sub_url; return $response; } @@ -182,7 +185,8 @@ function createMarzbanUser($plan, $chat_id, $plan_id) { return false; } -function getMarzbanUser($username, $server_id) { +function getMarzbanUser($username, $server_id) +{ $accessToken = getMarzbanToken($server_id); if (!$accessToken) { return false; @@ -191,7 +195,8 @@ function getMarzbanUser($username, $server_id) { return marzbanApiRequest("/api/user/{$username}", $server_id, 'GET', [], $accessToken); } -function modifyMarzbanUser($username, $server_id, $data) { +function modifyMarzbanUser($username, $server_id, $data) +{ $accessToken = getMarzbanToken($server_id); if (!$accessToken) { return false; @@ -200,11 +205,22 @@ function modifyMarzbanUser($username, $server_id, $data) { return marzbanApiRequest("/api/user/{$username}", $server_id, 'PUT', $data, $accessToken); } -function deleteMarzbanUser($username, $server_id) { +function deleteMarzbanUser($username, $server_id) +{ $accessToken = getMarzbanToken($server_id); if (!$accessToken) { return false; } return marzbanApiRequest("/api/user/{$username}", $server_id, 'DELETE', [], $accessToken); +} + +function resetMarzbanUserUsage($username, $server_id) +{ + $accessToken = getMarzbanToken($server_id); + if (!$accessToken) { + return false; + } + + return marzbanApiRequest("/api/user/{$username}/reset", $server_id, 'POST', [], $accessToken); } \ No newline at end of file diff --git a/src/api/marzneshin_api.php b/src/api/marzneshin_api.php index a281ffb..d565e87 100644 --- a/src/api/marzneshin_api.php +++ b/src/api/marzneshin_api.php @@ -1,26 +1,32 @@ prepare("SELECT url FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $server_url = $stmt->fetchColumn(); - if (!$server_url) return ['error' => 'Server not configured.']; + if (!$server_url) + return ['error' => 'Server not configured.']; $accessTokenResult = getMarzneshinToken($server_id); if (is_array($accessTokenResult) && isset($accessTokenResult['error'])) { return ['error' => 'Token Error: ' . $accessTokenResult['error']]; } $accessToken = $accessTokenResult; - + $url = rtrim($server_url, '/') . $endpoint; $headers = ['Content-Type: application/json', 'Accept: application/json', 'Authorization: Bearer ' . $accessToken]; $ch = curl_init(); curl_setopt_array($ch, [ - CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, - CURLOPT_TIMEOUT => 15, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_TIMEOUT => 15, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, ]); switch (strtoupper($method)) { @@ -41,51 +47,63 @@ function marzneshinApiRequest($endpoint, $server_id, $method = 'GET', $data = [] return json_decode($response_body, true); } -function marzneshinPublicApiRequest($endpoint, $server_id) { +function marzneshinPublicApiRequest($endpoint, $server_id) +{ $stmt = pdo()->prepare("SELECT url, sub_host FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $server_info = $stmt->fetch(); - if (!$server_info) return false; - + if (!$server_info) + return false; + $base_url = !empty($server_info['sub_host']) ? rtrim($server_info['sub_host'], '/') : rtrim($server_info['url'], '/'); $url = $base_url . $endpoint; $ch = curl_init(); curl_setopt_array($ch, [ - CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 10, - CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => 10, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, ]); $response = curl_exec($ch); curl_close($ch); return $response; } -function getMarzneshinToken($server_id) { +function getMarzneshinToken($server_id) +{ $cache_key = 'marzneshin_token_' . $server_id; $stmt_cache = pdo()->prepare("SELECT cache_value FROM cache WHERE cache_key = ? AND expire_at > ?"); $stmt_cache->execute([$cache_key, time()]); - if ($cached_token = $stmt_cache->fetchColumn()) return $cached_token; + if ($cached_token = $stmt_cache->fetchColumn()) + return $cached_token; $stmt = pdo()->prepare("SELECT url, username, password FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $server_info = $stmt->fetch(); - if (!$server_info) return ['error' => "Server info not found for server ID: {$server_id}"]; - + if (!$server_info) + return ['error' => "Server info not found for server ID: {$server_id}"]; + $url = rtrim($server_info['url'], '/') . '/api/admins/token'; $postData = http_build_query(['username' => $server_info['username'], 'password' => $server_info['password']]); - + $ch = curl_init(); curl_setopt_array($ch, [ - CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, - CURLOPT_POSTFIELDS => $postData, CURLOPT_TIMEOUT => 20, CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $postData, + CURLOPT_TIMEOUT => 20, + CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, CURLOPT_HTTPHEADER => ['Content-Type: application/x-www-form-urlencoded', 'Accept: application/json'], ]); - + $response_body = curl_exec($ch); $http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); - + $response = json_decode($response_body, true); if (isset($response['access_token'])) { $new_token = $response['access_token']; @@ -94,37 +112,40 @@ function getMarzneshinToken($server_id) { $stmt_insert_cache->execute([$cache_key, $new_token, $expire_time]); return $new_token; } - + $error_detail = $response['detail'] ?? $response_body; return ['error' => "HTTP {$http_code} - " . (is_string($error_detail) ? $error_detail : json_encode($error_detail))]; } -function getMarzneshinServices($server_id) { +function getMarzneshinServices($server_id) +{ $response = marzneshinApiRequest('/api/services', $server_id, 'GET'); return $response['items'] ?? []; } -function createMarzneshinUser($plan, $chat_id, $plan_id) { +function createMarzneshinUser($plan, $chat_id, $plan_id) +{ $server_id = $plan['server_id']; $service_id = $plan['marzneshin_service_id']; $username = $plan['full_username']; - + $stmt = pdo()->prepare("SELECT url, sub_host FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $server_info = $stmt->fetch(); - if (!$server_info) return false; + if (!$server_info) + return false; $base_sub_url = !empty($server_info['sub_host']) ? rtrim($server_info['sub_host'], '/') : rtrim($server_info['url'], '/'); $userData = [ 'username' => $username, 'data_limit' => $plan['volume_gb'] * 1024 * 1024 * 1024, 'expire_date' => date('c', time() + $plan['duration_days'] * 86400), - 'service_ids' => [(int)$service_id], + 'service_ids' => [(int) $service_id], 'expire_strategy' => 'fixed_date' ]; $response = marzneshinApiRequest('/api/users', $server_id, 'POST', $userData); - + if (isset($response['username'])) { // استخراج صحیح یزورنیم و ٟسورد $new_username = $response['username']; @@ -135,7 +156,7 @@ function createMarzneshinUser($plan, $chat_id, $plan_id) { $links_path = $subscription_path . 'links'; $full_subscription_url = $base_sub_url . $subscription_path; - + $links = []; $links_response_raw = marzneshinPublicApiRequest($links_path, $server_id); if (is_string($links_response_raw) && !str_contains(strtolower($links_response_raw), 'error')) { @@ -148,32 +169,33 @@ function createMarzneshinUser($plan, $chat_id, $plan_id) { 'links' => array_filter($links), ]; } - + error_log("[Marzneshin Create User Failed] Payload: " . json_encode($userData) . " | Response: " . json_encode($response)); return false; } // --- تاؚع دریافت اطلاعات کارؚر --- -function getMarzneshinUser($username, $server_id) { +function getMarzneshinUser($username, $server_id) +{ $user_response = marzneshinApiRequest("/api/users/{$username}", $server_id, 'GET'); if (isset($user_response['username'])) { $links = []; - + if (isset($user_response['key'])) { - $key = $user_response['key']; + $key = $user_response['key']; + + $links_endpoint = "/sub/{$username}/{$key}/links"; - $links_endpoint = "/sub/{$username}/{$key}/links"; - - $links_response_raw = marzneshinPublicApiRequest($links_endpoint, $server_id); - - if (is_string($links_response_raw) && !str_contains(strtolower($links_response_raw), 'error')) { + $links_response_raw = marzneshinPublicApiRequest($links_endpoint, $server_id); + + if (is_string($links_response_raw) && !str_contains(strtolower($links_response_raw), 'error')) { $links = explode("\n", trim($links_response_raw)); - } + } } - + return [ 'status' => $user_response['is_active'] ? 'active' : 'disabled', 'expire' => $user_response['expire_date'] ? strtotime($user_response['expire_date']) : 0, @@ -187,7 +209,8 @@ function getMarzneshinUser($username, $server_id) { return false; } -function modifyMarzneshinUser($username, $server_id, $data) { +function modifyMarzneshinUser($username, $server_id, $data) +{ $marzneshinData = []; if (isset($data['data_limit'])) { $marzneshinData['data_limit'] = $data['data_limit']; @@ -200,7 +223,15 @@ function modifyMarzneshinUser($username, $server_id, $data) { return $response && isset($response['username']); } -function deleteMarzneshinUser($username, $server_id) { +function deleteMarzneshinUser($username, $server_id) +{ $response = marzneshinApiRequest("/api/users/{$username}", $server_id, 'DELETE'); return is_null($response) || (isset($response['detail']) && str_contains($response['detail'], 'not found')); +} + +function resetMarzneshinUserUsage($username, $server_id) +{ + // مرزن؎ین همانند مرزؚان از endpoint reset ٟ؎تیؚانی می‌کند + $response = marzneshinApiRequest("/api/users/{$username}/reset", $server_id, 'POST'); + return $response && isset($response['username']); } \ No newline at end of file diff --git a/src/api/sanaei_api.php b/src/api/sanaei_api.php index fa6825c..caf1bef 100644 --- a/src/api/sanaei_api.php +++ b/src/api/sanaei_api.php @@ -2,7 +2,8 @@ // --- تواؚع ٟایه --- -function getSanaeiCookie($server_id) { +function getSanaeiCookie($server_id) +{ $cache_key = 'sanaei_cookie_' . $server_id; $stmt_cache = pdo()->prepare("SELECT cache_value FROM cache WHERE cache_key = ? AND expire_at > ?"); $stmt_cache->execute([$cache_key, time()]); @@ -13,16 +14,22 @@ function getSanaeiCookie($server_id) { $stmt = pdo()->prepare("SELECT url, username, password FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $server_info = $stmt->fetch(); - if (!$server_info) return false; + if (!$server_info) + return false; $url = rtrim($server_info['url'], '/') . '/login'; $postData = ['username' => $server_info['username'], 'password' => $server_info['password']]; $ch = curl_init(); curl_setopt_array($ch, [ - CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, - CURLOPT_POSTFIELDS => http_build_query($postData), CURLOPT_HEADER => true, - CURLOPT_TIMEOUT => 10, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => http_build_query($postData), + CURLOPT_HEADER => true, + CURLOPT_TIMEOUT => 10, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, ]); $response = curl_exec($ch); @@ -39,23 +46,31 @@ function getSanaeiCookie($server_id) { return false; } -function sanaeiApiRequest($endpoint, $server_id, $method = 'GET', $data = []) { +function sanaeiApiRequest($endpoint, $server_id, $method = 'GET', $data = []) +{ $stmt = pdo()->prepare("SELECT url FROM servers WHERE id = ?"); $stmt->execute([$server_id]); $server_url = $stmt->fetchColumn(); - if (!$server_url) return ['success' => false, 'msg' => 'Sanaei server is not configured.']; + if (!$server_url) + return ['success' => false, 'msg' => 'Sanaei server is not configured.']; $cookie = getSanaeiCookie($server_id); - if (!$cookie) return ['success' => false, 'msg' => 'Login failed']; + if (!$cookie) + return ['success' => false, 'msg' => 'Login failed']; $url = rtrim($server_url, '/') . $endpoint; $headers = ['Cookie: ' . $cookie, 'Accept: application/json']; - if ($method === 'POST') $headers[] = 'Content-Type: application/json'; + if ($method === 'POST') + $headers[] = 'Content-Type: application/json'; $ch = curl_init(); curl_setopt_array($ch, [ - CURLOPT_URL => $url, CURLOPT_RETURNTRANSFER => true, CURLOPT_HTTPHEADER => $headers, - CURLOPT_TIMEOUT => 10, CURLOPT_SSL_VERIFYPEER => false, CURLOPT_SSL_VERIFYHOST => false, + CURLOPT_URL => $url, + CURLOPT_RETURNTRANSFER => true, + CURLOPT_HTTPHEADER => $headers, + CURLOPT_TIMEOUT => 10, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, ]); if ($method === 'POST') { @@ -68,19 +83,22 @@ function sanaeiApiRequest($endpoint, $server_id, $method = 'GET', $data = []) { } -function getSanaeiInbounds($server_id) { +function getSanaeiInbounds($server_id) +{ $response = sanaeiApiRequest('/panel/api/inbounds/list', $server_id); return ($response['success'] && isset($response['obj'])) ? $response['obj'] : []; } -function _findSanaeiClientInAllInbounds($email_username, $server_id) { +function _findSanaeiClientInAllInbounds($email_username, $server_id) +{ $inbounds = getSanaeiInbounds($server_id); - if (empty($inbounds)) return false; + if (empty($inbounds)) + return false; foreach ($inbounds as $inbound_summary) { $inbound_id = $inbound_summary['id']; $response = sanaeiApiRequest("/panel/api/inbounds/get/{$inbound_id}", $server_id); - + if ($response && $response['success'] && isset($response['obj']['settings'])) { $settings = json_decode($response['obj']['settings'], true); if (isset($settings['clients'])) { @@ -95,14 +113,16 @@ function _findSanaeiClientInAllInbounds($email_username, $server_id) { return false; } -function createSanaeiUser($plan, $chat_id, $plan_id) { +function createSanaeiUser($plan, $chat_id, $plan_id) +{ $server_id = $plan['server_id']; $inbound_id = $plan['inbound_id']; - + $stmt_server = pdo()->prepare("SELECT url, sub_host FROM servers WHERE id = ?"); $stmt_server->execute([$server_id]); $server_info = $stmt_server->fetch(); - if(!$server_info) return false; + if (!$server_info) + return false; $base_sub_url = !empty($server_info['sub_host']) ? rtrim($server_info['sub_host'], '/') : rtrim($server_info['url'], '/'); $uuid = generateUUID(); @@ -110,37 +130,38 @@ function createSanaeiUser($plan, $chat_id, $plan_id) { $subId = generateUUID(16); $expire_time = ($plan['duration_days'] > 0) ? (time() + $plan['duration_days'] * 86400) * 1000 : 0; $total_bytes = ($plan['volume_gb'] > 0) ? $plan['volume_gb'] * 1024 * 1024 * 1024 : 0; - $client_settings = [ "id" => $uuid, "email" => $email, "totalGB" => $total_bytes, "expiryTime" => $expire_time, "enable" => true, "tgId" => (string)$chat_id, "subId" => $subId ]; - $data = ['id' => (int)$inbound_id, 'settings' => json_encode(['clients' => [$client_settings]])]; + $client_settings = ["id" => $uuid, "email" => $email, "totalGB" => $total_bytes, "expiryTime" => $expire_time, "enable" => true, "tgId" => (string) $chat_id, "subId" => $subId]; + $data = ['id' => (int) $inbound_id, 'settings' => json_encode(['clients' => [$client_settings]])]; $response = sanaeiApiRequest('/panel/api/inbounds/addClient', $server_id, 'POST', $data); if (isset($response['success']) && $response['success']) { $sub_link = $base_sub_url . '/sub/' . $subId; // اصلاحیه: ٟاس دادن server_id ØšÙ‡ تاؚع کمکی $links = fetchAndParseSubscriptionUrl($sub_link, $server_id); - + return ['username' => $email, 'subscription_url' => $sub_link, 'links' => $links]; } - + error_log("Failed to create Sanaei user. Response: " . json_encode($response)); return false; } -function getSanaeiUser($username, $server_id) { +function getSanaeiUser($username, $server_id) +{ $traffic_response = sanaeiApiRequest("/panel/api/inbounds/getClientTraffics/{$username}", $server_id); if (!$traffic_response || !$traffic_response['success'] || !isset($traffic_response['obj'])) { error_log("Could not fetch user traffic for {$username}."); return false; } $client_traffic_data = $traffic_response['obj']; - + $stmt_service = pdo()->prepare("SELECT sub_url FROM services WHERE marzban_username = ? AND server_id = ?"); $stmt_service->execute([$username, $server_id]); $sub_url = $stmt_service->fetchColumn(); // اصلاحیه: ٟاس دادن server_id ØšÙ‡ تاؚع کمکی $links = fetchAndParseSubscriptionUrl($sub_url, $server_id); - + return [ 'status' => ($client_traffic_data['enable'] && ($client_traffic_data['expiryTime'] == 0 || $client_traffic_data['expiryTime'] > time() * 1000)) ? 'active' : 'disabled', 'expire' => $client_traffic_data['expiryTime'] > 0 ? floor($client_traffic_data['expiryTime'] / 1000) : 0, @@ -150,33 +171,44 @@ function getSanaeiUser($username, $server_id) { ]; } -function modifySanaeiUser($username, $server_id, $data) { +function modifySanaeiUser($username, $server_id, $data) +{ $foundClientData = _findSanaeiClientInAllInbounds($username, $server_id); - if (!$foundClientData) return false; + if (!$foundClientData) + return false; $inbound_id = $foundClientData['inbound_id']; $uuid = $foundClientData['client']['id']; - + $traffic_response = sanaeiApiRequest("/panel/api/inbounds/getClientTraffics/{$username}", $server_id); - if (!$traffic_response || !$traffic_response['success'] || !isset($traffic_response['obj'])) return false; + if (!$traffic_response || !$traffic_response['success'] || !isset($traffic_response['obj'])) + return false; $currentClientData = $traffic_response['obj']; $update_payload = [ - 'id' => (int)$inbound_id, - 'settings' => json_encode(['clients' => [[ - 'id' => $uuid, 'email' => $username, 'enable' => true, - 'totalGB' => $data['data_limit'] ?? ($currentClientData['totalGB'] ?? 0), - 'expiryTime' => isset($data['expire']) ? $data['expire'] * 1000 : ($currentClientData['expiryTime'] ?? 0), - ]]]) + 'id' => (int) $inbound_id, + 'settings' => json_encode([ + 'clients' => [ + [ + 'id' => $uuid, + 'email' => $username, + 'enable' => true, + 'totalGB' => $data['data_limit'] ?? ($currentClientData['totalGB'] ?? 0), + 'expiryTime' => isset($data['expire']) ? $data['expire'] * 1000 : ($currentClientData['expiryTime'] ?? 0), + ] + ] + ]) ]; - + $response = sanaeiApiRequest("/panel/api/inbounds/updateClient/{$uuid}", $server_id, 'POST', $update_payload); return $response && $response['success']; } -function deleteSanaeiUser($username, $server_id) { +function deleteSanaeiUser($username, $server_id) +{ $foundClientData = _findSanaeiClientInAllInbounds($username, $server_id); - if (!$foundClientData) return true; + if (!$foundClientData) + return true; $inbound_id = $foundClientData['inbound_id']; $uuid = $foundClientData['client']['id']; @@ -185,12 +217,19 @@ function deleteSanaeiUser($username, $server_id) { return $response && $response['success']; } -function generateUUID($length = 36) { +function generateUUID($length = 36) +{ if ($length === 36) { - return sprintf('%04x%04x-%04x-%04x-%04x-%04x%04x%04x', - mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff), - mt_rand(0, 0x0fff) | 0x4000, mt_rand(0, 0x3fff) | 0x8000, - mt_rand(0, 0xffff), mt_rand(0, 0xffff), mt_rand(0, 0xffff) + return sprintf( + '%04x%04x-%04x-%04x-%04x-%04x%04x%04x', + mt_rand(0, 0xffff), + mt_rand(0, 0xffff), + mt_rand(0, 0xffff), + mt_rand(0, 0x0fff) | 0x4000, + mt_rand(0, 0x3fff) | 0x8000, + mt_rand(0, 0xffff), + mt_rand(0, 0xffff), + mt_rand(0, 0xffff) ); } else { $characters = '0123456789abcdefghijklmnopqrstuvwxyz'; @@ -200,4 +239,13 @@ function generateUUID($length = 36) { } return $randomString; } +} + +function resetSanaeiUserUsage($username, $server_id) +{ + // در ٟنل Sanaei/3x-ui، یک endpoint مستقیم ؚرای reset usage وجود ندارد + // ولی ؚا تن؞یم مجدد totalGB از طریق modifySanaeiUser، usage ØšÙ‡ طور خودکار ریست می‌؎ود + // ؚناؚراین این تاؚع صرفاً ؚرای سازگاری ؚا interface وجود دارد + // عملیات ریست در تاؚع modifySanaeiUser انجام ؎ده است + return true; } \ No newline at end of file diff --git a/src/bot.php b/src/bot.php index f3e56b7..4bff691 100644 --- a/src/bot.php +++ b/src/bot.php @@ -1104,6 +1104,55 @@ sendMessage($chat_id, "لطفا ٟاسخ خود را ؚرای تیکت $ticket_id وارد کنید:", $cancelKeyboard); } } + } elseif (strpos($data, 'approve_charge_') === 0 || strpos($data, 'reject_charge_') === 0) { + // Handle payment request approval/rejection + if ($isAnAdmin && !hasPermission($chat_id, 'manage_payment')) { + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '؎ما دسترسی لازم ؚرای مدیریت ٟرداخت‌ها را ندارید.', 'show_alert' => true]); + die; + } + + list($action, $charge_word, $request_id) = explode('_', $data); + + $stmt = pdo()->prepare("SELECT * FROM payment_requests WHERE id = ?"); + $stmt->execute([$request_id]); + $request = $stmt->fetch(); + + if (!$request || $request['status'] !== 'pending') { + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => 'این درخواست قؚلا ٟرداز؎ ؎ده است.', 'show_alert' => true]); + die; + } + + $admin_who_processed = $update['callback_query']['from']['id']; + + if ($action == 'approve') { + // Update user balance + $stmt_balance = pdo()->prepare("UPDATE users SET balance = balance + ? WHERE chat_id = ?"); + $stmt_balance->execute([$request['amount'], $request['user_id']]); + + // Update request status + pdo()->prepare("UPDATE payment_requests SET status = 'approved', processed_by_admin_id = ?, processed_at = NOW() WHERE id = ?")->execute([$admin_who_processed, $request_id]); + + // Notify user + sendMessage($request['user_id'], "✅ درخواست ؎ارژ کیف ٟول ؎ما ØšÙ‡ مؚلغ " . number_format($request['amount']) . " تومان تایید ؎د و ØšÙ‡ حساؚ ؎ما اضافه گردید."); + + // Update admin message + $new_caption = $update['callback_query']['message']['caption'] . "\n\n✅ توسط ؎ما تایید و واریز ؎د."; + editMessageCaption($chat_id, $message_id, $new_caption, null); + + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '✅ ؎ارژ تایید و واریز ؎د.']); + } elseif ($action == 'reject') { + // Update request status + pdo()->prepare("UPDATE payment_requests SET status = 'rejected', processed_by_admin_id = ?, processed_at = NOW() WHERE id = ?")->execute([$admin_who_processed, $request_id]); + + // Notify user + sendMessage($request['user_id'], "❌ درخواست ؎ارژ کیف ٟول ؎ما ØšÙ‡ مؚلغ " . number_format($request['amount']) . " تومان توسط ادمین رد ؎د. لطفاً ؚا ٟ؎تیؚانی تماس ؚگیرید."); + + // Update admin message + $new_caption = $update['callback_query']['message']['caption'] . "\n\n❌ توسط ؎ما رد ؎د."; + editMessageCaption($chat_id, $message_id, $new_caption, null); + + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '❌ درخواست رد ؎د.']); + } } elseif (strpos($data, 'approve_renewal_') === 0 || strpos($data, 'reject_renewal_') === 0) { list($action, $type, $request_id) = explode('_', $data); @@ -1230,7 +1279,24 @@ updateUserData($chat_id, 'awaiting_payment_screenshot', ['charge_amount' => $amount]); } elseif (strpos($data, 'cat_') === 0) { $categoryId = str_replace('cat_', '', $data); - showServersForCategory($chat_id, $categoryId); + + // Check if only one server exists for this category + $stmt = pdo()->prepare(" + SELECT DISTINCT s.id + FROM servers s + JOIN plans p ON s.id = p.server_id + WHERE p.category_id = ? AND p.status = 'active' AND s.status = 'active' + "); + $stmt->execute([$categoryId]); + $servers = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (count($servers) === 1) { + // Auto-skip to plan selection + showPlansForCategoryAndServer($chat_id, $categoryId, $servers[0]['id']); + } else { + // Show server selection + showServersForCategory($chat_id, $categoryId); + } deleteMessage($chat_id, $message_id); } elseif (strpos($data, 'show_plans_cat_') === 0) { preg_match('/show_plans_cat_(\d+)_srv_(\d+)/', $data, $matches); @@ -1285,58 +1351,7 @@ sendMessage($chat_id, $message, $cancelKeyboard); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); die; - } elseif ($data === 'confirm_renewal_payment') { - $state_data = $user_data['state_data']; - $total_cost = $state_data['renewal_total_cost']; - - if ($user_data['balance'] >= $total_cost) { - // ٟرداخت از موجودی - editMessageText($chat_id, $message_id, "⏳ در حال تمدید سرویس ؚا استفاده از موجودی ؎ما..."); - updateUserBalance($chat_id, $total_cost, 'deduct'); - - $result = applyRenewal($chat_id, $state_data['renewal_username'], $state_data['renewal_days'], $state_data['renewal_gb']); - - if ($result['success']) { - $new_balance = number_format($user_data['balance'] - $total_cost); - $success_msg = "✅ سرویس ؎ما ؚا موفقیت تمدید ؎د.\n\n" . - "💰 مؚلغ " . number_format($total_cost) . " تومان از حساؚ ؎ما کسر گردید.\n" . - "موجودی جدید: {$new_balance} تومان."; - editMessageText($chat_id, $message_id, $success_msg); - } else { - editMessageText($chat_id, $message_id, "❌ خطایی در تمدید سرویس رخ داد: " . $result['message']); - - updateUserBalance($chat_id, $total_cost, 'add'); - } - updateUserData($chat_id, 'main_menu'); - - } else { - - $stmt = pdo()->prepare( - "INSERT INTO renewal_requests (user_id, service_username, days_to_add, gb_to_add, total_cost) VALUES (?, ?, ?, ?, ?)" - ); - $stmt->execute([$chat_id, $state_data['renewal_username'], $state_data['renewal_days'], $state_data['renewal_gb'], $total_cost]); - $request_id = pdo()->lastInsertId(); - - $state_data['renewal_request_id'] = $request_id; - updateUserData($chat_id, 'awaiting_renewal_screenshot', $state_data); - - - $settings = getSettings(); - $payment_method = $settings['payment_method'] ?? []; - if (empty($payment_method['card_number'])) { - editMessageText($chat_id, $message_id, "موجودی ؎ما کافی نیست و رو؎ ٟرداخت کارت ØšÙ‡ کارت نیز توسط ادمین تن؞یم ن؎ده است. لطفا اؚتدا حساؚ خود را ؎ارژ کنید."); - } else { - $card_number = $payment_method['card_number'] ?? ''; - $card_holder = $payment_method['card_holder'] ?? ''; - $copy_enabled = $payment_method['copy_enabled'] ?? false; - $card_number_display = $copy_enabled ? "{$card_number}" : $card_number; - $message = "موجودی ؎ما کافی نیست. لطفا مؚلغ " . number_format($total_cost) . " تومان را ØšÙ‡ اطلاعات زیر واریز کرده و سٟس اسکرین‌؎ات رسید را ارسال کنید:\n\n" . - "💳 ؎ماره کارت:\n" . $card_number_display . "\n" . - "👀 صاحؚ حساؚ: {$card_holder}"; - editMessageText($chat_id, $message_id, $message); - } - } - apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); + // Note: confirm_renewal_payment handler حذف ؎د - این ؚخ؎ ØšÙ‡ سیستم جدید تمدید ؚر اساس ٟلن منتقل ؎ده است } elseif ($data == 'back_to_categories') { deleteMessage($chat_id, $message_id); $categories = getCategories(true); @@ -1440,15 +1455,118 @@ } $username = str_replace('renew_service_', '', $data); - updateUserData($chat_id, 'user_awaiting_renewal_days', ['renewal_username' => $username]); - $price_day = number_format($settings['renewal_price_per_day'] ?? 1000); - $message = "تمدید سرویس\n\n" . - "Û±. چند **روز** ØšÙ‡ اعتؚار سرویس ؎ما اضافه ؎ود؟\n\n" . - "▫ هزینه هر روز: {$price_day} تومان\n" . - "💡 ؚرای رد ؎دن و عدم تمدید زمان، عدد `0` را وارد کنید."; + // ذخیره username سرویس که قرار است تمدید ؎ود + updateUserData($chat_id, 'renewal_selecting_category', [ + 'renewal_username' => $username, + 'is_renewal' => true + ]); + + // نمای؎ دسته‌ؚندی‌ها + deleteMessage($chat_id, $message_id); + $categories = getCategories(true); + $keyboard_buttons = []; + foreach ($categories as $category) { + $keyboard_buttons[] = [['text' => '🛍 ' . $category['name'], 'callback_data' => 'renewal_cat_' . $category['id']]]; + } + $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت', 'callback_data' => "service_details_{$username}"]]; + sendMessage($chat_id, "🔄 تمدید سرویس\n\nؚرای تمدید سرویس، لطفاً دسته‌ؚندی مورد ن؞ر را انتخاؚ کنید:", ['inline_keyboard' => $keyboard_buttons]); + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); + } elseif (strpos($data, 'renewal_cat_') === 0) { + // handler انتخاؚ دسته‌ؚندی ؚرای تمدید + $categoryId = str_replace('renewal_cat_', '', $data); + $state_data = $user_data['state_data']; + $state_data['renewal_category_id'] = $categoryId; + + // Check if only one server exists for this category + $stmt = pdo()->prepare(" + SELECT DISTINCT s.id + FROM servers s + JOIN plans p ON s.id = p.server_id + WHERE p.category_id = ? AND p.status = 'active' AND s.status = 'active' + "); + $stmt->execute([$categoryId]); + $servers = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (count($servers) === 1) { + // Auto-skip to plan selection + $server_id = $servers[0]['id']; + $state_data['renewal_server_id'] = $server_id; + updateUserData($chat_id, 'renewal_selecting_plan', $state_data); + showPlansForCategoryAndServerRenewal($chat_id, $categoryId, $server_id, $state_data['renewal_username']); + } else { + updateUserData($chat_id, 'renewal_selecting_server', $state_data); + showServersForCategoryRenewal($chat_id, $categoryId, $state_data['renewal_username']); + } + deleteMessage($chat_id, $message_id); + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); + } elseif (strpos($data, 'renewal_show_plans_') === 0) { + // handler انتخاؚ سرور ؚرای تمدید + preg_match('/renewal_show_plans_cat_(\d+)_srv_(\d+)/', $data, $matches); + $category_id = $matches[1]; + $server_id = $matches[2]; + $state_data = $user_data['state_data']; + $state_data['renewal_server_id'] = $server_id; + updateUserData($chat_id, 'renewal_selecting_plan', $state_data); + + showPlansForCategoryAndServerRenewal($chat_id, $category_id, $server_id, $state_data['renewal_username']); + deleteMessage($chat_id, $message_id); + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); + } elseif (strpos($data, 'renewal_buy_plan_') === 0) { + // handler انتخاؚ ٟلن ؚرای تمدید + $parts = explode('_', $data); + $plan_id = $parts[3]; + + $state_data = $user_data['state_data']; + $username_to_renew = $state_data['renewal_username']; + + $plan = getPlanById($plan_id); + if (!$plan) { + apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id, 'text' => '❌ خطا: ٟلن یافت ن؎د.', 'show_alert' => true]); + die; + } + + $final_price = (float) $plan['price']; + $user_balance = $user_data['balance']; + + if ($user_balance >= $final_price) { + editMessageText($chat_id, $message_id, "⏳ در حال تمدید سرویس ؎ما..."); + + $renewal_result = applyPlanRenewal($chat_id, $username_to_renew, $plan_id, $final_price); + + if ($renewal_result['success']) { + editMessageText($chat_id, $message_id, $renewal_result['message']); + } else { + editMessageText($chat_id, $message_id, "❌ " . $renewal_result['message']); + } + + updateUserData($chat_id, 'main_menu'); + handleMainMenu($chat_id, $first_name); + } else { + // موجودی کافی نیست، نیاز ØšÙ‡ ٟرداخت + $needed_amount = $final_price - $user_balance; + $settings = getSettings(); + + $keyboard_buttons = []; + if (($settings['payment_gateway_status'] ?? 'off') == 'on' && !empty($settings['zarinpal_merchant_id'])) { + $keyboard_buttons[] = [['text' => '🌐 ٟرداخت آنلاین (زرین‌ٟال)', 'callback_data' => "charge_for_renewal_{$needed_amount}_{$plan_id}_{$username_to_renew}"]]; + } + if (!empty($settings['payment_method']['card_number'])) { + $keyboard_buttons[] = [['text' => '💳 ٟرداخت کارت ØšÙ‡ کارت', 'callback_data' => "manual_pay_for_renewal_{$needed_amount}_{$plan_id}_{$username_to_renew}"]]; + } + + if (empty($keyboard_buttons)) { + editMessageText($chat_id, $message_id, "❌ موجودی ؎ما کافی نیست و هیچ رو؎ ٟرداختی توسط ادمین فعال ن؎ده است."); + } else { + $message = "⚠ موجودی ؎ما کافی نیست!\n\n" . + "▫ قیمت ٟلن: " . number_format($final_price) . " تومان\n" . + "▫ موجودی ؎ما: " . number_format($user_balance) . " تومان\n" . + "💰 مؚلغ مورد نیاز: " . number_format($needed_amount) . " تومان\n\n" . + "لطفاً رو؎ ٟرداخت ؚرای تکمیل تمدید را انتخاؚ کنید:"; + editMessageText($chat_id, $message_id, $message, ['inline_keyboard' => $keyboard_buttons]); + } + } - editMessageCaption($chat_id, $message_id, $message, null); apiRequest('answerCallbackQuery', ['callback_query_id' => $callback_id]); } elseif (strpos($data, 'delete_service_confirm_') === 0) { $username = str_replace('delete_service_confirm_', '', $data); @@ -2769,99 +2887,70 @@ $data = 'config_inactive_reminder'; break; - case 'user_awaiting_renewal_days': - if (!is_numeric($text) || $text < 0) { - sendMessage($chat_id, "❌ لطفا فقط یک عدد صحیح (مثؚت یا صفر) وارد کنید."); + // Note: حذف state handlers قدیمی تمدید: user_awaiting_renewal_days, user_awaiting_renewal_gb و awaiting_renewal_screenshot + // سیستم جدید از طریق انتخاؚ ٟلن عمل می‌کند + + case 'admin_awaiting_charge_amount': + if (!hasPermission($chat_id, 'manage_payment')) { break; } - $state_data = $user_data['state_data']; - $state_data['renewal_days'] = (int) $text; - updateUserData($chat_id, 'user_awaiting_renewal_gb', $state_data); - - $settings = getSettings(); - $price_gb = number_format($settings['renewal_price_per_gb'] ?? 2000); - $message = "تمدید سرویس\n\n" . - "Û². چند **گیگاؚایت** ØšÙ‡ حجم سرویس ؎ما اضافه ؎ود؟\n\n" . - "▫ هزینه هر گیگ: {$price_gb} تومان\n" . - "💡 ؚرای رد ؎دن و عدم تمدید حجم، عدد `0` را وارد کنید."; - sendMessage($chat_id, $message); - break; - - case 'user_awaiting_renewal_gb': - if (!is_numeric($text) || $text < 0) { - sendMessage($chat_id, "❌ لطفا فقط یک عدد صحیح (مثؚت یا صفر) وارد کنید."); + if (!is_numeric($text) || $text <= 0) { + sendMessage($chat_id, "❌ لطفا یک مؚلغ معتؚر (عدد مثؚت) ØšÙ‡ تومان وارد کنید.", $cancelKeyboard); break; } + $amount = (int) $text; $state_data = $user_data['state_data']; - $days_to_add = $state_data['renewal_days']; - $gb_to_add = (int) $text; + $user_to_charge_id = $state_data['user_id']; - if ($days_to_add == 0 && $gb_to_add == 0) { - sendMessage($chat_id, "؎ما هیچ مقداری ؚرای تمدید وارد نکردید. عملیات لغو ؎د."); - updateUserData($chat_id, 'main_menu'); + $user_to_charge = getUser($user_to_charge_id); + if (!$user_to_charge) { + sendMessage($chat_id, "❌ کارؚری ؚا این ؎ناسه یافت ن؎د."); + updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); handleMainMenu($chat_id, $first_name); break; } - $settings = getSettings(); - $cost_days = $days_to_add * (int) ($settings['renewal_price_per_day'] ?? 1000); - $cost_gb = $gb_to_add * (int) ($settings['renewal_price_per_gb'] ?? 2000); - $total_cost = $cost_days + $cost_gb; - - $state_data['renewal_gb'] = $gb_to_add; - $state_data['renewal_total_cost'] = $total_cost; - updateUserData($chat_id, 'user_confirming_renewal', $state_data); - - $summary = "خلاصه درخواست تمدید ؎ما:\n\n" . - "▫ افزای؎ زمان: {$days_to_add} روز\n" . - "▫ افزای؎ حجم: {$gb_to_add} گیگاؚایت\n\n" . - "💰 هزینه کل: " . number_format($total_cost) . " تومان\n\n" . - "موجودی فعلی ؎ما: " . number_format($user_data['balance']) . " تومان\n\n" . - "آیا تایید می‌کنید؟"; - - $keyboard = ['inline_keyboard' => [[['text' => '✅ ؚله، ٟرداخت کن', 'callback_data' => 'confirm_renewal_payment']]]]; - sendMessage($chat_id, $summary, $keyboard); - break; + $new_balance = $user_to_charge['balance'] + $amount; + updateUserBalance($user_to_charge_id, $new_balance); - case 'awaiting_renewal_screenshot': - if (isset($update['message']['photo'])) { - $state_data = $user_data['state_data']; - $photo_id = $update['message']['photo'][count($update['message']['photo']) - 1]['file_id']; + sendMessage($chat_id, "✅ مؚلغ " . number_format($amount) . " تومان ØšÙ‡ موجودی کارؚر " . htmlspecialchars($user_to_charge['first_name']) . " ({$user_to_charge_id}) اضافه ؎د.\nموجودی جدید: " . number_format($new_balance) . " تومان."); + sendMessage($user_to_charge_id, "✅ مؚلغ " . number_format($amount) . " تومان ØšÙ‡ موجودی حساؚ ؎ما اضافه ؎د.\nموجودی جدید: " . number_format($new_balance) . " تومان."); - $stmt = pdo()->prepare("UPDATE renewal_requests SET photo_file_id = ? WHERE id = ?"); - $stmt->execute([$photo_id, $state_data['renewal_request_id']]); + updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); + handleMainMenu($chat_id, $first_name); + break; + case 'admin_awaiting_deduct_amount': + if (!hasPermission($chat_id, 'manage_payment')) { + break; + } + if (!is_numeric($text) || $text <= 0) { + sendMessage($chat_id, "❌ لطفا یک مؚلغ معتؚر (عدد مثؚت) ØšÙ‡ تومان وارد کنید.", $cancelKeyboard); + break; + } + $amount = (int) $text; + $state_data = $user_data['state_data']; + $user_to_deduct_id = $state_data['user_id']; - $request_id = $state_data['renewal_request_id']; - $caption = "درخواست تمدید سرویس جدید\n\n" . - "👀 کارؚر: " . htmlspecialchars($first_name) . " ({$chat_id})\n" . - "▫ سرویس: {$state_data['renewal_username']}\n" . - "⏰ تمدید زمان: {$state_data['renewal_days']} روز\n" . - "📊 تمدید حجم: {$state_data['renewal_gb']} گیگ\n" . - "💰 هزینه: " . number_format($state_data['renewal_total_cost']) . " تومان\n" . - "▫ ؎ماره درخواست: #R-{$request_id}"; + $user_to_deduct = getUser($user_to_deduct_id); + if (!$user_to_deduct) { + sendMessage($chat_id, "❌ کارؚری ؚا این ؎ناسه یافت ن؎د."); + updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); + handleMainMenu($chat_id, $first_name); + break; + } - $keyboard = [ - 'inline_keyboard' => [ - [ - ['text' => '✅ تایید تمدید', 'callback_data' => "approve_renewal_{$request_id}"], - ['text' => '❌ رد تمدید', 'callback_data' => "reject_renewal_{$request_id}"] - ] - ] - ]; + $new_balance = $user_to_deduct['balance'] - $amount; + if ($new_balance < 0) { + $new_balance = 0; // Ensure balance doesn't go negative + } + updateUserBalance($user_to_deduct_id, $new_balance); - $all_admins = getAdmins(); - $all_admins[ADMIN_CHAT_ID] = []; - foreach (array_keys($all_admins) as $admin_id) { - if (hasPermission($admin_id, 'manage_payment')) { - sendPhoto($admin_id, $photo_id, $caption, $keyboard); - } - } + sendMessage($chat_id, "✅ مؚلغ " . number_format($amount) . " تومان از موجودی کارؚر " . htmlspecialchars($user_to_deduct['first_name']) . " ({$user_to_deduct_id}) کسر ؎د.\nموجودی جدید: " . number_format($new_balance) . " تومان."); + sendMessage($user_to_deduct_id, "❌ مؚلغ " . number_format($amount) . " تومان از موجودی حساؚ ؎ما کسر ؎د.\nموجودی جدید: " . number_format($new_balance) . " تومان."); - sendMessage($chat_id, "✅ رسید ؎ما ؚرای ادمین ارسال ؎د. ٟس از ؚررسی، سرویس ؎ما تمدید خواهد ؎د."); - updateUserData($chat_id, 'main_menu'); - handleMainMenu($chat_id, $first_name); - } + updateUserData($chat_id, 'main_menu', ['admin_view' => 'admin']); + handleMainMenu($chat_id, $first_name); break; } die; diff --git a/src/includes/functions.php b/src/includes/functions.php index 736c792..966de2f 100644 --- a/src/includes/functions.php +++ b/src/includes/functions.php @@ -124,7 +124,12 @@ function forwardMessage($to_chat_id, $from_chat_id, $message_id) function sendPhoto($chat_id, $photo, $caption, $keyboard = null) { - $params = ['chat_id' => $chat_id, 'photo' => $photo, 'caption' => $caption, 'reply_markup' => handleKeyboard($keyboard), 'parse_mode' => 'HTML']; + $params = ['chat_id' => $chat_id, 'caption' => $caption, 'reply_markup' => handleKeyboard($keyboard), 'parse_mode' => 'HTML']; + if (file_exists($photo)) { + $params['photo'] = new CURLFile($photo); + } else { + $params['photo'] = $photo; + } return apiRequest('sendPhoto', $params); } @@ -166,10 +171,21 @@ function apiRequest($method, $params = []) $url = 'https://api.telegram.org/bot' . BOT_TOKEN . '/' . $method; $ch = curl_init(); + + $hasFile = false; + foreach ($params as $key => $value) { + if ($value instanceof CURLFile) { + $hasFile = true; + break; + } + } + + $postFields = $hasFile ? $params : http_build_query($params); + curl_setopt_array($ch, [ CURLOPT_URL => $url, CURLOPT_POST => true, - CURLOPT_POSTFIELDS => http_build_query($params), + CURLOPT_POSTFIELDS => $postFields, CURLOPT_RETURNTRANSFER => true, ]); $response = curl_exec($ch); @@ -603,22 +619,29 @@ function formatBytes($bytes, $precision = 2) function calculateIncomeStats() { $stats = [ - 'today' => - pdo() - ->query("SELECT SUM(p.price) FROM services s JOIN plans p ON s.plan_id = p.id WHERE DATE(s.purchase_date) = CURDATE()") - ->fetchColumn() ?? 0, - 'week' => - pdo() - ->query("SELECT SUM(p.price) FROM services s JOIN plans p ON s.plan_id = p.id WHERE s.purchase_date >= CURDATE() - INTERVAL 7 DAY") - ->fetchColumn() ?? 0, - 'month' => - pdo() - ->query("SELECT SUM(p.price) FROM services s JOIN plans p ON s.plan_id = p.id WHERE MONTH(s.purchase_date) = MONTH(CURDATE()) AND YEAR(s.purchase_date) = YEAR(CURDATE())") - ->fetchColumn() ?? 0, - 'year' => - pdo() - ->query("SELECT SUM(p.price) FROM services s JOIN plans p ON s.plan_id = p.id WHERE YEAR(s.purchase_date) = YEAR(CURDATE())") - ->fetchColumn() ?? 0, + 'today' => ( + pdo()->query("SELECT SUM(p.price) FROM services s JOIN plans p ON s.plan_id = p.id WHERE DATE(s.purchase_date) = CURDATE()")->fetchColumn() ?? 0 + ) + ( + pdo()->query("SELECT SUM(amount) FROM renewals WHERE DATE(renewal_date) = CURDATE()")->fetchColumn() ?? 0 + ), + + 'week' => ( + pdo()->query("SELECT SUM(p.price) FROM services s JOIN plans p ON s.plan_id = p.id WHERE s.purchase_date >= CURDATE() - INTERVAL 7 DAY")->fetchColumn() ?? 0 + ) + ( + pdo()->query("SELECT SUM(amount) FROM renewals WHERE renewal_date >= CURDATE() - INTERVAL 7 DAY")->fetchColumn() ?? 0 + ), + + 'month' => ( + pdo()->query("SELECT SUM(p.price) FROM services s JOIN plans p ON s.plan_id = p.id WHERE MONTH(s.purchase_date) = MONTH(CURDATE()) AND YEAR(s.purchase_date) = YEAR(CURDATE())")->fetchColumn() ?? 0 + ) + ( + pdo()->query("SELECT SUM(amount) FROM renewals WHERE MONTH(renewal_date) = MONTH(CURDATE()) AND YEAR(renewal_date) = YEAR(CURDATE())")->fetchColumn() ?? 0 + ), + + 'year' => ( + pdo()->query("SELECT SUM(p.price) FROM services s JOIN plans p ON s.plan_id = p.id WHERE YEAR(s.purchase_date) = YEAR(CURDATE())")->fetchColumn() ?? 0 + ) + ( + pdo()->query("SELECT SUM(amount) FROM renewals WHERE YEAR(renewal_date) = YEAR(CURDATE())")->fetchColumn() ?? 0 + ), ]; return $stats; } @@ -1114,6 +1137,24 @@ function modifyPanelUser($username, $server_id, $data) } } +function resetPanelUserUsage($username, $server_id) +{ + $stmt = pdo()->prepare("SELECT type FROM servers WHERE id = ?"); + $stmt->execute([$server_id]); + $type = $stmt->fetchColumn(); + + switch ($type) { + case 'marzban': + return resetMarzbanUserUsage($username, $server_id); + case 'sanaei': + return resetSanaeiUserUsage($username, $server_id); + case 'marzneshin': + return resetMarzneshinUserUsage($username, $server_id); + default: + return false; + } +} + function showPlanEditor($chat_id, $message_id, $plan_id, $prompt = null) { $plan = getPlanById($plan_id); @@ -1237,10 +1278,192 @@ function showPlansForCategoryAndServer($chat_id, $category_id, $server_id) // فرمت callback جدید ؚرای کد تخفیف: apply_discount_code_{cat_ID}_{srv_ID} $keyboard_buttons[] = [['text' => '🎁 اعمال کد تخفیف', 'callback_data' => "apply_discount_code_{$category_id}_{$server_id}"]]; // دکمه ؚازگ؎ت ØšÙ‡ لیست سرورها ؚرای همان دسته ؚندی - $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت ØšÙ‡ انتخاؚ سرور', 'callback_data' => 'cat_' . $category_id]]; + // Check if only one server exists to adjust back button + $stmt_count = pdo()->prepare(" + SELECT COUNT(DISTINCT s.id) + FROM servers s + JOIN plans p ON s.id = p.server_id + WHERE p.category_id = ? AND p.status = 'active' AND s.status = 'active' + "); + $stmt_count->execute([$category_id]); + $server_count = $stmt_count->fetchColumn(); + + if ($server_count == 1) { + $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت ØšÙ‡ دسته‌ؚندی‌ها', 'callback_data' => 'back_to_categories']]; + } else { + $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت ØšÙ‡ انتخاؚ سرور', 'callback_data' => 'cat_' . $category_id]]; + } sendMessage($chat_id, $message, ['inline_keyboard' => $keyboard_buttons]); } +// ===================================================================== +// --- تواؚع جدید تمدید سرویس ؚر اساس ٟلن --- +// ===================================================================== + +function applyPlanRenewal($chat_id, $username, $plan_id, $final_price) +{ + $plan = getPlanById($plan_id); + if (!$plan) { + return ['success' => false, 'message' => '❌ ٟلن یافت ن؎د.']; + } + + // دریافت اطلاعات سرویس از دیتاؚیس + $stmt = pdo()->prepare("SELECT server_id FROM services WHERE owner_chat_id = ? AND marzban_username = ?"); + $stmt->execute([$chat_id, $username]); + $server_id = $stmt->fetchColumn(); + + if (!$server_id) { + return ['success' => false, 'message' => 'سرویس در دیتاؚیس رؚات یافت ن؎د.']; + } + + // دریافت اطلاعات فعلی از ٟنل + $current_user_data = getPanelUser($username, $server_id); + if (!$current_user_data || isset($current_user_data['detail'])) { + return ['success' => false, 'message' => 'اطلاعات سرویس از ٟنل دریافت ن؎د.']; + } + + $update_data = []; + + // محاسؚه زمان جدید: اگر سرویس فعال است، ØšÙ‡ زمان فعلی اضافه ؎ود + $days_to_add = $plan['duration_days']; + $seconds_to_add = $days_to_add * 86400; + $current_expire = $current_user_data['expire'] ?? 0; + + // اگر سرویس منقضی ن؎ده و زمان دارد، ØšÙ‡ آن اضافه کن + if ($current_expire > 0 && $current_expire > time()) { + $new_expire = $current_expire + $seconds_to_add; + } else { + // سرویس منقضی ؎ده، از همین الان ؎روع کن + $new_expire = time() + $seconds_to_add; + } + $update_data['expire'] = $new_expire; + + // حجم جدید: حجم ٟلن جایگزین می‌؎ود + $new_volume_bytes = $plan['volume_gb'] * 1024 * 1024 * 1024; + $update_data['data_limit'] = $new_volume_bytes; + + // اعمال تغییرات در ٟنل (زمان و حجم) + $result = modifyPanelUser($username, $server_id, $update_data); + + if ($result && !isset($result['detail'])) { + // ریست کردن حجم مصرفی از طریق endpoint مخصوص + $reset_result = resetPanelUserUsage($username, $server_id); + + // ؚروزرسانی دیتاؚیس محلی + pdo()->prepare("UPDATE services SET expire_timestamp = ?, volume_gb = ? WHERE marzban_username = ? AND server_id = ?") + ->execute([$new_expire, $plan['volume_gb'], $username, $server_id]); + + // ثؚت تمدید در جدول renewals ؚرای محاسؚه درآمد (commented out - optional) + // $stmt_renewal = pdo()->prepare("INSERT INTO renewals (user_id, service_username, plan_id, amount, renewal_date) VALUES (?, ?, ?, ?, NOW())"); + // $stmt_renewal->execute([$chat_id, $username, $plan_id, $final_price]); + + // کسر موجودی + updateUserBalance($chat_id, $final_price, 'deduct'); + + $user_data = getUserData($chat_id); + $new_balance = $user_data['balance']; + + $success_msg = "✅ سرویس ؎ما ؚا موفقیت تمدید ؎د.\n\n" . + "📊 ٟلن: {$plan['name']}\n" . + "⏰ زمان اعتؚار: {$days_to_add} روز\n" . + "📊 حجم جدید: {$plan['volume_gb']} گیگاؚایت\n\n" . + "💰 مؚلغ " . number_format($final_price) . " تومان از حساؚ ؎ما کسر گردید.\n" . + "موجودی جدید: " . number_format($new_balance) . " تومان."; + + // نوتیفیکی؎ن ؚرای ادمین + $admin_notification = "✅ تمدید سرویس\n\n" . + "👀 کارؚر: $chat_id\n" . + "🔧 سرویس: $username\n" . + "📊 ٟلن: {$plan['name']}\n" . + "💳 مؚلغ: " . number_format($final_price) . " تومان"; + + sendMessage(ADMIN_CHAT_ID, $admin_notification); + + return ['success' => true, 'message' => $success_msg]; + } + + return ['success' => false, 'message' => 'خطا در ارتؚاط ؚا ٟنل ؚرای اعمال تغییرات.']; +} + +function showServersForCategoryRenewal($chat_id, $category_id, $renewal_username) +{ + // م؎اؚه showServersForCategory اما ؚا callback_data متفاوت + $category_stmt = pdo()->prepare("SELECT name FROM categories WHERE id = ?"); + $category_stmt->execute([$category_id]); + $category_name = $category_stmt->fetchColumn(); + + if (!$category_name) { + sendMessage($chat_id, "خطا: دسته‌ؚندی یافت ن؎د."); + return; + } + + $stmt = pdo()->prepare(" + SELECT DISTINCT s.id, s.name + FROM servers s + JOIN plans p ON s.id = p.server_id + WHERE p.category_id = ? AND p.status = 'active' AND s.status = 'active' + "); + $stmt->execute([$category_id]); + $servers = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (empty($servers)) { + sendMessage($chat_id, "متاسفانه در حال حاضر هیچ سروری در این دسته‌ؚندی ٟلن فعال ندارد."); + return; + } + + $message = "🔄 تمدید سرویس - دسته‌ؚندی «{$category_name}»\n\nلطفاً سرور (لوکی؎ن) مورد ن؞ر خود را انتخاؚ کنید:"; + $keyboard_buttons = []; + foreach ($servers as $server) { + $keyboard_buttons[] = [['text' => "🖥 {$server['name']}", 'callback_data' => "renewal_show_plans_cat_{$category_id}_srv_{$server['id']}"]]; + } + $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت', 'callback_data' => "service_details_{$renewal_username}"]]; + sendMessage($chat_id, $message, ['inline_keyboard' => $keyboard_buttons]); +} + +function showPlansForCategoryAndServerRenewal($chat_id, $category_id, $server_id, $renewal_username) +{ + // م؎اؚه showPlansForCategoryAndServer اما ؚا callback_data متفاوت + $stmt = pdo()->prepare("SELECT * FROM plans WHERE category_id = ? AND server_id = ? AND status = 'active' AND is_test_plan = 0"); + $stmt->execute([$category_id, $server_id]); + $plans = $stmt->fetchAll(PDO::FETCH_ASSOC); + + if (empty($plans)) { + sendMessage($chat_id, "هیچ ٟلن فعالی در این سرور و دسته‌ؚندی یافت ن؎د."); + return; + } + + $user_balance = getUserData($chat_id)['balance'] ?? 0; + $message = "🔄 تمدید سرویس - انتخاؚ ٟلن\n\nموجودی ؎ما: " . number_format($user_balance) . " تومان\n\nلطفاً ٟلن مورد ن؞ر خود را انتخاؚ کنید:"; + $keyboard_buttons = []; + + foreach ($plans as $plan) { + $price_formatted = number_format($plan['price']); + $button_text = "📊 {$plan['name']} - {$price_formatted} تومان"; + $keyboard_buttons[] = [['text' => $button_text, 'callback_data' => "renewal_buy_plan_{$plan['id']}"]]; + } + + // Check if only one server exists to adjust back button + $stmt_count = pdo()->prepare(" + SELECT COUNT(DISTINCT s.id) + FROM servers s + JOIN plans p ON s.id = p.server_id + WHERE p.category_id = ? AND p.status = 'active' AND s.status = 'active' + "); + $stmt_count->execute([$category_id]); + $server_count = $stmt_count->fetchColumn(); + + if ($server_count == 1) { + $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت', 'callback_data' => "renew_service_{$renewal_username}"]]; + } else { + $keyboard_buttons[] = [['text' => '◀ ؚازگ؎ت', 'callback_data' => "renewal_cat_{$category_id}"]]; + } + sendMessage($chat_id, $message, ['inline_keyboard' => $keyboard_buttons]); +} + +// ===================================================================== + + + function applyRenewal($chat_id, $username, $days_to_add, $gb_to_add) { $stmt = pdo()->prepare("SELECT server_id FROM services WHERE owner_chat_id = ? AND marzban_username = ?"); @@ -1302,16 +1525,16 @@ function showRenewalManagementMenu($chat_id, $message_id = null) { $settings = getSettings(); $status_icon = ($settings['renewal_status'] ?? 'off') == 'on' ? '✅' : '❌'; + $status_text = $status_icon == '✅' ? 'فعال' : 'غیرفعال'; + $message = "🔄 مدیریت تمدید سرویس\n\n" . - "▫ وضعیت کلی: " . ($status_icon == '✅' ? 'فعال' : 'غیرفعال') . "\n" . - "▫ هزینه هر روز تمدید: " . number_format($settings['renewal_price_per_day'] ?? 1000) . " تومان\n" . - "▫ هزینه هر گیگاؚایت تمدید: " . number_format($settings['renewal_price_per_gb'] ?? 2000) . " تومان"; + "▫ وضعیت کلی: " . $status_text . "\n\n" . + "📌 توجه: تمدید سرویس ؚر اساس انتخاؚ ٟلن انجام می‌؎ود.\n" . + "کارؚران ؚرای تمدید سرویس خود، یک ٟلن را انتخاؚ می‌کنند و قیمت آن ٟلن ؚرای تمدید محاسؚه می‌؎ود."; $keyboard = [ 'inline_keyboard' => [ [['text' => $status_icon . ' فعال/غیرفعال کردن', 'callback_data' => 'toggle_renewal_status']], - [['text' => '💰 تن؞یم قیمت روز', 'callback_data' => 'set_renewal_price_day']], - [['text' => '📊 تن؞یم قیمت حجم', 'callback_data' => 'set_renewal_price_gb']], [['text' => '◀ ؚازگ؎ت ØšÙ‡ ٟنل', 'callback_data' => 'back_to_admin_panel']], ] ]; diff --git a/src/install.php b/src/install.php index f678a43..96ff291 100644 --- a/src/install.php +++ b/src/install.php @@ -22,6 +22,7 @@ // --- داده‌های فرم --- $bot_token = trim($_POST['bot_token'] ?? ''); $admin_id = trim($_POST['admin_id'] ?? ''); +$domain_url = trim($_POST['domain_url'] ?? ''); $web_username = trim($_POST['web_username'] ?? ''); $web_password = trim($_POST['web_password'] ?? ''); @@ -183,6 +184,16 @@ function runDbUpgrades(PDO $pdo): array $errors[] = 'توکن رؚات الزامی است.'; if (empty($admin_id) || !is_numeric($admin_id)) $errors[] = 'آیدی عددی ادمین الزامی و ؚاید عدد ؚا؎د.'; + if (empty($domain_url)) + $errors[] = 'آدرس دامنه الزامی است.'; + + // حذف اسل؎ آخر از آدرس دامنه در صورت وجود + $domain_url = rtrim($domain_url, '/'); + + // ؚررسی فرمت URL + if (!empty($domain_url) && !filter_var($domain_url, FILTER_VALIDATE_URL)) { + $errors[] = 'فرمت آدرس دامنه صحیح نیست. ؚاید ؚا http:// یا https:// ؎روع ؎ود.'; + } // اگر username/password خالی ؚود، مقادیر ٟی؎‌فرض تولید ؎ود if (empty($web_username)) { @@ -236,7 +247,8 @@ function runDbUpgrades(PDO $pdo): array $config_content .= "define('DB_PASS', '{$db_pass}');" . PHP_EOL . PHP_EOL; $config_content .= "define('BOT_TOKEN', '{$bot_token}');" . PHP_EOL; $config_content .= "define('ADMIN_CHAT_ID', {$admin_id});" . PHP_EOL; - $config_content .= "define('SECRET_TOKEN', '{$secretToken}');" . PHP_EOL . PHP_EOL; + $config_content .= "define('SECRET_TOKEN', '{$secretToken}');" . PHP_EOL; + $config_content .= "define('BASE_URL', '{$domain_url}');" . PHP_EOL . PHP_EOL; $config_content .= "// Web Panel Credentials" . PHP_EOL; $config_content .= "define('WEB_USERNAME', '{$web_username}');" . PHP_EOL; $config_content .= "define('WEB_PASSWORD_HASH', '{$web_password_hash}');" . PHP_EOL; @@ -578,19 +590,17 @@ function runDbUpgrades(PDO $pdo): array
-
+
Û±
اطلاعات رؚات
-
+
Û²
دیتاؚیس
@@ -632,6 +642,12 @@ class="step 2 || ($step == 3 && empty($errors))) value="" required>

مثال: 123456789

+
+ + +

مثال: https://yourdomain.com (ؚدون اسل؎ آخر)

+
🔐 اطلاعات ورود ØšÙ‡ ٟنل تحت وؚ (اختیاری) @@ -661,6 +677,7 @@ class="step 2 || ($step == 3 && empty($errors))) +
diff --git a/src/web/dashboard.php b/src/web/dashboard.php index d40aa6e..b319317 100644 --- a/src/web/dashboard.php +++ b/src/web/dashboard.php @@ -20,48 +20,57 @@ // Get statistics $stats = []; -// Total users -$stmt = pdo()->query("SELECT COUNT(*) FROM users"); -$stats['total_users'] = $stmt->fetchColumn(); - -// Active users -$stmt = pdo()->query("SELECT COUNT(*) FROM users WHERE status = 'active'"); -$stats['active_users'] = $stmt->fetchColumn(); - -// Total services -$stmt = pdo()->query("SELECT COUNT(*) FROM services"); -$stats['total_services'] = $stmt->fetchColumn(); - -// Today income -$today_income = pdo() - ->query("SELECT SUM(p.price) FROM services s JOIN plans p ON s.plan_id = p.id WHERE DATE(s.purchase_date) = CURDATE()") - ->fetchColumn() ?? 0; -$stats['today_income'] = $today_income; - -// Month income -$month_income = pdo() - ->query("SELECT SUM(p.price) FROM services s JOIN plans p ON s.plan_id = p.id WHERE MONTH(s.purchase_date) = MONTH(CURDATE()) AND YEAR(s.purchase_date) = YEAR(CURDATE())") - ->fetchColumn() ?? 0; -$stats['month_income'] = $month_income; - -// Total servers -$stmt = pdo()->query("SELECT COUNT(*) FROM servers WHERE status = 'active'"); -$stats['total_servers'] = $stmt->fetchColumn(); - -// Pending payments -$stmt = pdo()->query("SELECT COUNT(*) FROM payment_requests WHERE status = 'pending'"); -$stats['pending_payments'] = $stmt->fetchColumn(); - -// Recent services (last 5) -$stmt = pdo()->query(" - SELECT s.*, p.name as plan_name, u.first_name - FROM services s - JOIN plans p ON s.plan_id = p.id - JOIN users u ON s.owner_chat_id = u.chat_id - ORDER BY s.id DESC - LIMIT 5 -"); -$recent_services = $stmt->fetchAll(PDO::FETCH_ASSOC); +try { + // Total users + $stmt = pdo()->query("SELECT COUNT(*) FROM users"); + $stats['total_users'] = $stmt->fetchColumn(); + + // Active users + $stmt = pdo()->query("SELECT COUNT(*) FROM users WHERE status = 'active'"); + $stats['active_users'] = $stmt->fetchColumn(); + + // Total services + $stmt = pdo()->query("SELECT COUNT(*) FROM services"); + $stats['total_services'] = $stmt->fetchColumn(); + + // Today income + $today_income = pdo() + ->query("SELECT SUM(p.price) FROM services s JOIN plans p ON s.plan_id = p.id WHERE DATE(s.purchase_date) = CURDATE()") + ->fetchColumn() ?? 0; + $stats['today_income'] = $today_income; + + // Month income + $month_income = pdo() + ->query("SELECT SUM(p.price) FROM services s JOIN plans p ON s.plan_id = p.id WHERE MONTH(s.purchase_date) = MONTH(CURDATE()) AND YEAR(s.purchase_date) = YEAR(CURDATE())") + ->fetchColumn() ?? 0; + $stats['month_income'] = $month_income; + + // Total servers + $stmt = pdo()->query("SELECT COUNT(*) FROM servers WHERE status = 'active'"); + $stats['total_servers'] = $stmt->fetchColumn(); + + // Pending payments - Handle if table doesn't exist + try { + $stmt = pdo()->query("SELECT COUNT(*) FROM payment_requests WHERE status = 'pending'"); + $stats['pending_payments'] = $stmt->fetchColumn(); + } catch (Exception $e) { + $stats['pending_payments'] = 0; + } + + // Recent services (last 5) + $stmt = pdo()->query(" + SELECT s.*, p.name as plan_name, u.first_name + FROM services s + JOIN plans p ON s.plan_id = p.id + JOIN users u ON s.owner_chat_id = u.chat_id + ORDER BY s.id DESC + LIMIT 5 + "); + $recent_services = $stmt->fetchAll(PDO::FETCH_ASSOC); + +} catch (Exception $e) { + die("Error loading dashboard: " . $e->getMessage()); +} renderHeader('دا؎ؚورد'); ?> diff --git a/src/web/includes/web_functions.php b/src/web/includes/web_functions.php index 44526f8..f321438 100644 --- a/src/web/includes/web_functions.php +++ b/src/web/includes/web_functions.php @@ -9,8 +9,13 @@ // Define USER_INLINE_KEYBOARD constant needed by functions.php if (!defined('USER_INLINE_KEYBOARD')) { - $settings = getSettings(); - define('USER_INLINE_KEYBOARD', ($settings['inline_keyboard'] ?? 'on') === 'on'); + try { + $settings = getSettings(); + define('USER_INLINE_KEYBOARD', ($settings['inline_keyboard'] ?? 'on') === 'on'); + } catch (Exception $e) { + // Default to 'on' if settings cannot be loaded + define('USER_INLINE_KEYBOARD', true); + } } /** @@ -164,19 +169,3 @@ function sanitizeInput($data) { return htmlspecialchars(strip_tags(trim($data))); } - -/** - * Helper wrappers for functions.php - */ -function getUserBalance($chat_id) -{ - $stmt = pdo()->prepare("SELECT balance FROM users WHERE chat_id = ?"); - $stmt->execute([$chat_id]); - return $stmt->fetchColumn() ?? 0; -} - -function getServers() -{ - $stmt = pdo()->query("SELECT * FROM servers WHERE status = 'active' ORDER BY id DESC"); - return $stmt->fetchAll(PDO::FETCH_ASSOC); -} diff --git a/src/web/pages/settings.php b/src/web/pages/settings.php index 0dc13b1..eb32c8a 100644 --- a/src/web/pages/settings.php +++ b/src/web/pages/settings.php @@ -68,8 +68,6 @@ // Renewal $settings_to_update['renewal_status'] = $_POST['renewal_status'] ?? 'off'; - $settings_to_update['renewal_price_per_day'] = (int) ($_POST['renewal_price_per_day'] ?? 1000); - $settings_to_update['renewal_price_per_gb'] = (int) ($_POST['renewal_price_per_gb'] ?? 2000); saveSettings($settings_to_update); $success = 'تن؞یمات ؚا موفقیت ذخیره ؎د.'; @@ -93,11 +91,11 @@
-
✅
+
✅
-
❌
+
❌
@@ -320,16 +318,6 @@
- -
- - -
- -
- - -
diff --git a/src/web/user/account.php b/src/web/user/account.php new file mode 100644 index 0000000..a384f19 --- /dev/null +++ b/src/web/user/account.php @@ -0,0 +1,189 @@ + + + + + + + + حساؚ کارؚری + + + + + + + +
+
+ +
+ + +
+
+
+ +
+

+
+ + ؎ناسه: +
+
+
+ + +
+
+
اطلاعات مالی
+
+
+
+
+
موجودی کیف ٟول +
+
+ تومان +
+
+ + ؎ارژ + +
+
+
+ + +
+
+
آمار سرویس‌ها
+
+
+
+
+
+ +
+
کل
+
+
+
+ +
+
فعال
+
+
+
+ +
+
منقضی
+
+
+
+
+ + + +
+ + + + + + + + + \ No newline at end of file diff --git a/src/web/user/assets/js/app.js b/src/web/user/assets/js/app.js index c946869..e9eb906 100644 --- a/src/web/user/assets/js/app.js +++ b/src/web/user/assets/js/app.js @@ -1,38 +1,43 @@ -const tg = window.Telegram.WebApp; +// Helper Functions for Telegram Web App Panel -// Initialize Telegram Web App -function initApp() { - tg.expand(); - - // Set theme - document.documentElement.setAttribute('data-theme', tg.colorScheme); - - // Listen for theme changes - tg.onEvent('themeChanged', function () { - document.documentElement.setAttribute('data-theme', tg.colorScheme); - }); - - // Main button setup - tg.MainButton.setParams({ - text: 'ؚستن ٟنل', - is_visible: false - }); -} - -// Show loading function showLoading() { - document.querySelector('.loading-overlay').style.display = 'flex'; + const loading = document.getElementById('loading'); + if (loading) { + loading.style.display = 'flex'; + } } -// Hide loading function hideLoading() { - document.querySelector('.loading-overlay').style.display = 'none'; + const loading = document.getElementById('loading'); + if (loading) { + loading.style.display = 'none'; + } } -// Format price function formatPrice(price) { return new Intl.NumberFormat('fa-IR').format(price) + ' تومان'; } -// Initialize -document.addEventListener('DOMContentLoaded', initApp); +function formatNumber(num) { + return new Intl.NumberFormat('fa-IR').format(num); +} + +function formatBytes(bytes) { + if (bytes === 0) return '0 B'; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB', 'TB']; + const i = Math.floor(Math.log(bytes) / Math.log(k)); + return Math.round(bytes / Math.pow(k, i) * 100) / 100 + ' ' + sizes[i]; +} + +// Jalali Date Helper (Simple implementation) +function jdate(format, timestamp) { + // This is a simplified version + // For production, use a proper Jalali date library + const date = new Date(timestamp * 1000); + const year = date.getFullYear(); + const month = String(date.getMonth() + 1).padStart(2, '0'); + const day = String(date.getDate()).padStart(2, '0'); + + return `${year}/${month}/${day}`; +} diff --git a/src/web/user/guides.php b/src/web/user/guides.php index a3863de..eb3a9bc 100644 --- a/src/web/user/guides.php +++ b/src/web/user/guides.php @@ -2,8 +2,8 @@ require_once __DIR__ . '/session.php'; requireUserLogin(); -$stmt = pdo()->query("SELECT * FROM guides WHERE status = 'active' ORDER BY id DESC"); -$guides = $stmt->fetchAll(PDO::FETCH_ASSOC); +$user = getCurrentUser(); +$guides = pdo()->query("SELECT * FROM guides WHERE status = 'active' ORDER BY id DESC")->fetchAll(PDO::FETCH_ASSOC); ?> @@ -11,12 +11,12 @@ - آموز؎‌ها + راهنماها + - @@ -26,33 +26,44 @@ -

آموز؎‌ها

+

راهنماها

-
- -

هیچ آموز؎ی یافت ن؎د.

+
+
+ +

هیچ راهنمایی موجود نیست.

+
-
-
-
-
-
-
- - -
- این آموز؎ ؎امل تصویر است که در رؚات قاؚل م؎اهده است. +
+
+
+
+ + +
- + +
+
+ + @@ -73,13 +84,33 @@ کیف ٟول - - - آموز؎ + + + ٟ؎تیؚانی
+ \ No newline at end of file diff --git a/src/web/user/index.php b/src/web/user/index.php index ec871ac..3f71952 100644 --- a/src/web/user/index.php +++ b/src/web/user/index.php @@ -1,215 +1,267 @@ true]); exit; } else { http_response_code(401); - echo json_encode(['error' => 'Invalid data']); + echo json_encode(['success' => false, 'error' => 'Invalid authentication']); exit; } } +// Require login for viewing the page +requireUserLogin(); + $user = getCurrentUser(); +$services = getUserServices($user['chat_id']); + +// Calculate stats +$total_services = count($services); +$active_services = 0; +$expired_services = 0; +$now = time(); + +foreach ($services as $service) { + if ($service['expire_timestamp'] < $now) { + $expired_services++; + } else { + $active_services++; + } +} + +// Get recent services (last 3) +$recent_services = array_slice($services, 0, 3); ?> + ٟنل کارؚری - + - -
+ + - -