added test for channel
This commit is contained in:
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Services\PriceChannelBroadcastService;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
class PriceChannelTest extends Command
|
||||
{
|
||||
protected $signature = 'price:channel-test {--send : ارسال واقعی به کانالها (نادیدهگرفتن cooldown)}';
|
||||
|
||||
protected $description = 'بررسی وضعیت ارسال قیمت به کانالها و ارسال آزمایشی';
|
||||
|
||||
public function handle(PriceChannelBroadcastService $service): int
|
||||
{
|
||||
$info = $service->diagnose();
|
||||
|
||||
$this->line('');
|
||||
$this->info('=== وضعیت ارسال قیمت به کانال ===');
|
||||
$this->line('قابلیت روشن (price_channel_enabled): ' . ($info['enabled'] ? 'بله' : 'خیر'));
|
||||
$this->line('پیامرسانهای انتخابشده: ' . (implode(', ', $info['selected_messengers']) ?: '— (خالی)'));
|
||||
$this->line('حداقل فاصله ارسال (ثانیه): ' . $info['min_seconds']);
|
||||
$this->line('فاصله cooldown گذشته؟ ' . ($info['cooldown_elapsed'] ? 'بله' : 'خیر (هنوز زوده)'));
|
||||
$this->line('تعداد محصولات واجد شرایط: ' . $info['eligible_products']);
|
||||
$this->line('مقصدهای فعال (آماده ارسال): ' . (implode(', ', $info['active_targets']) ?: '— (هیچ)'));
|
||||
|
||||
$this->line('');
|
||||
$this->info('--- جزئیات هر پیامرسان ---');
|
||||
$this->table(
|
||||
['پیامرسان', 'انتخابشده', 'توکن', 'مقصدها', 'ارسال میشود؟'],
|
||||
collect($info['messengers'])->map(fn ($m, $key) => [
|
||||
$key,
|
||||
$m['selected'] ? '✓' : '✗',
|
||||
$m['has_token'] ? '✓' : '✗ (خالی)',
|
||||
implode(', ', $m['destinations']) ?: '—',
|
||||
$m['will_send'] ? '✓' : '✗',
|
||||
])->values()->all()
|
||||
);
|
||||
|
||||
$this->line('');
|
||||
$this->info('--- پیشنمایش پیام ---');
|
||||
$this->line($info['message_preview'] !== '' ? $info['message_preview'] : '(پیام خالی — هیچ محصول واجد شرایطی نیست)');
|
||||
|
||||
if ($this->diagnoseBlocker($info)) {
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
if ($this->option('send')) {
|
||||
$this->line('');
|
||||
$this->info('=== ارسال آزمایشی (بدون cooldown) ===');
|
||||
$result = $service->forceSend();
|
||||
$this->line('ارسال شد؟ ' . ($result['sent'] ? 'بله' : 'خیر'));
|
||||
foreach ($result['results'] as $messenger => $chatResults) {
|
||||
$this->line("[{$messenger}]");
|
||||
foreach ((array) $chatResults as $chatId => $status) {
|
||||
$this->line(" {$chatId} → {$status}");
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$this->line('');
|
||||
$this->comment('برای ارسال واقعی: php artisan price:channel-test --send');
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/** پیام راهنما وقتی چیزی مانع ارسال است */
|
||||
private function diagnoseBlocker(array $info): bool
|
||||
{
|
||||
$blocked = false;
|
||||
|
||||
if (! $info['enabled']) {
|
||||
$this->warn('⚠ price_channel_enabled خاموش است — روشنش کن.');
|
||||
$blocked = true;
|
||||
}
|
||||
if ($info['selected_messengers'] === []) {
|
||||
$this->warn('⚠ هیچ پیامرسانی در price_channel_messengers انتخاب نشده.');
|
||||
$blocked = true;
|
||||
}
|
||||
if ($info['active_targets'] === []) {
|
||||
$this->warn('⚠ هیچ مقصد فعالی نیست — توکن بات و «مقصد ارسال» (price_channel_*_to) را پر کن.');
|
||||
$blocked = true;
|
||||
}
|
||||
if ($info['eligible_products'] === 0) {
|
||||
$this->warn('⚠ هیچ محصول واجد شرایطی نیست (active + قیمت>0 + open_buy/sell + داخل ساعت کاری).');
|
||||
$blocked = true;
|
||||
}
|
||||
|
||||
return $blocked;
|
||||
}
|
||||
}
|
||||
@@ -64,6 +64,71 @@ class PriceChannelBroadcastService
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* ارسال دستی (عیبیابی) — cooldown و شرط تغییر قیمت را نادیده میگیرد.
|
||||
* وضعیت روشن/خاموش، انتخاب پیامرسان، توکن و مقصد همچنان لازم است.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function forceSend(): array
|
||||
{
|
||||
$message = $this->buildMessage();
|
||||
$targets = $this->resolveActiveTargets();
|
||||
|
||||
$results = [];
|
||||
foreach ($targets as $messenger => $target) {
|
||||
$results[$messenger] = $message === ''
|
||||
? ['skipped' => 'پیام خالی است (محصول واجد شرایط نیست)']
|
||||
: $this->dispatchToMessenger($messenger, $target['token'], $target['chat_ids'], $message);
|
||||
}
|
||||
|
||||
if ($message !== '' && $targets !== []) {
|
||||
$this->markSent();
|
||||
}
|
||||
|
||||
return [
|
||||
'sent' => $message !== '' && $targets !== [],
|
||||
'results' => $results,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* گزارش وضعیت — چرا ارسال میشود یا نمیشود.
|
||||
*
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
public function diagnose(): array
|
||||
{
|
||||
$selected = $this->selectedMessengers();
|
||||
$messengers = [];
|
||||
|
||||
foreach (self::SUPPORTED_MESSENGERS as $messenger) {
|
||||
$token = trim((string) $this->config->getString($this->tokenKey($messenger), ''));
|
||||
$chatIds = $this->parseList($this->config->getString($this->chatIdsKey($messenger), ''));
|
||||
$isSelected = in_array($messenger, $selected, true);
|
||||
|
||||
$messengers[$messenger] = [
|
||||
'selected' => $isSelected,
|
||||
'has_token' => $token !== '',
|
||||
'destinations' => $chatIds,
|
||||
'will_send' => $isSelected && $token !== '' && $chatIds !== [],
|
||||
];
|
||||
}
|
||||
|
||||
$message = $this->buildMessage();
|
||||
|
||||
return [
|
||||
'enabled' => $this->config->getBool('price_channel_enabled', false),
|
||||
'selected_messengers' => $selected,
|
||||
'min_seconds' => $this->config->getInt('price_channel_min_seconds', 120),
|
||||
'cooldown_elapsed' => $this->cooldownElapsed(),
|
||||
'eligible_products' => count($this->eligibleProducts()),
|
||||
'messengers' => $messengers,
|
||||
'active_targets' => array_keys($this->resolveActiveTargets()),
|
||||
'message_preview' => $message,
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* پیامرسانهای قابل ارسال: انتخابشده + توکن معتبر + حداقل یک مقصد.
|
||||
*
|
||||
@@ -241,28 +306,48 @@ class PriceChannelBroadcastService
|
||||
return $lines;
|
||||
}
|
||||
|
||||
private function dispatchToMessenger(string $messenger, string $token, array $chatIds, string $message): void
|
||||
/**
|
||||
* @param array<int, string> $chatIds
|
||||
* @return array<string, string> chatId => نتیجه (ok / خطا)
|
||||
*/
|
||||
private function dispatchToMessenger(string $messenger, string $token, array $chatIds, string $message): array
|
||||
{
|
||||
$results = [];
|
||||
foreach ($chatIds as $chatId) {
|
||||
try {
|
||||
match ($messenger) {
|
||||
$response = match ($messenger) {
|
||||
'bale' => $this->sendBale($token, $chatId, $message),
|
||||
'telegram' => $this->sendTelegram($token, $chatId, $message),
|
||||
'rubika' => $this->sendRubika($token, $chatId, $message),
|
||||
default => null,
|
||||
};
|
||||
|
||||
$status = $response?->status() ?? 0;
|
||||
$ok = $response?->successful() ?? false;
|
||||
$results[$chatId] = $ok ? "ok ({$status})" : "failed ({$status}): " . trim((string) $response?->body());
|
||||
|
||||
if (! $ok) {
|
||||
Log::warning("PriceChannel {$messenger} send not ok", [
|
||||
'chat_id' => $chatId,
|
||||
'status' => $status,
|
||||
'body' => $response?->body(),
|
||||
]);
|
||||
}
|
||||
} catch (\Throwable $e) {
|
||||
$results[$chatId] = 'exception: ' . $e->getMessage();
|
||||
Log::warning("PriceChannel {$messenger} send failed", [
|
||||
'chat_id' => $chatId,
|
||||
'message' => $e->getMessage(),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
private function sendBale(string $token, string $chatId, string $message): void
|
||||
private function sendBale(string $token, string $chatId, string $message): \Illuminate\Http\Client\Response
|
||||
{
|
||||
Http::withOptions(Controller::httpClientOptions())
|
||||
return Http::withOptions(Controller::httpClientOptions())
|
||||
->withoutVerifying()
|
||||
->timeout(6)
|
||||
->post("https://tapi.bale.ai/bot{$token}/sendMessage", [
|
||||
@@ -271,9 +356,9 @@ class PriceChannelBroadcastService
|
||||
]);
|
||||
}
|
||||
|
||||
private function sendTelegram(string $token, string $chatId, string $message): void
|
||||
private function sendTelegram(string $token, string $chatId, string $message): \Illuminate\Http\Client\Response
|
||||
{
|
||||
Http::withOptions(Controller::httpClientOptions())
|
||||
return Http::withOptions(Controller::httpClientOptions())
|
||||
->timeout(6)
|
||||
->post("https://api.telegram.org/bot{$token}/sendMessage", [
|
||||
'chat_id' => $chatId,
|
||||
@@ -281,9 +366,9 @@ class PriceChannelBroadcastService
|
||||
]);
|
||||
}
|
||||
|
||||
private function sendRubika(string $token, string $chatId, string $message): void
|
||||
private function sendRubika(string $token, string $chatId, string $message): \Illuminate\Http\Client\Response
|
||||
{
|
||||
Http::withOptions(Controller::httpClientOptions())
|
||||
return Http::withOptions(Controller::httpClientOptions())
|
||||
->withoutVerifying()
|
||||
->timeout(6)
|
||||
->post("https://botapi.rubika.ir/v3/{$token}/sendMessage", [
|
||||
|
||||
@@ -360,9 +360,12 @@ WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_enabled'
|
||||
|
||||
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
|
||||
SELECT 'price_channel_messengers', '', 'پیامرسانهای ارسال قیمت', 1, 1,
|
||||
'کدام پیامرسانها قیمت بگیرند؟ با ویرگول جدا کنید. گزینهها: bale,telegram,rubika', 'text'
|
||||
'کدام پیامرسانها قیمت بگیرند؟ میتوانید چند مورد را انتخاب کنید. گزینهها: بله، تلگرام، روبیکا.', 'multiselect'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_messengers' LIMIT 1);
|
||||
|
||||
-- نوع multiselect را روی ردیفهای موجود هم اعمال کن (نصبهایی که قبلاً text گرفته بودند)
|
||||
UPDATE `settings` SET `type` = 'multiselect' WHERE `key` = 'price_channel_messengers';
|
||||
|
||||
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
|
||||
SELECT 'price_channel_diff', '0', 'اختلاف قیمت کانال (ریال)', 1, 1,
|
||||
'مبلغی که به قیمت خرید کانال اضافه و از قیمت فروش کم میشود (اسپرد اضافه فقط برای کانال).', 'price'
|
||||
|
||||
@@ -24,9 +24,11 @@ WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_enabled'
|
||||
|
||||
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
|
||||
SELECT 'price_channel_messengers', '', 'پیامرسانهای ارسال قیمت', 1, 1,
|
||||
'کدام پیامرسانها قیمت بگیرند؟ با ویرگول جدا کنید. گزینهها: bale,telegram,rubika', 'text'
|
||||
'کدام پیامرسانها قیمت بگیرند؟ میتوانید چند مورد را انتخاب کنید. گزینهها: بله، تلگرام، روبیکا.', 'multiselect'
|
||||
WHERE NOT EXISTS (SELECT 1 FROM `settings` WHERE `key` = 'price_channel_messengers' LIMIT 1);
|
||||
|
||||
UPDATE `settings` SET `type` = 'multiselect' WHERE `key` = 'price_channel_messengers';
|
||||
|
||||
INSERT INTO `settings` (`key`, `value`, `title_fa`, `can_edit`, `is_active`, `description`, `type`)
|
||||
SELECT 'price_channel_diff', '0', 'اختلاف قیمت کانال (ریال)', 1, 1,
|
||||
'مبلغی که به قیمت خرید کانال اضافه و از قیمت فروش کم میشود (اسپرد اضافه فقط برای کانال).', 'price'
|
||||
|
||||
Reference in New Issue
Block a user