Участник:FedyaBot/По стопам Константина Устиновича

Материал из Викиреальностя
Перейти к: навигация, поиск

«По стопам Константина Устиновича» — моя основная работа. Я был так вдохновлён, как он показывает свои фокусы с хранилищем даты в Малом Цирке, исправляя сразу много карточек с датами, что решил повторить его успех своею собственною работаю. К сожалению, я не возжелал возехаться с отвратной вещью жабий язык, разобраться в которой можно только, будучи не совсем в себе… Я решил написать своё сочинение на классическом языке, и мои методы действовали — помешала только лень с карточками. Я надеюсь, кто-то повторит мой путь, полный лишений, постов и самобичеваний.

Я хочу верить — найдётся хороший приемник.

Содержание

[править] Глава 1. Бутовская птицеферма в г. Томске

#! /usr/bin/perl
# {{autoupdated infobox}}
 
use utf8;
use MediaWiki::API;
use Encode;
use Switch;
use lib qw(./lib);
use wiki_functions;
use web_functions;
require "lib/wiki_variables.pl";
require "confa/bot_config.pl";

write_log("$ \nНачато исполнение скрипта");
my $home_api = 'http://wikireality.ru/w/api.php';
my $username = $bot_config::usernames{'wr'}; my $password = $bot_config::passwords{'wr'};
my $home = MediaWiki::API->new({ api_url => $home_api });
$home->{ua}->agent($bot_config::home_agent{'updater'});
$home->login({ lgname => $username, lgpassword => $password }) || die "Вали откуда пришёл, Царь гневается: $home->{error}->{details}.\n";
 
my %languages = %wiki_variables::languages;
my %months = %wiki_variables::months;
my $tasks;
my ($i, $a, $string, $api);

my $ret = $home->api({ action => 'query', list => 'categorymembers', cmtitle => 'Категория:Статьи_для_автоматического_обновления', cmnamespace => '0',
	cmtype => 'page', cmlimit => '5000', cmsort => 'sortkey', cmprop => 'title|sortkeyprefix' })
	|| die "Ошибка получения содержимого категории со статьями для обновления: $home->{error}->{details}.\n";
 
foreach $i(@{$ret->{query}->{categorymembers}}) # to-do - fuck off api query limit.
{
	my $sortkey = $i->{sortkeyprefix};
 
	if(length(encode_utf8($sortkey)) > 200 )
	{
		my $result = $home->api({ action => 'parse', page => $i->{title}, prop => 'categories' })
			|| write_log(sprintf("Ошибка получения содержимого для страницы %s - %s",$i->{title},$home->{error}->{details}));
		$sortkey = $result->{parse}->{categories}[0]->{sortkey};
	}
 
	my @parts = split(/:/, $sortkey);
	my $prefix = $parts[0];
 
	$tasks->{$prefix}->{$i->{title}} = $parts[1];	
}

