#!/usr/bin/php
<?php
/*
	WikiBot for PHP

	VERSION: 2010-10-08

	AUTHOR: [[Wikipedista:DaBler]]

	TODO:
	* co udělat s více <br/> na konci odrážek?
	* používat výjimky k hlášení chyb
	* interwiki (vysbírat, třidit, uniq, nakonec)
	* kategorie (vysbírat, třidit, uniq, nakonec)
	* seznam článků k editaci setřídit a uniq
	* rozšířit opravy pravopisu
	* možnost lokalizace (použití na jiných než cs wiki)
	* odstranit prázdné odrážky
	* z "; term" odstranit tučné a italiku
	* nahradit [http://cs.wikipedia.org/wiki/...] interními odkazy
	* nahradit <i> a <b>
*/

class WikiBot
{
	private $base;
	private $user;
	private $pass;

	private $token;
	private $user_agent;
	private $name;

	private static $dict = array();

	const COOKIEFILE = 'cookies.txt';
	const SLEEP = 4;

	public function __construct($base='http://cs.wikipedia.org/w/index.php', $user='DaBlerBot', $pass='heslo')
	{
		$this->base = $base;
		$this->user = $user;
		$this->pass = $pass;
		$this->token = '';
		$this->user_agent = 'WikiBot for PHP (bot=cs:User:DaBlerBot, operator=cs:User:DaBler)';
	}

	public function run($file='cs.txt')
	{
		echo "Logging into '{$this->base}'...\n";
		if(FALSE === $this->login())
		{
			echo "Login fails, exiting...\n";
			return FALSE;
		}
		echo "Logged in as {$this->user}\n";

		echo "Processing articles in file '${file}'...\n";

		$list = array();
		$fp = fopen($file, 'r');
		if($fp)
		{
			while(!feof($fp))
			{
				$b = fgets($fp, 4096);
				$b = str_replace("\n", '', $b);
				$b = str_replace("\r", '', $b);
				if($b)
					$list[] = $b;
			}
			fclose($fp);
		}
		else
		{
			echo "Warning: no input\n";
		}

		foreach($list as $name)
		{
			echo "Loading article '${name}'...\n";
			try
			{
				$page = $this->edit($name);
				if(FALSE === $page)
				{
					echo "Edit fails, exiting...\n";
					return FALSE;
				}
	
				echo "Processing...\n";
				$this->name = $name;
				$new = self::process($page['text']);
				$page['attr']['wpSummary'] = $new['summary'];
				$page['attr']['wpTextbox1'] = $new['text'];
	
				echo "Posting...\n";
				if(FALSE === $this->post($page, $name))
				{
					throw new Exception('Post fails!');
				}
			}
			catch(Exception $e)
			{
				echo 'Caught exception: ',  $e->getMessage(), "\n";
			}

			echo "OK (sleeping for ".self::SLEEP." seconds)...\n";
			sleep(self::SLEEP);
		}

		echo "Done.\n";
	}
	private function getToken()
	{
		$user = urlencode($this->user);
		$pass = urlencode($this->pass);
		$url = "{$this->base}?title=Special:Userlogin";

		$token = urlencode($this->token);

		$c = curl_init();
		curl_setopt($c, CURLOPT_URL, $url);
		curl_setopt($c, CURLOPT_COOKIEJAR, self::COOKIEFILE);
		curl_setopt($c, CURLOPT_FOLLOWLOCATION, 1);
		curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($c, CURLOPT_USERAGENT, "WikiBot for PHP (bot=cs:User:DaBlerBot, operator=cs:User:DaBler)");
		curl_setopt($c, CURLOPT_FOLLOWLOCATION, 1);
		curl_setopt($c, CURLOPT_USERAGENT, $this->user_agent);

		$r = curl_exec($c);

		if( FALSE === $r)
			return FALSE;

		$pattern = '/<input .*name="wpLoginToken" .*value="([0-9a-z]+)".*>/';
		$matches = array();
		preg_match($pattern, $r, $matches);
		if( count($matches) != 2)
			return FALSE;

		$this->token = $matches[1];

		return TRUE;
	}

