HEX
Server: nginx/1.18.0
System: Linux test-ipsremont 5.4.0-214-generic #234-Ubuntu SMP Fri Mar 14 23:50:27 UTC 2025 x86_64
User: ips (1000)
PHP: 8.0.30
Disabled: pcntl_alarm,pcntl_fork,pcntl_waitpid,pcntl_wait,pcntl_wifexited,pcntl_wifstopped,pcntl_wifsignaled,pcntl_wifcontinued,pcntl_wexitstatus,pcntl_wtermsig,pcntl_wstopsig,pcntl_signal,pcntl_signal_get_handler,pcntl_signal_dispatch,pcntl_get_last_error,pcntl_strerror,pcntl_sigprocmask,pcntl_sigwaitinfo,pcntl_sigtimedwait,pcntl_exec,pcntl_getpriority,pcntl_setpriority,pcntl_async_signals,pcntl_unshare,
Upload Files
File: /var/www/exnova-telegram-bot-v2/app/Models/User.php
<?php

namespace App\Models;


use Illuminate\Support\Facades\Log as SysLog;
use App\Jobs\InitTradingSession;
use App\Telegram\Messages\BlocksHandler;
use App\Telegram\Messages\ParamsReplacer;
use App\Telegram\Messages\Postman;
use App\Telegram\Messages\Preparer;
use App\Telegram\Utils\Curl;
use App\Twill\Capsules\Bots\Models\Bot;
use App\Twill\Capsules\Bots\Models\BotUser;
use App\Twill\Capsules\ExnovaAccounts\Models\ExnovaAccount;
use App\Twill\Capsules\Logs\Models\Log;
use App\Twill\Capsules\Spins\Models\Balance;
use App\Twill\Capsules\Spins\Models\Spin;
use App\Twill\Capsules\Strategies\Models\Strategy;
use App\Twill\Capsules\TradingSessions\Models\TradingSession;
use App\Twill\Capsules\UserSettings\Models\UserSetting;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Carbon;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;

class User extends Authenticatable
{

    /** @use HasFactory<\Database\Factories\UserFactory> */
    use HasFactory, Notifiable;

    /**
     * The attributes that are mass assignable.
     *
     * @var list<string>
     */
    protected $fillable = [
        'name',
        'email',
        'password',
        'telegram_id',
        'telegram_name',
        'first_name',
        'last_name',
        'locale',
        'state',
        'last_message_sent',
        'state_changed_date',
        'retention_sent_date',
        'referral_params',
        'country_code',
    ];

    /**
     * The attributes that should be hidden for serialization.
     *
     * @var list<string>
     */
    protected $hidden = [
        'password',
        'remember_token',
    ];

    /**
     * Get the attributes that should be cast.
     *
     * @return array<string, string>
     */
    protected function casts(): array
    {
        return [
            'email_verified_at' => 'datetime',
            'password' => 'hashed',
        ];
    }

    public function settings()
    {
        return $this->hasOne(UserSetting::class, 'user_id');
    }

    public function spins()
    {
        return $this->hasOne(Spin::class, 'user_id');
    }

    public function logs()
    {
        return $this->hasMany(Log::class, 'user_id');
    }

    public function getTelegramUserById($id)
    {
        return $this->where('telegram_id', '=', $id)->first();
    }

    public function getBotUser(int $botId)
    {
        $botUser = Botuser::query()
            ->where('users_id', $this->id)
            ->where('bots_id', $botId);

        $botUser = $botUser->first();

        return $botUser;
    }

