Files
html/api/linkedin-posts.php
2026-04-12 22:57:03 +02:00

156 lines
6.4 KiB
PHP

<?php
// WEVIA LinkedIn Posts API + collecteur
// Endpoint: /api/linkedin-posts.php
// GET ?action=list → all posts from DB
// GET ?action=sync → collecte LinkedIn + update stats
// POST ?action=add → add new post manually
header('Content-Type: application/json');
header('Access-Control-Allow-Origin: *');
$action = $_GET['action'] ?? 'list';
$pdo = new PDO("pgsql:host=127.0.0.1;dbname=wevia_db", "postgres", "");
$pdo->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']]);