Initial Commit
linter / quality (push) Canceled after 0s
tests / ci (8.3) (push) Canceled after 0s
tests / ci (8.4) (push) Canceled after 0s
tests / ci (8.5) (push) Canceled after 0s

This commit is contained in:
2026-08-28 23:53:45 +10:00
parent 3ce70b3701
commit 37ff8e0a88
13 changed files with 292 additions and 23 deletions
+29
View File
@@ -69,4 +69,33 @@ class StoryController extends Controller
Story::where('id', $id)->update(['viewed_at' => now()]);
return response()->json(['ok' => true]);
}
/**
* Fetch a single story item by its own id, regardless of whether it's
* still active used for opening a story someone shared in a DM long
* after it expired from the live tray.
*/
public function showItem(string $id): JsonResponse
{
$item = Story::find($id);
if (! $item) {
return response()->json(['message' => 'Story not found.'], 404);
}
return response()->json([
'username' => $item->username,
'profile_pic_url' => $item->profile_pic_url,
'start_index' => 0,
'items' => [[
'id' => $item->id,
'code' => $item->code,
'is_video' => $item->is_video,
'image_url' => $item->image_url,
'video_url' => $item->video_url,
'accessibility_caption' => $item->accessibility_caption,
'taken_at' => $item->taken_at,
]],
]);
}
}
+40 -5
View File
@@ -4,10 +4,25 @@ 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()
@@ -40,10 +55,23 @@ class ThreadController extends Controller
{
$thread = DirectThread::findOrFail($threadId);
$messages = $thread->messages()
->orderBy('timestamp')
->get()
->map(fn (DirectMessage $message) => [
$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,
@@ -55,10 +83,17 @@ class ThreadController extends Controller
'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,