From 1c4f5cdbc59c74dc9c66215f2067665359fda378 Mon Sep 17 00:00:00 2001 From: fs Date: Sun, 26 Jul 2026 23:30:11 +0200 Subject: [PATCH] Instagram-Sync reparieren: feed/user-Endpunkt als Strategie 1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit web_profile_info antwortet seit kurzem mit HTTP 400 und einem Instagram-internen Schema-Fehler ("Asset asset://laser.provider/ ig_business_category_subvertical has been deleted") — kein IP-Block. Gleichzeitig enthält das Profil-HTML kein edge_owner_to_timeline_media mehr, womit auch der zweite Fallback tot war und der Cache seit 20.06. nicht mehr aktualisiert wurde. api/v1/feed/user/{username}/username/ liefert weiterhin 200 mit den letzten Posts, nur in anderer Form (items[] mit image_versions2 statt edges[] mit display_url). Neuer Extractor dafür, eingehängt als Strategie 1; die bisherigen zwei bleiben als Fallback 2 und 3. Co-Authored-By: Claude Opus 5 (1M context) --- bin/instagram-sync.php | 64 ++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 9 deletions(-) diff --git a/bin/instagram-sync.php b/bin/instagram-sync.php index 31ecc71..049c60b 100644 --- a/bin/instagram-sync.php +++ b/bin/instagram-sync.php @@ -12,8 +12,9 @@ declare(strict_types=1); * Cron: 17 6,18 * * * cd /pfad/zur/site && /usr/bin/php bin/instagram-sync.php >> storage/logs/instagram.log 2>&1 * * Strategie-Kette (erster Erfolg gewinnt): - * 1. web_profile_info-API (JSON, inkl. der letzten 12 Posts) - * 2. Profil-HTML nach eingebettetem JSON parsen + * 1. feed/user-API (JSON, items[] — antwortet auch dann, wenn web_profile_info 400 wirft) + * 2. web_profile_info-API (JSON, edges[]) + * 3. Profil-HTML nach eingebettetem JSON parsen */ if (PHP_SAPI !== 'cli') { exit(1); @@ -89,23 +90,68 @@ $extractPosts = static function (array $data) use ($maxPosts): array { return $posts; }; -// --- Strategie 1: web_profile_info-API --- -$posts = []; +/** + * Posts aus der feed/user-Struktur ziehen (items[] mit image_versions2 statt edges[]). + * @return array> + */ +$extractFeedItems = static function (array $data) use ($maxPosts): array { + $posts = []; + foreach (array_slice($data['items'] ?? [], 0, $maxPosts) as $item) { + // Größten Bild-Kandidaten nehmen — wird unten ohnehin auf 640x800 gecroppt. + $url = ''; + $bestWidth = 0; + foreach ($item['image_versions2']['candidates'] ?? [] as $candidate) { + $width = (int) ($candidate['width'] ?? 0); + if ($width > $bestWidth && !empty($candidate['url'])) { + $bestWidth = $width; + $url = (string) $candidate['url']; + } + } + if (empty($item['code']) || $url === '') { + continue; + } + $posts[] = [ + 'shortcode' => (string) $item['code'], + 'caption' => trim((string) ($item['caption']['text'] ?? '')), + 'taken_at' => (int) ($item['taken_at'] ?? 0), + // media_type: 1 = Bild, 2 = Video/Reel, 8 = Karussell. + 'is_video' => (int) ($item['media_type'] ?? 1) === 2 || !empty($item['video_versions']), + 'display_url' => $url, + ]; + } + return $posts; +}; + $apiHeaders = [ 'x-ig-app-id: 936619743392459', 'Accept: application/json', 'Referer: https://www.instagram.com/' . $username . '/', ]; -$body = $fetch("https://www.instagram.com/api/v1/users/web_profile_info/?username={$username}", $apiHeaders); + +// --- Strategie 1: feed/user-API --- +$posts = []; +$body = $fetch("https://www.instagram.com/api/v1/feed/user/{$username}/username/?count=12", $apiHeaders); if ($body !== false) { $data = json_decode($body, true); if (is_array($data)) { - $posts = $extractPosts($data); - $log('Strategie 1 (web_profile_info): ' . count($posts) . ' Posts'); + $posts = $extractFeedItems($data); + $log('Strategie 1 (feed/user): ' . count($posts) . ' Posts'); } } -// --- Strategie 2: Profil-HTML nach eingebettetem JSON parsen --- +// --- Strategie 2: web_profile_info-API --- +if ($posts === []) { + $body = $fetch("https://www.instagram.com/api/v1/users/web_profile_info/?username={$username}", $apiHeaders); + if ($body !== false) { + $data = json_decode($body, true); + if (is_array($data)) { + $posts = $extractPosts($data); + $log('Strategie 2 (web_profile_info): ' . count($posts) . ' Posts'); + } + } +} + +// --- Strategie 3: Profil-HTML nach eingebettetem JSON parsen --- if ($posts === []) { $html = $fetch("https://www.instagram.com/{$username}/"); if ($html !== false) { @@ -138,7 +184,7 @@ if ($posts === []) { } } } - $log('Strategie 2 (HTML-Parsing): ' . count($posts) . ' Posts'); + $log('Strategie 3 (HTML-Parsing): ' . count($posts) . ' Posts'); } }