bOt tOOls v.1.0 - Perl Pidgin Plugin
Apr 29 2009 Programming

A wonderful script that squelches spambots in yahoo chatrooms.
Also ignores over-sized fonts, repeating, and non English characters.  

Using this removes 99% of porno bots that flood the chatrooms. Completely removing them from your view. Joins, parts, and messages will not be seen. You will have an enjoyable chatting experience with only real live people again.

Plus only the polite people.



The Unicode character matching is really nice, it will tell you the language when characters are not English.


Add people manually.


Show bOts ignored.


Show the people you have ignored manually, and more.
You can toggle the display of each of these on or off.

The latest version is 98.5. Scroll down for the download link.


$MODULE_NAME = "bOt_tOOls";

require Purple;
require Pidgin;

# E. Dziewa march 2009

%PLUGIN_INFO = (
    perl_api_version => 2,
    name       => "$MODULE_NAME",
    version        => "1.0",
    summary        => "$MODULE_NAME",
    description    => "Handles the ignoring of bOts",
    author     => "E. Dziewa",
    url            => "http://eric.dziewa.com",
    load       => "plugin_load",
    unload     => "plugin_unload"
);

our %nicklist;
our %ignored;
our $nickname = 'YOUR__NICK__NAME';
our $toggle;
our $bf = "\x{2691}"; # blackf lag
our $la = "\x{2190}"; # left arrow
our $ra = "\x{2192}"; # right arrow
our $oe = "\x{2686}"; # one eye
our $te = "\x{2687}"; # two eyes
our $wa = "\x{26a0}"; # warning sign
our $lb = "\x{26a1}"; # lightning bolt
our $le = "\x{2208}"; # left element
our $re = "\x{220b}"; # right element
our $lne = "\x{2209}"; # left not element
our $rne = "\x{220c}"; # right not element
our $numero = "\x{2116}";
our $tlimit;
our $count;

our $msg_repeat = 1;
our $msg_lang   = 1;
our $msg_font   = 1;
our $msg_spam   = 1;
our $msg_join   = 0;
our $msg_dbg    = 1;
our $msg_man    = 0;
our $commacount;

our @spamstrings = (
    'i feel like going shopping',
    'check da proof',
    'say something a real person would say',
    'say something intelligent',
    'private conversations are the best',
    'anyone wanna talk in private',
    'you gotta see this',
    'want to chat',
    'omg im 1\d too',
    'someone want to entertain this lonely girl',
    'someone give me a massage! hehe',
    'hey guy(?:s|z)!?$',
    'anyone feel like making me smile',
    'video games get real boring',
    'PM me I need to smile',
    'where are all the guy',
    'any cute guys in here\?',
    'study.+?(?:chat|pm)',
    'any (?:older|real) men',
    'am I the only girl here',
    'steal my towel',
    '><#[0-9A-Fa-f]{6}>',
    'any older guys in here\?',
    'check out my.+webcam',
    'anyone feeling? flirty\?',
    'my friend just left.+keep me company'
);

sub save_count {
    my $file = "/ABSOLUTE__PATH/count.txt";
    if ( @_ ) {
        my ($no) = shift;
        open( FH, ">", $file ) or Purple::Debug::misc( "$MODULE_NAME", " :: Couldn't open $file for writing. OS said $!" );
        print FH "$no\n";
        close FH;
        return 1;
    }
    else {
        my $retval;
        open( FH, "<", $file ) or Purple::Debug::misc( "$MODULE_NAME", " :: Couldn't open $file for reading. OS said $!" );
        while (<FH>) {
            if ( $. == 1 ) { ( $retval = $_ ) =~ /^(\d+)$/; }
            else { ; }
        }
        close FH;
        Purple::Debug::misc( "$MODULE_NAME", " :: savecount\(\) returning $retval\n" );
        return $retval;
    }
}

