diff --git a/api/agent-leads-sync.json b/api/agent-leads-sync.json index 9b05810d0..2235c8d3e 100644 --- a/api/agent-leads-sync.json +++ b/api/agent-leads-sync.json @@ -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, diff --git a/api/blade-actions-surfaced.json b/api/blade-actions-surfaced.json index 99805b58a..43b7ab7c3 100644 --- a/api/blade-actions-surfaced.json +++ b/api/blade-actions-surfaced.json @@ -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, diff --git a/api/mql-scoring-status.json b/api/mql-scoring-status.json index ac9fc4693..17011ea07 100644 --- a/api/mql-scoring-status.json +++ b/api/mql-scoring-status.json @@ -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", diff --git a/api/v83-business-kpi-latest.json b/api/v83-business-kpi-latest.json index d2e3328be..50e751462 100644 --- a/api/v83-business-kpi-latest.json +++ b/api/v83-business-kpi-latest.json @@ -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, diff --git a/api/v97-linkedin-control.php b/api/v97-linkedin-control.php new file mode 100644 index 000000000..66a9b94b1 --- /dev/null +++ b/api/v97-linkedin-control.php @@ -0,0 +1,227 @@ +/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']]); +} diff --git a/cartographie-screens.html b/cartographie-screens.html index e2db82ba5..e8a525234 100644 --- a/cartographie-screens.html +++ b/cartographie-screens.html @@ -70,7 +70,7 @@ select{padding:10px;background:#0a0e27;color:#fff;border:1px solid #3d4476;borde

🗺️ WEVADS Cartographie Exhaustive Ecrans

-
1729 ecrans total reperes sur 2 serveurs applicatifs | Genere le 2026-04-16 11:18 | WEVIAMaster multiagent
+
1730 ecrans total reperes sur 2 serveurs applicatifs | Genere le 2026-04-16 11:18 | WEVIAMaster multiagent
3914
Total ecrans
@@ -89,8 +89,8 @@ select{padding:10px;background:#0a0e27;color:#fff;border:1px solid #3d4476;borde
+ + + + + + \ No newline at end of file