#!/bin/php
<?php

/*
  This program is free software: you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation, either version 3 of the License, or
  (at your option) any later version.

  This program is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  GNU General Public License for more details.

  You should have received a copy of the GNU General Public License
  along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/

define('N',"\n");

$inifp='crawler.ini';

$help='DESCRIZIONE
 Questo script prende una lista di istanze mastodon sorellate, ciascuna
 con (o anche senza, però sarebbe meglio con) una relativa lista di
 istanze da essa bloccate, e genera una lista delle istanze note alle
 istanze sorellate, escludendone quelle bloccate - più, al momento,
 quelle il cui endpoint [istanza]/api/v1/instance non risponde, e quelle
 che hanno chiusa la registrazione di nuovi utenti.
SINTASSI
 crawler.php [opzioni]
OPZIONI
 -i, --inifp
  Imposta il file di configurazione (per default "'.$inifp.'").
  Tutte le altre opzioni, che siano specificate prima o dopo questa,
  hanno la precedenza su quelle definite nel file di configurazione.
 -t, --timeout
  Imposta il timeout delle richieste http.
 -e, --excludeafter
  Imposta il lasso di tempo (in secondi) dopo il quale un\'istanza che
  non si identifica (il cui endpoint [istanza]/api/v1/instance non
  risponde) viene eliminata dal listone di tutte le istanze testate.
 -b, --biglistfp
  Imposta il file da cui leggere le istanze già testate in passato (se
  non è specificata l\'opzione "-d", vedi sotto) e in cui scrivere
  il listone di tutte le istanze testate.
 -p, --prodlistfp
  Imposta il file in cui scrivere la lista delle istanze occhei.
 -s, --sistersfp
  Imposta il file da cui leggere le istanze sorelle e le relative liste
  di istanze sospese-silenziate.
 -d, --dontloadbl
  Evita di caricare il listone delle istanze già testate in passato.
 -h, --help
  Mostra questo aiuto ed esce.

 This program comes with ABSOLUTELY NO WARRANTY; for details see
 the source.
 This is free software, and you are welcome to redistribute it under
 certain conditions; see <http://www.gnu.org/licenses/> for details.'.N;

function mexit($msg,$code) {
	echo($msg);
	exit($code);
}

$opts=array(
	'excludeafter'=>60*60*24*30,
	'sistersfp'=>'istanzesorelle',
	'dontloadbl'=>0,
	'timeout'=>5,
	'biglistfp'=>'listona.json',
	'prodlistfp'=>'listina.json'
);

for ($i=1; $i<$argc; $i++) {
	if ($argv[$i]=='-i' || $argv[$i]=='--inifp') {
		if ($i+1>=$argc || $argv[$i+1]=='')
			mexit('L’opzione «'.$argv[$i].'» richiede di specificare un file di configurazione (usa «-h» per vedere la guida).'.N,1);
		$i++;
		$inifp=$argv[$i];
		if (!file_exists($inifp) || !is_file($inifp) || !is_readable($inifp))
			mexit('"'.$inifp.'" non esiste, non è un file o non è leggibile.'.N,1);
	}
}

if (file_exists($inifp)) {
	$buf=@parse_ini_file($inifp);
	if ($buf!==false) {
		foreach ($buf as $key=>$val) {
			if (array_key_exists($key,$opts))
				$opts[$key]=$val;
		}
	} else {
		echo('Attenzione: non ho potuto leggere la configurazione dal file "'.$inifp.'", potrebbe essere non leggibile o corrotto.'.N);
	}
}
$f=@fopen($inifp,'w');
if ($f!==false) {
	foreach ($opts as $key=>$val)
		fwrite($f,$key.'='.$val.N);
	fclose($f);
} else {
	echo('Attenzione: non ho potuto salvare la configurazione nel file "'.$inifp.'".'.N);
}

for ($i=1; $i<$argc; $i++) {
	if (substr($argv[$i],0,1)=='-') {
		switch($argv[$i]) {
			case '-i':
			case '--inifp':
			$i++;
			break;
			case '-e':
			case '--excludeafter':
			if ($i+1>=$argc || preg_match('/^[0-9]+$/',$argv[$i+1])!==1)
				mexit('L’opzione «'.$argv[$i].'» richiede un parametro numerico intero (usa «-h» per vedere la guida).'.N,1);
			$i++;
			$opts['excludeafter']=$argv[$i];
			break;
			case '-t':
			case '--timeout':
			if ($i+1>=$argc || preg_match('/^[0-9]+$/',$argv[$i+1])!==1)
				mexit('L’opzione «'.$argv[$i].'» richiede un parametro numerico intero (usa «-h» per vedere la guida).'.N,1);
			$i++;
			$opts['timeout']=$argv[$i];
			break;
			case '-b':
			case '--biglistfp':
			if ($i+1>=$argc || $argv[$i+1]=='')
				mexit('L’opzione «'.$argv[$i].'» richiede un parametro di tipo file (usa «-h» per vedere la guida).'.N,1);
			$i++;
			$opts['biglistfp']=$argv[$i];
			break;
			case '-p':
			case '--biglistfp':
			if ($i+1>=$argc || $argv[$i+1]=='')
				mexit('L’opzione «'.$argv[$i].'» richiede un parametro di tipo file (usa «-h» per vedere la guida).'.N,1);
			$i++;
			$opts['biglistfp']=$argv[$i];
			break;
			case '-s':
			case '--sistersfp':
			if ($i+1>=$argc || $argv[$i+1]=='')
				mexit('L’opzione «'.$argv[$i].'» richiede un parametro di tipo file (usa «-h» per vedere la guida).'.N,1);
			$i++;
			$opts['sistersfp']=$argv[$i];
			break;
			case '-d':
			case '--dontloadbl':
			$opts['dontloadbl']=1;
			break;
			case '-h':
			case '--help':
			mexit($help,1);
			break;
			default:
			mexit('Opzione "'.$argv[$i].'" sconosciuta (usa «-h» per vedere la guida).'.N,1);
			break;
		}
	} else {
		mexit('Opzione "'.$argv[$i].'" sconosciuta (usa «-h» per vedere la guida).'.N,1);
	}
}

$sisters=array();
echo('Carico il file delle istanze sorelle ("'.$opts['sistersfp'].'") ... ');
$buf=@file_get_contents($opts['sistersfp']);
if ($buf!==false) {
	echo('OK :-)'.N);
	$buf=explode(N,$buf);
	foreach ($buf as $val) {
		if ($val!='' && $val{0}!='#') {
			$kv=explode('|',$val);
			if ($kv[1]=='') $kv[1]=NULL;
			$sisters[$kv[0]]=$kv[1];
		}
	}
} else {
	mexit(N.'Non ho potuto aprire il file delle istanze sorelle "'.$opts['sistersfp'].'", muoio.'.N,1);
}
if (count($sisters)<1)
	mexit('Il file delle istanze sorelle "'.$opts['sistersfp'].'" non contiene alcuna voce, muoio.'.N,1);

$biglist=array();

if ($opts['dontloadbl']==0) {
	if (file_exists($opts['biglistfp']) && is_file($opts['biglistfp']) && is_readable($opts['biglistfp'])) {
		echo('Carico la listona pre-esistente ("'.$opts['biglistfp'].'") ... ');
		$buf=@file_get_contents($opts['biglistfp']);
		if ($buf!==false) {
			echo('OK :-)'.N);
			$biglist=json_decode($buf,true);
		} else {
			echo('ERRORE :-('.N);
		}
	}
}

$context=stream_context_create(array('http'=>array('timeout'=>$opts['timeout'])));

$blinstances=array();
foreach ($sisters as $dom=>$bluri) {
	if (!is_null($bluri)) {
		echo('Recupero la lista delle istanze bloccate da "'.$dom.'" ("'.$bluri.'") ... ');
		$f=@fopen($bluri,'r',false,$context);
		if ($f!==false) {
// le prime 4 righe non ci interessano
			for ($i=0; $i<4; $i++)
				fgets($f);
			while (!feof($f)) {
				$lin=fgets($f);
				if (preg_match('/^\|([^\|]*)\|([^\|]*)\|([^\|]*)\|$/',$lin,$buf)===1)
					$blinstances[]=$buf[1];
			}
			fclose($f);
			echo('OK :-)'.N);
		} else {
			echo('ERRORE :-('.N);
		}
	} else {
		echo('NON recupero la lista delle istanze bloccate da "'.$dom.'": la url della stessa non è definita.'.N);
	}
}
ksort($blinstances);

foreach ($sisters as $dom=>$bluri) {
	echo('Recupero la lista delle istanze note a "'.$dom.'" ... ');
	$buf=@file_get_contents('https://'.$dom.'/api/v1/instance/peers',false,$context);
	if ($buf!==false) {
		echo('OK :-)'.N);
		$peers=json_decode($buf,true);
		foreach ($peers as $pdom) {
			if (!in_array($pdom,$blinstances) && !array_key_exists($pdom,$biglist)) {
				$biglist[$pdom]=NULL;
			}
		}
	} else {
		echo('ERRORE :-('.N);
	}
}
ksort($biglist);

$prodlist=array();
$i=0;
$qinst=count($biglist);
foreach ($biglist as $dom=>$oinfo) {
	echo('Recupero le informazioni su "'.$dom.'" ('.($i+1).'/'.$qinst.' - '.round(100/$qinst*$i).'%) ... ');
	$buf=@file_get_contents('https://'.$dom.'/api/v1/instance',false,$context);
	if ($buf!==false) {
		echo('OK :-)'.N);
		$info=json_decode($buf,true);
		$info['cr-last_checked']=time();
		$info['cr-was_ok']=true;
		$biglist[$dom]=$info;
		if (!array_key_exists('registrations',$info) || $info['registrations']==true) {
			$prodlist[$dom]=$info;
			echo('"'.$dom.'" aggiunta alla lista delle istanze ok! :-)'.N);
		}
	} else {
		echo('ERRORE :-( ... ');
		if (is_null($oinfo) || time()-$oinfo['cr-last_checked']<=$opts['excludeafter']) {
			echo('ma riproveremo...'.N);
			$oinfo['cr-last_checked']=time();
			$oinfo['cr-was_ok']=false;
			$biglist[$dom]=$oinfo;
		} else {
			echo('e non riproveremo...'.N);
		}
	}
	$i++;
}

$json=json_encode($biglist,JSON_PRETTY_PRINT);
file_put_contents($opts['biglistfp'],$json);
$json=json_encode($prodlist,JSON_PRETTY_PRINT);
file_put_contents($opts['prodlistfp'],$json);

echo('Totale istanze nella listona: '.count($biglist).N);
echo('Totale istanze nella listina di quelle occhei: '.count($prodlist).N);

?>