	private function login()
	{
		$this->getToken();

		$user = urlencode($this->user);
		$pass = urlencode($this->pass);
		$url = "{$this->base}?title=Special:Userlogin&action=submitlogin&type=login";

		$token = urlencode($this->token);

		$c = curl_init();
		curl_setopt($c, CURLOPT_URL, $url);
		curl_setopt($c, CURLOPT_COOKIEJAR, self::COOKIEFILE);
		curl_setopt($c, CURLOPT_COOKIEFILE, self::COOKIEFILE);
		curl_setopt($c, CURLOPT_POST, TRUE);
		curl_setopt($c, CURLOPT_POSTFIELDS, "wpName=${user}&wpLoginAttempt=Log+in&wpPassword=${pass}&wpLoginToken=${token}" );
		curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($c, CURLOPT_USERAGENT, $this->user_agent);

		$r = curl_exec($c);

		if( FALSE !== $r && ($r === "" || FALSE !== strpos($r,"wgUserName=\"{$this->user}\"")) )
			return TRUE;
		else
			return FALSE;
	}

	private function edit($name)
	{
		$title = rawurlencode(str_replace(' ', '_', $name));
		$url = "{$this->base}?title=${title}&action=edit";

		$c = curl_init();
		curl_setopt($c, CURLOPT_URL, $url);
		curl_setopt($c, CURLOPT_COOKIEFILE, self::COOKIEFILE);
		curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($c, CURLOPT_USERAGENT, $this->user_agent);
		$r = curl_exec($c);

		if(FALSE === $r)
			return FALSE;

		xml_parse_into_struct(xml_parser_create('UTF-8'), $r, $val, $ind);

		$p = strpos($r, '<textarea');
		$k = substr($r, $p, strpos($r, '</textarea>') - $p);
		$k = substr($k, strpos($k, '>') + 1);
		$text = html_entity_decode($k, ENT_QUOTES, 'UTF-8');

		if(!isset($ind['INPUT']))
			throw new Exception('No input element (MediaWiki has generated non-valid (X)HTML?)');

		foreach($ind['INPUT'] as $_=>$num)
			$attr[$val[$num]['attributes']['NAME']] = $val[$num]['attributes']['VALUE'];

		unset($attr['search']);
		unset($attr['go']);
		unset($attr['fulltext']);
		unset($attr['wpPreview']);
		unset($attr['wpWatchthis']);
		unset($attr['wpDiff']);
		unset($attr['title']);

		return array('text' => $text, 'attr' => $attr);
	}

	private static function fix_headings_cb($m)
	{
		$str = $m[2];

		// '''
		$str = preg_replace('/(.*?)\'\'\'(.*?)\'\'\'(.*?)/m', '\1\2\3', $str);

		// ''
		$str = preg_replace('/(.*?)\'\'(.*?)\'\'(.*?)/m', '\1\2\3', $str);

		// [[|]]
		$str = preg_replace('/(.*?)\[\[(([^\]]*?)\|)?(.*?)\]\](.*?)/m', '\1\4\5', $str);

		// :
		$str = preg_replace('/^ *(.*?)( *:?)* *$/', '\1', $str);

		return "${m[1]} ${str} ${m[1]}";
	}

	private static function fix_headings(&$str)
	{
		$old = $str;

		// pekne nadpisy
		$str = preg_replace_callback('/^(=+) *(.+?) *(=+) *\r?$/m', "WikiBot::fix_headings_cb", $str);

		// nahrada nadpisu
		$str = preg_replace('/^(=+) Viz též (=+)\r?$/m', '\1 Související články \2', $str, -1, $tmp);
		$str = preg_replace('/^(=+) Podívejte se také na (=+)\r?$/m', '\1 Související články \2', $str, -1, $tmp);

		// pred nadpisem bude presne 1 enter, za nadpisem max. 1 enter
		$str = preg_replace('/^(=+ .+? =+)\n+$/m', "\\1\n", $str, -1, $tmp);
		$str = preg_replace('/^\n*(=+ .+? =+)$/m', "\n\\1", $str, -1, $tmp);

		return !($old == $str);
	}

	private static function fix_categories(&$str)
	{
		$old = $str;

		// kategorie v anglictine
		$str = preg_replace('/\[\[ *(:?) *Category *: *(.+?) *\]\]/im', '[[\1Kategorie:\2]]', $str, -1, $tmp);

		return !($old == $str);
	}