    public static function createUserFromTelegram($data)
    {
        $activated = true;

        if (empty($data['username']) && !empty($data['id'])) {
            $data['username'] = $data['id'];
        }

        $email = $data['username'] . "@example.com";
        $iniPassword = substr(md5(date('YmdHis')), 0, 6);
        $password = Hash::make($iniPassword);

        $userData = [
            'name' => strip_tags(@$data['username']),
            'telegram_name' => strip_tags(@$data['username']),
            'telegram_id' => strip_tags($data['id']),
            'first_name' => strip_tags(@$data['first_name']),
            'last_name' => strip_tags(@$data['last_name']),
            'email' => $email,
            'password' => $password,
            'token' => Str::random(64),
            'activated' => $activated,
            'bot_user_types' => ['user'],
            'locale' => $data['language_code'] ?? 'en',
            'referral_params' => !empty($data['referral_params']) ? $data['referral_params'] : null,
            'country_code' => !empty($data['country_code']) ? $data['country_code'] : null,
        ];


        if (isset($data['type']) && $data['type'] == 'group') {
            $userData['name'] = $data['title'] . $data['username'];
            $userData['telegram_name'] = $data['title'];
        }

        if (isset($data['type']) && $data['type'] == 'supergroup') {
            $userData['name'] = $data['username'];
            $userData['telegram_name'] = $data['title'];
        }

        $user = User::create($userData);

        if (!empty($data['photo_url'])) {
            $user->avatar = strip_tags($data['photo_url']);
        }
        $user->save();

        $userSettings = new UserSetting();
        $userSettings->user_id = $user->id;
        $userSettings->signal_generator_state = 0;
        $userSettings->save();

        $spins = new Spin();
        $spins->user_id = $user->id;
        $spins->amount = 100;
        $spins->save();

        return $user;
    }

    public function exnovaRegisterUser($email, $password, $country, $affid)
    {
        $url = 'https://api.trade.exnova.com/v5/users/register';

        $data = [
            'identifier' => $email,
            'password' => $password,
            'country_id' => $country,
            'aff' => (int)$affid,
            'afftrack' => 'tg_backend_reg',
            'accepted' => ['terms', 'privacy policy'],
        ];

        $curl = new Curl();
        $response = $curl->exnovaRequest($url, $data);

        return $response;
    }

    public function exnovaRefreshSsid()
    {
        if (!$this->activeExnovaAccount()) {
            return false;
        }

        $activeAccount = $this->activeExnovaAccount();
        $accountUpdatedAt = Carbon::parse($activeAccount->ssid_date);
        $date = Carbon::now()->subDay(7);
        if ($activeAccount->ssid_date && $activeAccount->ssid && $accountUpdatedAt > $date) {
            return $activeAccount->ssid;
        }
        $url = 'https://api.trade.exnova.com/v2/login';

        $data = [
            'identifier' => $activeAccount->email,
            'password' => $activeAccount->password,
        ];

        $curl = new Curl();
        $response = $curl->exnovaRequest($url, $data);


        if (!empty($response['status']) && $response['status'] == 'error') {
            throw new \Exception($response['message']);
        }

        if (!empty($response['code']) && $response['code'] == 'invalid_credentials') {
            $post = [];
            $post['bot_id'] = $this->bot_id;
            $blocksHandler = new BlocksHandler($this, $post);
            $bot = new Bot();
            $blocks = $bot->getBlocks('invalidCredentials', $this);
            $blocksHandler->handle($blocks);

            $errorMessage = 'Error during refreshing SSID. Invalid credentials for user: ' . $this->telegram_name . ' account: ' . $activeAccount->email;
            $this->sendMessageToChannel($errorMessage);
        } elseif (!empty($response['data']) && !empty($response['data']['ssid'])) {
            $activeAccount->ssid = $response['data']['ssid'];
            if (!empty( $response['data']['user_id'])) {
                $activeAccount->exnova_id = $response['data']['user_id'];
            }
            $activeAccount->ssid_date = Carbon::now();
            $activeAccount->save();
        } elseif (!empty($response['ssid'])) {
            $activeAccount->ssid = $response['ssid'];
            if (!empty( $response['user_id'])) {
                $activeAccount->exnova_id = $response['user_id'];
            }
            $activeAccount->ssid_date = Carbon::now();
            $activeAccount->save();
        } else {
            $errorMessage = 'Error during refreshing SSID. Invalid credentials for user: ' . $this->telegram_name . ' account: ' . $activeAccount->email . "\n" . json_encode($response);
            throw new \Exception($errorMessage);
        }

        return $activeAccount->ssid;
    }