sub conv_received_msg {
    my ($account, $sender, $message, $conv, $flags, $data) = @_;
    if ( $account->get_username() =~ /$sender/ ) {  return } # don't watch ourself
    elsif ( defined($nicklist{$sender}) ) { # only work on those permitted into the list
        if ( (! defined($toggle)) && ( $sender =~ /Yahoo/ ) ) {
            # triggered when Yahoo bot sends congratulations
            $toggle = 1;
            Purple::Debug::misc( "$MODULE_NAME", " :: Toggle on\n" );
        }

        push( @{$nicklist{$sender}}, $message ); # store last 3 messages from this nick
        if ( scalar @{$nicklist{$sender}} > 3 ) { shift @{$nicklist{$sender}} }

        if ( ( @{$nicklist{$sender}}[0] eq @{$nicklist{$sender}}[1] ) && ( @{$nicklist{$sender}}[0] eq @{$nicklist{$sender}}[2] ) ) { # 3x repeat
            delete $nicklist{$sender}; # bye
            $ignored{$sender} = 'human repeating';
            $count++; $commacount = commie();
            my $TO = $conv->get_chat_data();
            $TO->send( "$ra $sender $la repeating $bf auto-ignored $numero $commacount" ) if ( $msg_repeat );
            return 1;
        }

        elsif ( $message !~ /^[\x{0000}-\x{027f}]+$/ ) { # not entirely english
            my $TO = $conv->get_chat_data();

            if ( $message =~ /[\x{0250}-\x{02ae}]{3,}/ ) {
                delete $nicklist{$sender};
                $ignored{$sender} = 'human non english';
                $count++; $commacount = commie();
                $TO->send( "$ra $sender $la typing in International Phonetic Alphabet. $bf auto-ignored $numero $commacount" ) if ( $msg_lang );
                return 1;
            }
            elsif ( $message =~ /[\x{02af}-\x{02b0}]{3,}/ ) {
                delete $nicklist{$sender};
                $ignored{$sender} = 'human non english';
                $count++; $commacount = commie();
                $TO->send( "$ra $sender $la typing in Spacing Modifier Letters. $bf auto-ignored $numero $commacount" ) if ( $msg_lang );
                return 1;
            }
            elsif ( $message =~ /[\x{0374}-\x{03FF}]{3,}/ ) {
                delete $nicklist{$sender};
                $ignored{$sender} = 'human non english';
                $count++; $commacount = commie();
                $TO->send( "$ra $sender $la typing in Greek or Coptic. $bf auto-ignored $numero $commacount" ) if ( $msg_lang );
                return 1;
            }
            elsif ( $message =~ /[\x{0400}-\x{0523}]{3,}/g ) {
                delete $nicklist{$sender};
                $ignored{$sender} = 'human non english';
                $count++; $commacount = commie();
                $TO->send( "$ra $sender $la typing in Cyrillic. $bf auto-ignored $numero $commacount" ) if ( $msg_lang );
                return 1;
            }
            elsif ( $message =~ /[\x{0531}-\x{0589}]{3,}/g ) {
                delete $nicklist{$sender};
                $ignored{$sender} = 'human non english';
                $count++; $commacount = commie();
                $TO->send( "$ra $sender $la typing in Armenian. $bf auto-ignored $numero $commacount" ) if ( $msg_lang );
                return 1;
            }
            elsif ( $message =~ /[\x{05be}-\x{05f4}]{3,}/g ) {
                delete $nicklist{$sender};
                $ignored{$sender} = 'human non english';
                $count++; $commacount = commie();
                $TO->send( "$ra $sender $la typing in Hebrew. $bf auto-ignored $numero $commacount" ) if ( $msg_lang );
                return 1;
            }
            elsif ( $message =~ /[\x{0600}-\x{06ff}]{3,}/g ) {
                delete $nicklist{$sender};
                $ignored{$sender} = 'human non english';
                $count++; $commacount = commie();
                $TO->send( "$ra $sender $la typing in Arabic. $bf auto-ignored $numero $commacount" ) if ( $msg_lang );
                return 1;
            }

            #################################
            ### ^^^ 328 lines snipped ^^^ ###
            #################################

            else { return 0; }
        }

        elsif ( $message =~ /size="(6|7|8|9)/i ) { # big letters
            delete $nicklist{$sender};
            $ignored{$sender} = 'font size';
            $count++; $commacount = commie();
            my $TO = $conv->get_chat_data();
            $TO->send( "$ra $sender $la FONTSIZE ($1) $bf auto-ignored $numero $commacount" ) if ( $msg_font );
            return 1;
        }
        else {
            my $is_it_spam = 0;
            Purple::Debug::misc( "$MODULE_NAME", " :: Entering spam string loop\n") if ( $msg_dbg );
            foreach ( @spamstrings ) {
                if (  $message =~ /$_/ig ) {
                    Purple::Debug::misc( "$MODULE_NAME", " :: Spam match from $sender, $message <matches> $_\n") if ( $msg_dbg );
                    $is_it_spam = 1;
                    last;
                }
                else { next; }
            }
            if ( $is_it_spam ) {
                delete $nicklist{$sender};
                $ignored{$sender} = 'spam/cam bOt';
                $count++; $commacount = commie();
                my $TO = $conv->get_chat_data();
                $TO->send( "$ra $sender $la is a pOrn bOt! $bf auto-ignored $numero $commacount") if ( $msg_spam );
                return 1;
            }
            else { return 0 } # allow the message through
        }
    }
    else { return 1 } # not in the nick list
}


