setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); // === LIST POSTS === if ($action === 'list') { $posts = $pdo->query("SELECT id,post_date,title,excerpt,likes,comments,reposts,views,image,source,linkedin_url,status,auto_synced,updated_at FROM admin.linkedin_posts WHERE status='published' ORDER BY post_date DESC")->fetchAll(PDO::FETCH_ASSOC); echo json_encode(['posts' => $posts, 'count' => count($posts), 'updated' => date('c')]); exit; } // === SYNC FROM LINKEDIN === if ($action === 'sync') { require_once '/opt/wevads/vault/credentials.php'; $results = ['fetched' => 0, 'updated' => 0, 'new' => 0, 'errors' => []]; // LinkedIn company page: https://www.linkedin.com/company/69533182/ // Strategy: fetch the public company page and extract post data // LinkedIn blocks most collecteurs, so we use multiple fallback methods // Method 1: LinkedIn public RSS/feed (if available) $company_id = '69533182'; $company_url = "https://www.linkedin.com/company/$company_id/posts/"; // Method 2: Use a headless approach via S204 $ch = curl_init($company_url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15, CURLOPT_FOLLOWLOCATION => true, CURLOPT_USERAGENT => 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/121.0.0.0 Safari/537.36', CURLOPT_HTTPHEADER => ['Accept-Language: fr-FR,fr;q=0.9,en-US;q=0.8,en;q=0.7'], ]); $html = curl_exec($ch); $hc = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); $results['http_code'] = $hc; $results['html_size'] = strlen($html ?? ''); if ($html && $hc === 200) { // Parse LinkedIn page for post data // LinkedIn embeds post data in JSON-LD or script tags if (preg_match_all('/"numLikes":(\d+)/', $html, $likes_matches)) { $results['likes_found'] = count($likes_matches[1]); } if (preg_match_all('/"numComments":(\d+)/', $html, $comment_matches)) { $results['comments_found'] = count($comment_matches[1]); } // Try to extract post titles and stats from the page // LinkedIn public pages have limited data without auth $results['method'] = 'public_page'; } // Method 3: Use Groq to analyze the page content and extract stats if ($html && strlen($html) > 1000) { // Extract visible text $text = strip_tags($html); $text = preg_replace('/\s+/', ' ', $text); $text = mb_substr($text, 0, 3000); $ch2 = curl_init('https://api.groq.com/openai/v1/chat/completions'); curl_setopt_array($ch2, [ CURLOPT_HTTPHEADER => ['Authorization: Bearer ' . GROQ_KEY, 'Content-Type: application/json'], CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode([ 'model' => 'llama-3.1-8b-instant', 'messages' => [ ['role' => 'system', 'content' => 'Extract LinkedIn post stats from this page. Return JSON array: [{"title":"...","likes":N,"comments":N,"views":N}]. If no data found, return []'], ['role' => 'user', 'content' => $text] ], 'temperature' => 0.1, 'max_tokens' => 500, 'response_format' => ['type' => 'json_object'] ]), CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 15 ]); $gr = curl_exec($ch2); $ghc = curl_getinfo($ch2, CURLINFO_HTTP_CODE); curl_close($ch2); if ($ghc === 200) { $gd = json_decode($gr, true); $extracted = json_decode($gd['choices'][0]['message']['content'] ?? '{}', true); $results['ai_extracted'] = $extracted; } } // Update DB with any extracted stats // For now, just bump the updated_at timestamp to track sync attempts $pdo->exec("UPDATE admin.linkedin_posts SET updated_at = NOW() WHERE status = 'published'"); $results['db_updated'] = true; echo json_encode($results); exit; } // === ADD POST === if ($action === 'add' && $_SERVER['REQUEST_METHOD'] === 'POST') { $input = json_decode(file_get_contents('php://input'), true); if (!$input || !$input['title']) { echo json_encode(['error' => 'title required']); exit; } $ins = $pdo->prepare("INSERT INTO admin.linkedin_posts (post_date,title,excerpt,likes,comments,reposts,views,image,source,linkedin_url) VALUES (?,?,?,?,?,?,?,?,?,?) ON CONFLICT(title,post_date) DO UPDATE SET likes=EXCLUDED.likes,comments=EXCLUDED.comments,reposts=EXCLUDED.reposts,views=EXCLUDED.views,updated_at=NOW() RETURNING id"); $ins->execute([ $input['date'] ?? date('Y-m-d'), $input['title'], $input['excerpt'] ?? '', $input['likes'] ?? 0, $input['comments'] ?? 0, $input['reposts'] ?? 0, $input['views'] ?? 0, $input['image'] ?? '', $input['source'] ?? 'W', $input['linkedin_url'] ?? '' ]); $id = $ins->fetchColumn(); echo json_encode(['ok' => true, 'id' => $id]); exit; } // === UPDATE STATS === if ($action === 'update' && $_SERVER['REQUEST_METHOD'] === 'POST') { $input = json_decode(file_get_contents('php://input'), true); if (!$input || !$input['id']) { echo json_encode(['error' => 'id required']); exit; } $sets = []; $params = []; foreach (['likes', 'comments', 'reposts', 'views', 'linkedin_url', 'image'] as $field) { if (isset($input[$field])) { $sets[] = "$field = ?"; $params[] = $input[$field]; } } if ($sets) { $sets[] = "updated_at = NOW()"; $params[] = $input['id']; $pdo->prepare("UPDATE admin.linkedin_posts SET " . implode(', ', $sets) . " WHERE id = ?")->execute($params); } echo json_encode(['ok' => true]); exit; } echo json_encode(['error' => 'unknown action', 'actions' => ['list', 'sync', 'add', 'update']]);