#!/bin/php . */ require __DIR__ . "/../../vendor/autoload.php"; use LanguageDetection\Language; define('N',"\n"); $link=false; $logf=false; $jsonf=false; declare(ticks=1); if(defined("pcntl_signal")) { pcntl_signal(SIGTERM,'signalHandler');// Termination ('kill' was called) pcntl_signal(SIGHUP,'signalHandler');// Terminal log-out pcntl_signal(SIGINT,'signalHandler');// Interrupted (Ctrl-C is pressed) function signalHandler($signal) { global $link, $logf, $jsonf; lecho(N.'Sono stato interrotto.'.N); if ($link) { lecho('La connessione MySQL è aperta, la chiudo.'.N); mysqli_close($link); } if ($jsonf) { lecho('Il file di dump json è aperto, lo chiudo.'.N); // qui no, altrimenti "riprendi" fa poi casino // fwrite($jsonf,'"Fine?": true'.N.'}'.N); fclose($jsonf); } if ($logf) { lecho('Il file di log è aperto, lo chiudo.'.N); fclose($logf); } exit(2); } } $opts=array( 'timeout'=>3, 'log'=>true, 'jsonfp'=>__DIR__.'/instances.json', 'jsonwrite'=>true, 'jsonread'=>false ); use function mysqli_real_escape_string as myesc; function tosec($str) { if (preg_match('/^([0-9]+)([smogSMA]?)/',$str,$buf)===1) { switch ($buf[2]) { case '': case 's': return($buf[1]); break; case 'm': return($buf[1]*60); break; case 'o': return($buf[1]*60*60); break; case 'g': return($buf[1]*60*60*24); break; case 'S': return($buf[1]*60*60*24*7); break; case 'M': return($buf[1]*60*60*24*30); break; case 'A': return($buf[1]*60*60*24*365); break; } } else { return(false); } } function mexit($msg,$code) { global $link, $jsonf, $logf; lecho($msg); if ($link) mysqli_close($link); if ($jsonf) fclose($jsonf); if ($logf) fclose($logf); exit($code); } function lecho($msg,$logonly=false) { global $opts, $logf; if (!$logonly) echo($msg); if ($opts['log']) fwrite($logf,$msg); } $instsjfp=__DIR__.'/instances.job'; $currinstjfp=__DIR__.'/currinst.job'; if (file_exists($currinstjfp) && file_exists($instsjfp)) { $riprendi=true; } else { $riprendi=false; } $logfp=__DIR__.'/crawler.log'; if ($opts['log']) { if ($riprendi) $mode=array('a','aggiunta'); else $mode=array('w','scrittura'); $logf=@fopen($logfp,$mode[0]); if ($logf===false) { echo('Non ho potuto aprire in modalità '.$mode[1].' il file di log «'.$logfp.'».'.N); exit(1); } } $inifp=__DIR__.'/../sec/mastostartadmin.ini'; $iniarr=@parse_ini_file($inifp) or mexit('Impossibile aprire il file di configurazione «'.$inifp.'»'.N,1); $link=@mysqli_connect($iniarr['db_host'],$iniarr['db_admin_name'],$iniarr['db_admin_password'],$iniarr['db_name'],$iniarr['db_port'],$iniarr['db_socket']) or mexit('Impossibile connettersi al server MySQL: '.mysqli_connect_error().N,1); mysqli_set_charset($link,'utf8mb4') or mexit(mysqli_error($link).N,1); $tables=array(); $res=mysqli_query($link,'SHOW TABLES') or mexit(mysqli_error($link).N,1); while ($row=mysqli_fetch_row($res)) { $resb=mysqli_query($link,'SHOW COLUMNS FROM '.$row[0]) or mexit(mysqli_error($link).N,1); $fields=array(); // lo uso solo per alcuni tipi, quindi non sto a cercare completezza while ($rowb=mysqli_fetch_assoc($resb)) { if (preg_match('/(\w+)\((.*)\)( unsigned)?/',$rowb['Type'],$buf)===1) { switch ($buf[1]) { case 'char': case 'varchar': $fields[$rowb['Field']]=$buf[2]; break; case 'tinyint': if (array_key_exists(3,$buf)) $fields[$rowb['Field']]=array('min'=>0,'max'=>255); else $fields[$rowb['Field']]=array('min'=>-128,'max'=>127); break; case 'smallint': if (array_key_exists(3,$buf)) $fields[$rowb['Field']]=array('min'=>0,'max'=>65535); else $fields[$rowb['Field']]=array('min'=>-32768,'max'=>32767); break; case 'mediumint': if (array_key_exists(3,$buf)) $fields[$rowb['Field']]=array('min'=>0,'max'=>16777215); else $fields[$rowb['Field']]=array('min'=>-8388608,'max'=>8388607); break; case 'int': if (array_key_exists(3,$buf)) $fields[$rowb['Field']]=array('min'=>0,'max'=>4294967295); else $fields[$rowb['Field']]=array('min'=>-2147483648,'max'=>2147483647); break; // bigint non ci sta in php a meno di usare bcmath o gmp che non è detto siano abilitate sul server, in ogni caso poco importa perché valori bigint vengono usati solo internamente al db, non "vengono da fuori" case 'bigint': if (array_key_exists(3,$buf)) $fields[$rowb['Field']]=array('min'=>'0','max'=>'18446744073709551615'); else $fields[$rowb['Field']]=array('min'=>'-9223372036854775808','max'=>'9223372036854775807'); break; case 'decimal': // questo è da testare contro un decimale vero // fatto, il risultato è che in mysql devo usare decimal(14,4) if (preg_match('/,/',$buf[2])===1) { $lim=explode(',',$buf[2]); } else { $lim[0]=$buf[2]; $lim[1]=0; } $int=$lim[0]-$lim[1]; $sint=''; for ($i=0; $i<$int; $i++) $sint.='9'; $sdec=''; for ($i=0; $i<$lim[1]; $i++) $sdec.='9'; $max=$sint.'.'.$sdec; if (array_key_exists(3,$buf)) $fields[$rowb['Field']]=array('min'=>0,'max'=>floatval($max)); else $fields[$rowb['Field']]=array('min'=>floatval('-'.$max),'max'=>floatval($max)); break; default: $fields[$rowb['Field']]=$rowb['Type']; break; } } elseif ($rowb['Type']=='text') { $fields[$rowb['Field']]=65535; } else { $fields[$rowb['Field']]=$rowb['Type']; } } $tables[$row[0]]=$fields; } if ($riprendi) { lecho('Pare che ci sia un lavoro in sospeso, provo a riprenderlo...'.N); $buf=@file($instsjfp,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES) or mexit('Non ho potuto aprire in lettura il file «'.$instsjfp.'».'.N,1); $insts=array(); foreach ($buf as $line) $insts[]=$line; $buf=@file($currinstjfp,FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES) or mexit('Non ho potuto aprire in lettura il file «'.$currinstjfp.'».'.N,1); $buf=explode("\t",$buf[0]); $currinst=array('dom'=>$buf[0], 'i'=>$buf[1], 'qok'=>$buf[2], 'qgood'=>$buf[3]); $riprendi=true; } $tronconi=array(); function flushtronc($id) { global $tronconi; foreach ($tronconi as $row) { if (!is_null($id)) { if ($row['tab']=='Blacklist') $eurl='editblinst.php'; elseif ($row['tab']=='Instances') $eurl='editinst.php'; elseif ($row['tab']=='Languages') $eurl='editlang.php'; // questo qui sotto non è errore: la tabella InstTrends non ha ID perciò non è editabile, il massimo che si può fare è andare a vedere la tabella Instances e i trends collegati (l'id che viene passato è infatti quello della tabella Instances) elseif ($row['tab']=='InstTrends') $eurl='editinst.php'; } $msg=$row['ctx'].': ho dovuto troncare a '.$row['size'].' caratteri il valore da inserire nella colonna «'.$row['col'].'» della tabella «'.$row['tab'].'» perché troppo lungo ('.$row['len'].' caratteri).'; if (!is_null($id)) $msg.=' Puoi editarlo qui.'; notify($msg,2); } $tronconi=array(); } function truncs($str,$tab,$col,$ctx) { global $tables, $tronconi; $size=$tables[strtolower($tab)][$col]; $len=mb_strlen($str,'UTF-8'); if ($len>$size) { $tronconi[]=array('id'=>null,'tab'=>$tab,'col'=>$col,'ctx'=>$ctx,'len'=>$len,'size'=>$size); $str=mb_substr($str,0,$size-1,'UTF-8').'…'; } return($str); } function truncn($num,$tab,$col,$ctx) { global $tables; if (is_numeric($num)) { if ($num>$tables[strtolower($tab)][$col]['max']) { notify($ctx.': ho dovuto troncare «'.$num.'» al valore massimo «'.$tables[strtolower($tab)][$col]['max'].'» che può avere nella colonna «'.$col.'» della tabella «'.$tab.'»).',2); $num=$tables[strtolower($tab)][$col]['max']; } elseif ($num<$tables[strtolower($tab)][$col]['min']) { notify($ctx.': ho dovuto troncare «'.$num.'» al valore minimo «'.$tables[strtolower($tab)][$col]['min'].'» che può avere nella colonna «'.$col.'» della tabella «'.$tab.'»).',2); $num=$tables[strtolower($tab)][$col]['min']; } } else { notify($ctx.': truncn(): mi aspettavo un numero, invece non lo era; ritorno «0».',3); $num=0; } return($num); } $contextopts=array( 'http'=>array( 'timeout'=>$opts['timeout'] ), 'socket'=>array( 'tcp_nodelay'=>true ) ); $context=stream_context_create($contextopts); $blacklist=array(); lecho('Carico la blacklist dal database...'.N); $res=mysqli_query($link,'SELECT * FROM Blacklist') or mexit(mysqli_error($link).N,3); lecho(mysqli_num_rows($res).' istanze nella blacklist.'.N); while($row=mysqli_fetch_assoc($res)) { $blacklist[$row['Domain']]=$row; } function pgdatetomy($pgdate) { if (preg_match('/^(\d+)-(\d+)-(\d+)[ T]{1}(\d+):(\d+):(\d+)(\.\d+)?Z?$/',$pgdate,$buf)===1) { $mtime=mktime($buf[4],$buf[5],$buf[6],$buf[2],$buf[3],$buf[1]); if (array_key_exists(7,$buf)) $mtime=$mtime+floatval('0'.$buf[7]); return($mtime); } else { notify('pgdatetomy: «'.$pgdate.'» non è un formato di data riconosciuto! Ritorno il magico momento attuale.',3); return(time()); } } function blpgdumplinetomy($line) { $truefalse=array('f'=>0,'t'=>1); $row=explode("\t",$line); $row=array('Domain'=>$row[0], 'CreatedAt'=>pgdatetomy($row[1]), 'ModifiedAt'=>pgdatetomy($row[2]), 'Severity'=>$row[3], 'RejectMedia'=>$truefalse[$row[4]], 'RejectReports'=>$truefalse[$row[5]], 'PublicComment'=>$row[6]); return($row); } if (!$riprendi) { $blacklistnew=array(); $insts=array(); lecho('Carico le istanze di partenza...'.N); $res=mysqli_query($link,'SELECT Domain FROM StartNodes') or mexit(mysqli_error($link).N,3); lecho(mysqli_num_rows($res).' istanze di partenza.'.N); while($row=mysqli_fetch_assoc($res)) { $insts[]=$row['Domain']; lecho('Recupero la lista delle istanze note a «'.$row['Domain'].'» ... '); $buf=@file_get_contents('https://'.$row['Domain'].'/api/v1/instance/peers',false,$context); if ($buf!==false) { lecho('OK :-)'.N); $peers=json_decode($buf,true); foreach ($peers as $pdom) { if (willtrunc($pdom,'Instances','URI')) notify('L’istanza «'.$pdom.'» non sarà considerata perché il suo dominio è troppo lungo per il campo «URI» della tabella «Instances» nel DB',1); if (!in_array($pdom,$insts) && !willtrunc($pdom,'Instances','URI')) $insts[]=$pdom; } } else { lecho('ERRORE :-('.N); } lecho('Recupero la blacklist di «'.$row['Domain'].'» ... '); $buf=@file_get_contents('https://'.$row['Domain'].'/domain_blocks.txt',false,$context); if ($buf!==false) { lecho('OK :-)'.N); $buf=explode(N,$buf); foreach ($buf as $line) { if (preg_match('/(^#.*$)|(^\s*$)/',$line)===0) { $brow=blpgdumplinetomy($line); if (!array_key_exists($brow['Domain'],$blacklist)) { $blacklistnew[$brow['Domain']]=$brow; } $blacklist[$brow['Domain']]=$brow; } } } else { lecho('ERRORE :-('.N); } } foreach ($blacklistnew as $row) { if (!willtrunc($row['Domain'],'Blacklist','Domain')) { mysqli_query($link,'INSERT INTO Blacklist (ID, Domain, CreatedAt, ModifiedAt, Severity, RejectMedia, RejectReports, PrivateComment, PublicComment) VALUES (NULL, \''.myesc($link,$row['Domain']).'\', \''.myesc($link,$row['CreatedAt']).'\', \''.myesc($link,$row['ModifiedAt']).'\', \''.myesc($link,$row['Severity']).'\', \''.myesc($link,$row['RejectMedia']).'\', \''.myesc($link,$row['RejectReports']).'\', NULL, \''.myesc($link,$row['Domain']).'\')') or mexit(mysqli_error($link).N,3); flushtronc(mysqli_insert_id($link)); } else { notify('Non ho potuto inserire «'.$row['Domain'].'» nella tabella delle istanze blacklistate perché il dominio è troppo lungo per il campo corrispondente nel DB.',2); } } //lecho('Carico le istanze note dal DB e aggiungo alla lista di quelle da controllare quelle che non ci sono già.'.N); $res=mysqli_query($link,'SELECT URI FROM Instances') or mexit(mysqli_error($link).N,3); while($row=mysqli_fetch_assoc($res)) { if (!in_array($row['URI'],$insts)) $insts[]=$row['URI']; } sort($insts); ksort($blacklist); ksort($blacklistnew); lecho('Istanze recuperate: '.count($insts).N); lecho('Istanze blacklistate: '.count($blacklist).', di cui '.count($blacklistnew).' nuove aggiunte al DB.'.N); $instsf=@fopen($instsjfp,'w') or mexit('Non ho potuto aprire in scrittura il file «'.$instsjfp.'».'.N,1); foreach ($insts as $dom) fwrite($instsf,$dom.N); fclose($instsf); } function willtrunc($str,$tab,$col) { global $tables; if (mb_strlen($str,'UTF-8')>$tables[strtolower($tab)][$col]) return(true); else return(false); } function b2i($bool,$pre) { if (is_bool($bool)) { if ($bool) return(1); else return(0); } else { notify($pre.'il valore «'.$bool.'» non è booleano, lo assumo come falso e ritorno «0».',2); return(0); } } //is array, array key exists and value is not null function akeavinn($key,&$arr) { if (is_array($arr) && array_key_exists($key,$arr) && !is_null($arr[$key])) return(true); else return(false); } function nempty($str) { if (preg_match('/^\s*$/',$str)===1) return(null); else return($str); } function subarimp($glue,$key,&$arr) { $str=''; $i=1; $carr=count($arr); foreach ($arr as $inarr) { $str.=$inarr[$key]; if ($i<$carr) $str.=$glue; $i++; } return($str); } function notify($msg,$sev) { global $link, $tables; lecho('NOTIFICAZIÒ: '.strip_tags($msg).N); mysqli_query($link,'INSERT INTO Notifications (ID, Notification, Severity, Microtime, Seen) VALUES (NULL, \''.myesc($link,mb_substr($msg,0,$tables['notifications']['Notification'],'UTF-8')).'\', '.$sev.', \''.microtime(true).'\', 0)') or mexit(mysqli_error($link).N,3); } /** */ /** * Effettua una chiamata alla API di Mastodon. * * @param string $host L'host da chiamare (e.g.: "mastodon.bida.im") * @param string $path Il path della API (e.g.: "/api/v1/timelines/public?local=true") * @return mixed L'oggetto ritornato dalla chiamata, già parsato da json_decode, o NULL se la chiamata fallisce */ function get_api($host, $path) { global $context; try { $buf = @file_get_contents('https://' . $host . $path, false, $context); } catch(Exception $e) { echo "error:"; echo $e; return NULL; } if ($buf!==false) { $data = json_decode($buf, true); return $data; } else { return NULL; } } /** * Torna un elenco di linguaggi riconosciuti nel toot fornito con relativa probabilità. * * @param mixed $toot Il toot da analizzare, come ritornato dalle API * @return array Mappa tra codice lingua e probabilità che il toot sia in quella lingua. */ function get_toot_languages($toot) { $l = $toot['language']; $res = []; if($l !== NULL) { // la lingua è specificata già nel toot: usa quella $langs[$l] = 1; } else { // la lingua non è specificata: deducila $text = strip_tags($toot['content']); $ld = new Language; $langs = $ld->detect($text)->bestResults()->close(); } // raggruppa le lingue derivate, e.g.: "zh" e "zh-CN" $grouped_langs = array(); foreach($langs as $key => $value) { $l = explode("-", $key)[0]; if(array_key_exists($l, $grouped_langs)) { $grouped_langs[$l] = max($grouped_langs[$l], $value); } else { $grouped_langs[$l] = $value; } } return $grouped_langs; } /** * Date le probabilità di lingua per ogni toot, calcola la media. * * @param array $detected_langs Array di mappe tra lingua e probabilità * @return array Mappa tra lingua e probabilità */ function summary($detected_langs) { $res = Array(); foreach($detected_langs as $langs) { foreach($langs as $l => $weight) { if(!array_key_exists($l, $res)) { $res[$l] = 0; } $res[$l] += $weight; } } foreach($res as $l => $sumweight) { $res[$l] = $sumweight / count($detected_langs); } return $res; } /** * Helper function per usort: compara due array usando il primo elemento. * * @param array $entry1 Primo array da comparare * @param array $entry2 Secondo array da comparare * @return number -1, 0 o 1 a seconda che $entry1[0] sia minore, uguale o superiore a $entry2[0] */ function sort_weights($entry1, $entry2) { $w1 = $entry1[0]; $w2 = $entry2[0]; return $w1 < $w2 ? 1 : $w1 == $w2 ? 0 : -1; } /** * Data una mappa di lingue, ritorna una lista di linguaggi considerati probabili. * * @param array $summary Mappa tra lingue e probabilità * @return string[] Elenco di lingue considerate probabili */ function get_languages($summary) { $lst = []; foreach($summary as $code => $weight) { $lst[] = [$weight, $code]; } usort($lst, 'sort_weights'); $languages = []; $lastweight = 0; foreach($lst as $entry) { $l = $entry[1]; $weight = $entry[0]; if($weight < $lastweight * 2 / 3) { break; } $languages[] = $l; $lastweight = $weight; } return $languages; } /** * Ritorna una lista di lingue probabili per la data istanza. * * @param string $host Hostname dell'istanza (e.g.: "mastodon.bida.im") * @return string[] Lista di lingue probabili */ function get_instance_langs($host) { $data = get_api($host, '/api/v1/timelines/public?local=true'); if($data == NULL) { return []; } $detected_langs = array_map('get_toot_languages', $data); $summary = summary($detected_langs); $languages = get_languages($summary); return $languages; } /** */ /** * ucfirst UTF-8 aware function * * @param string $string * @return string * @see http://ca.php.net/ucfirst */ function my_ucfirst($string, $e ='utf-8') { if (function_exists('mb_strtoupper') && function_exists('mb_substr') && !empty($string)) { $string = mb_strtolower($string, $e); $upper = mb_strtoupper($string, $e); preg_match('#(.)#us', $upper, $matches); $string = $matches[1] . mb_substr($string, 1, mb_strlen($string, $e), $e); } else { $string = ucfirst($string); } return $string; } function langs($instid, $uri) { global $info, $instrow, $link; $instlangs=array(); $languages = get_instance_langs($uri); if(count($languages) == 0 && akeavinn('languages',$info)) { $languages = $info['languages']; } echo "Lingue trovate: " . implode(", ", $languages).N; $pos=0; foreach($languages as $lang) { $res=mysqli_query($link,'SELECT * FROM Languages WHERE Code=\''.myesc($link,$lang).'\'') or mexit(mysqli_error($link).N,3); if (mysqli_num_rows($res)<1) { $NameIt=myesc($link,truncs(my_ucfirst(locale_get_display_name($lang,'it')),'Languages','NameIT','«'.$instrow['URI'].'»')); $NameEn=myesc($link,truncs(my_ucfirst(locale_get_display_name($lang,'en')),'Languages','NameEN','«'.$instrow['URI'].'»')); $NameFr=myesc($link,truncs(my_ucfirst(locale_get_display_name($lang,'fr')),'Languages','NameFR','«'.$instrow['URI'].'»')); $NameEs=myesc($link,truncs(my_ucfirst(locale_get_display_name($lang,'es')),'Languages','NameES','«'.$instrow['URI'].'»')); $NameOrig=myesc($link,truncs(my_ucfirst(locale_get_display_name($lang,$lang)),'Languages','NameES','«'.$instrow['URI'].'»')); $q = 'INSERT INTO Languages (ID, Code, NameIT, NameEN, NameFR, NameES, NameOrig) VALUES (NULL, \''.myesc($link,truncs($lang,'Languages','Code','«'.$instrow['URI'].'»')).'\', \''.$NameIt.'\', \''.$NameEn.'\', \''.$NameFr.'\', \''.$NameEs.'\', \''.$NameOrig.'\')'; mysqli_query($link, $q) or mexit(mysqli_error($link).N,3); $langid=mysqli_insert_id($link); flushtronc($langid); } else { $row=mysqli_fetch_assoc($res); $langid=$row['ID']; } $pos++; $instlangs[]=array('InstID'=>$instid,'LangID'=>$langid,'Pos'=>$pos,'Code'=>$lang); } // } return($instlangs); } function varbdump($var) { ob_start(); var_dump($var); $content=ob_get_contents(); ob_end_clean(); return($content); } function mdasortbykey(&$arr,$key,$rev=false) { $karr=array(); foreach ($arr as $akey=>$subarr) $karr[$subarr[$key]]=array($akey,$subarr); if (!$rev) ksort($karr); else krsort($karr); $arr=array(); foreach ($karr as $akey=>$subarr) $arr[$subarr[0]]=$subarr[1]; } /* * Nodeinfo ('https://'.$dom.'/nodeinfo/2.0') è stato aggiunto nella 3.0.0 * Trends ('https://'.$dom.'/api/v1/trends') è stato aggiunto nella 3.0.0 * Activity ('https://'.$dom.'/api/v1/instance/activity') è stato aggiunto nella 2.1.2 */ if ($opts['jsonwrite']) { if ($riprendi) $mode=array('a','aggiunta'); else $mode=array('w','scrittura'); $jsonf=@fopen($opts['jsonfp'],$mode[0]) or mexit('Non ho potuto aprire in modalità '.$mode[1].' il file di dump delle info json «'.$opts['jsonfp'].'».',1); if ($mode[0]=='w') fwrite($jsonf,'{'.N); } $cinsts=count($insts); $i=0; $qok=0; $qgood=0; if ($riprendi) { $i=$currinst['i']; $qok=$currinst['qok']; $qgood=$currinst['qgood']; } while ($i<$cinsts) { $dom=$insts[$i]; @file_put_contents($currinstjfp,$dom."\t".$i."\t".$qok."\t".$qgood.N) or mexit('Non ho potuto aprire in scrittura il file «'.$currinstjfp.'».',1); $i++; $ok=true; $info=null; lecho('~~~~~~~~~~~~~~~'.N); lecho('Provo a recuperare info su «'.$dom.'» ['.$i.'/'.$cinsts.' ('.$qok.' OK; '.$qgood.' BUONE) - '.round(100/$cinsts*$i).'%]'.N); lecho('Provo a recuperare le informazioni API sull’istanza ... '); $buf=@file_get_contents('https://'.$dom.'/api/v1/instance',false,$context); if ($buf!==false) { $info=json_decode($buf,true); if (is_array($info)) { lecho('OK :-)'.N); lecho('Provo a recuperare le informazioni Nodeinfo sull’istanza ... '); $buf=@file_get_contents('https://'.$dom.'/nodeinfo/2.0',false,$context); if ($buf!==false) { lecho('OK :-)'.N); $info['x-nodeinfo']=json_decode($buf,true); // per ora teniamo solo quelle che, se si identificano, si identificano come mastodon o corgidon (derivato di mastodon) // teniamo d'occhio le notifiche di cui sotto per includere eventualmente altri derivati di mastodon? // visti fin qui, verificare cosa sono: epicyon if (is_array($info['x-nodeinfo']) && array_key_exists('software',$info['x-nodeinfo']) && array_key_exists('name',$info['x-nodeinfo']['software']) &&!is_null($info['x-nodeinfo']['software']['name'])) { if (preg_match('/^mastodon|corgidon/',$info['x-nodeinfo']['software']['name'])===0) $ok=false; $res=mysqli_query($link,'SELECT Name FROM Platforms WHERE Name=\''.myesc($link,$info['x-nodeinfo']['software']['name']).'\'') or mexit(mysqli_error($link).N,3); if (mysqli_num_rows($res)<1) { $res=mysqli_query($link,'INSERT INTO Platforms (Name) VALUES (\''.myesc($link,truncs($info['x-nodeinfo']['software']['name'],'Platforms','Name','«'.$info['uri'].'»')).'\')') or mexit(mysqli_error($link).N,3); notify('«'.$info['uri'].'» utilizza come software «'.$info['x-nodeinfo']['software']['name'].'»; lo aggiungo alla tabella delle piattaforme incontrate. Se non si tratta di mastodon o corgidon, che già vengono accettati, sarebbe buona cosa verificare se è una variante di mastodon e quanto è compatibile, per valutare se accettare le istanze che lo utilizzano.',1); flushtronc(null); } } } else { lecho('ERRORE :-('.N); } if ($ok && array_key_exists('version',$info)) { if ($info['version']>='2.1.2') { lecho('Provo a recuperare le informazioni API sull’attività dell’istanza ... '); $buf=@file_get_contents('https://'.$dom.'/api/v1/instance/activity',false,$context); if ($buf!==false) { lecho('OK :-)'.N); $info['x-activity']=json_decode($buf,true); } else { lecho('ERRORE :-('.N); } } if ($info['version']>='3.0.0') { lecho('Provo a recuperare le informazioni API sui trends dell’istanza ... '); $buf=@file_get_contents('https://'.$dom.'/api/v1/trends',false,$context); if ($buf!==false) { lecho('OK :-)'.N); $info['x-trends']=json_decode($buf,true); } else { lecho('ERRORE :-('.N); } } } } else { $ok=false; lecho('ERRORE :-('.N); } } else { $ok=false; lecho('ERRORE :-('.N); // questo è anche il limbo delle istanze che non rispondono, perciò controlliamo se già esistono nel db e, nel caso, aggiorniamo InstChecks $res=mysqli_query($link,'SELECT * FROM Instances WHERE URI=\''.myesc($link,mb_substr($dom,0,$tables['instances']['URI'],'UTF-8')).'\'') or mexit(mysqli_error($link).N,3); if (mysqli_num_rows($res)>0) { lecho('«'.$dom.'» non risponde, ma è presente nel database; aggiorno InstChecks.'.N); $row=mysqli_fetch_assoc($res); mysqli_query($link,'INSERT INTO InstChecks (InstID, Time, Status) VALUES ('.$row['ID'].', '.time().', 0)') or mexit(mysqli_error($link).N,3); } } if (is_array($info) && count($info)>0) { lecho('Dumpone json di tutte le info recuperate:'.N.json_encode($info,JSON_PRETTY_PRINT).N,true); if ($opts['jsonwrite']) fwrite($jsonf,'"'.$dom.'": '.json_encode($info,JSON_PRETTY_PRINT).','.N); } if ($ok && !is_null($info) && akeavinn('uri',$info) && !is_null(nempty($info['uri'])) && !willtrunc($info['uri'],'Instances','URI') && akeavinn('version',$info) && preg_match('/pleroma|pixelfed/i',$info['version'])===0) { $qok++; $instrow=array('ID'=>null, 'New'=>0, 'Good'=>0, 'Chosen'=>0, 'Visible'=>0, 'Blacklisted'=>0, 'URI'=>null, 'Title'=>null, 'ShortDesc'=>null, 'LongDesc'=>null, 'OurDesc'=>null, 'LocalityID'=>null, 'Email'=>null, 'Software'=>null, 'Version'=>null, 'UserCount'=>null, 'StatusCount'=>null, 'DomainCount'=>null, 'ActiveUsersMonth'=>null, 'ActiveUsersHalfYear'=>null, 'Thumb'=>null, 'RegOpen'=>null, 'RegReqApproval'=>null, 'MaxTootChars'=>null, 'AdmAccount'=>null, 'AdmDisplayName'=>null, 'AdmCreatedAt'=>null, 'AdmNote'=>null, 'AdmURL'=>null, 'AdmAvatar'=>null, 'AdmHeader'=>null); if (array_key_exists($info['uri'],$blacklist)) $instrow['Blacklisted']=1; $instrow['URI']=$info['uri']; if (akeavinn('title',$info)) $instrow['Title']=nempty(truncs($info['title'],'Instances','Title','«'.$instrow['URI'].'»')); if (akeavinn('short_description',$info)) $instrow['ShortDesc']=nempty(truncs($info['short_description'],'Instances','ShortDesc','«'.$instrow['URI'].'»')); if (akeavinn('description',$info)) $instrow['LongDesc']=nempty(truncs($info['description'],'Instances','LongDesc','«'.$instrow['URI'].'»')); if (akeavinn('email',$info)) $instrow['Email']=nempty(truncs($info['email'],'Instances','Email','«'.$instrow['URI'].'»')); if (akeavinn('version',$info)) $instrow['Version']=nempty(truncs($info['version'],'Instances','Version','«'.$instrow['URI'].'»')); if (akeavinn('stats',$info)) { if (akeavinn('user_count',$info['stats'])) $instrow['UserCount']=truncn($info['stats']['user_count'],'Instances','UserCount','«'.$instrow['URI'].'»'); if (akeavinn('status_count',$info['stats'])) $instrow['StatusCount']=truncn($info['stats']['status_count'],'Instances','StatusCount','«'.$instrow['URI'].'»'); if (akeavinn('domain_count',$info['stats'])) $instrow['DomainCount']=truncn($info['stats']['domain_count'],'Instances','DomainCount','«'.$instrow['URI'].'»'); } if (akeavinn('thumbnail',$info)) $instrow['Thumb']=nempty(truncs($info['thumbnail'],'Instances','Thumb','«'.$instrow['URI'].'»')); if (akeavinn('max_toot_chars',$info)) $instrow['MaxTootChars']=truncn($info['max_toot_chars'],'Instances','MaxTootChars','«'.$instrow['URI'].'»'); if (akeavinn('registrations',$info)) $instrow['RegOpen']=b2i($info['registrations'],'Istanza «'.$instrow['URI'].'»: '); if (akeavinn('approval_required',$info)) $instrow['RegReqApproval']=b2i($info['approval_required'],'Istanza «'.$instrow['URI'].'»: '); if (akeavinn('contact_account',$info)) { if (akeavinn('acct',$info['contact_account'])) $instrow['AdmAccount']=nempty(truncs($info['contact_account']['acct'],'Instances','AdmAccount','«'.$instrow['URI'].'»')); if (akeavinn('display_name',$info['contact_account'])) $instrow['AdmDisplayName']=nempty(truncs($info['contact_account']['display_name'],'Instances','AdmDisplayName','«'.$instrow['URI'].'»')); if (akeavinn('created_at',$info['contact_account'])) $instrow['AdmCreatedAt']=pgdatetomy($info['contact_account']['created_at']); if (akeavinn('note',$info['contact_account'])) $instrow['AdmNote']=nempty(truncs(strip_tags($info['contact_account']['note'],''),'Instances','AdmNote','«'.$instrow['URI'].'»')); if (akeavinn('url',$info['contact_account'])) $instrow['AdmURL']=nempty(truncs($info['contact_account']['url'],'Instances','AdmURL','«'.$instrow['URI'].'»')); if (akeavinn('avatar',$info['contact_account'])) $instrow['AdmAvatar']=nempty(truncs($info['contact_account']['avatar'],'Instances','AdmAvatar','«'.$instrow['URI'].'»')); if (akeavinn('header',$info['contact_account'])) $instrow['AdmHeader']=nempty(truncs($info['contact_account']['header'],'Instances','AdmHeader','«'.$instrow['URI'].'»')); } if (akeavinn('x-nodeinfo',$info)) { if (akeavinn('software',$info['x-nodeinfo']) && akeavinn('name',$info['x-nodeinfo']['software'])) $instrow['Software']=nempty(truncs($info['x-nodeinfo']['software']['name'],'Instances','Software','«'.$instrow['URI'].'»')); if (akeavinn('usage',$info['x-nodeinfo']) && akeavinn('users',$info['x-nodeinfo']['usage'])) { if (akeavinn('activeMonth',$info['x-nodeinfo']['usage']['users'])) $instrow['ActiveUsersMonth']=truncn($info['x-nodeinfo']['usage']['users']['activeMonth'],'Instances','ActiveUsersMonth','«'.$instrow['URI'].'»'); if (akeavinn('activeHalfyear',$info['x-nodeinfo']['usage']['users'])) $instrow['ActiveUsersHalfYear']=truncn($info['x-nodeinfo']['usage']['users']['activeHalfyear'],'Instances','ActiveUsersHalfYear','«'.$instrow['URI'].'»'); } } $whynot=array(); if ($instrow['Blacklisted']==1) $whynot[]='è nella blacklist'; if (is_null($instrow['RegOpen'])) { $whynot[]='non se ne conosce lo stato delle registrazioni (aperte/chiuse)'; } elseif ($instrow['RegOpen']==0) { $whynot[]='ha le registrazioni chiuse'; } if (is_null($instrow['UserCount'])) { $whynot[]='non se ne conosce il numero di utenti'; } elseif ($instrow['UserCount']<10 || $instrow['UserCount']>30000) { $whynot[]='il numero di utenti non è compreso tra 10 e 30.000'; } if (is_null($instrow['DomainCount'])) { $whynot[]='non se ne conosce il numero di istanze note'; } elseif ($instrow['DomainCount']<500) { $whynot[]='il numero di istanze note è minore di 500'; } if (!is_null($instrow['ActiveUsersMonth'])) { if ($instrow['ActiveUsersMonth']<10) $whynot[]='il numero di utenti attivi nell’ultimo mese è minore di 10'; } elseif (!is_null($instrow['StatusCount']) && $instrow['StatusCount']/$instrow['UserCount']<10) { $whynot[]='il numero medio di toots per utente è minore di 10'; } if (count($whynot)==0) { $instrow['Good']=1; lecho('Siamo in presenza di un’istanza BUONA! :-)'.N); $qgood++; } else { lecho('Siamo in presenza di un’istanza CATTIVA: '.implode('; ',$whynot).' :-('.N); } $res=mysqli_query($link,'SELECT * FROM Instances WHERE URI=\''.myesc($link,$instrow['URI']).'\'') or mexit(mysqli_error($link).N,3); if (mysqli_num_rows($res)>0) { lecho('«'.$instrow['URI'].'» è già presente nel DB, la aggiorno...'.N); $oldinstrow=mysqli_fetch_assoc($res); flushtronc($oldinstrow['ID']); $instid=$oldinstrow['ID']; $instrow['ID']=$oldinstrow['ID']; $instrow['New']=$oldinstrow['New']; if ($instrow['Good']==1 && $oldinstrow['Good']==0) { notify('L’istanza «'.$instrow['URI'].'» non era papabile, ma lo è diventata!',1); } elseif ($instrow['Good']==0 && $oldinstrow['Good']==1) { notify('L’istanza «'.$instrow['URI'].'» era papabile, ma non lo è più per i seguenti motivi: '.implode('; ',$whynot),3); } $instrow['Chosen']=$oldinstrow['Chosen']; $instrow['Visible']=$oldinstrow['Visible']; if ($instrow['ShortDesc']!=$oldinstrow['ShortDesc']) notify('