sub nick_joined {
    my ( $conv, $nick, $flags, $new ) = @_;
    if ( $nick =~ /$nickname/ ) { return 0 }
    elsif (
        ( $nick =~ /\w{1}\d{4}\w+\d{3}/   )       ||
        ( $nick =~ /^looku_/ )                ||
        ( $nick =~ /^[A-Za-z]+_\d{7}$/  )     ||
        ( $nick =~ /^[A-Za-z]+\d{3}[A-Za-z]{3}$/ )    ||
        ( $nick =~ /^pnema/ ) ||
        ( $nick =~ /^[A-Za-z]+_[A-Za-z]+_\d{3}$/ ) ) { # common bot names

        Purple::Debug::misc( "$MODULE_NAME", " :: Obvious bOt nick $nick ignored\n") if ( $msg_dbg );
        $ignored{$nick} = 'bOt nick';
        $count++; $commacount = commie();
        if ( $toggle == 1 ) {
            my $TO = $conv->get_chat_data();
            $TO->send( "$ra $nick $la obvious bOt nick $bf auto-ignored $numero $commacount" ) if ( $msg_join );
            return 1;
        }
        return 1;
    }
    else {
        $nicklist{$nick} = [];
        Purple::Debug::misc( "$MODULE_NAME", " :: Adding $nick to nicklist joined\n") if ( $msg_dbg );
        return 0;
    }
}

sub nick_left {
    my ( $conv, $nick, $flags, $new ) = @_;
    if ( defined($nicklist{$nick}) ) {
        delete $nicklist{$nick};
        Purple::Debug::misc( "$MODULE_NAME", " :: Removing $nick from nicks list parted\n") if ( $msg_dbg );
        return 0
    }
    else {
        Purple::Debug::misc( "$MODULE_NAME", " :: Removing $nick from ignored list parted\n") if ( $msg_dbg );
        delete $ignored{$nick};
        return 1
    }
}

sub shownicks {
    Purple::Debug::misc( "$MODULE_NAME", " :: Nicks ->\n");
    for ( keys %nicklist ) {
        Purple::Debug::misc( "$MODULE_NAME", " :: $_\n");
    }
    Purple::Debug::misc( "$MODULE_NAME", " :: End nicks\n");
    return 0;
}

sub showbots {
    Purple::Debug::misc( "$MODULE_NAME", " :: bOts ->\n");
    for ( keys %ignored ) {
        Purple::Debug::misc( "$MODULE_NAME", "$_\t$ignored{$_}\n");
    }
    Purple::Debug::misc( "$MODULE_NAME", " :: End bOts\n");
    return 0;
}

sub init_vars {
    %nicklist = ();
    %ignored = ();
    $toggle = ();
    if ( ($count) = save_count() ) {
        Purple::Debug::misc( "$MODULE_NAME", " :: Initialized count to $count\n" );
    }
    else {
        $count = 0;
        Purple::Debug::misc( "$MODULE_NAME", " :: Initialized count to -zero-\n" );
    }
    Purple::Debug::misc( "$MODULE_NAME", " :: Joined:: vars truncated\n");
    return 0;
}

sub destroy_vars {
    %nicklist = ();
    %ignored = ();
    $toggle = ();
    if ( save_count($count) ) {
        Purple::Debug::misc( "$MODULE_NAME", " :: Saved count of $count\n" );
    }
    else {
        Purple::Debug::misc( "$MODULE_NAME", " :: Couldn't save count $count\n" );
    }
    Purple::Debug::misc( "$MODULE_NAME", " :: Left vars truncated\n");
    return 0;
}