	private static function fix_bullets_cb($m)
	{
		if( preg_match('/^#REDIRECT *(\[\[[^\]]+?\]\]) *\r?$/', $m[0]) || preg_match('/^#PŘESMĚRUJ *(\[\[[^\]]+?\]\]) *\r?$/', $m[0]) )
			return "$m[0]";
		else
			return "$m[1] $m[2]";
	}

	private static function fix_bullets(&$str)
	{
		$old = $str;

		// odrazky orezat o mezery
		$str = preg_replace_callback('/^([\*#;:]+) *(.*?) *\r?$/m', "WikiBot::fix_bullets_cb", $str, -1, $tmp);

		// zbavit se <br/> u odrazek
		$str = preg_replace('|^([\*#;:]+) (.*?) *<br */?'.'>\r?$|m', '\1 \2', $str, -1, $tmp);

		return !($old == $str);
	}

	private static function fix_entities(&$str)
	{
		$old = $str;

		// HTML entity
		$str = preg_replace('/–/m', '–', $str, -1, $tmp);
		$str = preg_replace('/—/m', '—', $str, -1, $tmp);
		$str = preg_replace('/…/m', '…', $str, -1, $tmp);

		return !($old == $str);
	}

	private static function pref($prefixes, $words)
	{
		$ret = array();
		$prefixes = explode('|', $prefixes);

		foreach($prefixes as $p)
			foreach($words as $k => $v)
				$ret += array($p.$k => $p.$v);

		return $ret;
	}

	private static function postf($postfixes, $words)
	{
		$ret = array();
		$postfixes = explode('|', $postfixes);

		foreach($words as $k => $v)
			foreach($postfixes as $p)
				$ret += array($k.$p => $v.$p);

		return $ret;
	}

	private static function up($arr)
	{
		$ret = array();
	
		foreach($arr as $k => $v)
		{
			$ret += array( $k => $v );
			$ret += array( ucfirst($k) => ucfirst($v) );
		}
	
		return $ret;
	}

