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 22:55:32 +10:00
parent d8d4ecbe8a
commit f00405ed24
16 changed files with 1008 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
#[Signature('app:debug-playwright')]
#[Description('Command description')]
class DebugPlaywright extends RunsPlaywrightScript
{
protected $signature = 'direct:debug';
protected $description = 'Run the Playwright network/DOM debugging script against Instagram Direct inbox';
protected string $script = 'debugger.spec.js';
}
@@ -0,0 +1,38 @@
<?php
namespace App\Console\Commands;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\Process;
abstract class RunsPlaywrightScript extends Command
{
protected string $script;
protected int $processTimeout = 300;
public function handle(): int
{
$this->info("Running {$this->script}...");
$result = Process::path(base_path('playwright'))
->timeout($this->processTimeout)
->run("npx playwright test {$this->script} --reporter=line");
$this->line($result->output());
if (! $result->successful()) {
$this->error($result->errorOutput());
return self::FAILURE;
}
$this->afterRun();
$this->info('Done.');
return self::SUCCESS;
}
protected function afterRun(): void
{
// no-op by default
}
}
@@ -0,0 +1,71 @@
<?php
namespace App\Console\Commands;
use App\Models\DirectMessage;
use App\Models\DirectThread;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
use Illuminate\Console\Command;
#[Signature('direct:scrape-messages')]
#[Description('Scrape Instagram Direct threads and messages via Playwright')]
class ScrapeDirectMessages extends RunsPlaywrightScript
{
protected string $script = 'scrape-messages.spec.js';
protected int $processTimeout = 600;
protected function afterRun(): void
{
$path = base_path('playwright/output/scrape-messages.json');
if (! file_exists($path)) {
$this->warn('No output file found — nothing to upsert.');
return;
}
$data = json_decode(file_get_contents($path), true);
foreach ($data['threads'] ?? [] as $thread) {
DirectThread::updateOrCreate(
['thread_id' => $thread['thread_id']],
[
'thread_key' => $thread['thread_key'],
'thread_title' => $thread['thread_title'],
'is_group' => $thread['is_group'],
'participants' => $thread['participants'],
'last_activity_at' => $thread['last_activity_at'],
'muted' => $thread['muted'],
]
);
}
foreach ($data['messages'] ?? [] as $message) {
DirectMessage::updateOrCreate(
['id' => $message['id']],
[
'thread_id' => $message['thread_id'],
'user_id' => $message['user_id'],
'username' => $message['username'],
'full_name' => $message['full_name'],
'item_type' => $message['content_type'],
'text' => $message['text'],
'timestamp' => $message['timestamp_ms'],
'shared_url' => $message['share']['url'] ?? null,
'shared_preview_image' => $message['share']['preview_image'] ?? null,
'shared_title' => $message['share']['title'] ?? null,
'shared_caption' => $message['share']['caption'] ?? null,
'reactions' => $message['reactions'],
'replied_to_id' => $message['replied_to_id'],
'replied_to_text' => $message['replied_to_text'],
]
);
}
$this->info(sprintf(
'Upserted %d threads and %d messages.',
count($data['threads'] ?? []),
count($data['messages'] ?? [])
));
}
}
+50
View File
@@ -0,0 +1,50 @@
<?php
namespace App\Console\Commands;
use App\Models\Post;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
#[Signature('direct:scrape-feed')]
#[Description('Scrape the Instagram following feed via Playwright')]
class ScrapeFeed extends RunsPlaywrightScript
{
protected string $script = 'scrape-feed.spec.js';
protected function afterRun(): void
{
$path = base_path('playwright/output/scrape-feed.json');
if (! file_exists($path)) {
$this->warn('No output file found — nothing to upsert.');
return;
}
$posts = json_decode(file_get_contents($path), true);
foreach ($posts as $post) {
Post::updateOrCreate(
['id' => $post['id']],
[
'code' => $post['code'],
'username' => $post['username'],
'full_name' => $post['full_name'],
'caption' => $post['caption'],
'accessibility_caption' => $post['accessibility_caption'],
'is_video' => $post['is_video'],
'is_carousel' => $post['is_carousel'],
'like_count' => $post['like_count'],
'comment_count' => $post['comment_count'],
'taken_at' => $post['taken_at'],
'image_url' => $post['image_url'],
'video_url' => $post['video_url'],
'carousel_images' => $post['carousel_images'],
'profile_pic_url' => $post['profile_pic_url'],
]
);
}
$this->info('Upserted ' . count($posts) . ' posts.');
}
}
+47
View File
@@ -0,0 +1,47 @@
<?php
namespace App\Console\Commands;
use App\Models\Story;
use Illuminate\Console\Attributes\Description;
use Illuminate\Console\Attributes\Signature;
#[Signature('direct:scrape-stories')]
#[Description('Scrape Instagram story content via Playwright')]
class ScrapeStories extends RunsPlaywrightScript
{
protected string $script = 'scrape-story-content.spec.js';
protected function afterRun(): void
{
$path = base_path('playwright/output/scrape-stories.json');
if (! file_exists($path)) {
$this->warn('No output file found — nothing to upsert.');
return;
}
$items = json_decode(file_get_contents($path), true);
foreach ($items as $item) {
Story::updateOrCreate(
['id' => $item['id']],
[
'user_id' => $item['user_id'],
'username' => $item['username'],
'profile_pic_url' => $item['profile_pic_url'],
'code' => $item['code'],
'is_video' => $item['is_video'],
'taken_at' => $item['taken_at'],
'expiring_at' => $item['expiring_at'],
'image_url' => $item['image_url'],
'video_url' => $item['video_url'],
'accessibility_caption' => $item['accessibility_caption'],
'muted' => $item['muted'],
]
);
}
$this->info('Upserted ' . count($items) . ' story items.');
}
}