sub un {
    my ($conv, $cmd, $data, @args) = @_;
    if ( exists $ignored{@args[0]} ) {
        delete $ignored{@args[0]};
        Purple::Debug::misc( "$MODULE_NAME", " :: Deleted @args[0] from ignored\n");
        $nicklist{@args[0]} = [];
        Purple::Debug::misc( "$MODULE_NAME", " :: Added @args[0] to nicks\n");
    }
    else {
        Purple::Debug::misc( "$MODULE_NAME", " :: ERROR @args[0] not in ignored list\n");
    }
    return Purple::Cmd::Return::OK;
}

sub bot {
    my ($conv, $cmd, $data, @args) = @_;
    if ( exists $nicklist{@args[0]} ) {
        delete $nicklist{@args[0]};
        Purple::Debug::misc( "$MODULE_NAME", " :: Deleted @args[0] from nicks\n");
        $ignored{@args[0]} = 'bOt';
        Purple::Debug::misc( "$MODULE_NAME", " :: Added @args[0] to ignored : $ignored{@args[0]}\n");
    }
    else {
        Purple::Debug::misc( "$MODULE_NAME", " :: ERROR @args[0]} not in nicklist\n");
    }
    return Purple::Cmd::Return::OK;
}

sub lame {
    my ($conv, $cmd, $data, @args) = @_;
    if ( exists $nicklist{@args[0]} ) {
        delete $nicklist{@args[0]};
        Purple::Debug::misc( "$MODULE_NAME", " :: Deleted @args[0] from nicks\n");
        $ignored{@args[0]} = 'human requested';
        $count++; $commacount = commie();
        Purple::Debug::misc( "$MODULE_NAME", " :: Added @args[0] to ignored : $ignored{@args[0]}\n");
            my $TO = $conv->get_chat_data();
            $TO->send( "$oe $te $oe $ra @args[0] $la $oe $te $oe ignored. $numero $commacount" ) if ( $msg_man );
    }
    else {
        Purple::Debug::misc( "$MODULE_NAME", " :: ERROR @args[0]} not in nicklist\n");
    }
    return Purple::Cmd::Return::OK;
}

sub scan_chan {
my ($conv, $cmd, $data, @args) = @_;
my @chat = Purple::get_chats();
my $TO = $chat[0]->get_chat_data();
my $i = 0; my $string = ();

    if ( (defined(@args[0])) && (@args[0] =~ /b/) ) {
        $TO->send( "$le scanning for bOts $re" );
        if ( defined( %ignored ) ) {
            my @temp;
            for ( keys %ignored ) {
                if ( $ignored{$_} =~ /bOt/ ) {
                    $i++; push(@temp, $_);
                }
            }
            if ( $i > 0 ) {
                $string = " found ";
                $string .= "$i bOts. ";
                foreach ( @temp ) { $string .= " $_,"; }
                chop $string;
                $string .= ". $bf ignored";
                $TO->send("$string");
            }
            else {
                $TO->send( "no bOts found" );
            }
        }
        else {
            $TO->send( "no bOts found" );
        }
    }
    else {
        $TO->send("$le scanning for llamas $re");
        if ( defined( %ignored ) ) {
            my @temp;
            for ( keys %ignored ) {
                if ( $ignored{$_} !~ /bOt/ ) {
                    $i++; push(@temp, $_);
                }
            }
            if ( $i > 0 ) {
                $string = " found ";
                $string .= "$i.  $oe $te $oe ";
                foreach ( @temp ) { $string .= " $_,"; }
                chop $string;
                $string .= ". (probably) $bf ignored";
                $TO->send("$string");
            }
            else {
                $TO->send( " none found." );
            }
        }
        else {
            $TO->send( " none found." );
        }
    }
}