	public static function get_dictionary()
	{
		$replace = array(
			'ocely' => 'oceli',
			'mysly' => 'mysli',
			'směsy' => 'směsi',
			'myšy' => 'myši',
			'mosazy' => 'mosazi',
			'lžy' => 'lži',

			'potmně' => 'potmě',

			'zda-li' => 'zdali',
		);

		// nejde o spřažený výraz
		$replace += array(
			'nashledanou' => 'na shledanou',
			'naviděnou' => 'na viděnou',
			'naslyšenou' => 'na slyšenou',
			'narozloučenou' => 'na rozloučenou',
			'naodchodnou' => 'na odchodnou',
			'nauvítanou' => 'na uvítanou',
			'nazotavenou' => 'na zotavenou',
			'naposilněnou' => 'na posilněnou',
			'napováženou' => 'na pováženou',
			'nauváženou' => 'na uváženou',
			'narozmyšlenou' => 'na rozmyšlenou',
			'navysvětlenou' => 'na vysvětlenou',
			'nasrozuměnou' => 'na srozuměnou',
			'naupřesněnou' => 'na upřesněnou',
			'navybranou' => 'na vybranou',
			'nazavolanou' => 'na zavolanou',
			'narozdíl' => 'na rozdíl',
		);

		// vyjímka => výjimka
		$replace += self::postf(
			'ka|ce|ek|kách|kám|kami|kou|ku|ky',
			array( 'vyjím' => 'výjim' )
		);

		// (bez)vyjímečný => (bez)výjimečný
		$replace += self::pref(
			'|bez',
			self::postf(
				'ý|á|é|ého|ém|ému|í|ost|ostech|ostem|osti|ostmi|ou|ých|ým|ýma|ými|ě',
				array( 'vyjímečn' => 'výjimečn' )
			)
		);

		// (nej)vyjímečnější => (nej)výjimečnější
		$replace += self::pref(
			'|nej',
			self::postf(
				'|ch|ho|m|ma|mi|mu',
				array( 'vyjímečnější' => 'výjimečnější' )
			)
		);

		// telefoní => telefonní
		$replace += self::postf(
			'|ch|ho|m|ma|mi|mu',
			array( 'telefoní' => 'telefonní' )
		);

		// standart- => standard-
		$replace += self::postf(
			'ech|em|ům',
			array( 'standart' => 'standard' )
		);

		// (nad)standartní => (nad)standardní
		$replace += self::pref(
			'|nad',
			self::postf(
				'|ch|ho|m|ma|mi|mu',
				array( 'standartní' => 'standardní' )
			)
		);

		// standartnost => standardnost
		$replace += self::postf(
			'|ech|em|i|mi',
			array( 'standartnost' => 'standardnost' )
		);

		// standartizace => standardizace
		$replace += self::postf(
			'e|emi|i|ích|ím',
			array( 'standartizac' => 'standardizac' )
		);

		// (nej)standartnější => (nej)standardnější
		$replace += self::pref(
			'|nej',
			self::postf(
				'|ch|ho|m|ma|mi|mu',
				array( 'standartnější' => 'standardnější' )
			)
		);

		// (za|vz|o)pomě(n|l) => (za|cz|o)pomně(n|l)
		$replace += self::pref(
			'za|vz|o',
			self::postf(
				'í|ích|ím|ími',
				array( 'poměn' => 'pomněn' )
			)
			+
			self::postf(
				'|a|i|o|y',
				array(
					'poměn' => 'pomněn',
					'poměl' => 'pomněl'
				)
			)
		);

		// vlasní => vlastní
		$replace += self::postf(
			'|ch|ho|m|ma|mi|mu',
			array( 'vlasní' => 'vlastní' )
		);

		// vlasnost => vlastnost
		$replace += self::postf(
			'|ech|em|i|mi',
			array( 'vlasnost' => 'vlastnost' )
		);

		$replace += array(
			'vlasně' => 'vlastně'
		);

		// vlasnický => vlastnický
		$replace += self::postf(
			'ý|á|é|ého|ém|ému|ou|ých|ým|ýma|ými',
			array( 'vlasnick' => 'vlastnick' )
		);

		$replace += array(
			'vlasničtí' => 'vlastničtí'
		);

		// vlasnictví => vlastnictví
		$replace += self::postf(
			'|ch|m|mi',
			array( 'vlasnictví' => 'vlastnictví' )
		);

		// vlasník => vlastník
		$replace += self::postf(
			'k|ci|cích|ka|kem|kovi|ku|kům|ky',
			array( 'vlasní' => 'vlastní' )
		);

		// (vy)vlasnit => (vy)vlastnit
		$replace += self::pref(
			'|vy',
			self::postf(
				'it|ěme|ěte|í|i|íc|íce|il|ila|ili|ilo|ily|ím|íme|íš|íte|iti',
				array( 'vlasn' => 'vlastn' )
			)
		);

		// součastný => současný
		$replace += self::postf(
			'ý|á|é|ého|ém|ému|í|ost|ostech|ostem|osti|ostmi|ou|ých|ým|ýma|ými',
			array( 'součastn' => 'současn' )
		);

		// tamnější => tamější, vyší => vyšší
		$replace += self::postf(
			'|ch|ho|m|ma|mi|mu',
			array(
				'vyší' => 'vyšší',
				'tamnější' => 'tamější'
			)
		);

		// roviný => rovinný
		$replace += self::postf(
			'ý|á|é|ého|ém|ému|í|ost|ostech|ostem|osti|ostmi|ých|ým|ýma|ými',
			array( 'rovin' => 'rovinn' )
		);

		// kamený => kamenný
		$replace += self::postf(
			'ý|á|é|ého|ém|ému|ost|ostech|ostem|osti|ostmi|ou|ých|ým|ýma|ými',
			array( 'kamen' => 'kamenn' )
		);

		// (ne|mnoho)straný => (ne|mnoho)stranný
		$replace += self::pref(
			'mnoho|ne',
			self::postf(
				'ý|á|é|ého|ém|ému|í|ost|ostech|ostem|osti|ostmi|ou|ých|ým|ýma|ými|ě',
				array( 'stran' => 'strann' )
			)
		);

		// postraní => postranní
		$replace += self::postf(
			'|ch|ho|m|ma|mi|mu',
			array( 'postraní' => 'postranní' )
		);

		// proměný => proměnný
		$replace += self::postf(
			'ý|á|é|ého|ém|ému|ost|ostech|ostem|osti|ostmi|ých|ým|ýma|ými',
			array( 'proměn' => 'proměnn' )
		);


		// -dení => -denní
		$replace += self::pref(
			'|celo|celotý|čtrnácti|jedno|jednotý|každo|každotý|kolika|kolikatý|několika|několikatý|ob|rovno|tý|dvou|dvoutý|tří|třítý|čtyř|čtyčtý|pěti|pětitý|šesti|šestitý|sedmi|sedmitý|osmi|osmitý|devíti|devítitý|deseti|desetitý|více|vícetý',
			self::postf(
				'í|ích|ího|ím|íma|ími|ímu|ě',
				array( 'den' => 'denn' )
			)
		);

		// i s velkým počátečním písmenem
		$replace = self::up($replace);

		// tyhle náhrady jsou case sensitive
		$replace += array(
			'khz' => 'kHz',
			'Mhz' => 'MHz',
			'Ghz' => 'GHz',
		);

		self::$dict = $replace;
	}

