Initial Commit
This commit is contained in:
@@ -5,7 +5,7 @@ namespace App\Console\Commands;
|
||||
use Illuminate\Console\Attributes\Description;
|
||||
use Illuminate\Console\Attributes\Signature;
|
||||
|
||||
#[Signature('app:debug-playwright')]
|
||||
#[Signature('direct:debug-playwright')]
|
||||
#[Description('Command description')]
|
||||
class DebugPlaywright extends RunsPlaywrightScript
|
||||
{
|
||||
|
||||
@@ -55,6 +55,7 @@ class ScrapeDirectMessages extends RunsPlaywrightScript
|
||||
'shared_preview_image' => $message['share']['preview_image'] ?? null,
|
||||
'shared_title' => $message['share']['title'] ?? null,
|
||||
'shared_caption' => $message['share']['caption'] ?? null,
|
||||
'attachments' => $message['attachments'] ?? null,
|
||||
'reactions' => $message['reactions'],
|
||||
'replied_to_id' => $message['replied_to_id'],
|
||||
'replied_to_text' => $message['replied_to_text'],
|
||||
|
||||
@@ -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,
|
||||
]],
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -15,7 +15,7 @@ class DirectMessage extends Model
|
||||
|
||||
protected $fillable = [
|
||||
'id', 'thread_id', 'user_id', 'username', 'full_name', 'item_type', 'text',
|
||||
'media_url', 'is_video', 'timestamp', 'shared_url', 'shared_preview_image',
|
||||
'media_url', 'is_video', 'attachments', 'timestamp', 'shared_url', 'shared_preview_image',
|
||||
'shared_title', 'shared_caption', 'reactions', 'replied_to_id', 'replied_to_text',
|
||||
];
|
||||
|
||||
@@ -23,6 +23,7 @@ class DirectMessage extends Model
|
||||
'is_video' => 'boolean',
|
||||
'timestamp' => 'integer',
|
||||
'reactions' => 'array',
|
||||
'attachments' => 'array',
|
||||
'scraped_at' => 'datetime',
|
||||
];
|
||||
|
||||
|
||||
Reference in New Issue
Block a user