    public function exnovaAuthorizeAccount($email, $password)
    {
        $url = 'https://api.trade.exnova.com/v2/login';

        $data = [
            'identifier' => $email,
            'password' => $password,
        ];

        $curl = new Curl();
        $response = $curl->exnovaRequest($url, $data);
        if (!empty($response['status']) && $response['status'] == 'error') {
            throw new \Exception($response['message']);
        }

        return $response;
    }

    public function exnovaAccounts()
    {
        return $this->hasMany(ExnovaAccount::class);
    }

    public function activeExnovaAccount()
    {
        return $this->exnovaAccounts()->getResults()->where('is_active', 1)->first();
    }

    public function tradingSessions()
    {
        return $this->hasMany(TradingSession::class);
    }

    public function activeTradingSession()
    {
        return $this->tradingSessions()->getResults()->where('is_active', 1)->first();
    }

    public function balances()
    {
        return $this->hasMany(Balance::class);
    }

    public function debug($message = '', $force = false)
    {
        if ($force || $this->telegram_name == 'lifecraft' || $this->telegram_name == 'Mendoza911') {
            $postman = new Postman($this, []);
            if (empty($message)) {
                $message = 'Empty message';
            }
            if (is_array($message)) {
                $message = json_encode($message);
            }

            $message = Preparer::cleanText($message);

            $postman->sendMessage($message);
        }
    }

    public function superDebug($session, $addMessage = '')
    {
        $message = '';

        $date = date('Y-m-d H:i:s');
        $message .= $date . "   ";
        $message .= 'Session #'.$session->id . " " . $addMessage . "    ";

        $message .= sprintf("CPU Load: %.2f%%", $this->getServerLoad()). "    ";
        $message .= sprintf("Memory Usage: %.2f MB", $this->getServerMemoryUsage()) . "  ";
        $totalActiveSessions = TradingSession::query()
            ->where('is_active', 1)
            ->count();

        $message .= "   Total active sessions: " . $totalActiveSessions;

        if ($addMessage == 'finished') {
            $diff = (strtotime($session->updated_at) - strtotime($session->created_at));
            $message .= "   Session duration: " . gmdate("H:i:s", $diff);
            $tradingDiff = (strtotime($session->updated_at) - strtotime($session->start_trading_at));
            $message .= "   Trading duration: " . gmdate("H:i:s", $tradingDiff);
            $message .= "   Trades: " . $session->used_spins;
        }
        SysLog::channel('trading')->info($message);

//        $this->sendMessageToChannel($message);
    }

    function getServerMemoryUsage() {
        $meminfo = file_get_contents("/proc/meminfo");
        $data = [];
        foreach (explode("\n", $meminfo) as $line) {
            if (preg_match('/^(\w+):\s+(\d+)\s+kB$/', $line, $matches)) {
                $data[$matches[1]] = (int)$matches[2];
            }
        }

        $total = $data['MemTotal'] ?? 0;
        $available = $data['MemAvailable'] ?? 0;

        return ($total - $available) / 1024; // Convert to MB
    }

    public function getServerLoad() {
        if (is_readable("/proc/stat")) {
            $stat1 = file_get_contents("/proc/stat");
            sleep(1);
            $stat2 = file_get_contents("/proc/stat");

            $cpu1 = explode(" ", preg_replace("/cpu +/", "", explode("\n", $stat1)[0]));
            $cpu2 = explode(" ", preg_replace("/cpu +/", "", explode("\n", $stat2)[0]));

            $idle1 = $cpu1[3];
            $idle2 = $cpu2[3];

            $total1 = array_sum($cpu1);
            $total2 = array_sum($cpu2);

            $idle = $idle2 - $idle1;
            $total = $total2 - $total1;

            $cpuUsage = 100 * ($total - $idle) / $total;

            return round($cpuUsage, 2);
        } else {
            return 0;
        }
    }

