crawler.php 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. #!/bin/php
  2. <?php
  3. /*
  4. This program is free software: you can redistribute it and/or modify
  5. it under the terms of the GNU General Public License as published by
  6. the Free Software Foundation, either version 3 of the License, or
  7. (at your option) any later version.
  8. This program is distributed in the hope that it will be useful,
  9. but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. GNU General Public License for more details.
  12. You should have received a copy of the GNU General Public License
  13. along with this program. If not, see <http://www.gnu.org/licenses/>.
  14. */
  15. define('N',"\n");
  16. $inifp=null;
  17. $opts=array(
  18. 'excludeafter'=>60*60*24*30,
  19. 'startinstancesfp'=>null,
  20. 'loadbiglist'=>true,
  21. 'onlinecheck'=>true,
  22. 'timeout'=>5,
  23. 'biglistfp'=>null,
  24. 'prodlistfp'=>null
  25. );
  26. $help='DESCRIZIONE
  27. Questo script parte da una selezione di istanze Mastodon («istanze
  28. di partenza»), ciascuna con una relativa lista di istanze da essa
  29. bloccate (che può anche essere omessa), e genera/aggiorna due liste:
  30. una che conterrà i dati di tutte le istanze di partenza e delle istanze
  31. ad esse note (comprese quelle bloccate, escluse soltanto quelle che non
  32. rispondono da un lasso di tempo impostabile), e una che conterrà solo
  33. le istanze non bloccate, che danno possibilità di iscrizione di nuovi
  34. utenti, il cui numero di utenti è compreso tra 11 e 30000, che
  35. conoscono almeno altre 500 istanze, e la cui media di toot per utente
  36. è maggiore o uguale a 10.
  37. SINTASSI
  38. crawler.php -i <file> | -s <file> -b <file> -p <file> [altre opzioni]
  39. OPZIONI
  40. -i, --inifp <file>
  41. Imposta un file di configurazione da cui leggere le opzioni.
  42. Il formato di questo file è semplice: una opzione per riga in formato
  43. <opzione>=<valore>, dove «opzione» è una qualsiasi tra le opzioni
  44. descritte qui nel suo formato lungo, tranne «inifp» e «help».
  45. Esempio: «startinstancesfp=startinstances.txt».
  46. Il file di configurazione può non contenere tutte le opzioni
  47. disponibili.
  48. Nota bene: tutte le opzioni impostate da riga di comando, che siano
  49. specificate prima o dopo questa, hanno la precedenza su quelle
  50. definite nel file di configurazione.
  51. -s, --startinstancesfp <file>
  52. DEVE essere specificata.
  53. Imposta il file da cui leggere le istanze di partenza e le relative
  54. liste di istanze sospese-silenziate.
  55. Il formato del file è questo: per ogni riga:
  56. <uri della istanza di partenza>|[uri della relativa lista di istanze
  57. bloccate]
  58. Ogni riga vuota o che cominci con il carattere «#» sarà ignorata.
  59. Il formato del file delle istanze bloccate è questo: per ogni riga:
  60. |<uri della istanza bloccata>|<tipo di blocco>(Silenziata|Sospesa)|
  61. [riferimento al motivo del blocco.
  62. Le prime 4 righe del file saranno ignorate, così come le righe che non
  63. corrispondessero al formato di cui sopra.
  64. In futuro utilizzeremo un altro formato, per ora ci stiamo adeguando
  65. a quello impiegato da mastodon.bida.im per la sua lista di istanze
  66. bloccate.
  67. -b, --biglistfp <file>
  68. DEVE essere specificata.
  69. Imposta il file da cui leggere le istanze già testate in passato
  70. (se il file esiste e non è specificata l’opzione «-d», vedi sotto)
  71. e in cui scrivere tutti i dati recuperabili delle istanze testate.
  72. -p, --prodlistfp <file>
  73. DEVE essere specificata.
  74. Imposta il file da cui leggere (se esiste) e in cui scrivere i dati
  75. relativi alle istanze corrispondenti ai criteri di selezione descritti
  76. nel paragrafo «DESCRIZIONE».
  77. -t, --timeout <secondi>
  78. Imposta il timeout delle richieste http(s) in secondi.
  79. DEFAULT: '.$opts['timeout'].' secondi.
  80. -e, --excludeafter <tempo>
  81. Imposta il lasso di tempo dopo il quale un’istanza che non risponde
  82. viene eliminata dal listone di tutte le istanze testate.
  83. «tempo» deve essere specificato come un numero, seguito eventualmente
  84. da un carattere che ne indica l’unità di misura: «s» o nessun
  85. carattere per secondi, «m» per minuti, «o» per ore, «g» per giorni,
  86. «S» per settimane, «M» per mesi (30 giorni), «A» per anni.
  87. DEFAULT: 1 mese.
  88. -l, --loadbiglist <si|no>
  89. Dice al programma se caricare o meno il listone delle istanze già
  90. testate in passato.
  91. DEFAULT: «si».
  92. -c, --onlinecheck <si|no>
  93. Dice al programma se interrogare o meno le istanze note.
  94. Se impostato a «no» forza a «si» «loadbiglist» (vedi opzione
  95. precedente).
  96. DEFAULT: «si».
  97. -h, --help
  98. Mostra questo aiuto ed esce.
  99. This program comes with ABSOLUTELY NO WARRANTY; for details see
  100. the source.
  101. This is free software, and you are welcome to redistribute it under
  102. certain conditions; see <http://www.gnu.org/licenses/> for details.'.N;
  103. function mexit($msg,$code) {
  104. echo($msg);
  105. exit($code);
  106. }
  107. function tosec($str) {
  108. if (preg_match('/^([0-9]+)([smogSMA]?)/',$str,$buf)===1) {
  109. switch ($buf[2]) {
  110. case '':
  111. case 's':
  112. return($buf[1]);
  113. break;
  114. case 'm':
  115. return($buf[1]*60);
  116. break;
  117. case 'o':
  118. return($buf[1]*60*60);
  119. break;
  120. case 'g':
  121. return($buf[1]*60*60*24);
  122. break;
  123. case 'S':
  124. return($buf[1]*60*60*24*7);
  125. break;
  126. case 'M':
  127. return($buf[1]*60*60*24*30);
  128. break;
  129. case 'A':
  130. return($buf[1]*60*60*24*365);
  131. break;
  132. }
  133. } else {
  134. return(false);
  135. }
  136. }
  137. for ($i=1; $i<$argc; $i++) {
  138. if ($argv[$i]=='-i' || $argv[$i]=='--inifp') {
  139. if ($i+1>=$argc || $argv[$i+1]=='')
  140. mexit('L’opzione «'.$argv[$i].'» richiede di specificare un file di configurazione (usa «-h» per vedere la guida).'.N,1);
  141. $i++;
  142. $inifp=$argv[$i];
  143. }
  144. }
  145. if (!is_null($inifp)) {
  146. $buf=@parse_ini_file($inifp);
  147. if ($buf!==false) {
  148. foreach ($buf as $key=>$val) {
  149. if (array_key_exists($key,$opts))
  150. $opts[$key]=$val;
  151. else
  152. echo('Attenzione: l’opzione «'.$key.'» in «'.$inifp.'» è sconosciuta e sarà ignorata.'.N);
  153. }
  154. if (array_key_exists('excludeafter',$opts)) {
  155. $opts['excludeafter']=tosec($opts['excludeafter']);
  156. if ($opts['excludeafter']===false)
  157. mexit('L’opzione «excludeafter» specificata in «'.$inifp.'» non è in un formato corretto (usa «-h» per vedere la guida).'.N,1);
  158. }
  159. } else {
  160. mexit('Attenzione: non ho potuto leggere la configurazione dal file «'.$inifp.'».'.N,1);
  161. }
  162. }
  163. for ($i=1; $i<$argc; $i++) {
  164. if (substr($argv[$i],0,1)=='-') {
  165. switch($argv[$i]) {
  166. case '-i':
  167. case '--inifp':
  168. $i++;
  169. break;
  170. case '-e':
  171. case '--excludeafter':
  172. if ($i+1>=$argc)
  173. $i++;
  174. $opts['excludeafter']=tosec($argv[$i]);
  175. if ($opts['excludeafter']===false)
  176. mexit('Opzione «'.$argv[$i].'»: formato non corretto (usa «-h» per vedere la guida).'.N,1);
  177. break;
  178. case '-t':
  179. case '--timeout':
  180. if ($i+1>=$argc || preg_match('/^[0-9]+$/',$argv[$i+1])!==1)
  181. mexit('L’opzione «'.$argv[$i].'» richiede un parametro numerico intero (usa «-h» per vedere la guida).'.N,1);
  182. $i++;
  183. $opts['timeout']=$argv[$i];
  184. break;
  185. case '-b':
  186. case '--biglistfp':
  187. if ($i+1>=$argc || $argv[$i+1]=='')
  188. mexit('L’opzione «'.$argv[$i].'» richiede un parametro di tipo file (usa «-h» per vedere la guida).'.N,1);
  189. $i++;
  190. $opts['biglistfp']=$argv[$i];
  191. break;
  192. case '-p':
  193. case '--prodlistfp':
  194. if ($i+1>=$argc || $argv[$i+1]=='')
  195. mexit('L’opzione «'.$argv[$i].'» richiede un parametro di tipo file (usa «-h» per vedere la guida).'.N,1);
  196. $i++;
  197. $opts['prodlistfp']=$argv[$i];
  198. break;
  199. case '-s':
  200. case '--startinstancesfp':
  201. if ($i+1>=$argc || $argv[$i+1]=='')
  202. mexit('L’opzione «'.$argv[$i].'» richiede un parametro di tipo file (usa «-h» per vedere la guida).'.N,1);
  203. $i++;
  204. $opts['startinstancesfp']=$argv[$i];
  205. break;
  206. case '-l':
  207. case '--loadbiglist':
  208. if ($i+1>=$argc || ($argv[$i+1]!='si' && $argv[$i+1]!='no'))
  209. mexit('L’opzione «'.$argv[$i].'» richiede un parametro («si/no») (usa «-h» per vedere la guida).'.N,1);
  210. $i++;
  211. $opts['loadbiglist']=true;
  212. if ($argv[$i]=='no') $opts['loadbiglist']=false;
  213. break;
  214. case '-c':
  215. case '--onlinecheck':
  216. if ($i+1>=$argc || ($argv[$i+1]!='si' && $argv[$i+1]!='no'))
  217. mexit('L’opzione «'.$argv[$i].'» richiede un parametro («si/no») (usa «-h» per vedere la guida).'.N,1);
  218. $i++;
  219. $opts['onlinecheck']=true;
  220. if ($argv[$i]=='no') $opts['onlinecheck']=false;
  221. break;
  222. case '-h':
  223. case '--help':
  224. mexit($help,1);
  225. break;
  226. default:
  227. mexit('Opzione «'.$argv[$i].'» sconosciuta (usa «-h» per vedere la guida).'.N,1);
  228. break;
  229. }
  230. } else {
  231. mexit('Opzione «'.$argv[$i].'» sconosciuta (usa «-h» per vedere la guida).'.N,1);
  232. }
  233. }
  234. $buf=null;
  235. if (is_null($opts['startinstancesfp']))
  236. $buf.='- Non hai specificato il file delle istanze di partenza («-s/--startinstancesfp»)'.N;
  237. if (is_null($opts['biglistfp']))
  238. $buf.='- Non hai specificato il file da cui leggere e in cui salvare i dati di tutte le istanze testate («-b/--biglistfp»)'.N;
  239. if (is_null($opts['prodlistfp']))
  240. $buf.='- Non hai specificato il file da cui leggere e in cui salvare i dati di tutte le istanze testate che corrispondono ai criteri di selezione («-p/--prodlistfp»)'.N;
  241. if (!is_null($buf))
  242. mexit('ERRORI'.N.$buf.'Usa «-h/--help» per leggere la guida.'.N,1);
  243. if (!$opts['onlinecheck'])
  244. $opts['loadbiglist']=true;
  245. $biglist=array();
  246. $ibiglistc=0;
  247. if ($opts['loadbiglist']) {
  248. if (file_exists($opts['biglistfp']) && is_file($opts['biglistfp']) && is_readable($opts['biglistfp'])) {
  249. echo('Carico la listona pre-esistente («'.$opts['biglistfp'].'») ... ');
  250. $buf=@file_get_contents($opts['biglistfp']);
  251. if ($buf!==false) {
  252. echo('OK :-)'.N);
  253. $biglist=json_decode($buf,true);
  254. $ibiglistc=count($biglist);
  255. } else {
  256. echo('ERRORE :-('.N);
  257. }
  258. }
  259. }
  260. $blinstances=array();
  261. if ($opts['onlinecheck']) {
  262. $startinstances=array();
  263. echo('Carico il file delle istanze di partenza («'.$opts['startinstancesfp'].'») ... ');
  264. $buf=@file_get_contents($opts['startinstancesfp']);
  265. if ($buf!==false) {
  266. echo('OK :-)'.N);
  267. $buf=explode(N,$buf);
  268. foreach ($buf as $val) {
  269. if ($val!='' && $val[0]!='#') {
  270. $kv=explode('|',$val);
  271. if ($kv[1]=='') $kv[1]=null;
  272. $startinstances[$kv[0]]=$kv[1];
  273. }
  274. }
  275. } else {
  276. mexit(N.'Non ho potuto caricare il file delle istanze di partenza «'.$opts['startinstancesfp'].'», muoio.'.N,1);
  277. }
  278. if (count($startinstances)<1)
  279. mexit('Il file delle istanze di partenza «'.$opts['startinstancesfp'].'» non contiene alcuna voce, muoio.'.N,1);
  280. $context=stream_context_create(array('http'=>array('timeout'=>$opts['timeout'])));
  281. foreach ($startinstances as $dom=>$bluri) {
  282. if (!is_null($bluri)) {
  283. echo('Recupero la lista delle istanze bloccate da «'.$dom.'» («'.$bluri.'») ... ');
  284. $f=@fopen($bluri,'r',false,$context);
  285. if ($f!==false) {
  286. // le prime 4 righe non ci interessano
  287. for ($i=0; $i<4; $i++)
  288. fgets($f);
  289. while (!feof($f)) {
  290. $lin=fgets($f);
  291. if (preg_match('/^\|([^\|]*)\|([^\|]*)\|([^\|]*)\|$/',$lin,$buf)===1)
  292. $blinstances[]=$buf[1];
  293. }
  294. fclose($f);
  295. echo('OK :-)'.N);
  296. } else {
  297. echo('ERRORE :-('.N);
  298. }
  299. } else {
  300. echo('NON recupero la lista delle istanze bloccate da «'.$dom.'»: la uri della stessa non è definita.'.N);
  301. }
  302. }
  303. ksort($blinstances);
  304. echo(count($blinstances).' istanze bloccate.'.N);
  305. foreach ($startinstances as $dom=>$bluri) {
  306. if (!array_key_exists($dom,$biglist))
  307. $biglist[$dom]=null;
  308. echo('Recupero la lista delle istanze note a «'.$dom.'» ... ');
  309. $buf=@file_get_contents('https://'.$dom.'/api/v1/instance/peers',false,$context);
  310. if ($buf!==false) {
  311. echo('OK :-)'.N);
  312. $peers=json_decode($buf,true);
  313. foreach ($peers as $pdom) {
  314. if (!array_key_exists($pdom,$biglist)) {
  315. $biglist[$pdom]=null;
  316. }
  317. }
  318. } else {
  319. echo('ERRORE :-('.N);
  320. }
  321. }
  322. ksort($biglist);
  323. $diff=count($biglist)-$ibiglistc;
  324. if ($diff>=0) $diff='+'.$diff;
  325. echo('Totale istanze note: '.count($biglist).' ('.$diff.' rispetto all\'ultima volta).'.N);
  326. }
  327. $prodlist=array();
  328. $iprodlistc=0;
  329. $buf=@file_get_contents($opts['prodlistfp']);
  330. if ($buf!==false) {
  331. $prodlist=json_decode($buf,true);
  332. $iprodlistc=count($prodlist);
  333. }
  334. $newbiglist=array();
  335. $i=0;
  336. $biglistc=count($biglist);
  337. foreach ($biglist as $dom=>$oinfo) {
  338. if ($opts['onlinecheck']) {
  339. echo('Recupero le informazioni su «'.$dom.'» ('.($i+1).'/'.$biglistc.' - '.round(100/$biglistc*$i).'%) ... ');
  340. $buf=@file_get_contents('https://'.$dom.'/api/v1/instance',false,$context);
  341. if ($buf!==false) {
  342. echo('OK :-)'.N);
  343. $info=json_decode($buf,true);
  344. if (!is_null($oinfo) && array_key_exists('cr-checks',$oinfo))
  345. $info['cr-checks']=$oinfo['cr-checks'];
  346. $info['cr-checks'][]=array('time'=>time(),'ok'=>true);
  347. $newbiglist[$dom]=$info;
  348. } else {
  349. echo('ERRORE :-( ... ');
  350. $lastokk=null;
  351. if (!is_null($oinfo) && array_key_exists('cr-checks',$oinfo)) {
  352. foreach ($oinfo['cr-checks'] as $key=>$val)
  353. if ($val['ok']) $lastokk=$key;
  354. }
  355. if (is_null($oinfo) || is_null($lastokk) || time()-$oinfo['cr-checks'][$lastokk]['time']<=$opts['excludeafter']) {
  356. echo('ma riproveremo...'.N);
  357. $oinfo['cr-checks'][]=array('time'=>time(),'ok'=>false);
  358. $newbiglist[$dom]=$oinfo;
  359. } else {
  360. echo('e non riproveremo...'.N);
  361. $oinfo=null;
  362. }
  363. $info=$oinfo;
  364. }
  365. $i++;
  366. } else {
  367. $info=$oinfo;
  368. }
  369. if (!is_null($info)
  370. && !in_array($dom,$blinstances)
  371. && array_key_exists('registrations',$info) && $info['registrations']==true
  372. && array_key_exists('stats',$info) && array_key_exists('user_count',$info['stats']) && $info['stats']['user_count']>10 && $info['stats']['user_count']<=30000
  373. && array_key_exists('domain_count',$info['stats']) && $info['stats']['domain_count']>=500
  374. && array_key_exists('status_count',$info['stats']) && $info['stats']['status_count']/$info['stats']['user_count']>=10
  375. /* && array_key_exists('contact_account',$info) && array_key_exists('created_at',$info['contact_account'])
  376. && time()-strtotime($info['contact_account']['created_at'])>=6*30*24*60*60*/
  377. ) {
  378. if (array_key_exists($dom,$prodlist)) {
  379. if (array_key_exists('short_description',$info) && (!array_key_exists('short_description',$prodlist[$dom]) || $prodlist[$dom]['short_description']!=$info['short_description'])) {
  380. $info['short_description_changed']=true;
  381. $info['prev_short_description']=$prodlist[$dom]['short_description'];
  382. } else {
  383. $info['short_description_changed']=false;
  384. }
  385. if (array_key_exists('description',$info) && (!array_key_exists('description',$prodlist[$dom]) || $prodlist[$dom]['description']!=$info['description'])) {
  386. $info['description_changed']=true;
  387. $info['prev_description']=$prodlist[$dom]['description'];
  388. } else {
  389. $info['description_changed']=false;
  390. }
  391. echo('«'.$dom.'» era nella lista delle istanze occhei ed è stata AGGIORNATA! :-)'.N);
  392. if (array_key_exists('show',$prodlist[$dom]))
  393. $info['show']=$prodlist[$dom]['show'];
  394. else
  395. $info['show']=-1;
  396. } else {
  397. $info['short_description_changed']=false;
  398. $info['description_changed']=false;
  399. $info['show']=-1;
  400. echo('«'.$dom.'» non era nella lista delle istanze occhei ed è stata AGGIUNTA! :-)'.N);
  401. }
  402. $prodlist[$dom]=$info;
  403. } else {
  404. if (array_key_exists($dom,$prodlist)) {
  405. echo('«'.$dom.'» era nella lista delle istanze occhei ma è stata SCARTATA! :-('.N);
  406. } else {
  407. echo('«'.$dom.'» non era nella lista delle istanze occhei e NON CI È ENTRATA! :-('.N);
  408. }
  409. }
  410. }
  411. if ($opts['onlinecheck']) {
  412. $json=json_encode($newbiglist,JSON_PRETTY_PRINT);
  413. file_put_contents($opts['biglistfp'],$json);
  414. $newbiglistc=count($newbiglist);
  415. $diff=$newbiglistc-$ibiglistc;
  416. if ($diff>=0) $diff='+'.$diff;
  417. echo('Totale istanze nella listona: '.$newbiglistc.' ('.$diff.' rispetto all’ultima volta)'.N);
  418. } else {
  419. echo('Totale istanze nella listona: '.count($biglist).N);
  420. }
  421. $json=json_encode($prodlist,JSON_PRETTY_PRINT);
  422. file_put_contents($opts['prodlistfp'],$json);
  423. $diff=count($prodlist)-$iprodlistc;
  424. if ($diff>=0) $diff='+'.$diff;
  425. echo('Totale istanze nella listina di quelle occhei: '.count($prodlist).' ('.$diff.' rispetto all’ultima volta)'.N);
  426. ?>