# Обновление статей про участников вики-проектов
foreach my $user(keys %{$tasks->{user}})
{
	my (@editcount, @userpages, @groups, @registration, $sign, @babel,
		@city, $birth, $usertools, $wikitext, $category, @proj, @nickname);
 
	my @splitted = split(/,/, $tasks->{user}->{$user});
 
	for($i = 0; $i < scalar @splitted; $i++)
	{
		$string = $splitted[$i];
		if($string =~ m/(.*?)\[(.*?)\]/)
		{
			$proj[$i] = $1;
			$nickname[$i] = $2;
		}
		else
		{
			$proj[$i] = $string;
			$nickname[$i] = $user;		
		}
	}
 
	for($i = 0; $i < scalar @proj; $i++)
	{
		my $url = api_url($proj[$i]);
		unless($url)
		{
			write_log(sprintf("Не удалось получить адрес API для проекта %s (участник %s)",$proj[$i],$nickname[$i]));
			next;
		}
		my $pname = project_name($proj[$i]);
		my $domain = $url;
		$domain =~ s/http:\/\/(.*?)\/(.*)/$1/i;

		$api = security_api($domain, $url);
		unless($api)
		{
			write_log(sprintf("Не удалось получить объект API для домена %s (участник %s)",$domain,$nickname[$i]");
			next;		
		}
		
		my $username = $nickname[$i];
		my $res = $api->api({ action => 'query', list => 'users', usprop => 'groups|editcount|registration', ususers => $username })
			|| write_log("Ошибка получения данных об участнике $nickname[$i]: $api->{error}->{details}");
 
		if($res && $res->{query} && $res->{query}->{users}[0])
		{
			my $info = $res->{query}->{users}[0];
 
			if(!$info->{missing} && $info->{editcount})
			{
				push(@editcount, sprintf("%s (%s)",round($info->{editcount}),$pname));
 
				my $groups_arr = $info->{groups};
				my @g = ();
				foreach my $gr(@$groups_arr)
				{
					next if $gr =~ m/^(\*|filemover|user|autoconfirmed|uploader)$/i;
					push(@g, flag_name($gr,$proj[$i]));
				}
				push(@groups, join(', ', @g) . " (" . $pname . ")") if(@g);
				
				$res = $api->api({
					action => 'query', list => 'usercontribs', uclimit => 1, ucuser => $username, ucdir => 'newer', ucprop => 'timestamp'				
				}) || write_log("Ошибка запроса вклада участника " . $info->{name} . " в " . $proj[$i] . " " . $api->{error}->{details});
				my $first = convert_time($res->{query}->{usercontribs}[0]->{timestamp});
				
				if($info->{registration} && $first eq convert_time($info->{registration}))
				{
					push(@registration, convert_time($info->{registration}) . " (" . $pname . ")");				
				}
				elsif($info->{registration} && $first ne convert_time($info->{registration}))
				{
					push(@registration, convert_time($info->{registration}) . ", первая правка " . $first . " (" . $pname . ")");				
				}
				else
				{
					push(@registration, "неизвестно, первая правка " . $first . " (" . $pname . ")");
					write_log("Не удалось получить дату регистрации для " . $info->{name} . " в " . $proj[$i] . ": возможно, зарегистрирован ранее создания журналов");
				}
			}
		}
		
		$res = $api->api({ action => 'query', prop => 'info', titles => 'User:' . $username })
			|| write_log("Ошибка получения информации о странице User:" . $username . ": " . $api->{error}->{details});
		my $missing = exists((values %{$res->{query}->{pages}})[0]->{missing});
		push(@userpages, external_page($proj[$i], $missing ? 'Special' : 'User', $missing ? 'Contributions/' . $username : $username, $i == 0 ? ucfirst($pname) : $pname));
 
		if($i == 0)
		{
			$res = $api->api({ action => 'query', list => 'usercontribs', uclimit => '1', ucuser => $username, ucnamespace => '3', ucprop => 'ids|title' })
				|| write_log("Ошибка получения вклада участника $nickname[$i] в $proj[$i]: " . $api->{error}->{details});
			if($res->{query}->{usercontribs}[0])
			{
				my $lastpage = $res->{query}->{usercontribs}[0]->{title};
 
				$res = $api->api({ action => 'query', prop => 'revisions', titles => $lastpage, rvlimit => '1', rvuser => $username, rvprop => 'content' })
					|| write_log("Ошибка получения последней правки участника $nickname[$i] в $proj[$i]: " . $api->{error}->{details});
				if($res && $res->{query})
				{
					my $diff = (values %{$res->{query}->{pages}})[0]->{revisions}[0]->{'*'};
					$sign = $2 if($diff =~ m/\[\[(User|Участник):$username\|(.*?)\]\]/i && $2 ne $username);
				}
			}
		}
 
		if($proj[$i] eq 'ruwiki')
		{
			my $userpage = "Участник:$username";
 
			$res = $api->api({ action => 'parse', prop => 'categories', page => $userpage })
				|| write_log("Ошибка получения категорий для $nickname[$i] в ruwiki: " . $api->{error}->{details});
 
			if($res->{parse}->{categories})
			{
				my $categories = $res->{parse}->{categories};
 
				for($a = 0; $a < scalar @$categories; $a++)
				{
					$category = $categories->[$a]->{'*'};
					if($category =~ m/Участники_из_.*/i)
					{
						$res = $api->api({ action => 'parse', prop => 'wikitext', page => 'Категория:' . $category })
							|| write_log("Ошибка получения викитекста для $catpage: " . $api->{error}->{details});
 
						if($res->{parse}->{wikitext}->{'*'})
						{
							$wikitext = $res->{parse}->{wikitext}->{'*'};
							push(@city, $1) if ($wikitext =~ m/\[\[Категория\:Википедия\:Участники по.*?\|(.*?)\]\]/i);
						}
					}
					elsif($category =~ m/User_([a-z][a-z][a-z]?-N?[0-5]?)/i)
					{
						push(@babel, $1);
					}
				}
 
				$res = $api->api({ action => 'parse', prop => 'wikitext', page => $userpage })
					|| write_log("Ошибка получения викитекста для $userpage: " . $api->{error}->{details});
				if($res->{parse}->{wikitext}->{'*'})
				{
					$wikitext = $res->{parse}->{wikitext}->{'*'};
					
					my $m;
					if($wikitext =~ m/(Участник|User)\:Box\/Родился\|(день=)?(.*?)\|(месяц=)?(.*?)\|(год=)?(.*?)}}/i)
					{
						$m = length($5) == 1 ? '0' . $5 : $5;
						$birth = $3 . ' ' . $months{$m} . ' ' . $7;
					}
					elsif(!$m && $wikitext =~ m/(Участник|User)\:Box\/Родился\|(день=)?(.*?)\|(месяц=)?(.*?)}}/i)
					{
						$m = length($5) == 1 ? '0' . $5 : $5;
						$birth = $3 . ' ' . $months{$m};
					}
				}
			}
			$usertools .= '{{usertools|ruwiki|' . $username . '}}';
		}
	}
 
	if(@userpages)
	{
		my $out = '{{#switch: {{{1|}}}' . "\n";
		$out .= '|Число правок=' . join(' + ', @editcount) . "\n" if(@editcount);
		$out .= '|Личные страницы=' . join('<br />', @userpages) . "\n" if(@userpages);
		$out .= '|Права участника=' . join('<br />', @groups) . "\n" if(@groups);
		$out .= '|Начало участия=' . join('<br />', @registration) . "\n" if(@registration);
		$out .= '|Подпись=' . $sign . "\n" if($sign);
		$out .= '|Знание языков=' . join(', ', @babel) . "\n" if(@babel);
		$out .= '|Город=' . join(', ', @city) . "\n" if(@city);
		$out .= '|День рождения=' . $birth . "\n" if($birth);
		$out .= '|Инструменты=' . join(' · ', $usertools) . "\n" if($usertools);		
		$out .= '}}';
 
		$home->edit({
			action => 'edit', title => 'Data:User:' . $user, text => $out, minor => 1, bot => 1,
			summary => '[[Project:Updater|Updater]]: Обновление информации для шаблона-карточки участника'			
		}) || write_log("Ошибка записи страницы информации для user - $user: " . $home->{error}->{details});
		write_log("Элемент {user}->" . $user . " обработан");
	}
}

