- Implemented a new PHP script to retrieve insights for the last N days (default 30). - Supports command-line options for token, account ID, days, API version, and output file. - Fetches data at campaign, adset, and ad levels, with filtering for active statuses. - Handles JSON output and optional file saving, including directory creation if necessary. - Includes error handling for cURL requests and JSON responses.
258 lines
6.7 KiB
PHP
258 lines
6.7 KiB
PHP
<?php
|
|
/**
|
|
* Simple CLI script to fetch active Meta Ads insights for:
|
|
* campaign, adset, ad levels for the last N days (default 30).
|
|
*
|
|
* Usage:
|
|
* php tools/meta-ads-active-insights.php --token=TOKEN --account=act_123456789 --days=30
|
|
* php tools/meta-ads-active-insights.php --token=TOKEN --account=123456789 --output=tmp/meta_insights.json
|
|
*
|
|
* You can also use env vars:
|
|
* META_ACCESS_TOKEN
|
|
* META_AD_ACCOUNT_ID
|
|
*/
|
|
|
|
if ( PHP_SAPI !== 'cli' )
|
|
{
|
|
fwrite( STDERR, "This script must be run from CLI.\n" );
|
|
exit( 1 );
|
|
}
|
|
|
|
$options = getopt( '', [
|
|
'token::',
|
|
'account::',
|
|
'days::',
|
|
'api-version::',
|
|
'output::',
|
|
'help::'
|
|
] );
|
|
|
|
if ( isset( $options['help'] ) )
|
|
{
|
|
echo "Usage:\n";
|
|
echo " php tools/meta-ads-active-insights.php --token=TOKEN --account=act_123 --days=30\n";
|
|
echo " php tools/meta-ads-active-insights.php --token=TOKEN --account=123 --output=tmp/meta.json\n";
|
|
echo "\n";
|
|
echo "Options:\n";
|
|
echo " --token Meta access token (or META_ACCESS_TOKEN env)\n";
|
|
echo " --account Ad account id, with or without act_ prefix (or META_AD_ACCOUNT_ID env)\n";
|
|
echo " --days Number of days back, default 30\n";
|
|
echo " --api-version Graph API version, default v25.0\n";
|
|
echo " --output Optional output JSON file path\n";
|
|
exit( 0 );
|
|
}
|
|
|
|
$token = trim( (string) ( $options['token'] ?? getenv( 'META_ACCESS_TOKEN' ) ?? '' ) );
|
|
$account_id = trim( (string) ( $options['account'] ?? getenv( 'META_AD_ACCOUNT_ID' ) ?? '' ) );
|
|
$days = (int) ( $options['days'] ?? 30 );
|
|
$api_version = trim( (string) ( $options['api-version'] ?? 'v25.0' ) );
|
|
$output = trim( (string) ( $options['output'] ?? '' ) );
|
|
|
|
if ( $token === '' )
|
|
{
|
|
fwrite( STDERR, "Missing token. Use --token or META_ACCESS_TOKEN.\n" );
|
|
exit( 1 );
|
|
}
|
|
|
|
if ( $account_id === '' )
|
|
{
|
|
fwrite( STDERR, "Missing ad account id. Use --account or META_AD_ACCOUNT_ID.\n" );
|
|
exit( 1 );
|
|
}
|
|
|
|
if ( strpos( $account_id, 'act_' ) !== 0 )
|
|
{
|
|
$account_id = 'act_' . preg_replace( '/\D+/', '', $account_id );
|
|
}
|
|
|
|
if ( $account_id === 'act_' )
|
|
{
|
|
fwrite( STDERR, "Invalid ad account id.\n" );
|
|
exit( 1 );
|
|
}
|
|
|
|
if ( $days < 1 )
|
|
{
|
|
$days = 1;
|
|
}
|
|
|
|
$since = date( 'Y-m-d', strtotime( '-' . ( $days - 1 ) . ' days' ) );
|
|
$until = date( 'Y-m-d' );
|
|
$base_url = 'https://graph.facebook.com/' . rawurlencode( $api_version ) . '/' . rawurlencode( $account_id ) . '/insights';
|
|
|
|
$levels = [
|
|
'campaign' => [
|
|
'fields' => 'account_id,campaign_id,campaign_name,spend,impressions,clicks,ctr,cpc,action_values,purchase_roas,date_start,date_stop',
|
|
'filtering_field' => 'campaign.effective_status'
|
|
],
|
|
'adset' => [
|
|
'fields' => 'account_id,campaign_id,campaign_name,adset_id,adset_name,spend,impressions,clicks,ctr,cpc,action_values,purchase_roas,date_start,date_stop',
|
|
'filtering_field' => 'adset.effective_status'
|
|
],
|
|
'ad' => [
|
|
'fields' => 'account_id,campaign_id,campaign_name,adset_id,adset_name,ad_id,ad_name,spend,impressions,clicks,ctr,cpc,action_values,purchase_roas,date_start,date_stop',
|
|
'filtering_field' => 'ad.effective_status'
|
|
]
|
|
];
|
|
|
|
$result = [
|
|
'meta' => [
|
|
'account_id' => $account_id,
|
|
'api_version' => $api_version,
|
|
'days' => $days,
|
|
'since' => $since,
|
|
'until' => $until,
|
|
'generated_at' => date( 'c' )
|
|
],
|
|
'campaign' => [],
|
|
'adset' => [],
|
|
'ad' => [],
|
|
'summary' => []
|
|
];
|
|
|
|
try
|
|
{
|
|
foreach ( $levels as $level => $cfg )
|
|
{
|
|
fwrite( STDERR, "Fetching level: {$level}\n" );
|
|
|
|
$params = [
|
|
'access_token' => $token,
|
|
'level' => $level,
|
|
'fields' => $cfg['fields'],
|
|
'time_increment' => 1,
|
|
'time_range' => json_encode( [ 'since' => $since, 'until' => $until ] ),
|
|
'filtering' => json_encode( [
|
|
[
|
|
'field' => $cfg['filtering_field'],
|
|
'operator' => 'IN',
|
|
'value' => [ 'ACTIVE' ]
|
|
]
|
|
] ),
|
|
'limit' => 500
|
|
];
|
|
|
|
$rows = fetch_all_pages( $base_url, $params );
|
|
$result[ $level ] = $rows;
|
|
$result['summary'][ $level . '_rows' ] = count( $rows );
|
|
}
|
|
}
|
|
catch ( Exception $e )
|
|
{
|
|
fwrite( STDERR, "Error: " . $e -> getMessage() . "\n" );
|
|
exit( 1 );
|
|
}
|
|
|
|
$json = json_encode( $result, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT );
|
|
|
|
if ( $json === false )
|
|
{
|
|
fwrite( STDERR, "Failed to encode JSON output.\n" );
|
|
exit( 1 );
|
|
}
|
|
|
|
if ( $output !== '' )
|
|
{
|
|
$output_dir = dirname( $output );
|
|
if ( $output_dir !== '' && $output_dir !== '.' && !is_dir( $output_dir ) )
|
|
{
|
|
if ( !mkdir( $output_dir, 0775, true ) && !is_dir( $output_dir ) )
|
|
{
|
|
fwrite( STDERR, "Failed to create output directory: {$output_dir}\n" );
|
|
exit( 1 );
|
|
}
|
|
}
|
|
|
|
if ( file_put_contents( $output, $json ) === false )
|
|
{
|
|
fwrite( STDERR, "Failed to write output file: {$output}\n" );
|
|
exit( 1 );
|
|
}
|
|
|
|
fwrite( STDERR, "Saved: {$output}\n" );
|
|
}
|
|
else
|
|
{
|
|
echo $json . PHP_EOL;
|
|
}
|
|
|
|
exit( 0 );
|
|
|
|
function fetch_all_pages( $url, $params = null )
|
|
{
|
|
$all_rows = [];
|
|
$next_url = $url;
|
|
$next_params = $params;
|
|
|
|
while ( $next_url )
|
|
{
|
|
$payload = request_json( $next_url, $next_params );
|
|
|
|
if ( isset( $payload['data'] ) && is_array( $payload['data'] ) )
|
|
{
|
|
foreach ( $payload['data'] as $row )
|
|
{
|
|
$all_rows[] = $row;
|
|
}
|
|
}
|
|
|
|
$next_url = '';
|
|
$next_params = null;
|
|
|
|
if ( isset( $payload['paging']['next'] ) && is_string( $payload['paging']['next'] ) )
|
|
{
|
|
$next_url = $payload['paging']['next'];
|
|
}
|
|
}
|
|
|
|
return $all_rows;
|
|
}
|
|
|
|
function request_json( $url, $params = null )
|
|
{
|
|
if ( is_array( $params ) )
|
|
{
|
|
$query = http_build_query( $params );
|
|
$url .= ( strpos( $url, '?' ) === false ? '?' : '&' ) . $query;
|
|
}
|
|
|
|
$ch = curl_init( $url );
|
|
curl_setopt_array( $ch, [
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_SSL_VERIFYPEER => true,
|
|
CURLOPT_CONNECTTIMEOUT => 20,
|
|
CURLOPT_TIMEOUT => 120
|
|
] );
|
|
|
|
$response = curl_exec( $ch );
|
|
$curl_error = curl_error( $ch );
|
|
$http_code = (int) curl_getinfo( $ch, CURLINFO_HTTP_CODE );
|
|
curl_close( $ch );
|
|
|
|
if ( $response === false )
|
|
{
|
|
throw new Exception( 'cURL error: ' . $curl_error );
|
|
}
|
|
|
|
$decoded = json_decode( $response, true );
|
|
if ( !is_array( $decoded ) )
|
|
{
|
|
throw new Exception( 'Invalid JSON response. HTTP ' . $http_code . '. Body: ' . substr( (string) $response, 0, 1000 ) );
|
|
}
|
|
|
|
if ( isset( $decoded['error'] ) )
|
|
{
|
|
$message = (string) ( $decoded['error']['message'] ?? 'Unknown API error' );
|
|
$code = (string) ( $decoded['error']['code'] ?? '' );
|
|
$subcode = (string) ( $decoded['error']['error_subcode'] ?? '' );
|
|
throw new Exception( 'Meta API error: ' . $message . ' (code: ' . $code . ', subcode: ' . $subcode . ')' );
|
|
}
|
|
|
|
if ( $http_code >= 400 )
|
|
{
|
|
throw new Exception( 'HTTP error ' . $http_code . '. Body: ' . substr( (string) $response, 0, 1000 ) );
|
|
}
|
|
|
|
return $decoded;
|
|
}
|