sub shown {
    my ($conv, $cmd, $data, @args) = @_;
    Purple::Debug::misc( "$MODULE_NAME", " :: (R)epeat $msg_repeat, (L)anguage $msg_lang, (F)ontsize $msg_font, (S)pam Msg $msg_spam, bOt (J)oins $msg_join, (D)ebug $msg_dbg, (M)anual $msg_man\n" );
    # defaults
    # our $msg_repeat = 1;
    # our $msg_lang   = 1;
    # our $msg_font   = 1;
    # our $msg_spam   = 1;
    # our $msg_join   = 0;
    # our $msg_dbg    = 1;
    if ($args[0] =~ /R/) { ($msg_repeat) ? ($msg_repeat = 0) : ($msg_repeat = 1) }
    elsif ($args[0] =~ /L/) { ($msg_lang) ? ($msg_lang = 0) : ($msg_lang = 1) }
    elsif ($args[0] =~ /F/) { ($msg_font) ? ($msg_font = 0) : ($msg_font = 1) }
    elsif ($args[0] =~ /S/) { ($msg_spam) ? ($msg_spam = 0) : ($msg_spam = 1) }
    elsif ($args[0] =~ /J/) { ($msg_join) ? ($msg_join = 0) : ($msg_join = 1) }
    elsif ($args[0] =~ /D/) { ($msg_dbg) ? ($msg_dbg = 0) : ($msg_dbg = 1) }
    elsif ($args[0] =~ /M/) { ($msg_man) ? ($msg_man = 0) : ($msg_man = 1) }
    elsif ($args[0]) { Purple::Debug::misc( "$MODULE_NAME", " :: \$args[0] $args[0] defined but doesn't match\n" ) if ( $msg_dbg ); }
    else { Purple::Debug::misc( "$MODULE_NAME", " :: \@args[0] Undefined no variables toggled\n" ) if ( $msg_dbg ); }
    Purple::Debug::misc( "$MODULE_NAME", " :: (R)epeat $msg_repeat, (L)anguage $msg_lang, (F)ontsize $msg_font, (S)pam Msg $msg_spam, bOt (J)oins $msg_join, (D)ebug $msg_dbg, (M)anual $msg_man\n" );
}

sub commie {
    my $txt = reverse $count;
    $txt =~ s/(\d\d\d)(?=\d)(?!\d*\.)/$1,/g;
    return scalar reverse $txt;
}

sub plugin_load {
    my $plugin = shift;
    ($count) = save_count();

    Purple::Debug::misc( "$MODULE_NAME", " ::ON::\n");

    $conv = Purple::Conversations::get_handle();

    Purple::Signal::connect($conv, "chat-joined", $plugin, \&init_vars, "A");

    Purple::Signal::connect($conv, "chat-left", $plugin, \&destroy_vars, "B");

    Purple::Signal::connect($conv, "receiving-chat-msg", $plugin, \&conv_received_msg, "C");

    Purple::Signal::connect($conv, "chat-buddy-joining", $plugin, \&nick_joined, "D");

    Purple::Signal::connect($conv, "chat-buddy-leaving", $plugin, \&nick_left, "E");

    my $help = "
/scan      -scans for llamas and announce
/scan b    -scans for bots and announce
/shownicks -Prints nicklist to debug
/showbots  -Prints botlist to debug
/un        -Un-ignores given nick         (removes from botlist)
/bot       -Adds given name to bot list   (removes from nicklist)
/lame      -Adds given name to lamer list (removes from nicklist)
/shown     -Toggles which auto-ignores are announced. Writes to debug
           (R L F S J D M) Repeat, Language, Fontsize, Spam, Join, Debug, Manual
";

    Purple::Cmd::register($plugin, "shownicks", $conv, Purple::Cmd::Priority::DEFAULT,
    PURPLE::CMD::FLAG::IM | Purple::Cmd::Flag::CHAT | Purple::Cmd::Flag::ALLOW_WRONG_ARGS,
    0, \&shownicks, "$help", $plugin);

    Purple::Cmd::register($plugin, "showbots", $conv, Purple::Cmd::Priority::DEFAULT,
    PURPLE::CMD::FLAG::IM | Purple::Cmd::Flag::CHAT | Purple::Cmd::Flag::ALLOW_WRONG_ARGS,
    0, \&showbots, "$help", $plugin);

    Purple::Cmd::register($plugin, "un", "ws", Purple::Cmd::Priority::DEFAULT,
    PURPLE::CMD::FLAG::IM | Purple::Cmd::Flag::CHAT | Purple::Cmd::Flag::ALLOW_WRONG_ARGS,
    0, \&un, "$help", $plugin);

    Purple::Cmd::register($plugin, "bot", "ws", Purple::Cmd::Priority::DEFAULT,
    PURPLE::CMD::FLAG::IM | Purple::Cmd::Flag::CHAT | Purple::Cmd::Flag::ALLOW_WRONG_ARGS,
    0, \&bot, "$help", $plugin);

    Purple::Cmd::register($plugin, "lame", "ws", Purple::Cmd::Priority::DEFAULT,
    PURPLE::CMD::FLAG::IM | Purple::Cmd::Flag::CHAT | Purple::Cmd::Flag::ALLOW_WRONG_ARGS,
    0, \&lame, "$help", $plugin);

    Purple::Cmd::register($plugin, "scan", "ws", Purple::Cmd::Priority::DEFAULT,
    PURPLE::CMD::FLAG::IM | Purple::Cmd::Flag::CHAT | Purple::Cmd::Flag::ALLOW_WRONG_ARGS,
    0, \&scan_chan, "$help", $plugin);

    Purple::Cmd::register($plugin, "shown", "ws", Purple::Cmd::Priority::DEFAULT,
    PURPLE::CMD::FLAG::IM | Purple::Cmd::Flag::CHAT | Purple::Cmd::Flag::ALLOW_WRONG_ARGS,
    0, \&shown, "$help", $plugin);
}

