From fdf343abf77e8d1a09f23b28c9f4c616d199fb9c Mon Sep 17 00:00:00 2001 From: pezcurrel Date: Sat, 26 Oct 2024 23:11:05 +0200 Subject: [PATCH] Now, if an event has changed, gancioff reposts it; many other minor changes; bumped version to 0.4 --- gancioff | 208 +++++++++++++++++++++++++++++++------------------------ 1 file changed, 117 insertions(+), 91 deletions(-) diff --git a/gancioff b/gancioff index 38eab23..a4b5a02 100755 --- a/gancioff +++ b/gancioff @@ -18,7 +18,7 @@ $SNAME='GancioFF'; $ENAME=strtolower($SNAME); -$SVERS='0.3'; +$SVERS='0.4'; require __DIR__.'/lib/gettlds.php'; require __DIR__.'/lib/mastodon-postLength.php'; @@ -127,12 +127,6 @@ always_link_gancio_post = true -h / --help Show this help text and exit. --t / --test - Do a test: {$SNAME} will try as always to read the configuration file, fetch - the defined Mastodon instance’s info, load the state file and fetch the feed, - but it will post only the first event it may find there, with a visibility of - «direct», even if according to the state file it has already been posted, and - won’t update the state file. -p / --do-post When a state file already exists, this option defaults to «y» («yes»), which means that {$SNAME} will try to post all the new events it may find in the @@ -145,10 +139,18 @@ always_link_gancio_post = true refuses to run unless you explicitly set this option to «y» or «n»: this is a way to prevent you from unintentionally flooding your Mastodon instance with all the events in the feed. - When “test mode” is active (see the previous option description), setting - this option has no effect. + When “test mode” is active (see the next option description), setting this + option has no effect. +-t / --test + Do a test: {$SNAME} will try as always to read the configuration file, fetch + the defined Mastodon instance’s info, load the state file and fetch the feed, + but it will post only the first event it may find there, with a visibility of + «direct», even if according to the state file it has already been posted, and + won’t update the state file. + This option also activates “verbose mode” (see below). -v / --verbose - Show some more messages about what the script is doing. + When this option is not set {$SNAME} prints only warning and error messages; + when it is set it also prints informational messages about what it’s doing. -- Treat every possible subsequent argument as non-options. Useful only in the very improbable case your config file is named «--help» or as another option. @@ -195,6 +197,7 @@ for ($i=1; $i<$argc; $i++) { exit(0); } elseif ($argv[$i]=='-t' || $argv[$i]=='--test') { $opts['test']=true; + $opts['verbose']=true; } elseif ($argv[$i]=='-p' || $argv[$i]=='--do-post') { if ($i+1>=$argc) dieYoung("Error: option «{$argv[$i]}» requires an argument; use «-h» or «--help» to display help.\n",1); if ($argv[$i+1]=='y') @@ -235,20 +238,20 @@ if ($opts['update-language-codes']) { if ($res['content']===false) dieYoung("Error: could not connect to «{$url}».\n",1); if ($res['httpcode']!='200') dieYoung("Error: got http code «{$res['httpcode']}» from «{$url}».\n",1); $res=explode("\r\n",$res['content']); -// alb|sqi|sq|Albanian|albanais -// tup|||Tupi languages|tupi, langues +// alb|sqi|sq|Albanian|albanais +// tup|||Tupi languages|tupi, langues foreach ($res as $val) if (preg_match('#^[a-z]{3}\|([a-z]{3})?\|([a-z]{2})\|.+\|.+$#',$val,$matches)===1) $langCodes[]=$matches[2]; $count=count($langCodes); if (@file_put_contents($langsFP,implode("\n",$langCodes)."\n")===false) dieYoung("Error: could not save the {$count} ISO 639-1 language code(s) i got from «{$url}» into «{$langsFP}».\n",1); - echo "Info: successfully saved the {$count} ISO 639-1 language code(s) i got from «{$url}» into «{$langsFP}».\n"; + vecho($opts['verbose'],"Info: successfully saved the {$count} ISO 639-1 language code(s) i got from «{$url}» into «{$langsFP}».\n"); exit(0); } if (($langs=@file($langsFP,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES))===false) dieYoung("Error: could not load ISO 639-1 language codes from «{$langsFP}».\n",1); if (is_null($confFP)) dieYoung("Error: you have not specified a configuration file; use «-h» or «--help» to display help.\n",1); -echo "Info: trying to load configuration file «{$confFP}» from directory «".getcwd()."».\n"; +vecho($opts['verbose'],"Info: trying to load configuration file «{$confFP}» from directory «".getcwd()."».\n"); if (!file_exists($confFP)) dieYoung("Error: «{$confFP}» does not exist.\n",1); if (!is_file($confFP)) dieYoung("Error: «{$confFP}» is not a file.\n",1); if (!is_readable($confFP)) dieYoung("Error: «{$confFP}» is not readable.\n",1); @@ -259,7 +262,7 @@ if (!in_array($conf['posts_visibility'],['public', 'unlisted', 'private', 'direc if (!is_null($conf['max_post_length'])) { if (preg_match('#^\d+$#',$conf['max_post_length'])!==1 || $conf['max_post_length']+0<10) dieYoung("Error: configuration file: «max_post_length» must be an integer greater than or equal to 10.\n",1); $conf['max_post_length']+=0; - echo "Info: got «{$conf['max_post_length']}» as «max_post_length» from configuration file.\n"; + vecho($opts['verbose'],"Info: got «{$conf['max_post_length']}» as «max_post_length» from configuration file.\n"); } if (!is_bool($conf['always_link_gancio_post']) && preg_match('#^(true|false)$#',$conf['always_link_gancio_post'])!==1) { dieYoung("Error: configuration file: «always_link_gancio_post» must be «true» or «false».\n",1); @@ -267,12 +270,12 @@ if (!is_bool($conf['always_link_gancio_post']) && preg_match('#^(true|false)$#', ($conf['always_link_gancio_post']=='true') ? $conf['always_link_gancio_post']=true : $conf['always_link_gancio_post']=false; } if ($opts['test']) $conf['posts_visibility']='direct'; -echo "Info: got good configuration from configuration file.\n"; +vecho($opts['verbose'],"Info: got good configuration from configuration file.\n"); $tldsregex=gettlds(__DIR__.'/storage/tlds.txt',true); $url="https://{$conf['fedi_hostname']}/api/v2/instance"; -echo "Info: trying to fetch instance info from «{$url}».\n"; +vecho($opts['verbose'],"Info: trying to fetch instance info from «{$url}».\n"); $res=curl($url,null,["Authorization: Bearer {$conf['fedi_token']}", 'Accept: application/json']); if ($res['content']===false) dieYoung("Error: could not connect to «{$url}» (error: «{$res['error']}»).\n",1); $res['content']=@json_decode($res['content'],true); @@ -282,17 +285,17 @@ if ($res['httpcode']!='200') dieYoung("Error: got http code «{$res['httpcode']} if (!isset($res['content']['configuration']['media_attachments']['image_size_limit'])) dieYoung("Error: JSON from «{$url}» doesn’t declare «image_size_limit».\n",1); if (!is_int($res['content']['configuration']['media_attachments']['image_size_limit'])) dieYoung("Error: JSON from «{$url}» declares «image_size_limit» with an unexpected format.\n",1); $conf['max_image_size']=$res['content']['configuration']['media_attachments']['image_size_limit']+0; -echo "Info: got «{$conf['max_image_size']}» as «max_image_size» from «{$url}».\n"; +vecho($opts['verbose'],"Info: got «{$conf['max_image_size']}» as «max_image_size» from «{$url}».\n"); if (!isset($res['content']['configuration']['statuses']['max_characters'])) dieYoung("Error: JSON from «{$url}» doesn’t declare «max_characters».\n",1); if (!is_int($res['content']['configuration']['statuses']['max_characters'])) dieYoung("Error: JSON from «{$url}» declares «max_characters» with an unexpected format.\n",1); if (is_null($conf['max_post_length'])) { $conf['max_post_length']=$res['content']['configuration']['statuses']['max_characters']+0; - echo "Info: got «{$conf['max_post_length']}» as «max_post_length» from «{$url}».\n"; + vecho($opts['verbose'],"Info: got «{$conf['max_post_length']}» as «max_post_length» from «{$url}».\n"); } //print_r($conf); $guids=[]; -echo "Info: trying to load GUIDs of already posted events from state file «{$conf['state_file_absolute_path']}».\n"; +vecho($opts['verbose'],"Info: trying to load GUIDs of already posted events from state file «{$conf['state_file_absolute_path']}».\n"); if (file_exists($conf['state_file_absolute_path'])) { if (!is_file($conf['state_file_absolute_path'])) dieYoung("Error: «{$conf['state_file_absolute_path']}» exists but it’s not a file.\n",1); if (!is_readable($conf['state_file_absolute_path'])) dieYoung("Error: «{$conf['state_file_absolute_path']}» exists but it’s not readable.\n",1); @@ -304,10 +307,10 @@ if (file_exists($conf['state_file_absolute_path'])) { $i=0; $fh=fopen($conf['state_file_absolute_path'],'w'); foreach ($buff as $key=>$val) { - if (preg_match('#^(\d+)\t(\S+)$#',$val,$matches)===1) { + if (preg_match('#^(\d+)\t([a-z0-9]{64})\t(\S+)$#',$val,$matches)===1) { if ($matches[1]+0>=$graceLine) { - fwrite($fh,"{$matches[1]}\t{$matches[2]}\n"); - $guids[$matches[2]]=$matches[1]; + fwrite($fh,"{$matches[1]}\t{$matches[2]}\t{$matches[3]}\n"); + $guids[$matches[3]]=['timestamp'=>$matches[1], 'hash'=>$matches[2]]; } else { $i++; } @@ -316,16 +319,17 @@ if (file_exists($conf['state_file_absolute_path'])) { } } fclose($fh); - echo 'Info: got '.count($guids)." GUID(s) for already posted event(s) from state file «{$conf['state_file_absolute_path']}»; removed {$i} line(s) older than one year.\n"; + vecho($opts['verbose'],'Info: got '.count($guids)." GUID(s) for already posted event(s) from state file «{$conf['state_file_absolute_path']}»; removed {$i} line(s) older than one year.\n"); } elseif (is_null($opts['do-post']) && !$opts['test']) { - dieYoung("Error: state file «{$conf['state_file_absolute_path']}» doesn’t exist yet, so this is probably a first run on feed «{$conf['feed_url']}»; thus, all the events {$SNAME} may find in the feed will be considered new and, as a precaution against flooding your local timeline, you have to explicitly declare whether you want it to post them all, or not, by explicitly setting option «-p» or «--do-post» to «y» («yes») or «n» («no»); mind that in both cases they will be recorded as posted in the state file, and won’t be posted again on subsequent runs (you can use «-h» or «--help» to display help).\n",1); + echo "State file «{$conf['state_file_absolute_path']}» doesn’t exist yet, so this is probably a first run on feed «{$conf['feed_url']}»; thus, all the events {$SNAME} may find in the feed will be considered new and, as a precaution against flooding your local timeline, you have to explicitly declare whether you want it to post them all, or not, by explicitly setting option «-p» or «--do-post» to «y» («yes») or «n» («no»); mind that in both cases they will be recorded as posted in the state file, and won’t be posted again on subsequent runs (you can use «-h» or «--help» to display help).\n"; + exit(0); } else { - echo "Info: state file «{$conf['state_file_absolute_path']}» was not found.\n"; + vecho($opts['verbose'],"Info: state file «{$conf['state_file_absolute_path']}» was not found.\n"); } +//print_r($guids);die(); if (is_null($opts['do-post']) || $opts['test']) $opts['do-post']=true; -$newItemsCount=0; -echo "Info: trying to fetch feed from «{$conf['feed_url']}».\n"; +vecho($opts['verbose'],"Info: trying to fetch feed from «{$conf['feed_url']}».\n"); $feed=curl($conf['feed_url'],null,['Accept: application/xml']); if ($feed['content']===false) dieYoung("Error: could not connect to «{$conf['feed_url']}» (error: «{$feed['error']}»).\n",1); if ($feed['httpcode']!='200') dieYoung("Error: «{$conf['feed_url']} returned http code «{$res['httpcode']}».\n",1); @@ -335,49 +339,65 @@ if ($feed===false) dieYoung("Error: got no valid XML from «{$conf['feed_url']} if (!isset($feed->channel->item) || !is_iterable($feed->channel->item) || !is_countable($feed->channel->item)) dieYoung("Error: feed from «{$conf['feed_url']}» had unexpected format.\n",1); $itemsCount=$feed->channel->item->count(); if ($itemsCount==0) exitYoung("Info: feed from «{$conf['feed_url']}» was empty, bye.\n"); -foreach ($feed->channel->item as $item) - if (isset($item->guid) && $item->guid->__toString()!='' && !array_key_exists($item->guid->__toString(),$guids)) - $newItemsCount++; -echo "Info: got good feed with {$itemsCount} events ({$newItemsCount} new) from «{$conf['feed_url']}».\n"; -if (!$opts['test'] && $newItemsCount==0) exitYoung("Info: feed from «{$conf['feed_url']}» had no new events, bye.\n"); -if (!$opts['test'] && ($fh=@fopen($conf['state_file_absolute_path'],'a'))===false) dieYoung("Error: could not open «{$conf['state_file_absolute_path']}» in «append» mode.\n",1); +$tsfp="{$conf['state_file_absolute_path']}.tmp"; +if (!$opts['test'] && ($fh=@fopen($tsfp,'w'))===false) dieYoung("Error: could not open «{$tsfp}» in «write» mode.\n",1); +$itemsToPost=0; $goodPostsCount=0; $index=0; foreach ($feed->channel->item as $item) { $index++; -// print_r($item); - if (!isset($item->guid) || ($guid=$item->guid->__toString())=='') { - fwrite(STDERR,"Warning: event #{$index} has no GUID, skipping.\n"); - } elseif (!array_key_exists($guid,$guids) || $opts['test']) { + //print_r($item); + if (!isset($item->guid) || !isset($item->title) || !isset($item->link) || !isset($item->description) || !isset($item->pubDate)) { + fwrite(STDERR,"Warning: event #{$index} has unexpected format, skipping.\n"); + } else { + $guid=$item->guid->__toString(); $file=null; - if (isset($item->enclosure[0]['url']) && isset($item->enclosure[0]['type']) && isset($item->enclosure[0]['length'])) + $hash=$item->title.$item->pubDate; + if (isset($item->enclosure[0]['url']) && isset($item->enclosure[0]['type']) && isset($item->enclosure[0]['length'])) { $file=['url'=>$item->enclosure[0]['url']->__toString(), 'type'=>$item->enclosure[0]['type']->__toString(), 'length'=>$item->enclosure[0]['length']->__toString()]; - if (isset($item->description)) { - $buff=$item->description->__toString(); - if ($buff=='') { - $ptext=''; - } elseif (preg_match('#^\n?

(.+)

(.+)
\((\w+)\W+(\d+)\W+(\w+)\W+(\d+:\d+)\)
(.+)$#iuU',$buff,$matches)===1) { -// print_r($matches); - $matches[1]=hent($matches[1]); - $matches[2]=hent($matches[2]); - $ptext="{$matches[1]}\n\n".ucfirst($matches[3])." {$matches[4]} {$matches[5]} alle {$matches[6]} presso {$matches[2]}\n\n".html2text($matches[7]); - } else { - $ptext=html2text($item->description); - } + $hash.=$item->enclosure[0]['url'].$item->enclosure[0]['type'].$item->enclosure[0]['length']; } - if (isset($item->link) && $item->link->__toString()!='') { - $plink="\n\n".$item->link->__toString(); + $buff=$item->description->__toString(); + if ($buff=='') { + $ptext=''; + } elseif (preg_match('#^\n?

(.+)

(.+)
\((\w+)\W+(\d+)\W+(\w+)\W+(\d+:\d+)\)
(.+)$#iuU',$buff,$matches)===1) { + //print_r($matches); + $matches[1]=hent($matches[1]); + $matches[2]=hent($matches[2]); + $ptext="{$matches[1]}\n\n".ucfirst($matches[3])." {$matches[4]} {$matches[5]} alle {$matches[6]} presso {$matches[2]}\n\n".html2text($matches[7]); } else { - $plink=''; + $ptext=html2text($item->description); } + $hash.=$buff; + $plink="\n\n".$item->link->__toString(); + $hash.=$item->link; if (isset($item->category) && is_countable($item->category) && is_iterable($item->category) && $item->category->count()>0) { $pcats=[]; - foreach ($item->category as $val) $pcats[]=hashtag($val->__toString()); + foreach ($item->category as $val) { + $pcats[]=hashtag($val->__toString()); + $hash.=$val; + } $pcats="\n\n".implode(' ',$pcats); } else { $pcats=''; } + $hash=hash('sha256',$hash); + if (array_key_exists($guid,$guids)) { + if ($hash==$guids[$guid]['hash']) { + vecho($opts['verbose'],"Info: event «{$guid}» is not new and has not changed; skipping.\n"); + $state='old'; + } else { + vecho($opts['verbose'],"Info: event «{$guid}» is not new, but it has changed; processing.\n"); + $state='changed'; + $itemsToPost++; + } + } else { + vecho($opts['verbose'],"Info: event «{$guid}» is new; processing.\n"); + $state='new'; + $itemsToPost++; + } + if ($opts['test']) $state='new'; $post="{$ptext}{$plink}{$pcats}"; if (postLength($post,$tldsregex['tlds'])<=$conf['max_post_length'] && !$conf['always_link_gancio_post']) { $plink=''; @@ -387,47 +407,48 @@ foreach ($feed->channel->item as $item) { $post="{$ptext}{$plink}{$pcats}"; while (postLength($post,$tldsregex['tlds'])>$conf['max_post_length'] && $ptext!='') { $ptext=preg_replace('#\S+\W*$#','',$ptext); -// echo "[[[{$ptext}]]]\n"; + //echo "[[[{$ptext}]]]\n"; $post="{$ptext}[…]{$plink}{$pcats}"; } + //echo "--- #{$index}: {$guid} ---\n{$post}\n--- (length: ".postLength($post,$tldsregex['tlds']).") ---\n\n"; if (postLength($post,$tldsregex['tlds'])>$conf['max_post_length']) { fwrite(STDERR,"Warning: could not shorten post for event «{$guid}» to make it fit into {$conf['max_post_length']} characters; won’t post.\n"); - } else { - if ($opts['verbose']) echo "--- #{$index}: {$guid} ---\n{$post}\n--- (length: ".postLength($post,$tldsregex['tlds']).") ---\n\n"; + } elseif ($state=='new' || $state=='changed') { if ($opts['do-post']) { $doPost=false; $postData=[]; -// print_r($file); + //print_r($file); if (is_null($file)) { - if ($opts['verbose']) echo "Info: event «{$guid}» has no attachment.\n"; + vecho($opts['verbose'],"Info: {$state} event «{$guid}» has no attachment.\n"); $doPost=true; } elseif ($file['length']>$conf['max_image_size']) { - fwrite(STDERR,"Warning: the size of attachment «{$file['ulr']}» is greater than image upload max size on «{$conf['fedi_hostname']}»; won’t post status for event «{$guid}».\n"); + fwrite(STDERR,"Warning: the size of attachment «{$file['ulr']}» is greater than image upload max size on «{$conf['fedi_hostname']}»; won’t post status for {$state} event «{$guid}».\n"); } else { + vecho($opts['verbose'],"Info: {$state} event «{$guid}» has an attachment; processing.\n"); $res=curl($file['url']); if ($res['content']===false) { - fwrite(STDERR,"Warning: could not connect to «{$file['url']}» (error: «{$res['error']}»); won’t post status for event «{$guid}».\n"); + fwrite(STDERR,"Warning: could not connect to «{$file['url']}» (error: «{$res['error']}»); won’t post status for {$state} event «{$guid}».\n"); } elseif ($res['httpcode']!='200') { - fwrite(STDERR,"Warning: «{$file['url']}» returned http code «{$res['httpcode']}»; won’t post status for event «{$guid}».\n"); + fwrite(STDERR,"Warning: «{$file['url']}» returned http code «{$res['httpcode']}»; won’t post status for {$state} event «{$guid}».\n"); } else { -// we don't use CURLStringFile because in php 7.3 it is not available -// $pd=['file'=>new CURLStringFile($res['content'],'file',$file['type']), 'description'=>'Flyer dell’evento']; + // we don't use CURLStringFile because in php 7.3 it is not available + //$pd=['file'=>new CURLStringFile($res['content'],'file',$file['type']), 'description'=>'Flyer dell’evento']; $tfp=__DIR__.'/storage/'.basename($file['url']); if (@file_put_contents($tfp,$res['content'])===false) { - fwrite(STDERR,"Warning: could not save «{$tfp}»; won’t post status for event «{$guid}».\n"); + fwrite(STDERR,"Warning: could not save «{$tfp}»; won’t post status for {$state} event «{$guid}».\n"); } else { $pd=['file'=>curl_file_create($tfp,$file['type'],'file'), 'description'=>'Flyer dell’evento']; $url="https://{$conf['fedi_hostname']}/api/v2/media"; $res=curl($url,'/api/v2/media',["Authorization: Bearer {$conf['fedi_token']}", 'Accept: application/json'],$pd); if ($res['content']===false) { - fwrite(STDERR,"Warning: could not connect to «{$url}» (error: «{$res['error']}»); won’t post status for event «{$guid}».\n"); + fwrite(STDERR,"Warning: could not connect to «{$url}» (error: «{$res['error']}»); won’t post status for {$state} event «{$guid}».\n"); } elseif (is_null($res['content']=@json_decode($res['content'],true))) { - fwrite(STDERR,"Warning: «{$url}» did not return valid JSON; won’t post status for event «{$guid}».\n"); + fwrite(STDERR,"Warning: «{$url}» did not return valid JSON; won’t post status for {$state} event «{$guid}».\n"); } elseif ($res['httpcode']!='200' && $res['httpcode']!='202') { (isset($res['content']['error'])) ? $buff=" (error: «{$res['content']['error']}»)" : $buff=''; - fwrite(STDERR,"Warning: «{$url}» returned http code «{$res['httpcode']}»{$buff}; won’t post status for event «{$guid}».\n"); + fwrite(STDERR,"Warning: «{$url}» returned http code «{$res['httpcode']}»{$buff}; won’t post status for {$state} event «{$guid}».\n"); } elseif (!isset($res['content']['id'])) { - fwrite(STDERR,"Warning: no «id» in JSON from «{$url}»; file has not been uploaded successfully; won’t post status for event «{$guid}».\n"); + fwrite(STDERR,"Warning: no «id» in JSON from «{$url}»; file has not been uploaded successfully; won’t post status for {$state} event «{$guid}».\n"); } else { $id=$res['content']['id']; if ($res['httpcode']=='202') { @@ -442,10 +463,11 @@ foreach ($feed->channel->item as $item) { } } if (!is_null($id)) { + vecho($opts['verbose'],"Info: successfully posted attachment for {$state} event «{$guid}».\n"); $postData['media_ids[]']=$id; $doPost=true; } else { - fwrite(STDERR,"Warning: server took too long to process file, or could not; won’t post status for event «{$guid}».\n"); + fwrite(STDERR,"Warning: server took too long to process file, or could not; won’t post status for {$state} event «{$guid}».\n"); } } if (@unlink($tfp)===false) fwrite(STDERR,"Warning: could not delete «{$tfp}».\n"); @@ -460,46 +482,44 @@ foreach ($feed->channel->item as $item) { $headers=["Authorization: Bearer {$conf['fedi_token']}", 'Accept: application/json', 'Idempotency-Key: '.md5(implode('-',$postData).time())]; $res=curl($url,'/api/v1/statuses',$headers,$postData); if ($res['content']===false) { - fwrite(STDERR,"Warning: could not connect to «{$url}» (error: «{$res['error']}»); could not post status for event «{$guid}».\n"); + fwrite(STDERR,"Warning: could not connect to «{$url}» (error: «{$res['error']}»); could not post status for {$state} event «{$guid}».\n"); } elseif (is_null($res['content']=@json_decode($res['content'],true))) { - fwrite(STDERR,"Warning: «{$url}» did not return good JSON; could not post status for event «{$guid}».\n"); + fwrite(STDERR,"Warning: «{$url}» did not return good JSON; could not post status for {$state} event «{$guid}».\n"); } elseif ($res['httpcode']!='200') { (isset($res['content']['error'])) ? $buff=" (error: «{$res['content']['error']}»)" : $buff=''; - fwrite(STDERR,"Warning: «{$url}» returned http code «{$res['httpcode']}»{$buff}; could not post status for event «{$guid}».\n"); + fwrite(STDERR,"Warning: «{$url}» returned http code «{$res['httpcode']}»{$buff}; could not post status for {$state} event «{$guid}».\n"); } elseif (!isset($res['content']['url'])) { - fwrite(STDERR,"Warning: JSON from «{$url}» had unexpected format; could not post status for event «{$guid}».\n"); + fwrite(STDERR,"Warning: JSON from «{$url}» had unexpected format; could not post status for {$state} event «{$guid}».\n"); } else { - echo "Info: successfully posted status for event «{$guid}» (URL: «{$res['content']['url']}»).\n"; -// print_r($res['content']); - $now=time(); - $guids[$guid]=$now; - if (!$opts['test']) fwrite($fh,"{$now}\t{$guid}\n"); + vecho($opts['verbose'],"Info: successfully posted status for {$state} event «{$guid}» (URL: «{$res['content']['url']}»).\n"); + //print_r($res['content']); + $guids[$guid]=['timestamp'=>time(), 'hash'=>$hash]; $goodPostsCount++; } } } else { - echo "Info: would have posted status for event «{$guid}».\n"; - $now=time(); - $guids[$guid]=$now; - if (!$opts['test']) fwrite($fh,"{$now}\t{$guid}\n"); + vecho($opts['verbose'],"Info: would have tried to post status for {$state} event «{$guid}».\n"); + if ($state=='new' || $state=='changed') $guids[$guid]=['timestamp'=>time(), 'hash'=>$hash]; $goodPostsCount++; } } - } else { - if ($opts['verbose']) echo "Info: event «{$guid}» has already been posted on ".date('c',$guids[$guid]).", skipping.\n"; + if (!$opts['test'] && array_key_exists($guid,$guids)) fwrite($fh,"{$guids[$guid]['timestamp']}\t{$guids[$guid]['hash']}\t{$guid}\n"); } if ($opts['test']) break;// to test a single post } -if (!$opts['test']) fclose($fh); +if (!$opts['test']) { + fclose($fh); + rename($tsfp,$conf['state_file_absolute_path']); +} if (!$opts['test']) { if ($opts['do-post']) - echo "Info: succesfully posted {$goodPostsCount} of {$newItemsCount} new event(s) (of {$itemsCount} total events in the feed).\n"; + vecho($opts['verbose'],"Info: succesfully posted {$goodPostsCount} of {$itemsToPost} new or edited event(s) (of {$itemsCount} total events in the feed).\n"); else - echo "Info: would have tried to post {$newItemsCount} new event(s) of {$itemsCount} total events in the feed.\n"; + vecho($opts['verbose'],"Info: would have tried to post {$itemsToPost} new or changed event(s) of {$itemsCount} total events in the feed.\n"); } elseif ($goodPostsCount==1) { - echo "Info: successfully posted the first of {$itemsCount} total events in the feed ({$newItemsCount} are new).\n"; + vecho($opts['verbose'],"Info: successfully posted the first of {$itemsCount} total events in the feed ({$itemsToPost} are new or changed).\n"); } else { - echo "Info: failed to post the first of {$itemsCount} total events in the feed ({$newItemsCount} are new).\n"; + vecho($opts['verbose'],"Info: failed to post the first of {$itemsCount} total events in the feed ({$itemsToPost} are new or changed).\n"); } exit(0); @@ -561,6 +581,10 @@ function hent($str) { return html_entity_decode($str,ENT_QUOTES,'UTF-8'); } +function vecho($do,$msg) { + if ($do) echo $msg; +} + function sighandler($sig) { global $fh; if (isset($fh)) fclose($fh); @@ -569,11 +593,13 @@ function sighandler($sig) { } function dieYoung($msg,$ec) { + if (isset($fh)) fclose($fh); fwrite(STDERR,$msg); die($ec); } function exitYoung($msg) { + if (isset($fh)) fclose($fh); echo $msg; exit(0); }