    public function sendMessageToChannel($message = '')
    {
        $channelId = Settings::get('admin.messages', 'errors_channel');

        if (empty($message)) {
            $message = 'Empty message';
        }
        if (is_array($message)) {
            $message = json_encode($message);
        }

        $paramsReplacer = new ParamsReplacer($this, []);
        $message = $paramsReplacer->replaceParams($message, true);

        $message = Preparer::cleanText($message);

        $channel = User::where('telegram_id', $channelId)->first();
        $postman = new Postman($channel, []);
        $postman->sendMessage($message, []);

        $this->debug("<b>DEBUG VISIBLE ONLY FOR DEVELOPERS</b>\n".$message);
    }

    public function getBalances()
    {
        if (substr_count($this->email, 'lkashdjhskdjfh') && $this->balance_type == 'real') {
            // PROMO account
            return ['type' => 'real', 'amount' => 999, 'currency' => 'USD'];
        }

        if (!$activeAccount = $this->activeExnovaAccount()) {
            return [];
        }

        $this->exnovaRefreshSsid();

        $output = [];
        $returnStatus = null;
        $shellCommand = 'timeout 10s node ' . base_path() . '/ssid-ws.js ' . $this->activeExnovaAccount()->ssid . ' allBalances';

        $result = exec($shellCommand, $output, $returnStatus);

        if ($returnStatus != 0) {
            $this->sendMessageToChannel("@{$this->telegram_name} \n Get balances command failed. Response: " . json_encode($output) . "\n Status:" . $returnStatus);
        }
        $balances = json_decode($result, true);

        if (empty($balances)) {
            return [];
        }

        foreach ($balances as $balance) {
            $balanceObject = Balance::where('exnova_id', $balance['id'])->first();
            if (is_null($balanceObject)) {
                $balanceObject = new Balance();
            }
            if (!$balanceObject->exnova_id || $balanceObject->amount != $balance['amount'] || $balanceObject->bonusAmount != $balance['bonusAmount']) {
                $balanceObject->exnova_id = $balance['id'];
                $balanceObject->user_id = $this->id;
                $balanceObject->exnova_account_id = $activeAccount->id;
                $balanceObject->amount = $balance['amount'];
                $balanceObject->bonusAmount = $balance['bonusAmount'];
                $balanceObject->totalAmount = $balance['amount'] + $balance['bonusAmount'];
                $balanceObject->currency = $balance['currency'];
                $balanceObject->type = $balance['type'];
                $balanceObject->save();
            }

            if ($balance['type'] == $activeAccount->mode) {
                $activeAccount->currency = $balance['currency'];
                $activeAccount->save();
            }

        }

        return $balances;
    }

    public function realBalance()
    {
        $balances = $this->getBalances();
        foreach ($balances as $balance) {
            if ($balance['type'] == 'real') {
                $balance['amount'] += $balance['bonusAmount'];
                return $balance;
            }
        }

        return null;
    }

    public function demoBalance()
    {
        $balances = $this->getBalances();
        foreach ($balances as $balance) {
            if ($balance['type'] == 'demo') {
                $balance['amount'] += $balance['bonusAmount'];
                return $balance;
            }
        }

        return null;
    }

    public function getRequiredSessionBalance($betAmount)
    {
        $currentStrategy = $this->getCurrentStrategy();

        $coef = $currentStrategy['coef'];
        $max_steps = $currentStrategy['steps'];
        $max_trades = $currentStrategy['trades'];

        $sum = 0;
        for ($i = 0; $i < $max_steps; $i++) {
            $sum += $betAmount * pow($coef, $i);
        }

        $sum *= $max_trades;

        return $sum;
    }

    public function getRequiredSessionSpins()
    {
        $currentStrategy = $this->getCurrentStrategy();
        $max_steps = $currentStrategy['steps'];
        $max_trades = $currentStrategy['trades'];
        $requiredSpins = $max_steps * $max_trades;

        return $requiredSpins;
    }