La «Descrizione breve» dell’istanza «'.$instrow['URI'].'» è cambiata. La vecchia era...

'.$oldinstrow['ShortDesc'].'

La nuova è...

'.$instrow['ShortDesc'].'
',1); if ($instrow['LongDesc']!=$oldinstrow['LongDesc']) notify('

La «Descrizione lunga» dell’istanza «'.$instrow['URI'].'» è cambiata. La vecchia era...

'.$oldinstrow['LongDesc'].'

La nuove è...

'.$instrow['LongDesc'].'
',1); $instrow['OurDesc']=$oldinstrow['OurDesc']; $instrow['LocalityID']=$oldinstrow['LocalityID']; $query='UPDATE Instances SET '; foreach ($instrow as $field=>$value) { if (!is_null($value)) $query.=$field.'=\''.myesc($link,$value).'\', '; else $query.=$field.'=NULL, '; } $query=substr($query,0,-2).' WHERE Instances.ID='.$instrow['ID']; lecho('QUERONA DI UPDATE: «'.$query.'».'.N); mysqli_query($link,$query) or mexit(mysqli_error($link).N,3); $res=mysqli_query($link,'SELECT InstID, LangID, Pos, Code FROM InstLangs LEFT JOIN Languages ON Languages.ID=LangID WHERE InstID='.$instrow['ID'].' ORDER BY Pos ASC') or mexit(mysqli_error($link).N,3); $oldinstlangs=array(); while ($row=mysqli_fetch_assoc($res)) $oldinstlangs[]=$row; $instlangs=langs($instrow['ID'], $instrow['URI']); if ($instlangs!=$oldinstlangs) { notify('La lista delle lingue utilizzate dichiarate dall’istanza «'.$instrow['URI'].'» è cambiata da «'.subarimp(', ','Code',$oldinstlangs).'» a «'.subarimp(', ','Code',$instlangs).'».',1); mysqli_query($link,'DELETE FROM InstLangs WHERE InstID='.$instrow['ID']) or mexit(mysqli_error($link).N,3); foreach ($instlangs as $row) { mysqli_query($link,'INSERT INTO InstLangs (InstID, LangID, Pos) VALUES ('.$row['InstID'].', '.$row['LangID'].', '.$row['Pos'].')') or mexit(mysqli_error($link).N,3); } } } else { lecho('«'.$info['uri'].'» non è già presente nel DB, la aggiungo...'.N); $instrow['New']=1; $fields=array(); $values=''; foreach ($instrow as $field=>$value) { $fields[]=$field; if (!is_null($value)) $values.='\''.myesc($link,$value).'\', '; else $values.='NULL, '; } $values=substr($values,0,-2); $query='INSERT INTO Instances ('.implode(', ',$fields).') VALUES ('.$values.')'; lecho('QUERONA DI INSERT: «'.$query.'»'.N); mysqli_query($link,$query) or mexit(mysqli_error($link).N,3); $instid=mysqli_insert_id($link); flushtronc($instid); $instlangs=langs($instid, $instrow['URI']); foreach ($instlangs as $row) { mysqli_query($link,'INSERT INTO InstLangs (InstID, LangID, Pos) VALUES ('.$row['InstID'].', '.$row['LangID'].', '.$row['Pos'].')') or mexit(mysqli_error($link).N,3); } if ($instrow['Good']==1) notify('La nuova istanza «'.$instrow['URI'].'» è papabile!',1); } if (array_key_exists('x-activity',$info) && is_array($info['x-activity'])) { mysqli_query($link,'DELETE FROM InstActivity WHERE InstID='.$instid); $pos=0; foreach ($info['x-activity'] as $buf) { if (akeavinn('week',$buf) && akeavinn('statuses',$buf) && akeavinn('logins',$buf) && akeavinn('registrations',$buf)) { $pos++; $query='INSERT INTO InstActivity (InstID, Week, Statuses, Logins, Registrations, Pos) VALUES (\''.$instid.'\', \''.myesc($link,$buf['week']).'\', \''.myesc($link,$buf['statuses']).'\', \''.myesc($link,$buf['logins']).'\', \''.myesc($link,$buf['registrations']).'\', '.$pos.')'; mysqli_query($link,$query) or mexit(mysqli_error($link).N,3); } } } if (array_key_exists('x-trends',$info) && is_array($info['x-trends'])) { $trends=array(); foreach ($info['x-trends'] as $buf) { if (akeavinn('name',$buf) && akeavinn('url',$buf) && akeavinn('history',$buf) && is_array($buf['history'])) { $trend=0; foreach ($buf['history'] as $row) { if ($row['uses']>0) $trend+=($row['accounts']/$row['uses']); } $trends[]=array( 'InstID'=>$instid, 'LastDay'=>$buf['history'][0]['day'], 'Name'=>$buf['name'], 'URL'=>$buf['url'], 'Pos'=>null, 'trend'=>$trend ); } } mdasortbykey($trends,'trend',true); // print_r($trends); mysqli_query($link,'DELETE FROM InstTrends WHERE InstID='.$instid); $pos=0; foreach ($trends as $trend) { $pos++; $query='INSERT INTO InstTrends (InstID, LastDay, Name, URL, Pos) VALUES ('.$trend['InstID'].', \''.$trend['LastDay'].'\', \''.myesc($link,truncs($trend['Name'],'InstTrends','Name','«'.$instrow['URI'].'»')).'\', \''.myesc($link,truncs($trend['URL'],'InstTrends','URL','«'.$instrow['URI'].'»')).'\', '.$pos.')'; mysqli_query($link,$query) or mexit(mysqli_error($link).N,3); // questo qui sotto non è errore, vedi il commento relativo nella funzione flushtronc($instid); } } mysqli_query($link,'INSERT INTO InstChecks (InstID, Time, Status) VALUES ('.$instid.', '.time().', 1)') or mexit(mysqli_error($link).N,3); } } mysqli_close($link); if ($opts['jsonwrite']) { fwrite($jsonf,'"Fine?": true'.N.'}'.N); fclose($jsonf); } unlink($instsjfp); unlink($currinstjfp); exit(0); ?>