From 9f9738591cb7db91cd2c4fff2016fcc9ab01199b Mon Sep 17 00:00:00 2001 From: t1amak Date: Thu, 29 May 2025 00:34:19 +0000 Subject: [PATCH] First working version. --- .gitignore | 0 LICENSE | 4 +- README.md | 3 - config.sample.php | 1 + salvium_tipbot.php | 98 +++------------------- salvium_tipbot_monitor.php | 140 +++++++++++--------------------- src/salvium_tipbot_commands.php | 87 ++++++++++++++++++++ src/salvium_tipbot_common.php | 24 ++++++ src/salvium_tipbot_db.php | 44 ++++++++++ src/salvium_tipbot_wallet.php | 23 ++++-- 10 files changed, 235 insertions(+), 189 deletions(-) mode change 100644 => 100755 .gitignore mode change 100644 => 100755 LICENSE mode change 100644 => 100755 README.md mode change 100644 => 100755 config.sample.php mode change 100644 => 100755 salvium_tipbot.php mode change 100644 => 100755 salvium_tipbot_monitor.php create mode 100644 src/salvium_tipbot_commands.php create mode 100644 src/salvium_tipbot_common.php mode change 100644 => 100755 src/salvium_tipbot_db.php mode change 100644 => 100755 src/salvium_tipbot_wallet.php diff --git a/.gitignore b/.gitignore old mode 100644 new mode 100755 diff --git a/LICENSE b/LICENSE old mode 100644 new mode 100755 index dbe6e02..10828e1 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,3 @@ -// LICENSE -/* MIT License Copyright (c) 2025 [Your Name] @@ -12,4 +10,4 @@ copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. -*/ + diff --git a/README.md b/README.md old mode 100644 new mode 100755 index bd7e522..051c22d --- a/README.md +++ b/README.md @@ -1,5 +1,3 @@ -// README.md -/* # Salvium Tip Bot A PHP-based Telegram tip bot for the Salvium (Monero fork) cryptocurrency. @@ -26,4 +24,3 @@ A PHP-based Telegram tip bot for the Salvium (Monero fork) cryptocurrency. ## License MIT -*/ diff --git a/config.sample.php b/config.sample.php old mode 100644 new mode 100755 index ef7f5ee..2257685 --- a/config.sample.php +++ b/config.sample.php @@ -12,4 +12,5 @@ return [ 'DB_CHARSET' => 'utf8mb4', 'TELEGRAM_BOT_TOKEN' => 'YOUR_TELEGRAM_BOT_TOKEN_HERE', + 'IPV4_ONLY' => 1 ]; diff --git a/salvium_tipbot.php b/salvium_tipbot.php old mode 100644 new mode 100755 index aecbe80..a34e15e --- a/salvium_tipbot.php +++ b/salvium_tipbot.php @@ -7,6 +7,8 @@ use Salvium\SalviumWallet; $config = require __DIR__ . '/config.php'; require_once __DIR__ . '/src/salvium_tipbot_db.php'; require_once __DIR__ . '/src/salvium_tipbot_wallet.php'; +require_once __DIR__ . '/src/salvium_tipbot_common.php'; +require_once __DIR__ . '/src/salvium_tipbot_commands.php'; $db = new SalviumTipBotDB($config); $wallet = new SalviumWallet( @@ -16,97 +18,21 @@ $wallet = new SalviumWallet( $config['SALVIUM_RPC_PASSWORD'] ); -function sendMessage(int $chatId, string $text, array $options = []): void { - global $config; - $payload = array_merge(['chat_id' => $chatId, 'text' => $text], $options); - file_get_contents("https://api.telegram.org/bot{$config['TELEGRAM_BOT_TOKEN']}/sendMessage?" . http_build_query($payload)); -} - $update = json_decode(file_get_contents("php://input"), true); if (!$update || !isset($update['message'])) exit; $message = $update['message']; -$chatId = $message['chat']['id']; -$userId = $message['from']['id']; -$username = $message['from']['username'] ?? ''; -$text = trim($message['text'] ?? ''); -$args = explode(' ', $text); +$args = explode(' ', trim($message['text'] ?? '')); $command = strtolower($args[0] ?? ''); -switch ($command) { - case '/start': - sendMessage($chatId, "Welcome to the Salvium Tip Bot! Use /deposit to get started."); - break; +$context = [ + 'chat_id' => $message['chat']['id'], + 'chat_name' => $message['chat']['title'] ?? $message['chat']['first_name'] ?? '', + 'username' => $message['from']['username'] ?? '', + 'user_id' => (int) $message['from']['id'], + 'raw' => $message['text'] ?? '', +]; - case '/deposit': - $user = $db->getUserByTelegramId($userId); - if (!$user) { - $subaddress = $wallet->getNewSubaddress(); - if (!$subaddress) { - sendMessage($chatId, "Error generating subaddress. Try again later."); - exit; - } - $db->createUser($userId, $subaddress); - $user = ['salvium_subaddress' => $subaddress]; - } - sendMessage($chatId, "Your Salvium deposit address is: {$user['salvium_subaddress']}"); - break; - - case '/balance': - $user = $db->getUserByTelegramId($userId); - if (!$user) { - sendMessage($chatId, "You don't have an account yet. Use /deposit to create one."); - break; - } - sendMessage($chatId, "Your balance: {$user['tip_balance']} XSL."); - break; - - case '/withdraw': - if (count($args) < 3) { - sendMessage($chatId, "Usage: /withdraw
"); - break; - } - list(, $address, $amount) = $args; - $amount = (float)$amount; - $user = $db->getUserByTelegramId($userId); - if (!$user || $user['tip_balance'] < $amount) { - sendMessage($chatId, "Insufficient balance or invalid account."); - break; - } - if (!preg_match('/^4[0-9AB][1-9A-HJ-NP-Za-km-z]{93}$/', $address)) { - sendMessage($chatId, "Invalid address format."); - break; - } - $db->updateUserTipBalance($user['id'], $amount, 'subtract'); - $db->logWithdrawal($user['id'], $address, $amount); - sendMessage($chatId, "Withdrawal request submitted. Processing soon."); - break; - - default: - if (str_starts_with($command, '/tip')) { - if (count($args) < 3) { - sendMessage($chatId, "Usage: /tip "); - break; - } - list(, $targetUsername, $amount) = $args; - $amount = (float)$amount; - $sender = $db->getUserByTelegramId($userId); - $recipient = $db->getUserByTelegramId(ltrim($targetUsername, '@')); - if (!$sender || $sender['tip_balance'] < $amount) { - sendMessage($chatId, "Insufficient funds or invalid sender."); - break; - } - if (!$recipient) { - sendMessage($chatId, "Recipient not found. Ask them to run /start first."); - break; - } - $db->updateUserTipBalance($sender['id'], $amount, 'subtract'); - $db->addTip($sender['id'], $recipient['id'], $amount, $chatId); - sendMessage($chatId, "Tipped {$targetUsername} {$amount} XSL successfully!"); - sendMessage($recipient['telegram_user_id'], "You received a tip of {$amount} XSL! Use /balance to check."); - } else { - sendMessage($chatId, "Unknown command."); - } - break; -} +$handler = new SalviumTipBotCommands($db, $wallet); +$handler->handle($command, $args, $context); ?> diff --git a/salvium_tipbot_monitor.php b/salvium_tipbot_monitor.php old mode 100644 new mode 100755 index aecbe80..ad9895f --- a/salvium_tipbot_monitor.php +++ b/salvium_tipbot_monitor.php @@ -1,12 +1,12 @@ $chatId, 'text' => $text], $options); - file_get_contents("https://api.telegram.org/bot{$config['TELEGRAM_BOT_TOKEN']}/sendMessage?" . http_build_query($payload)); +// Handle incoming transfers +$incoming = $wallet->getTransfers('in'); +if ($incoming) { + foreach ($incoming as $tx) { + if ($db->isTxidLogged($tx['txid'])) continue; + + $subaddress = $tx['address']; + $user = $db->getUserBySubaddress($subaddress); + if (!$user) continue; + + $amount = $tx['amount'] / 1e8; // Convert atomic to major units (1 SAL = 1e8 atomic) + $db->logDeposit($user['id'], $tx['txid'], $amount, $tx['height']); + $db->updateUserTipBalance($user['id'], $amount, 'add'); + + sendMessage($user['telegram_user_id'], "Deposit received: {$amount} SAL added to your balance."); + } } -$update = json_decode(file_get_contents("php://input"), true); -if (!$update || !isset($update['message'])) exit; +// Handle pending withdrawals +$withdrawals = $db->getPendingWithdrawals(); +foreach ($withdrawals as $withdrawal) { + $result = $wallet->transfer([ + [ + 'address' => $withdrawal['address'], + 'amount' => (int)($withdrawal['amount'] * 1e8) // major to atomic + ] + ]); -$message = $update['message']; -$chatId = $message['chat']['id']; -$userId = $message['from']['id']; -$username = $message['from']['username'] ?? ''; -$text = trim($message['text'] ?? ''); -$args = explode(' ', $text); -$command = strtolower($args[0] ?? ''); - -switch ($command) { - case '/start': - sendMessage($chatId, "Welcome to the Salvium Tip Bot! Use /deposit to get started."); - break; - - case '/deposit': - $user = $db->getUserByTelegramId($userId); - if (!$user) { - $subaddress = $wallet->getNewSubaddress(); - if (!$subaddress) { - sendMessage($chatId, "Error generating subaddress. Try again later."); - exit; - } - $db->createUser($userId, $subaddress); - $user = ['salvium_subaddress' => $subaddress]; - } - sendMessage($chatId, "Your Salvium deposit address is: {$user['salvium_subaddress']}"); - break; - - case '/balance': - $user = $db->getUserByTelegramId($userId); - if (!$user) { - sendMessage($chatId, "You don't have an account yet. Use /deposit to create one."); - break; - } - sendMessage($chatId, "Your balance: {$user['tip_balance']} XSL."); - break; - - case '/withdraw': - if (count($args) < 3) { - sendMessage($chatId, "Usage: /withdraw
"); - break; - } - list(, $address, $amount) = $args; - $amount = (float)$amount; - $user = $db->getUserByTelegramId($userId); - if (!$user || $user['tip_balance'] < $amount) { - sendMessage($chatId, "Insufficient balance or invalid account."); - break; - } - if (!preg_match('/^4[0-9AB][1-9A-HJ-NP-Za-km-z]{93}$/', $address)) { - sendMessage($chatId, "Invalid address format."); - break; - } - $db->updateUserTipBalance($user['id'], $amount, 'subtract'); - $db->logWithdrawal($user['id'], $address, $amount); - sendMessage($chatId, "Withdrawal request submitted. Processing soon."); - break; - - default: - if (str_starts_with($command, '/tip')) { - if (count($args) < 3) { - sendMessage($chatId, "Usage: /tip "); - break; - } - list(, $targetUsername, $amount) = $args; - $amount = (float)$amount; - $sender = $db->getUserByTelegramId($userId); - $recipient = $db->getUserByTelegramId(ltrim($targetUsername, '@')); - if (!$sender || $sender['tip_balance'] < $amount) { - sendMessage($chatId, "Insufficient funds or invalid sender."); - break; - } - if (!$recipient) { - sendMessage($chatId, "Recipient not found. Ask them to run /start first."); - break; - } - $db->updateUserTipBalance($sender['id'], $amount, 'subtract'); - $db->addTip($sender['id'], $recipient['id'], $amount, $chatId); - sendMessage($chatId, "Tipped {$targetUsername} {$amount} XSL successfully!"); - sendMessage($recipient['telegram_user_id'], "You received a tip of {$amount} XSL! Use /balance to check."); - } else { - sendMessage($chatId, "Unknown command."); - } - break; + if ($result && isset($result['tx_hash'])) { + $db->updateWithdrawalTxid($withdrawal['id'], $result['tx_hash']); + $db->updateWithdrawalStatus($withdrawal['id'], 'sent'); + sendMessage($withdrawal['user_id'], "Withdrawal of {$withdrawal['amount']} SAL sent. TxID: {$result['tx_hash']}"); + } else { + $db->updateWithdrawalStatus($withdrawal['id'], 'failed'); + sendMessage($withdrawal['user_id'], "Withdrawal failed. Please try again later or contact support."); + } } + +// Handle pending tips +$users = []; +$tips = $db->getAllPendingTips(); +foreach ($tips as $tip) { + $recipientId = $tip['recipient_user_id']; + if (!isset($users[$recipientId])) { + $users[$recipientId] = $db->getUserByTelegramId($recipientId); + } + $db->updateUserTipBalance($recipientId, $tip['amount'], 'add'); + $db->markTipsAsCredited([$tip['id']]); + sendMessage($recipientId, "You received a credited tip of {$tip['amount']} SAL. Use /balance to check."); +} + ?> diff --git a/src/salvium_tipbot_commands.php b/src/salvium_tipbot_commands.php new file mode 100644 index 0000000..e359696 --- /dev/null +++ b/src/salvium_tipbot_commands.php @@ -0,0 +1,87 @@ +db = $db; + $this->wallet = $wallet; + } + + public function handle(string $command, array $args, array $context): void { + $method = 'cmd_' . ltrim($command, '/'); + if (method_exists($this, $method)) { + $response = $this->$method($args, $context); + } else { + //$response = "Unknown command."; + $response = ""; + } + + sendMessage($context['chat_id'], $response); + $this->db->logMessage($context['chat_id'], $context['chat_name'], $context['username'], $context['raw'], $response); + } + + private function cmd_start(array $args, array $ctx): string { + if (!empty($ctx['username'])) { + $this->db->updateUsername($ctx['user_id'], $ctx['username']); + } + return "Welcome to the Salvium Tip Bot! Use /deposit to get started."; + } + + private function cmd_deposit(array $args, array $ctx): string { + $user = $this->db->getUserByTelegramId($ctx['user_id']); + if (!$user) { + $sub = $this->wallet->getNewSubaddress(); + if (!$sub) return "Error generating subaddress."; + $this->db->createUser($ctx['user_id'], $sub); + if (!empty($ctx['username'])) { + $this->db->updateUsername($ctx['user_id'], $ctx['username']); + } + return "Your deposit address: $sub"; + } + return "Your deposit address: {$user['salvium_subaddress']}"; + } + + private function cmd_balance(array $args, array $ctx): string { + $user = $this->db->getUserByTelegramId($ctx['user_id']); + return $user ? "Your balance: {$user['tip_balance']} SAL" : "No account found. Use /deposit first."; + } + + private function cmd_withdraw(array $args, array $ctx): string { + if (count($args) < 3) return "Usage: /withdraw
"; + + list(, $address, $amount) = $args; + $amount = (float) $amount; + $user = $this->db->getUserByTelegramId($ctx['user_id']); + + if (!$user || $user['tip_balance'] < $amount) return "Insufficient balance or invalid account."; + if (!isValidSalviumAddress($address)) return "Invalid SAL address format."; + + $this->db->updateUserTipBalance($user['id'], $amount, 'subtract'); + $this->db->logWithdrawal($user['id'], $address, $amount); + return "Withdrawal request submitted. Processing soon."; + } + + + private function cmd_tip(array $args, array $ctx): string { + if (count($args) < 3) return "Usage: /tip "; + + list(, $targetUsername, $amount) = $args; + $amount = (float)$amount; + $sender = $this->db->getUserByTelegramId($ctx['user_id']); + $recipient = $this->db->getUserByUsername(ltrim($targetUsername, '@')); + + if (!$sender || $sender['tip_balance'] < $amount) return "Insufficient funds or invalid sender."; + if (!$recipient) return "Recipient not found. Ask them to run /start first."; + + $this->db->updateUserTipBalance($sender['id'], $amount, 'subtract'); + $this->db->addTip($sender['id'], $recipient['id'], $amount, $ctx['chat_id']); + sendMessage($recipient['telegram_user_id'], "You received a tip of {$amount} SAL! Use /balance to check."); + + return "Tipped {$targetUsername} {$amount} SAL successfully!"; + } +} +?> diff --git a/src/salvium_tipbot_common.php b/src/salvium_tipbot_common.php new file mode 100644 index 0000000..bfaf0c7 --- /dev/null +++ b/src/salvium_tipbot_common.php @@ -0,0 +1,24 @@ + $chatId, 'text' => $text], $options); + + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_POST, true); + curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($payload)); + if (!empty($config['IPV4_ONLY'])) { + curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); + } + curl_exec($ch); + curl_close($ch); +} + +function isValidSalviumAddress(string $address): bool { + // Accepts standard and subaddress prefixes for Salvium (e.g. SaLvd, SaLvs) + return preg_match('/^SaLv[a-zA-Z0-9]{95}$/', $address) === 1; +} + +?> diff --git a/src/salvium_tipbot_db.php b/src/salvium_tipbot_db.php old mode 100644 new mode 100755 index 9af18ca..5e6f05b --- a/src/salvium_tipbot_db.php +++ b/src/salvium_tipbot_db.php @@ -63,6 +63,17 @@ class SalviumTipBotDB { FOREIGN KEY (sender_user_id) REFERENCES users(id), FOREIGN KEY (recipient_user_id) REFERENCES users(id) ); + + CREATE TABLE bot_log ( + id INT AUTO_INCREMENT PRIMARY KEY, + chat_id BIGINT NOT NULL, + chat_name VARCHAR(255), + username VARCHAR(255), + message TEXT, + response TEXT, + logged_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ); + */ public function getUserByTelegramId(int $telegramUserId): array|false { @@ -71,6 +82,17 @@ class SalviumTipBotDB { return $stmt->fetch(PDO::FETCH_ASSOC); } + public function getUserByUsername(string $username): array|false { + $stmt = $this->pdo->prepare("SELECT * FROM users WHERE username = ?"); + $stmt->execute([$username]); + return $stmt->fetch(PDO::FETCH_ASSOC); + } + + public function updateUsername(int $telegramUserId, string $username): void { + $stmt = $this->pdo->prepare("UPDATE users SET username = ? WHERE telegram_user_id = ?"); + $stmt->execute([$username, $telegramUserId]); + } + public function getUserBySubaddress(string $subaddress): array|false { $stmt = $this->pdo->prepare("SELECT * FROM users WHERE salvium_subaddress = ?"); $stmt->execute([$subaddress]); @@ -127,6 +149,11 @@ class SalviumTipBotDB { return $stmt->execute([$senderUserId, $recipientUserId, $amount, $channelId]); } + public function getAllPendingTips(): array { + $stmt = $this->pdo->query("SELECT * FROM tips WHERE status = 'pending'"); + return $stmt->fetchAll(PDO::FETCH_ASSOC); + } + public function getPendingTipsForUser(int $recipientUserId): array { $stmt = $this->pdo->prepare("SELECT * FROM tips WHERE recipient_user_id = ? AND status = 'pending'"); $stmt->execute([$recipientUserId]); @@ -143,5 +170,22 @@ class SalviumTipBotDB { $stmt = $this->pdo->query("SELECT * FROM withdrawals WHERE status = 'pending'"); return $stmt->fetchAll(PDO::FETCH_ASSOC); } + + public function logMessage(int $chatId, string $chatName, string $username, string $message, string $response): void { + try { + $sql = "INSERT INTO bot_log (chat_id, chat_name, username, message, response) VALUES (:chat_id, :chat_name, :username, :message, :response)"; + $stmt = $this->pdo->prepare($sql); + $stmt->execute([ + ':chat_id' => $chatId, + ':chat_name' => $chatName, + ':username' => $username, + ':message' => $message, + ':response' => $response, + ]); + } catch (PDOException $e) { + error_log("Error logging message: " . $e->getMessage()); + } + } + } ?> diff --git a/src/salvium_tipbot_wallet.php b/src/salvium_tipbot_wallet.php old mode 100644 new mode 100755 index 0713b7b..2b34bbb --- a/src/salvium_tipbot_wallet.php +++ b/src/salvium_tipbot_wallet.php @@ -68,14 +68,25 @@ class SalviumWallet { return $result['addresses'] ?? false; } - public function transfer(array $destinations, int $mixin = 11, int $unlockTime = 0, bool $getTxKey = false, bool $doNotRelay = false): array|false { + public function transfer(array $destinations, int $accountIndex = 0, array $subaddrIndices = [0], int $priority = 0, int $ringSize = 16, bool $getTxKey = true): array|false { $params = [ - 'destinations' => $destinations, - 'mixin' => $mixin, - 'unlock_time' => $unlockTime, - 'get_tx_key' => $getTxKey, - 'do_not_relay' => $doNotRelay + 'destinations' => array_map(function ($dest) { + return [ + 'address' => $dest['address'], + 'amount' => (int)($dest['amount']), // already in atomic units + 'asset_type' => 'SAL1' + ]; + }, $destinations), + 'source_asset' => 'SAL1', + 'dest_asset' => 'SAL1', + 'tx_type' => 3, + 'account_index' => $accountIndex, + // 'subaddr_indices' => $subaddrIndices, + 'priority' => $priority, + 'ring_size' => $ringSize, + 'get_tx_key' => $getTxKey ]; + return $this->_callRpc('transfer', $params); }