Initial commit - purely ai generated code

This commit is contained in:
t1amak
2025-05-27 11:13:33 +00:00
commit 0fbf1f0200
7 changed files with 524 additions and 0 deletions
+8
View File
@@ -0,0 +1,8 @@
// .gitignore
// config.php
config.php
*.log
.DS_Store
*.swp
.vscode/
.idea/
+15
View File
@@ -0,0 +1,15 @@
// LICENSE
/*
MIT License
Copyright (c) 2025 [Your Name]
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
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.
*/
+28
View File
@@ -0,0 +1,28 @@
// README.md
/*
# Salvium Tip Bot
A PHP-based Telegram tip bot for the Salvium (Monero fork) cryptocurrency.
## Features
- Telegram tipping via subaddresses
- Wallet interaction via JSON-RPC
- Secure MySQL backend using PDO
- Deposit, withdraw, balance, and tipping commands
- Cron-compatible monitoring of deposits and withdrawals
## Installation
1. Clone the repo
2. Update `config.php` with your RPC, DB, and Telegram Bot credentials
3. Run `salvium_tipbot.php` as a Telegram webhook listener
4. Schedule `salvium_tipbot_monitor.php` using `cron` for periodic checks
## Requirements
- PHP 8.1+
- MySQL 5.7+/MariaDB
- Telegram Bot Token
- Salvium RPC Wallet node
## License
MIT
*/
+112
View File
@@ -0,0 +1,112 @@
<?php
// salvium_tipbot.php
use Salvium\SalviumTipBotDB;
use Salvium\SalviumWallet;
$config = require __DIR__ . '/config.php';
require_once __DIR__ . '/src/salvium_tipbot_db.php';
require_once __DIR__ . '/src/salvium_tipbot_wallet.php';
$db = new SalviumTipBotDB($config);
$wallet = new SalviumWallet(
$config['SALVIUM_RPC_HOST'],
$config['SALVIUM_RPC_PORT'],
$config['SALVIUM_RPC_USERNAME'],
$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);
$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 <address> <amount>");
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 <username> <amount>");
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;
}
?>
+112
View File
@@ -0,0 +1,112 @@
<?php
// salvium_tipbot.php
use Salvium\SalviumTipBotDB;
use Salvium\SalviumWallet;
$config = require __DIR__ . '/config.php';
require_once __DIR__ . '/src/salvium_tipbot_db.php';
require_once __DIR__ . '/src/salvium_tipbot_wallet.php';
$db = new SalviumTipBotDB($config);
$wallet = new SalviumWallet(
$config['SALVIUM_RPC_HOST'],
$config['SALVIUM_RPC_PORT'],
$config['SALVIUM_RPC_USERNAME'],
$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);
$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 <address> <amount>");
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 <username> <amount>");
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;
}
?>
+147
View File
@@ -0,0 +1,147 @@
<?php
// src/salvium_tipbot_db.php
namespace Salvium;
use PDO;
use PDOException;
class SalviumTipBotDB {
private PDO $pdo;
public function __construct(array $config) {
try {
$dsn = "mysql:host={$config['DB_HOST']};dbname={$config['DB_NAME']};charset={$config['DB_CHARSET']}";
$this->pdo = new PDO($dsn, $config['DB_USER'], $config['DB_PASS']);
$this->pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
error_log("Database connection failed: " . $e->getMessage());
die("Database connection error.");
}
}
// --- Table Creation SQL (Commented for reference) ---
/*
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
telegram_user_id BIGINT UNIQUE NOT NULL,
salvium_subaddress VARCHAR(128) UNIQUE NOT NULL,
tip_balance DECIMAL(20, 12) DEFAULT 0.000000000000,
withdrawal_address VARCHAR(128),
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
);
CREATE TABLE deposits (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
txid VARCHAR(64) UNIQUE NOT NULL,
amount DECIMAL(20, 12) NOT NULL,
block_height INT NOT NULL,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE withdrawals (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
txid VARCHAR(64),
address VARCHAR(128) NOT NULL,
amount DECIMAL(20, 12) NOT NULL,
status ENUM('pending', 'sent', 'failed') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (user_id) REFERENCES users(id)
);
CREATE TABLE tips (
id INT PRIMARY KEY AUTO_INCREMENT,
sender_user_id INT NOT NULL,
recipient_user_id INT NOT NULL,
amount DECIMAL(20, 12) NOT NULL,
channel_id BIGINT,
status ENUM('pending', 'credited') DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (sender_user_id) REFERENCES users(id),
FOREIGN KEY (recipient_user_id) REFERENCES users(id)
);
*/
public function getUserByTelegramId(int $telegramUserId): array|false {
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE telegram_user_id = ?");
$stmt->execute([$telegramUserId]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
public function getUserBySubaddress(string $subaddress): array|false {
$stmt = $this->pdo->prepare("SELECT * FROM users WHERE salvium_subaddress = ?");
$stmt->execute([$subaddress]);
return $stmt->fetch(PDO::FETCH_ASSOC);
}
public function createUser(int $telegramUserId, string $subaddress): bool {
$stmt = $this->pdo->prepare("INSERT INTO users (telegram_user_id, salvium_subaddress) VALUES (?, ?)");
return $stmt->execute([$telegramUserId, $subaddress]);
}
public function updateUserTipBalance(int $userId, float $amount, string $operation = 'add'): bool {
$sql = $operation === 'add' ? "UPDATE users SET tip_balance = tip_balance + ? WHERE id = ?" : "UPDATE users SET tip_balance = tip_balance - ? WHERE id = ?";
$stmt = $this->pdo->prepare($sql);
return $stmt->execute([$amount, $userId]);
}
public function setWithdrawalAddress(int $userId, string $address): bool {
$stmt = $this->pdo->prepare("UPDATE users SET withdrawal_address = ? WHERE id = ?");
return $stmt->execute([$address, $userId]);
}
public function logDeposit(int $userId, string $txid, float $amount, int $blockHeight): bool {
$stmt = $this->pdo->prepare("INSERT INTO deposits (user_id, txid, amount, block_height) VALUES (?, ?, ?, ?)");
return $stmt->execute([$userId, $txid, $amount, $blockHeight]);
}
public function isTxidLogged(string $txid): bool {
$stmt = $this->pdo->prepare("SELECT COUNT(*) FROM deposits WHERE txid = ?");
$stmt->execute([$txid]);
return $stmt->fetchColumn() > 0;
}
public function logWithdrawal(int $userId, string $address, float $amount): int|false {
$stmt = $this->pdo->prepare("INSERT INTO withdrawals (user_id, address, amount) VALUES (?, ?, ?)");
if ($stmt->execute([$userId, $address, $amount])) {
return $this->pdo->lastInsertId();
}
return false;
}
public function updateWithdrawalTxid(int $withdrawalId, string $txid): bool {
$stmt = $this->pdo->prepare("UPDATE withdrawals SET txid = ? WHERE id = ?");
return $stmt->execute([$txid, $withdrawalId]);
}
public function updateWithdrawalStatus(int $withdrawalId, string $status): bool {
$stmt = $this->pdo->prepare("UPDATE withdrawals SET status = ? WHERE id = ?");
return $stmt->execute([$status, $withdrawalId]);
}
public function addTip(int $senderUserId, int $recipientUserId, float $amount, ?int $channelId = null): bool {
$stmt = $this->pdo->prepare("INSERT INTO tips (sender_user_id, recipient_user_id, amount, channel_id) VALUES (?, ?, ?, ?)");
return $stmt->execute([$senderUserId, $recipientUserId, $amount, $channelId]);
}
public function getPendingTipsForUser(int $recipientUserId): array {
$stmt = $this->pdo->prepare("SELECT * FROM tips WHERE recipient_user_id = ? AND status = 'pending'");
$stmt->execute([$recipientUserId]);
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
public function markTipsAsCredited(array $tipIds): bool {
$placeholders = implode(',', array_fill(0, count($tipIds), '?'));
$stmt = $this->pdo->prepare("UPDATE tips SET status = 'credited' WHERE id IN ($placeholders)");
return $stmt->execute($tipIds);
}
public function getPendingWithdrawals(): array {
$stmt = $this->pdo->query("SELECT * FROM withdrawals WHERE status = 'pending'");
return $stmt->fetchAll(PDO::FETCH_ASSOC);
}
}
?>
+102
View File
@@ -0,0 +1,102 @@
<?php
// src/salvium_tipbot_wallet.php
namespace Salvium;
class SalviumWallet {
private string $host;
private int $port;
private ?string $username;
private ?string $password;
public function __construct(string $host, int $port, ?string $username = null, ?string $password = null) {
$this->host = $host;
$this->port = $port;
$this->username = $username;
$this->password = $password;
}
private function _callRpc(string $method, array $params = []): array|false {
$url = "http://{$this->host}:{$this->port}/json_rpc";
$request = json_encode([
'jsonrpc' => '2.0',
'id' => '0',
'method' => $method,
'params' => $params
]);
$headers = ['Content-Type: application/json'];
$options = [
CURLOPT_URL => $url,
CURLOPT_RETURNTRANSFER => true,
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => $request,
CURLOPT_HTTPHEADER => $headers,
];
if ($this->username && $this->password) {
$options[CURLOPT_USERPWD] = "{$this->username}:{$this->password}";
}
$ch = curl_init();
curl_setopt_array($ch, $options);
$response = curl_exec($ch);
if (curl_errno($ch)) {
error_log('RPC Curl Error: ' . curl_error($ch));
curl_close($ch);
return false;
}
curl_close($ch);
$decoded = json_decode($response, true);
return $decoded['result'] ?? false;
}
public function getWalletBalance(): array|false {
return $this->_callRpc('get_balance');
}
public function getNewSubaddress(int $accountIndex = 0, ?string $label = null): string|false {
$params = ['account_index' => $accountIndex];
if ($label) $params['label'] = $label;
$result = $this->_callRpc('create_address', $params);
return $result['address'] ?? false;
}
public function getAddresses(): array|false {
$result = $this->_callRpc('get_address');
return $result['addresses'] ?? false;
}
public function transfer(array $destinations, int $mixin = 11, int $unlockTime = 0, bool $getTxKey = false, bool $doNotRelay = false): array|false {
$params = [
'destinations' => $destinations,
'mixin' => $mixin,
'unlock_time' => $unlockTime,
'get_tx_key' => $getTxKey,
'do_not_relay' => $doNotRelay
];
return $this->_callRpc('transfer', $params);
}
public function getTransfers(string $inOrOut = 'in', bool $pending = false, bool $failed = false): array|false {
$params = [
$inOrOut => true,
'pending' => $pending,
'failed' => $failed
];
$result = $this->_callRpc('get_transfers', $params);
return $result[$inOrOut] ?? false;
}
public function getPayments(string $paymentId): array|false {
$result = $this->_callRpc('get_payments', ['payment_id' => $paymentId]);
return $result['payments'] ?? false;
}
public function getHeight(): int|false {
$result = $this->_callRpc('get_height');
return $result['height'] ?? false;
}
}
?>