# Обновление статей про вики-проекты на движке MediaWiki
foreach my $site(keys %{$tasks->{wiki}}) {
	my ($engine, $founded, $articles, $pages,
		$users, $activity,
		$logo, $license, $tcy, $pr,
		$api, $api_url, $res, $status);
	
	my @data = split(/,/, $tasks->{wiki}->{$site});
	my $domain = $data[0]; my $code = $data[1];
	
	my $fullurl = 'http://' . $domain . '/';
	
	$api_url = api_url($code) if($code);
	$api_url = 'http://' .url . '/w/api.php' if(!$api_url);
	$api = security_api($domain, $api_url);
	$res = $api->api({ action => 'query', meta => 'siteinfo' }) if($api);
	if($res->{query}) {
		$status = 'ok';
	} else {
		$api_url = 'http://' .url . '/api.php';
		$api = security_api($domain, $api_url);
		$res = $api->api({ action => 'query', meta => 'siteinfo' }) if($api);
		$status = 'ok' if($res && $res->{query});
	}
	
	if($status eq 'ok') {
		$engine = 'MediaWiki';
	
	#	Не работает из-за проблем с HTTP::Message.
	#	my $file_url = logo_url($fullurl);
	#	my $file_name = 'Лого_' . $site . '.' . ext($file_url);
	#	$status = mw_upload($home, $file_name, $file_url, 'Логотип для вики-проекта ' . $site, undef);
	#	$status eq 'done' ?	$logo = '[[File:' . $file_name . '|135px|center]]' : write_log($status);
		
		$res = $api->api({ action => 'query', meta => 'siteinfo', siprop => 'statistics' });
		if($res->{query}) {
			my $stats = $res->{query}->{statistics};
			
			$articles = round($stats->{articles});
			$pages = round($stats->{pages});
			$users = round($stats->{users}) . ' (' . round($stats->{activeusers}) . ' активных)';
			
			my $c = 0; undef $status;
			while($status ne 'ok') {
				$c++;
				if($c > 50) {
					write_log("Не удалось получить первые " . $c-1 . " правок для " . $domain);
					last;
				}
				$res = $api->api({ action => 'query', prop => 'revisions', revids => $c, rvprop => 'timestamp|ids' });
				if($res->{query}->{pages}) {
					my $rev = (values %{$res->{query}->{pages}})[0]->{revisions}->[0]->{timestamp};
					if($rev) {
						$founded = convert_time($rev);
						$status = 'ok';
					}				
				}
			}
			$founded .= ' (' . $c . ' правка)' if($founded && $c != 1);
		}
		
		# Тестировать:
		my $info = element_by_id($fullurl . 'wiki/Special:Random', 'copyright');
		switch($info) {
			case /GNU FDL/ { $license = 'GFDL'; }
			case /CC-BY-SA 3.0/ { $license = 'CC-BY-SA 3.0'; }
		}
	} else {
		write_log("Не удалось получить объект API для вики-проекта " . $domain);
	}	
	$tcy = site_tcy($fullurl);
	$pr = site_pr($domain);
	
	my $out = '{{#switch: {{{1|}}}' . "\n";
	$out .= '|URL=[' . $fullurl . ' ' . $domain . ']';
	$out .= '|Логотип=' . $logo . "\n" if($logo);
	$out .= '|Дата основания=' . $founded . "\n" if($founded);
	$out .= '|Название движка=' . $engine . "\n" if($engine);
	if($articles) {
		$out .= '|Число статей=' . $articles . "\n";
		$out .= '|Число страниц=' . $pages . "\n";
		$out .= '|Число участников=' . $users . "\n";
	}
	$out .= '|Лицензия=' . $license . "\n" if($license);
	$out .= '|тИЦ=' . $tcy . "\n" if($tcy);
	$out .= '|PR=' . $pr . "\n" if($pr);
	$out .= '}}';
	
	$home->edit({
		action => 'edit', title => 'Data:Wiki:' . $site, text => $out, minor => 1, bot => 1,
		summary => '[[Project:Updater|Updater]]: Обновление информации для шаблона-карточки вики-проекта'			
	}) || write_log("Ошибка записи страницы информации для wiki - $site: " . $home->{error}->{details});
	write_log("Элемент {wiki}->" . $site . " обработан");
}
write_log("Исполнение скрипта завершено");