sub plugin_unload {
    %nicklist = ();
    %ignored = ();
    (save_count($count))
    ? Purple::Debug::misc( "$MODULE_NAME", " :: savecount\(\) success $count::\n")
    : Purple::Debug::misc( "$MODULE_NAME", " :: savecount\(\) failed $count::\n");
    Purple::Debug::misc( "$MODULE_NAME", " ::OFF::\n");
}


Download

Download ← Version 2

Download ← Version 3

Download ← Version 98.5

   
Comments
Aug 24 2009
Comment #1 posted by Abhijeet at 2:47 pm

Thks Eric for this Plugin is awesome

Sep 1 2009
Comment #2 posted by Webdesign_Freelancer at 10:44 am

Armorattak - Downloaded strawberry perl as you suggested - never used it - Can you walk me through the steps on installing this plugin?

Sep 12 2009
Comment #3 posted by Eric at 3:15 am

Installing should be effortless now that versions 2 and 3 are done. Just drop it in your plugins directory. Let me know if you have any problems.

Version 2 adds IM support, and a function that displays ignored totals.

Download ← Version 2

Version 3 adds color, gui toggling, gui bindings, and a few more display functions.

Download ← Version 3

Screenshots:








Feb 16 2010
Comment #4 posted by vivek at 8:56 am

Hiya thanks eric. :)

Jan 1 2011
Comment #5 posted by Abhijeet at 1:52 am

Hi Eric, I am waiting for a quick article from you on this on my website itshacked.com

Mar 22 2011
Comment #6 posted by liveinreal at 4:56 pm

HI i am totaly new to this linux word if u plz tell the steps as i dont even know where the plugin file is of pidgin

Mar 23 2011
Comment #7 posted by Eric at 4:36 pm

livenreal:
First off get the latest bOt_tOOls here. Then you unzip it in .purple/plugins/ which is in your home directory. Your home directory is /home/yourusername. So if your username is liveinreal ... /home/liveinreal/.purple/plugins.
The directories might not exist, if so create them.
$mkdir -p /home/liveinreal/.purple/plugins

Jan 15 2012
Comment #8 posted by Steve at 2:38 pm

Hi. Any chance of this becoming usable with finch only? Thanks.

Jan 18 2012
Comment #9 posted by Eric at 1:07 am

Steve:
First off get the latest bOt_tOOls here. Try commenting the require Pidgin line so it looks like this #require Pidgin.

Jan 19 2012
Comment #10 posted by Steve at 1:14 pm

Eric: Thanks a million, it seems to work perfectly! This is very much needed and appreciated. :)

Jul 11 2012
Comment #11 posted by porter at 2:29 pm

I put it in the plugins folder but i still cant get it to work... i have windows xp, and i'm using pidgin 2.10.6 (libpurple)

does this still work for the new pidgin?

Jul 16 2012
Comment #12 posted by Eric at 8:04 pm

porter: Please try the latest version here.

Comments for this entry available via RSS.
Comment Area
Your Name
Your Email (will not be published)
Your Website
Your Comment
Profanity is Prohibited
eric.dziewa.com is running WordPress.
WhiteSpace theme designed by E. Dziewa.
All content © E. Dziewa.
Thanks for stopping by.