auto-sync-1455
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"agent": "V45_Leads_Sync",
|
||||
"ts": "2026-04-20T14:40:02+02:00",
|
||||
"ts": "2026-04-20T14:50:02+02:00",
|
||||
"paperclip_total": 48,
|
||||
"active_customer": 4,
|
||||
"warm_prospect": 5,
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"generated_at": "2026-04-20T14:45:01.851456",
|
||||
"generated_at": "2026-04-20T14:55:01.860831",
|
||||
"stats": {
|
||||
"total": 26,
|
||||
"pending": 20,
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"ok": true,
|
||||
"agent": "V42_MQL_Scoring_Agent_REAL",
|
||||
"ts": "2026-04-20T12:40:02+00:00",
|
||||
"ts": "2026-04-20T12:50:01+00:00",
|
||||
"status": "DEPLOYED_AUTO",
|
||||
"deployed": true,
|
||||
"algorithm": "weighted_behavioral_signals",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"ok": true,
|
||||
"version": "V83-business-kpi",
|
||||
"ts": "2026-04-20T12:47:54+00:00",
|
||||
"ts": "2026-04-20T12:50:42+00:00",
|
||||
"summary": {
|
||||
"total_categories": 7,
|
||||
"total_kpis": 56,
|
||||
|
||||
227
api/v97-linkedin-control.php
Normal file
227
api/v97-linkedin-control.php
Normal file
@@ -0,0 +1,227 @@
|
||||
<?php
|
||||
// V97 LinkedIn Control API - approve/schedule/mark-published + LinkedIn API integration
|
||||
header('Content-Type: application/json');
|
||||
header('Access-Control-Allow-Origin: *');
|
||||
|
||||
$action = $_GET['action'] ?? $_POST['action'] ?? 'overview';
|
||||
$queue_file = '/opt/weval-l99/linkedin-post-queue.jsonl';
|
||||
$published_file = '/opt/weval-l99/linkedin-published.jsonl';
|
||||
$scheduled_file = '/opt/weval-l99/linkedin-scheduled.jsonl';
|
||||
$stats_file = '/opt/weval-l99/linkedin-page-stats.json';
|
||||
$log_file = '/var/log/v97-linkedin-control.log';
|
||||
|
||||
foreach([$queue_file,$published_file,$scheduled_file] as $f){if(!file_exists($f))@file_put_contents($f,'');}
|
||||
|
||||
function getEntries($f){
|
||||
$r=[];
|
||||
foreach(@file($f)?:[] as $l){$e=@json_decode(trim($l),true);if($e)$r[]=$e;}
|
||||
return $r;
|
||||
}
|
||||
function writeAll($f,$entries){
|
||||
$content='';
|
||||
foreach($entries as $e)$content.=json_encode($e)."\n";
|
||||
@file_put_contents($f,$content);
|
||||
}
|
||||
function logit($msg){@file_put_contents('/var/log/v97-linkedin-control.log',date('c')." $msg\n",FILE_APPEND);}
|
||||
|
||||
// Get LinkedIn token from secrets.env
|
||||
function getLinkedinToken(){
|
||||
$token=trim(@shell_exec('grep "^LINKEDIN_ACCESS_TOKEN=" /etc/weval/secrets.env 2>/dev/null | cut -d= -f2-'));
|
||||
return $token ?: null;
|
||||
}
|
||||
function getLinkedinOrgId(){
|
||||
return trim(@shell_exec('grep "^LINKEDIN_ORG_ID=" /etc/weval/secrets.env 2>/dev/null | cut -d= -f2-')) ?: '69533182';
|
||||
}
|
||||
|
||||
switch($action){
|
||||
|
||||
case 'overview':
|
||||
$queue=getEntries($queue_file);
|
||||
$published=getEntries($published_file);
|
||||
$scheduled=getEntries($scheduled_file);
|
||||
$stats=@json_decode(@file_get_contents($stats_file),true)?:[];
|
||||
$pixel=@json_decode(@file_get_contents('http://localhost/api/v85-demo-tracker.php'),true)?:[];
|
||||
$token=getLinkedinToken();
|
||||
echo json_encode([
|
||||
'v'=>'V97-linkedin-control',
|
||||
'ts'=>date('c'),
|
||||
'queue_drafts'=>count(array_filter($queue,fn($e)=>($e['status']??'draft_queued')==='draft_queued')),
|
||||
'approved'=>count(array_filter($queue,fn($e)=>($e['status']??'')==='approved')),
|
||||
'scheduled'=>count($scheduled),
|
||||
'published_count'=>count($published),
|
||||
'published_today'=>count(array_filter($published,fn($e)=>substr($e['published_at']??'',0,10)===date('Y-m-d'))),
|
||||
'linkedin_api_ready'=>!empty($token),
|
||||
'linkedin_org_id'=>getLinkedinOrgId(),
|
||||
'page_stats'=>$stats,
|
||||
'pixel_hits_month'=>$pixel['month_hits_total']??0,
|
||||
'next_scheduled'=>array_filter(array_slice($scheduled,0,3)),
|
||||
'recent_published'=>array_slice($published,-5),
|
||||
],JSON_PRETTY_PRINT);
|
||||
break;
|
||||
|
||||
case 'approve':
|
||||
$id=$_POST['id']??$_GET['id']??'';
|
||||
$queue=getEntries($queue_file);
|
||||
$updated=false;
|
||||
foreach($queue as &$e){
|
||||
if(($e['id']??'')===$id){$e['status']='approved';$e['approved_at']=date('c');$updated=true;}
|
||||
}
|
||||
writeAll($queue_file,$queue);
|
||||
logit("approved $id");
|
||||
echo json_encode(['ok'=>$updated,'id'=>$id,'action'=>'approved']);
|
||||
break;
|
||||
|
||||
case 'schedule':
|
||||
$id=$_POST['id']??$_GET['id']??'';
|
||||
$when=$_POST['when']??$_GET['when']??date('c',time()+3600);
|
||||
$queue=getEntries($queue_file);
|
||||
$post=null;
|
||||
foreach($queue as $e){if(($e['id']??'')===$id){$post=$e;break;}}
|
||||
if(!$post){echo json_encode(['ok'=>false,'err'=>'not_found']);break;}
|
||||
$post['status']='scheduled';
|
||||
$post['scheduled_at']=$when;
|
||||
$scheduled=getEntries($scheduled_file);
|
||||
$scheduled[]=$post;
|
||||
writeAll($scheduled_file,$scheduled);
|
||||
// Remove from queue
|
||||
$queue=array_filter($queue,fn($e)=>($e['id']??'')!==$id);
|
||||
writeAll($queue_file,$queue);
|
||||
logit("scheduled $id for $when");
|
||||
echo json_encode(['ok'=>true,'id'=>$id,'scheduled_at'=>$when]);
|
||||
break;
|
||||
|
||||
case 'publish_now':
|
||||
$id=$_POST['id']??$_GET['id']??'';
|
||||
$queue=getEntries($queue_file);
|
||||
$post=null;$qi=-1;
|
||||
foreach($queue as $i=>$e){if(($e['id']??'')===$id){$post=$e;$qi=$i;break;}}
|
||||
if(!$post){echo json_encode(['ok'=>false,'err'=>'not_found']);break;}
|
||||
|
||||
$token=getLinkedinToken();
|
||||
$orgId=getLinkedinOrgId();
|
||||
$result=['ok'=>false];
|
||||
|
||||
if($token){
|
||||
// Real LinkedIn API call (UGC Posts API)
|
||||
$body=[
|
||||
'author'=>"urn:li:organization:$orgId",
|
||||
'lifecycleState'=>'PUBLISHED',
|
||||
'specificContent'=>[
|
||||
'com.linkedin.ugc.ShareContent'=>[
|
||||
'shareCommentary'=>['text'=>$post['post']],
|
||||
'shareMediaCategory'=>'NONE',
|
||||
]
|
||||
],
|
||||
'visibility'=>['com.linkedin.ugc.MemberNetworkVisibility'=>'PUBLIC'],
|
||||
];
|
||||
$ch=curl_init('https://api.linkedin.com/v2/ugcPosts');
|
||||
curl_setopt_array($ch,[
|
||||
CURLOPT_POST=>1,CURLOPT_POSTFIELDS=>json_encode($body),
|
||||
CURLOPT_RETURNTRANSFER=>1,CURLOPT_TIMEOUT=>30,
|
||||
CURLOPT_HTTPHEADER=>['Authorization: Bearer '.$token,'Content-Type: application/json','X-Restli-Protocol-Version: 2.0.0'],
|
||||
]);
|
||||
$resp=curl_exec($ch);
|
||||
$code=curl_getinfo($ch,CURLINFO_HTTP_CODE);
|
||||
curl_close($ch);
|
||||
if($code>=200 && $code<300){
|
||||
$d=@json_decode($resp,true);
|
||||
$post['linkedin_post_id']=$d['id']??'';
|
||||
$post['published_via']='linkedin_api';
|
||||
$post['published_at']=date('c');
|
||||
$post['status']='published';
|
||||
$result=['ok'=>true,'via'=>'linkedin_api','linkedin_id'=>$post['linkedin_post_id']];
|
||||
}else{
|
||||
$result=['ok'=>false,'via'=>'linkedin_api','code'=>$code,'err'=>substr($resp,0,200)];
|
||||
logit("publish_api fail $id code=$code resp=".substr($resp,0,100));
|
||||
}
|
||||
}else{
|
||||
// No token - mark as manually published (assumes Yacine published via copy)
|
||||
$post['published_via']='manual';
|
||||
$post['published_at']=date('c');
|
||||
$post['status']='published';
|
||||
$result=['ok'=>true,'via'=>'manual','note'=>'No LinkedIn API token - marked as manually published'];
|
||||
}
|
||||
|
||||
if($result['ok']){
|
||||
$published=getEntries($published_file);
|
||||
$published[]=$post;
|
||||
writeAll($published_file,$published);
|
||||
$queue=array_values(array_filter($queue,fn($e)=>($e['id']??'')!==$id));
|
||||
writeAll($queue_file,$queue);
|
||||
logit("published $id via ".$result['via']);
|
||||
}
|
||||
echo json_encode($result,JSON_PRETTY_PRINT);
|
||||
break;
|
||||
|
||||
case 'reject':
|
||||
$id=$_POST['id']??$_GET['id']??'';
|
||||
$queue=getEntries($queue_file);
|
||||
$queue=array_values(array_filter($queue,fn($e)=>($e['id']??'')!==$id));
|
||||
writeAll($queue_file,$queue);
|
||||
logit("rejected $id");
|
||||
echo json_encode(['ok'=>true,'id'=>$id,'action'=>'rejected']);
|
||||
break;
|
||||
|
||||
case 'auto_publish_due':
|
||||
// Cron-driven: publish all scheduled posts whose time has come
|
||||
$scheduled=getEntries($scheduled_file);
|
||||
$now=time();
|
||||
$published_new=0;$failed=0;$remaining=[];
|
||||
foreach($scheduled as $p){
|
||||
$due=strtotime($p['scheduled_at']??'2099-01-01');
|
||||
if($due<=$now){
|
||||
$_GET['id']=$p['id'];
|
||||
// Publish via API or mark manual
|
||||
$token=getLinkedinToken();
|
||||
if($token){
|
||||
$body=[
|
||||
'author'=>"urn:li:organization:".getLinkedinOrgId(),
|
||||
'lifecycleState'=>'PUBLISHED',
|
||||
'specificContent'=>[
|
||||
'com.linkedin.ugc.ShareContent'=>[
|
||||
'shareCommentary'=>['text'=>$p['post']],
|
||||
'shareMediaCategory'=>'NONE',
|
||||
]
|
||||
],
|
||||
'visibility'=>['com.linkedin.ugc.MemberNetworkVisibility'=>'PUBLIC'],
|
||||
];
|
||||
$ch=curl_init('https://api.linkedin.com/v2/ugcPosts');
|
||||
curl_setopt_array($ch,[CURLOPT_POST=>1,CURLOPT_POSTFIELDS=>json_encode($body),CURLOPT_RETURNTRANSFER=>1,CURLOPT_TIMEOUT=>30,CURLOPT_HTTPHEADER=>['Authorization: Bearer '.$token,'Content-Type: application/json','X-Restli-Protocol-Version: 2.0.0']]);
|
||||
$resp=curl_exec($ch);$code=curl_getinfo($ch,CURLINFO_HTTP_CODE);curl_close($ch);
|
||||
if($code>=200 && $code<300){
|
||||
$p['published_at']=date('c');$p['published_via']='linkedin_api_cron';$p['status']='published';
|
||||
$published=getEntries($published_file);$published[]=$p;writeAll($published_file,$published);
|
||||
$published_new++;
|
||||
}else{$failed++;$remaining[]=$p;logit("cron publish fail ".$p['id']." code=$code");}
|
||||
}else{
|
||||
// Move to queue with priority flag for Yacine
|
||||
$queue=getEntries($queue_file);
|
||||
$p['status']='due_pending_manual';
|
||||
$p['due_since']=$p['scheduled_at'];
|
||||
$queue[]=$p;writeAll($queue_file,$queue);
|
||||
logit("due but no token - moved to queue ".$p['id']);
|
||||
}
|
||||
}else{
|
||||
$remaining[]=$p;
|
||||
}
|
||||
}
|
||||
writeAll($scheduled_file,$remaining);
|
||||
echo json_encode(['ok'=>true,'published'=>$published_new,'failed'=>$failed,'remaining_scheduled'=>count($remaining)]);
|
||||
break;
|
||||
|
||||
case 'log':
|
||||
$tail=@shell_exec("tail -50 $log_file 2>/dev/null") ?: '';
|
||||
echo json_encode(['log'=>$tail]);
|
||||
break;
|
||||
|
||||
case 'all_queues':
|
||||
echo json_encode([
|
||||
'queue_drafts'=>getEntries($queue_file),
|
||||
'scheduled'=>getEntries($scheduled_file),
|
||||
'published'=>getEntries($published_file),
|
||||
],JSON_PRETTY_PRINT);
|
||||
break;
|
||||
|
||||
default:
|
||||
echo json_encode(['err'=>'unknown_action','available'=>['overview','approve','schedule','publish_now','reject','auto_publish_due','log','all_queues']]);
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
556
cartographie-screens.html.pre-autodisc-20260420_145004
Normal file
556
cartographie-screens.html.pre-autodisc-20260420_145004
Normal file
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user