sub write_log {
	my $line = shift;
	my $time = timestamp(time());
	$line = $time . ": " . $line . ".\n";
	
	open(LOG, ">>update_data.log");
	binmode LOG, ':utf8';
	print LOG "$line";
	close(LOG;
}

[править] Глава 2. Пароли и явки

Мои пароли и явки были записаны на отдельной бумажке.

#! /usr/bin/perl
package bot_config;
use utf8;
our %usernames = ('wr' => 'FedyaBot','cw' => 'FedyaBot');
our %passwords = ('wr' => 'daposhlivsenahuisindeikoy','cw' => 'pizdecgolovavcirkeveshaet');
our %home_agent = ('updater' => 'MediaWikiBot/0.37 Wiki-infobox updater/1.0');
our %fake_agent = ('updater' => 'Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2.22) Gecko/20110902 Firefox/3.6.22');
our $open_proxy = 'http://64.90.37.42:16522/';

1;

[править] Глава 3. Обработка паутины

#! /usr/bin/perl
package web_functions;
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(element_by_id site_security secure_www site_tcy site_pr ext round);

use utf8;
use Switch;
use LWP::UserAgent;
use HTML::TreeBuilder;
use wiki_functions;
use lib qw(/home/script/perl/conf);
require "bot_config.pl";

1;

sub element_by_id
{
	my ($url, $id) = @_;
	return undef unless($url || $id);
	
	my $ua = secure_www($url);
	return undef unless($ua);
	
	my $res = $ua->get($url);	
	if($res->is_success())
	{
		my $content = $res->content();
		my $parser = HTML::TreeBuilder->new_from_content($content);
		my $node = $parser->look_down("id" => $id);
		if($node)
		{
			$node = $node->as_XML();
			chomp($node);
			$node =~ s/^<.*?id=["']$id["'].*?>(.*)<\/.*?>$/$1/;
		
			return $node;
		}
	}
	
	return undef;
}

sub site_security
{
	my $url = shift;
	return undef unless($url);
	
	unless(is_secure($url))
	{
		switch($url)
		{
			case /^([a-z0-9_-]+)?\.livejournal\.com$/ { return 'secure'; }
			else { return undef; }	
		}
	}
	return $level;
}

sub secure_www
{
	my $url = shift;
	return undef unless($url);
	$url =~ s/^http:\/\/([a-z]+\.)?([a-z]+\.)?([a-z0-9\-]+\.[a-z]+)\/.*$/$3/i;
	
	my $ua;
	my $agent = 'Robot (+http://wikireality.ru/wiki/Project:Updater)';
	my $fake_agent = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17';
	my $timeout = 50;
	
	switch(site_security($url))
	{
		case 'secure'
		{
			$ua = LWP::UserAgent->new( agent => $agent, timeout => $timeout );
			return $ua;
		}
		case 'medium'
		{
			$ua = LWP::UserAgent->new( agent => $agent, timeout => $timeout );
			return $ua;
		}
		case 'insecure'
		{
			$ua = LWP::UserAgent->new( agent => $fake_agent, timeout => $timeout );
			$ua->proxy('http', $bot_config::open_proxy);
			return $ua;
		}
		else { return undef; }	
	}
}

# Похоже устарело, тр***ть восемь пианисток, яндексовые м**илы.
sub site_tcy
{
	my $url = shift;
	return undef if(!$url);
	
	my $ua = LWP::UserAgent->new( agent => 'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.2.17) Gecko/20110420 Firefox/3.6.17', timeout => 35 );
	my $res = $ua->get('http://bar-navig.yandex.ru/u?ver=2&show=32&url=' . $url);

	if($res->is_success())
	{
		$res = $res->content;
		$res =~ s/\n//g;
		$res =~ s/^.*value=["'](.*?)["'].*$/$1/;
		return $res;
	}
	
	return undef;
}

sub site_pr
{
	my $domain = shift;
	return undef unless($domain);
	
	my $res = element_by_id("http://pr-cy.ru/analysis/$domain", 'indicators-tbl');
	return undef unless($res);
	$res =~ s/\n//g;
	$res =~ s/^.*PageRank <\/td><td valign="top"><b>(.*?)\/10.*$/$1/;
	
	return $res;
}

sub ext
{
	my $string = shift;
	return undef unless($string);
	$string =~ m/^.*\.([a-z]+)$/;
	return $1;
}

sub round
{
	my $int = shift;
	return undef unless($int);
	$int = sprintf("%.0f", $int);
	
	if($int > 1100)
	{
		$int =~ m/^(.*?)([0-9])([0-9])([0-9])$/;
		my($r3, $r4) = ($3, $4);
		$r3 = $r4 > 4 ? $r3+1 : $r3;
		$r3 = $r3 > 4 ? '5' : '0';
		return sprintf("~ %s%S%s0",$1,$2,$r3);
	}
	elsif($int > 200)
	{
		$int =~ m/^(.*?)([0-9])([0-9])$/;
		my($n2, $n3) = ($2, $3);
		$n2 = $n3 > 4 ? $n2+1 : $n2;
		return sprintf("~ %s%s0",$1,$n2);
	}
	else
	{
		return $int;
	}
}

[править] Глава 4. Методы работы с вики

#! /usr/bin/perl
package wiki_functions;
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(is_secure security_api iwiki_link format_time project_name api_url flag_name convert_time timestamp external_page logo_url mw_upload);

use utf8;
use MediaWiki::API;
use Switch;
use web_functions;
use lib qw("C:\DmitryMedvedev\bot");
require "wiki_variables.pl";
require "bot_config.pl";

1;

sub is_secure
{
	my $url = shift;
	switch($url)
	{
		case /^([w]+\.)?[a-z]+\.wik(imedia|ipedia|tionary|isource|iquote|ibooks|iversity|inews)\.org/ { return 'secure'; }
		case /^([w]+\.)?(wikireality\.ru|cyclowiki\.org|provizorii\.ru|mediawiki\.org|[a-z]+?\.[a-z0-9]+\.wikia\.com|wikilogia\.ru)/ { return 'secure'; }
		case /^([w]+\.)?(traditio\.ru|wikifocus\.org|anticopyright\.ru|ejwiki\.org|lurkmore\.ru)/ { return 'medium'; }
		case /^([w]+\.)?(absurdopedia\.net|[a-z]+\.pifia\.ru|miningwiki\.ru|starcraft-wiki\.ru|novopedia\.net)/ { return 'insecure'; }
		else { return undef; }
	} 
}

sub security_api
{
	my ($domain, $url) = @_;
	switch(is_secure($domain))
	{
		case 'secure'
		{
			$api = MediaWiki::API->new({ api_url => $url });
			$api->{ua}->agent($bot_config::home_agent{'updater'});
			return $api;
		}
		case 'medium'
		{
			$api = MediaWiki::API->new({ api_url => $url });
			$api->{ua}->agent($bot_config::home_agent{'updater'});
			return $api;
		}
		case 'insecure'
		{
			$api = MediaWiki::API->new({ api_url => $url });
			$api->{ua}->agent($bot_config::fake_agent{'updater'});
			$api->{ua}->proxy('http', $bot_config::open_proxy);
			return $api;
		}
		else { return undef; } 
	}
}
 
sub iwiki_link
{
	my $code = shift;
	
	switch($code)
	{
		case /^(lurk|lm)$/ { return 'lurk'; }
		case /^(ac|kp|provizorii|absurd|commons|meta|mw)$/ { return $code; }
		case /^(c|cw|cyclo|cp)$/ { return 'cw'; }
		else
		{
			$code =~ m/([a-z_]+)wik([a-z_]+)/;
			return undef unless($1 || $2);
			
			my $i;
			switch($2)
			{
				case 'i' { $i = 'w'; }
				case /^(t|tionary)$/ { $i = 'wikt'; }
				case 'iquote' { $i = 'q'; }
				case 'ibooks' { $i = 'b'; }
				case 'iversity' { $i = 'v'; }
				case 'inews' { $i = 'n'; }
				case 'isource' { $i = 's'; }
				else { undef $i; }
			}
			return undef unless($i);
			
			my $lang = $1;
			return $i if($lang eq 'en');
			
			return sprintf("%s:%s",$i,$lang);
		}	
	} 
}

# @_: 'ruwiki', 'User', 'Drbug', 'Русская Википедия' -> [[:w-ru:User:Drbug|Русская Википедия]]
sub external_page
{
	my ($code, $ns, $page, $title) = @_;
	
	my $prefix = defined($ns) ? "$ns:" : '';
	my $text = defined($title) ? $title : $page;
	if(iwiki_link($code))
	{
		return sprintf("[[%s:%s%s|%s]]",$iwiki,$prefix,$page,$text);
	}
	
	my $path;
	switch($code)
	{
		case 'wr' { $path = 'http://wikireality.ru/wiki/'; }
		case /^(wf|wikifocus)$/ { $path = 'http://wikifocus.org/wiki/'; }
		case 'ejwiki' { $path = 'http://ejwiki.org/wiki/'; }
		else { undef $path; }
	}
	if($path) 
	{
		$path .= $prefix . $page;
		$path =~ s/ /_/;
		return sprintf("[%s %s]",$path,$text);
	}
	
	return undef;
}

sub project_name
{
	my $code = shift;
	
	switch($code)
	{
		case 'wr' { return 'Викиреальность'; }
		case /^(c|cw|cp|cyclo)$/ { return 'Циклопедия'; }
		case /^(lurk|lm)$/ { return 'Луркоморье'; }
		case 'provizorii' { return 'Провизории'; }
		case 'absurd' { return 'Абсурдопедия'; }
		case 'ejwiki' { return 'Ежевика'; }
		case 'commons' { return 'Викисклад'; }
		case /^(m|meta)$/ { return 'Мета Фонда Викимедиа'; }
		case /^(mw|mediawiki)$/ { return 'MediaWiki.org'; }
		case /^(wf|wikifocus)$/ { return 'ВикиФокус'; }
		case undef { return undef; }
		else
		{ 
			$code =~ m/([a-z_]+)wik([a-z_]+)/;
			return undef unless($1 || $2);
			
			my ($name, $gender);
			switch($2)
			{
				case 'i' { $name = 'Википедия'; $gender = 'f'; }
				case /^(t|tionary)$/ { $name = 'Викисловарь'; $gender = 'm'; }
				case 'iquote' { $name = 'Викицитатник'; $gender = 'm'; }
				case 'ibooks' { $name = 'Викиучебник'; $gender = 'm'; }
				case 'iversity' { $name = 'Викиверситет'; $gender = 'm'; }
				case 'inews' { $name = 'Викиновости'; $gender = 'p'; }
				case 'isource' { $name = 'Викитека'; $gender = 'f'; }
			}
			return undef unless($name);
			
			my $lang = $wiki_variables::languages{$1};
			return $code unless($lang);
			
			$lang = lcfirst($lang);
			
			if($lang =~ m/т$/) {
				return sprintf("%s на %sе",$name,$lang);
			} elsif ($lang !~ m/ий$/) {
				return sprintf("%s на языке %s",$name,$lang);
			}
			
			if($gender ne 'm') {
				my $result = $gender eq 'f' ? 'ая' : 'ие';
				$lang =~ s/ий$/$result/;
			}
						
			return sprintf("%s %s",$lang,$name);
		}		
	}
}
 
sub api_url
{
	my $code = shift;
	
	switch($code)
	{
		case 'wr' { return 'http://www.wikireality.ru/w/api.php'; }
		case /^(c|cw|cp|cyclo)$/ { return 'http://cyclowiki.org/w/api.php'; }
		case /^(lurk|lm)$/ { return 'http://lurkmore.ru/api.php'; }
		case 'provizorii' { return 'http://provizorii.ru/api.php'; }
		case 'absurd' { return 'http://absurdopedia.net/api.php'; }
		case 'ejwiki' { return 'http://ejwiki.org/w/api.php'; }
		case 'commons' { return 'http://commons.wikimedia.org/w/api.php'; }
		case /^(m|meta)$/ { return 'http://meta.wikimedia.org/w/api.php'; }
		case /^(mw|mediawiki)$/ { return 'http://www.mediawiki.org/w/api.php'; }
		case /^(wf|wikifocus)$/ { return 'http://wikifocus.org/w/api.php'; }
		case undef { return undef; }
		else
		{ 
			$code =~ m/([a-z_]+)wik([a-z_]+)/;
			return undef unless($1 || $2);
			
			my $domain;
			switch($2) {
				case 'i' { $domain = 'wikipedia.org'; }
				case /^(t|tionary)$/ { $domain = 'wiktionary.org'; }
				case 'iquote' { $domain = 'wikiquote.org'; }
				case 'ibooks' { $domain = 'wikibooks.org'; }
				case 'iversity' { $domain = 'wikiversity.org'; }
				case 'inews' { $domain = 'wikinews.org'; }
				case 'isource' { $domain = 'wikisource.org'; }
			}
			return undef unless($domain);
			
			return sprintf("http://%s.%s/w/api.php",$1,$domain);
		}		
	}
}
 
sub flag_name
{
	my ($code, $site) = @_;

	switch($code)
	{
		case 'autoeditor' { return 'автопатрулируемый'; }
		case /^(editor|patroller)$/ { return ($site eq 'wr') ? 'редактор' : 'патрулирующий';  }
		case 'citizen' { return 'редактор'; }
		case 'guard' { return 'смотритель'; }
		case 'referee' { return 'эксперт'; }
		case 'wizard' { return 'мастер'; }
		case 'olsysop' { return 'распорядитель'; }
		case 'reviewer' { return 'выверяющий'; }
		case /^rollback(er)?$/ { return ($site eq 'absurd') ? 'рыцарь' : 'откатывающий'; }
		case 'ipblock-exempt' { return 'исключение из IP-блокировок'; }
		case 'closer' { return 'подводящий итоги'; }
		case 'sysop' { return 'администратор'; }
		case 'bureaucrat' { return 'бюрократ'; } 
		case 'checkuser' { return 'проверяющий участников'; }
		case 'oversight' { return 'ревизор'; }
		case 'steward' { return 'стюард'; }
		case 'magician' { return 'фокусник'; }
		else { return $code; }
	}
}

sub logo_url
{
	my $url = shift;
	return undef unless($url);
	
	my $string = element_by_id($url . 'wiki/Special:Random', 'p-logo');
	chomp($string);
	$string =~ m/.*?url\((.*?)\).*?/;
	
	return $1;
}

# Метод отказывается работать, да пошёл он на сибирский п***с
#sub mw_upload
#{
#	my ($api, $name, $url, $sum, $reupload) = @_;
#	
#	my $res = $api->api({ action => 'query', prop => 'info', titles => $name }) || return $api->{error}->{details};
#	my $missing = exists((values %{$res->{query}->{pages}})[0]->{missing});
#
#	my $ua = secure_www($url);
#	return sprintf("Не удалось получить объект LWP::UserAgent для %s (загрузка %s )",$url,$name) unless($ua);
##	my $response = $ua->get($url);
#	$response = $response->content if($response->is_success());
#	open(FILE, ">>$name");
#	print FILE "$response";
#	binmode FILE;
#	my ($buffer, $data);
#	while ( read(FILE, $buffer, 65536) ) { $data .= $buffer; }
#	close(FILE);
#	
#	if((!$missing && $reupload) || $missing) {
#		$api->edit({ action => 'upload', filename => $name, comment => $sum, text => $sum, file => $response,
#			ignorewarnings => 1 }) || return 'Ошибка при загрузке файла ' . $name . ': ' . $api->{error}->{details};
#		unlink $name;
#		return 'done';
#	}
#	return sprintf("Файл %s уже существует, опция $reupload отключена",$name);
#}

sub convert_time
{
	my $time = shift;
	return undef if(!$time);
	
	$time =~ m/(20[0-9][0-9])\-([01][0-9])\-([0-3])([0-9])T.*Z/;
	my $m = $3 == '0' ? $4 : $3 . $4;
	
	return sprintf("%s %s %s",$m,$wiki_variables::months{$2},$1);
}

[править] Глава 5. Учебник иностранных языков

#! /usr/bin/perl
package wiki_variables;
use utf8;
 
our %languages = ( "ab" => "Абхазский", "av" => "Аварский", "ae" => "Авестийский", "az" => "Азербайджанский", "ay" => "Аймара", "ak" => "Акан", "sq" => "Албанский", "am" => "Амхарский", "en" => "Английский", "ar" => "Арабский", "hy" => "Армянский", "as" => "Ассамский", "aa" => "Афарский", "af" => "Африкаанс", "eu" => "Баскский", "ba" => "Башкирский", "be" => "Белорусский", "bn" => "Бенгальский", "my" => "Бирманский", "bi" => "Бислама", "bg" => "Болгарский",  "bs" => "Боснийский", "br" => "Бретонский", "cy" => "Валлийский", "hu" => "Венгерский", "ve" => "Венда", "vo" => "Волапюк", "wo" => "Волоф", "vi" => "Вьетнамский", "gl" => "Галисийский", "lg" => "Ганда", "hz" => "Гереро", "nl" => "Голландский", "kl" => "Гренландский", "el" => "Греческий", "ka" => "Грузинский", "gu" => "Гуджарати", "gd" => "Гаэльский", "da" => "Датский", "dz" => "Дзонг-кэ", "dv" => "Мальдивский", "zu" => "Зулу", "he" => "Иврит", "ig" =>"Игбо", "yi" => "Идиш", "id" => "Индонезийский", "ia" => "Интерлингва", "ie" => "Интерлингве", "iu" => "Инуктитут", "ik" => "Инупиак", "ga" => "Ирландский", "is" => "Исландский", "es" => "Испанский", "it" => "Итальянский", "yo" => "Йоруба", "kk" => "Казахский", "kn" => "Каннада", "kr" => "Канури", "ca" => "Каталанский", "ks" => "Кашмири", "qu" => "Кечуа", "ki" => "Кикуйю", "kj" => "Киньяма", "zh" => "Китайский", "kv" => "Коми", "kg" => "Конго", "ko" => "Корейский", "kw" => "Корнский", "co" => "Корсиканский", "xh" => "Коса", "ku" => "Курдский", "km" => "Кхмерский", "lo" => "Лаосский", "la" => "Латинский", "lv" => "Латышский", "ln" => "Лингала", "lt" => "Литовский", "lu" => "Луба-катанга", "lb" => "Люксембургский", "mk" => "Македонский", "mg" => "Малагасийский", "ms" => "Малайский", "mt" => "Мальтийский", "mi" => "Маори", "mr" => "Маратхи", "mh" => "Маршалльский", "me" => "Мерянский", "mo" => "Молдавский", "mn" => "Монгольский", "nv" => "Навахо", "na" => "Науру", "nd" => "Ндебеле северный", "nr" => "Ндебеле южный", "ng" => "Ндунга", "de" => "Немецкий", "ne" => "Непальский", "no" => "Норвежский", "ny" => "Ньянджа", "nn" => "Нюнорск", "oj" => "Оджибве", "oc" => "Окситанский", "om" => "Оромо", "os" => "Осетинский", "pi" => "Пали", "pa" => "Пенджабский", "fa" => "Персидский язык", "pl" => "Польский", "pt" => "Португальский", "ps" => "Пушту", "rm" => "Ретороманский", "rw" => "Руанда", "ro" => "Румынский", "rn" => "Рунди", "ru" => "Русский", "sm" => "Самоанский", "sg" => "Санго", "sa" => "Санскрит", "sc" => "Сардинский", "ss" => "Свази", "sr" => "Сербский", "si" => "Сингальский", "sd" => "Синдхи", "sk" => "Словацкий", "sl" => "Словенский", "so" => "Сомали", "st" => "Сото южный", "sw" => "Суахили", "su" => "Сунданский", "tl" => "Тагальский", "tg" => "Таджикский", "th" => "Тайский", "ty" => "Таитянский", "ta" => "Тамильский", "tt" => "Татарский", "tw" => "Тви", "te" => "Телугу", "bo" => "Тибетский", "ti" => "Тигринья", "to" => "Тонга", "tn" => "Тсвана", "ts" => "Тсонга", "tr" => "Турецкий", "tk" => "Туркменский", "uz" => "Узбекский", "ug" => "Уйгурский", "uk" => "Украинский", "ur" => "Урду", "fo" => "Фарерский", "fj" => "Фиджи", "fi" => "Финский", "fr" => "Французский", "fy" => "Фризский", "ff" => "Фулах", "ha" => "Хауса", "hi" => "Хинди", "ho" => "Хиримоту", "cu" => "Старославянский", "ch" => "Чаморро", "ce" => "Чеченский", "cs" => "Чешский", "za" => "Чжуанский", "cv" => "Чувашский", "sv" => "Шведский", "sn" => "Шона", "ee" => "Эве", "eo" => "Эсперанто", "et" => "Эстонский", "jv" => "Яванский", "ja" => "Японский" );
 
our %months = ( "01" => "января", "02" => "февраля", "03" => "марта", "04" => "апреля", "05" => "мая", "06" => "июня", "07" => "июля", "08" => "августа", "09" => "сентября", "10" => "октября", "11" => "ноября", "12" => "декабря" );

[править] Глава 6. Генератор карты

Эта часть почти полностью использует труды Константина Устиновича.

print '<includeonly>{{#switch: {{NAMESPACE}}|=[[Категория:Статьи для автоматического обновления|{{{prefix}}}:{{{preload_data|}}}]]|}}<div class="p-summary" style="border-color: #{{{border-color|D8BFD8}}}">{{summary/title|{{PAGENAME}}||#{{{border-color|D8BFD8}}}}}';

sub wide { my($x, $pname, @else) = @_;
	return "{{#if: {{{$pname|}}}|" .
			"{{#if: {{{x$x|}}}" .
				'|' . "{{summary/line|{{{$pname}}}}}" .
				'|' . "{{summary/field|$pname|{{{$pname}}}}}" . 
			"}}" .
			(@else ? '|' . join("", @else) : '') .
		"}}";
}

foreach my $i(1..25) {
	print "{{#if:{{{m$i|}}}|";
	
	print '{{', "#switch:{{{m$i}}}",
		'|*=', "{{summary/section|{{{t$i}}}}}",
		'|+=', wide($i, "{{{t$i}}}",
					'{{#ifexist:data:{{{prefix}}}:{{PAGENAME}}',
						'|', "{{#if: {{{x$i|}}}",
							'|', "{{summary/line|{{data:{{{prefix}}}:{{PAGENAME}}|1={{{t$i}}}}}}}" ,
							'|', "{{summary/field|{{{t$i}}}|{{data:{{{prefix}}}:{{PAGENAME}}|1={{{t$i}}}}}}}" ,
							'}}',
						'|',
					'}}'
				),
		'|~=', wide($i, "{{{t$i}}}"),		
	'}}|}}';
}

print "</div></includeonly><noinclude>{{doc}}</noinclude>";

[править] Пояснение для глухонемных

Форматированный код шаблона {{autoupdated infobox}}.

----
Шапка: выводится один раз в начале.

<includeonly>
{{#switch: {{NAMESPACE}}
	|=[[Категория:Статьи для автоматического обновления|{{{prefix}}}:{{{preload_data|}}}]]
	|
}}
<div class="p-summary" style="border-color: #{{{border-color|D8BFD8}}}">
{{summary/title|{{PAGENAME}}||#{{{border-color|D8BFD8}}}}}

----
Основная часть: Повторяется 25 раз, <N> = <N>+1, в начале 1

{{#if:{{{m<N>|}}} # Если определен тип параметра <N>

	|{{#switch:{{{m<N>}}} # В зависимости от его значения
	
		|*={{summary/section|{{{t<N>}}}}} # * → выдать секцию, содержащую текст параметра <N>
		
		|+={{#if: {{{ {{{t<N>}}} |}}} # Если параметр с названием <N> может обновляться ботом
		
			|{{#if: {{{x<N>}}} # Если для параметра <N> определено значение x
			
				|{{summary/line|{{{ {{{t<N>}}} }}}}} # Выдать строку, содержащую текст параметра <N>
				
				|{{summary/field| {{{t<N>}}} |{{{ {{{t<N>}}} }}}}} # Иначе выдать поле с названием и текстом параметра <N>
				
			}}
			
			|{{#ifexist:data:{{{prefix}}}:{{PAGENAME}} # Иначе, если существует страница Data:Prefix:Pagename
			
				|{{#if: {{{x<N>}}} # Если для параметра <N> определено значение x
				
					|{{summary/line|{{data:{{{prefix}}}:{{PAGENAME}}|<N>={{{t<N>}}}}}}} # Выдать строку, содержащую значение со страницы данных
					
					|{{summary/field|{{{t<N>}}}|{{data:{{{prefix}}}:{{PAGENAME}}|<N>={{{t<N>}}}}}}} # Иначе выдать поле с названием параметра <N> и строку, содержащую значение со страницы данных
					
				}}
				
				| # Если нет источников данных, ничего не выдать
				
			}}
			
		}}
		
		|~={{#if: {{{{{{t<N>}}}|}}} # Если параметр с названием <N> должен определяться человеком
		
			|{{#if: {{{x<N>}}} # Если для параметра <N> определено значение x
			
				|{{summary/line|{{{{{{t<N>}}}}}}}} # Выдать строку, содержащую текст параметра <N>
				
				|{{summary/field|{{{t<N>}}}|{{{{{{t<N>}}}}}}}} # Иначе выдать поле с названием и текстом параметра <N>
				
			}}
			
		}}
		
	}}
		
	| # Если параметр <N> не определен, ничего не выдать
	
}}

----
Подвал: выводится один раз в конце

</div>
</includeonly>
<noinclude>{{doc}}</noinclude>
FedyaBot/По стопам Константина Устиновича относится к теме «Программирование»   ±