	private static function fix_orthography(&$str)
	{
		$old = $str;

		$sep = '[ ,\.;*#\-–—…&\(\)\/\[\]|\'{}=:<>?!"„“\n]';

		$replace = self::$dict;

		foreach($replace as $pattern => $replacement)
		{
			$str = preg_replace("/(${sep})${pattern}(${sep})/m", "\\1${replacement}\\2", $str);
		}

		// dělá to co má, jen by to asi mělo být ve funkci fix_extlinks()
		$str = preg_replace('|\[http://http://([^\]]+?) *\]|m', '[http://\1]', $str);

		return !($old == $str);
	}

	private static function fix_links_ex_cb($m)
	{
		$link = $m[1];
		$delim = $m[2];
		$fragment = $m[3];

		// TODO: link do čitelné podoby
		$link = preg_replace("/_/m", " ", $link);

		// TODO: fragment do čitelné podoby
		$fragment = preg_replace("/_/m", " ", $fragment);

		return "${link}${delim}${fragment}";
	}

	private function fix_links_cb($m)
	{
		$link = $m[1];
		$delim = $m[2];
		$desc = $m[3];

		if($link === $desc)
			$desc = $delim = '';

		$link = preg_replace_callback('/^([^#]*)(#?)(.*)/', "WikiBot::fix_links_ex_cb", $link);

		if($link === $this->name)
		{
			if($desc == '')
				return "'''${link}'''";
			else
				return "'''${desc}'''";
		}
		else
			return "[[${link}${delim}${desc}]]";
	}


	private function fix_links(&$str)
	{
		$old = $str;

		$str = preg_replace_callback("/\\[\\[([^\\|\\]]+)(\\|?)([^\\]]+)?\\]\\]/", array($this, "fix_links_cb"), $str);

		return !($old == $str);
	}


	private function process($str)
	{
		$summary = "[[Wikipedie:WCW]]:";

		// windowsove entery za linuxove, kvuli enterum kolem nadpisu
		$str = preg_replace('/\r\n/', "\n", $str);

		if(self::fix_headings($str))
			$summary .= " opravy nadpisů,";

		if(self::fix_bullets($str))
			$summary .= " opravy odrážek,";

		if(self::fix_entities($str))
			$summary .= " náhrada HTML entit,";

		if(self::fix_categories($str))
			$summary .= " opravy kategorií v angličtině, ";

		if(self::fix_orthography($str))
			$summary .= " opravy pravopisu, ";

 		if(self::fix_links($str))
 			$summary .= " opravy odkazů, ";

		if($str == '')
			throw new Exception('Empty output!');

		$summary .= "…";

		return array('text' => $str, 'summary' => $summary);
	}

	private function post($page, $name)
	{
		$title = rawurlencode(str_replace(' ', '_', $name));

		$mp = array();
		foreach($page['attr'] as $aname=>$val)
			$mp[$aname] = $val;

		$url = "{$this->base}?title=${title}&action=submit";

		$c = curl_init();
		curl_setopt($c, CURLOPT_URL, $url);
		curl_setopt($c, CURLOPT_COOKIEFILE, self::COOKIEFILE);
		curl_setopt($c, CURLOPT_POST, 1);
		curl_setopt($c, CURLOPT_POSTFIELDS, $mp);
		curl_setopt($c, CURLOPT_HTTPHEADER, array('Expect:'));
		curl_setopt($c, CURLOPT_RETURNTRANSFER, 1);
		curl_setopt($c, CURLOPT_USERAGENT, $this->user_agent);
		$ret = curl_exec($c);

		if( '' === $ret )
			return TRUE;
		else
			return FALSE;
	}

}

$bot = new WikiBot();
$bot->get_dictionary();
$bot->run();

?>