108 lines
4.0 KiB
PHP
108 lines
4.0 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use App\Models\DirectMessage;
|
|
use App\Models\DirectThread;
|
|
use App\Models\Story;
|
|
use Illuminate\Http\JsonResponse;
|
|
|
|
class ThreadController extends Controller
|
|
{
|
|
/**
|
|
* Instagram's own story permalinks embed both the poster's username and
|
|
* the story item's numeric pk: instagram.com/stories/{username}/{pk}.
|
|
* That pk is the same id our story scraper stores.
|
|
*/
|
|
private function parseStorySharePk(?string $url): ?array
|
|
{
|
|
if (! $url || ! preg_match('#/stories/([^/?]+)/(\d+)#', $url, $matches)) {
|
|
return null;
|
|
}
|
|
|
|
return ['username' => $matches[1], 'item_id' => $matches[2]];
|
|
}
|
|
|
|
public function index(): JsonResponse
|
|
{
|
|
$threads = DirectThread::query()
|
|
->with(['messages' => fn ($query) => $query->orderByDesc('timestamp')->limit(1)])
|
|
->orderByDesc('last_activity_at')
|
|
->get()
|
|
->map(function (DirectThread $thread) {
|
|
$last = $thread->messages->first();
|
|
|
|
return [
|
|
'thread_id' => $thread->thread_id,
|
|
'thread_title' => $thread->thread_title,
|
|
'is_group' => $thread->is_group,
|
|
'participants' => $thread->participants,
|
|
'last_activity_at' => $thread->last_activity_at,
|
|
'muted' => $thread->muted,
|
|
'last_message' => $last ? [
|
|
'username' => $last->username,
|
|
'text' => $last->text,
|
|
'item_type' => $last->item_type,
|
|
'timestamp' => $last->timestamp,
|
|
] : null,
|
|
];
|
|
});
|
|
|
|
return response()->json($threads);
|
|
}
|
|
|
|
public function show(string $threadId): JsonResponse
|
|
{
|
|
$thread = DirectThread::findOrFail($threadId);
|
|
|
|
$messages = $thread->messages()->orderBy('timestamp')->get();
|
|
|
|
// Batch-check which story shares actually still exist in our local
|
|
// `stories` table, so we only need one query for the whole thread
|
|
// rather than one per message.
|
|
$storyPks = $messages
|
|
->map(fn (DirectMessage $m) => $this->parseStorySharePk($m->shared_url)['item_id'] ?? null)
|
|
->filter()
|
|
->unique()
|
|
->values();
|
|
|
|
$existingStoryIds = Story::whereIn('id', $storyPks)->pluck('id')->all();
|
|
|
|
$messages = $messages->map(function (DirectMessage $message) use ($existingStoryIds) {
|
|
$storyShare = $this->parseStorySharePk($message->shared_url);
|
|
|
|
return [
|
|
'id' => $message->id,
|
|
'user_id' => $message->user_id,
|
|
'username' => $message->username,
|
|
'full_name' => $message->full_name,
|
|
'item_type' => $message->item_type,
|
|
'text' => $message->text,
|
|
'timestamp' => $message->timestamp,
|
|
'shared_url' => $message->shared_url,
|
|
'shared_preview_image' => $message->shared_preview_image,
|
|
'shared_title' => $message->shared_title,
|
|
'shared_caption' => $message->shared_caption,
|
|
'attachments' => $message->attachments,
|
|
'reactions' => $message->reactions,
|
|
'replied_to_id' => $message->replied_to_id,
|
|
'replied_to_text' => $message->replied_to_text,
|
|
'story_share' => $storyShare ? [
|
|
'username' => $storyShare['username'],
|
|
'item_id' => $storyShare['item_id'],
|
|
'exists_locally' => in_array($storyShare['item_id'], $existingStoryIds, true),
|
|
] : null,
|
|
];
|
|
});
|
|
|
|
return response()->json([
|
|
'thread_id' => $thread->thread_id,
|
|
'thread_title' => $thread->thread_title,
|
|
'is_group' => $thread->is_group,
|
|
'participants' => $thread->participants,
|
|
'muted' => $thread->muted,
|
|
'messages' => $messages,
|
|
]);
|
|
}
|
|
}
|