    public function getCurrentStrategy()
    {
        $strategy = $this->settings()->getResults()->current_strategy_id;
        $settings = $this->settings()->getResults();
        $allStrategies = Strategy::all()
            ->keyBy('id')
            ->toArray();

        $manualStrategyId = Strategy::MANUAL_STRATEGY_ID;

        $allStrategies[$manualStrategyId] = array_merge($allStrategies[$manualStrategyId], [
            'coef' => $settings->manual_strategy_coef,
            'steps' => $settings->manual_strategy_steps,
            'trades' => $settings->manual_strategy_trades,
        ]);

        return $allStrategies[$strategy];
    }

    public function startAutotradeSession($botId, $forceOrManual = false, $direction = null, $state = TradingSession::STATE_JUST_STARTED)
    {
        try {
            $currentStrategy = $this->getCurrentStrategy();
            $newSession = new TradingSession();

            $data = [
                'is_active' => 1,
                'bot_id' => $botId,
                'user_id' => $this->id,
                'coef' => $currentStrategy['coef'],
                'max_steps' => $currentStrategy['steps'],
                'max_trades' => $currentStrategy['trades'],
                'expiration_time' => $currentStrategy['expiration_time'],
                'start_spins' => $this->spins()->getResults()->amount,
                'strategy_id' => $this->settings()->getResults()->current_strategy_id,
                'is_real' => $this->activeExnovaAccount()->mode == 'real' ? 1 : 0,
                'used_spins' => 0,
                'profit' => 0,
                'state' => $state,
                'currency' => $this->exnovaAccounts()->getResults()->where('is_active')->first()->currency ?? 'USD',
            ];

            if ($direction == 'buy') {
                $data['direction'] = 1;
            } elseif ($direction == 'sell') {
                $data['direction'] = -1;
            }

            if ($forceOrManual == 2 || $forceOrManual == 4) {
                $data['is_manual'] = 1;
            }

            $newSession->fill($data);
            $newSession->save();

            return $newSession;
        } catch (\Exception $e) {
            $errorMessage = "User: {$this->telegram_name} \nAccount: {$this->activeExnovaAccount()->email} \nAutotrading command execution failed. \n" . $e->getMessage();
            $this->sendMessageToChannel($errorMessage);

            return ['error' => $e->getMessage()];
        }
    }


    public function getLocaleValue($data)
    {
        if (!empty($data[$this->locale]) && !empty(trim(strip_tags($data[$this->locale])))) {
            return $data[$this->locale];
        }

        return $data['en'];
    }

    public function getTodayStats()
    {
        $todayStats = Settings::get('trading.stats', 'today');
        $todayStatsDate = Settings::get('trading.stats', 'date');

        if (strtotime($todayStatsDate) < strtotime(date('Y-m-d'))) {
            $successDeals = rand(75,90);
            $todayStats = [
                'totalDeals' => rand(311,2883),
                'successDeals' => $successDeals . '%',
                'failedDeals' => (100 - $successDeals) . '%',
                'totalProfit' => rand(8311, 35837) . '$',
            ];

            Settings::save('trading.stats', 'today', $todayStats);
            Settings::save('trading.stats', 'date', date('Y-m-d'));
        }

        return $todayStats;
    }

    public function formatCurrency($amount, $signed = false)
    {
        $activeAccount = $this->activeExnovaAccount();
        $balance = Balance::query()->where('exnova_account_id', $activeAccount->id)->where('type', $activeAccount->mode)->first();
        if (is_object($balance)) {
            $currency = $balance->currency;
        } else {
            $currency = $this->activeExnovaAccount()->currency;
        }

        $sign = '';
        if ($signed) {
            if ($amount<0) {
                $sign = '-';
                $amount = abs($amount);
            } else {
                $sign = '+';
            }
        }

        if ($currency == 'USD') {
            return $sign . '$' . number_format($amount, 2, '.', ' ');
        } else {
            return $sign . number_format($amount, 2, '.', ' ') . ' ' . $currency;
        }
    }

    public function isTranslatable()
    {
        return false;
    }

}