#!/usr/bin/perl
#
# This file is part of Querybot (-a modular perl jabber bot)
# http://github.com/micressor/jabber-querybot
#
# Copyright (C) 2009-2012 Marco Balmer <marco@balmer.name>
#
# Querybot 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.
#
# Querybot 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 Querybot. If not, see <http://www.gnu.org/licenses/>.

=head1 NAME

jabber-querybot - a modular perl jabber bot

=head1 DESCRIPTION

jabber-querybot connects a jabber account and wait for messages. If a message
comes in, it forward it to your self programmend modul. The return string of
your module, jabber-querybot send it back to the jabber sender.

It is designed to be re-usable and to make it easy to write small
Jabber bots that do one thing and do it well. A simple concept with a
lot of examples and experiences are implemented.

1. Create a jabber account on a jabber-server around

2. Create a bot application:

=over 4

 cd examples
 cp Querymodule.pm /etc/jabber-querybot/Mybot.pm
 cd /etc/jabber-querybot
 ln -s Mybot.pm Querymodule.pm

=back 

Modify login parameters to your jabber-bot-account

vim Mybot.pm

=over 4

  our $hostname	       = "swissjabber.ch";
  our $user            = "";
  our $password        = "";
  our $ident           = "Testbot";
  our $bot_admin       = "\@swissjabber.ch";
  our $port            = "5222";
  our $timeout         = "5";
  our $service_name    = "$user\@$hostname";
  our $bot_description = "Bot help title
  Bot description";

=back

For each jabber message, jabber-querybot will execute sub run_query,
that you can write here your application.

You can control how your jabber response will be:

=over 4

=item * error = error message stanza

=item * presence = error as presence stanza

=item * ignore = ignore message

=back

=head1 OPTIONS

jabber-querybot has a lot of variables which you can easy modify for 
what you need:

=head2 querystatus

$querystatus = [ 0 | 1 ]

=over 4

=item * 0 = Bot will not proceed any incoming jabber messages.

=item * 1 = Bot will proceed incoming messages.

=back

=head2 penalty_status

If the bot has too much workload, it goes to penalty status and wait some 
time until his status change back to normal.

$timer_reconnect_default = 21600

Every 21600 seconds (6 hours) the bot will shutdown automatically, wait 10 
seconds and starting up again. 

$timer_auto_query = 0

If you set in your module this variable to 60, the bot will every 60 seconds
call the function run_auto_query() which you may use for several things.

=head2 System load

If your systems load is >=6, this bot will shutdown the jabber connection 
and check every 10 seconds systems load. If load <=2, bot will start over.

=head1 EXAMPLES

/usr/share/doc/jabber-querybot/examples/Testbot.pm

=head1 FILES

/etc/jabber-querybot/Querymodule.pm

/usr/bin/jabber-querybot

=head1 RESOURCES

http://github.com/micressor/jabber-querybot/

=head1 METHODS

=cut

use strict;

use lib "/etc/jabber-querybot";
use lib "./lib"; 			# that we can compile locally.

use Net::XMPP qw(Client Message);
use Digest::MD5 qw(md5 md5_hex md5_base64);
use Time::HiRes qw(gettimeofday);
use Sys::Syslog;
use Sys::CpuLoad;
use XML::Smart;
use Encode;
use utf8;

# jabber-querybot modules
use JabberQuerybot;

### CONFIGURATION ###
use Querymodule;
my $release = "0.1.0";
### CONFIGURATION ###

$SIG{KILL}      = \&Stop;
$SIG{TERM}      = \&Stop;
$SIG{INT}       = \&Stop;

$Querymodule::Con	
	= new Net::XMPP::Client(debuglevel=>0,debugfile=>"stdout");


### BEGIN CONFIGURATION ###

my $ident			= $Querymodule::ident;
my $service_name		= $Querymodule::service_name;
my $bot_admin			= $Querymodule::bot_admin;
my $hostname			= $Querymodule::hostname;
my $port			= $Querymodule::port;
my $user			= $Querymodule::user;
my $password			= $Querymodule::password;
my $timeout			= $Querymodule::timeout;
my $timer_auto_query_run	= $Querymodule::timer_auto_query;
my $status_auto_query		= $Querymodule::status_auto_query;
my $stanza_penalty_calc_default	= 60;
if ($Querymodule::stanza_penalty_calc_default)
 {
   $stanza_penalty_calc_default	= $Querymodule::stanza_penalty_calc_default;
 } ### if ($Querymodule::stanza_penalty_calc_default)
my $resource			= $ident;
my $bot_work_message		= "$ident workload is normal";
my $bot_idle_message		= "$ident is waiting for queries";
my $bot_msg_maintenance		= "Sorry, $ident is temporary in maintenance.";
my $bot_description		= $Querymodule::bot_description;

### END CONFIGURATION ###


### Start of Main-Script ###

openlog($ident,"","local0");

my $Con_stat;
my $querystatus 		= 0;
my $querycount			= 0;
my $uptime			= 0;
my $timer_presence		= -10;
my $timer_idle			= 0;
my $timer_reconnect		= 0;
my $timer_penalty		= 0;
my $bot_status			= "";
my $penalty_status		= "normal";
my $penalty_counter_penalties	= "0";
my $penalty_counter_normal	= "0";
my $msg_per			= 0;
my $uptime_hours		= 0;
my $stanza_counter_total	= 0;
my $stanza_counter_total_penalty= 0;
my $stanza_counter_total_q	= 0;
my $stanza_counter_message	= 0;
my $stanza_counter_message_q	= 0;
my $stanza_counter_presence	= 0;
my $stanza_counter_presence_q	= 0;
my $stanza_counter_iq		= 0;
my $stanza_counter_iq_q		= 0;
my $stanza_penalty_calc		= 0;

my $timer_idle_default		= 3600;
my $timer_presence_default 	= 7200;
my $timer_reconnect_default	= 21600;


my $timer_auto_query		= 0;
my @system_load 		= "";
my $system_load_status 		= "0";  # 0 = System load is ok
					# 1 = system load is too high

check_before_start();

querybot_log("info","Init(): Startup $ident...");

while(system_load_check() ne 0)
{
  querybot_log("info","main->system_load_check(): Load to high, will not start");
  sleep 10
}

connect_bot();

# Loop until we're finished.
while ($Querymodule::Con) 
 {
 reconnect_server() unless defined($Querymodule::Con->Process(1));
 $timer_presence++;
 $timer_idle++;
 $uptime++;
 $timer_auto_query++;
 $timer_reconnect++;
 $timer_penalty++;
 calculate_stats();
 # Check if system load is too high.
 if(system_load_check() == 1)
 {
   querybot_log("info","main->system_load_check(): System load too high, will shutdown querybot.");
   disconnect_server();
   while(system_load_check() ne 0)
   {
     sleep 10;
     querybot_log("info",'main->system_load_check(): Load still too high ('.$system_load[0].')');
   }
   connect_bot();
 }

 #
 # Send let's go
 #
 if($timer_presence == 0)
  {
   jabber_set_presence(undef,"Ok, let's go, I am ready for queries...");
   $querystatus		=1;
   $timer_penalty	=0;
  } ### if($timer_presence == 0)

 if($timer_penalty >= 60)
  {
  $timer_penalty=0;
  $stanza_counter_total_penalty = $stanza_counter_total;
  } ### if($timer_penalty > 60)
 
 if($timer_reconnect >= $timer_reconnect_default and $querystatus == 1)
  {
   querybot_log("info","reconnect_mode(): Execute...");
   disconnect_server();
   querybot_log("info","reconnect_mode(): Wait...");
   sleep(10);
   connect_bot();
   querybot_log("info","reconnect_mode(): Done");
  } ### END of if($timer_reconnect....
 
 #
 # Change presence status only, if queries are enabled
 #
 unless($querystatus == 0)
 {

 #
 # Send timer presence (for transports like icq and msn
 #
 if(($timer_presence > $timer_presence_default) and ($timer_idle > $timer_idle_default)
 	and $penalty_status eq "normal")
  {
        querybot_log("info","mail(): $bot_idle_message workload: $msg_per queries/hour");
	jabber_set_presence(
	"away","$bot_idle_message workload: $msg_per queries/hour","force");
	$timer_presence = 1;
  } ### if(($timer_presence > $timer_presence_default) and ($timer_idle > $timer_idle_default))

 #
 # Run auto queries, if configured
 #
 if($status_auto_query == 1 and ($querystatus==1 and ($timer_auto_query >= $timer_auto_query_run)))
  {
   querybot_log("notice","run_auto_query(): Execute...");
   $timer_auto_query=0; my ($ret,$status) = run_auto_query();
   querybot_log("notice","run_auto_query(): Exit: $ret $status");
  } ### if($status_auto_query == 1 and ($querystatus==1 and ($timer_auto_query >= $timer_auto_query_run)))

  } ### END of unless($querystatus)
} ### while

### End of Main-Script ###

sub InMessage
{

=head2 InMessage()

An incoming jabber message to the bot will hook this function.

=over 2

=item * Read parameters of incoming stanza

=back 

=cut

my $t0 			= gettimeofday;
my $sid			= shift;
my $message		= shift;
my $from                = $message->GetFrom();

=head2

=over 2

=item * Decode utf8 string

=back

=cut

my $from_utf8_decoded	= encode("utf8", $from);
my $barejid             = get_barejid($from_utf8_decoded);

$stanza_counter_message++;

=head2

=over 2

=item * increment timer overload and do not process message if bot is 
overloaded.

=back 

=cut

if(penalty_handler($barejid) > 0)
 {
  return undef;
 }

my $digest_jid 		= md5_hex($barejid);
my $to                  = $message->GetTo();
my $body                = $message->GetBody();
my $subject             = $message->GetSubject();
my $type                = $message->GetType();
my $thread		= $message->GetThread();

=head2

=over 2

=item * Ignore message if it is from myself

=back

=cut

if($service_name eq $barejid)
 {
  querybot_log("info","InMessage(): Msg from myself -- ignored");
  return -1;
 } ### if($service_name eq $barejid)

=head2

=over 2

=item * Be sure, that it is not a message from another transport

=back

=cut

unless($barejid =~ /\@/)
 {
 querybot_log("info","InMessage($barejid): System msg (without @) -- ignored ");
 return 0;
 } ### unless($barejid =~ /\@/)
if($body eq "")
 {
 querybot_log("info","InMessage($barejid): Msg (empty body) -- ignored ");
 return 0;
 } ### if($body eq "")

=head2

=over 2

=item * Check any systemcommands for the bot

=back

=cut

my ($ret_msg);
if($body =~ /!/ or $body eq "help")
 {
  ($ret_msg) 		= systemcommands($from, $body,$type,$thread); 
  my $elapsed 		= calculate_elapsed_time($t0);
  my $ret_msg_footer 	= jabber_add_footer($ret_msg,$elapsed);
  querybot_log("info","InMessage($barejid): Admin command `$body` --  processed");
  jabber_send_message($from,$type,$thread,$ret_msg_footer);
  return 0;
 } ### unless($barejid eq $bot_admin)

=head2

=over 2

=item * If the bot has sleeping status, change it to work

=back

=cut

unless ($querystatus == 0)
{
if ($bot_status eq 'away')
 {
  jabber_set_presence(undef,$bot_work_message);
 } ### if ($bot_status eq 'away')
} ### END of unless($querystatus ....

=head2

=over 2

=item * We process only normal text or chat type jabber messages

=back

=cut

if (($type eq 'normal' or $type eq '' or $type eq 'chat') 
	and ($querystatus==1 or ($bot_admin eq $barejid))) 
 {
 
  # If timer_presence is < 0 and a stanza is incoming, querystatus will
  # never go to 1. 
  if($timer_presence > 0 and $penalty_status eq "normal")
   {
    jabber_set_presence(undef,$bot_work_message);
    $timer_idle 		= 0;
    $timer_presence 		= 0;
   } ### if($timer_presence > 0)

my (	$response,
	$response_status,
	$response_status_code,
	$s_proceeded
   );

=head2

=over 2

=item * And now we give the real text string which was incoming to the
bot via run_query().

=item * If run_query() say us 'ignore` we do a log entry and do not answer
via jabber to the user.

=back

=cut

  ($response_status,$response_status_code,$response,$s_proceeded) 
  = run_query($body,$from,$barejid,$digest_jid,$subject);

  if ($response_status eq "ignore")
   {
    querybot_log("info","InMessage($from)->run_query(): -- Message ignored ");
    return -1;
   } ### if ($response_status eq "ignore") 

  if ($response_status eq "presence")
   {
    querybot_log("info","InMessage($from)->run_query(): -- Presence sent");
    jabber_set_presence(undef,$response);
    return -1;
   } ### if ($response_status eq "presence")

  if ($response_status eq "error")
   {

=head2

=over 2

=item * If run_query() says 'error` we send a jabber error stanza wiht the
status message from run_query() back to the user.

=back

=cut

    sendError(	$message,
    		$from,
		$to,
		$response_status_code,
		$response);

    querybot_log("info","InMessage($from)->run_query(): -- Error delivered");
    return -1;
   } ### if ($response_status eq "error")


=head2

=over 2

=item * If there was no error, we update the statistic vars and send the
answer from run_query() back to the jabber user.

=back

=cut

  $querycount++;

  my $elapsed 			= calculate_elapsed_time($t0);
  my $ret_msg_with_footer 	= jabber_add_footer($response,$elapsed);


my $ret_jabber_send_msg = jabber_send_message($from,$type,$thread,$ret_msg_with_footer);
querybot_log("info","InMessage($barejid/$digest_jid): Msg \`$type\` in in $elapsed"); 
 } ### 
querybot_log("debug","InMessage($barejid): Msg \`$type\` string \`$body\'"); 

 return 0;
} ### InMessage()

######################################################################
sub connect_server {
######################################################################

querybot_log("info","connect_server(): Connecting to $hostname jabber-server");

=head2 connect_server()

This function connects to the jabber server with the given credentials from
Querymodule.pm.

=cut

$Querymodule::Con->Connect(
                hostname		=> $hostname,
                port			=> $port,
                timeout			=> $timeout
	);

  if ($Querymodule::Con->Connected()) 
   {
    querybot_log("info","Connect_server(): Connection successfully");
   }
  else
   {
    querybot_log("info","Connect_server(): Ooops... no connection for $user\@$hostname, we're waiting 60 secs ...");
    
    for(my $i=0;$i< 60; $i++)
     {
      sleep(1);
     }
    connect_server();
   }

=head2

Set the call back functions. This functions will be executed
if a message of the types <message/> <iq/> or <presence/>
are incoming.

=cut

querybot_log("info","connect_server(): Setting up callbacks");
$Querymodule::Con->SetCallBacks	(
  "message" 	=> \&InMessage, 
  "iq" 		=> \&InIQ, 
  "presence" 	=> \&InPresence 
			);

querybot_log("info","connect_server(): Send authentication $user\@$hostname");
my ($auth_status,$auth_message) = $Querymodule::Con->AuthSend ( 
  username=>	$user,
  password=>	$password,
  resource=>	$resource
);
querybot_log("info","connect_server(): Authentication $auth_status");

if ($auth_status ne "ok") {
die "Authentication status: $auth_status";
}

}
######################################################################


######################################################################
sub Stop {
######################################################################

=head2 Stop()

Shutdown jabber connection and exit main program

=cut

disconnect_server();
querybot_log("info","Stop(): Exit: 0");

exit(0);

}

######################################################################
sub disconnect_server {
######################################################################

=head2 disconnect_server()

Only disconnect from the jabber-server.

=cut

querybot_log("info","disconnect_server(): Disconnecting");

$Querymodule::Con->Disconnect;

} ### END of disconnect_server


######################################################################
sub reconnect_server {
######################################################################

=head2 reconnect_server()

Reconnect and create a log entry.

=cut

querybot_log("info","reconnect_server(): connection lost, reconnecting...");
connect_server();

}
######################################################################
sub InIQ {
######################################################################

my $sid 	= shift;
my $iq		= shift;
my $from 	= $iq->GetFrom();
my $barejid	= get_barejid($from);

=head2 InIQ()

We do not proceed any iq (information query), this is only for 
statisic.

=cut

$stanza_counter_iq++;

querybot_log("debug","InIQ($barejid): received -- ignored");

#
# increment timer overload
# 
if(penalty_handler($barejid) > 0)
 {
  #
  # Do not process message if we are overloaded.
  #
  querybot_log("debug","pentalty_handler(): InIQ($barejid):");
  return undef;
 }


}
######################################################################
sub InPresence {
######################################################################


my $sid 		= shift;
my $presence 		= shift;
my $from 		= $presence->GetFrom();
my $barejid		= get_barejid($from);
my $to 			= $presence->GetTo();
my $type 		= $presence->GetType();
my $status 		= $presence->GetStatus();
my $xml			= $presence->GetXML();

=head2 InPresence()

=over 2

=item * Increment timer overload

=item * Do not process message if we are overloaded penalty_handler().

=back

=cut

$stanza_counter_presence++;

if(penalty_handler($barejid) > 0)
 {
  querybot_log("debug","penalty_handler(): Presence($barejid):");
  return undef;
 }

=head2

We have a problem in Net::Jabber. An incoming message with a ` in resource 
blocks the bot. We will hotfix that for the moment.

=cut

if ($from =~ /`/)
 {
  querybot_log("info","Presence($barejid): Got ``` -- this is a hotfix, will send unsubscribe");
  sendPresence($presence,$from,$to,'unsubscribe');
  sendPresence($presence,$from,$to,'unsubscribed');
  return 0;
 } ### END of unless

=head2

A subscription type `subscribe` is incoming. Send `subscribed` tho the user 
and say hello ;)

=cut

if ($type eq 'subscribe')
	{
	
=head2

=over 2

=item * Send presence to user

=back

=cut

         querybot_log("notice","Presence($barejid): Got `subscribe` -- do the same");
	 sendPresence($presence,$from,$to,'subscribe');
	 sendPresence($presence,$from,$to,'subscribed');

	} ### END of if ($type eq subscibe

=head2

=over 2

=item * Remove subscription if a user remove this bot from his roster

=back

=cut

if ($type eq 'unsubscribe' or $type eq 'unsubscribed')
        {
          	querybot_log("notice","Presence($barejid): Got `unsubscribe` -- do the same");
                sendPresence($presence,$from,$to,'unsubscribe');
		#
		# Remove jid from querybot roster
		#
		my $IqRemoveJid 	= new Net::XMPP::IQ();
		$IqRemoveJid->SetTo($barejid);
		$IqRemoveJid->SetType("set");
		my $IqRemoveQuery 	= $IqRemoveJid->NewChild("jabber:iq:roster");
		$IqRemoveQuery		= $IqRemoveQuery->AddItem(	subscription=>"remove",
		 							jid=>$barejid);
		$Querymodule::Con->Send($IqRemoveJid);
          	querybot_log("info","Presence($barejid): Removed `$barejid` from roster");
		#
		#
		# Send unsubscribed to jid
		#
                sendPresence($presence,$from,$to,'unsubscribed');
        }
}

######################################################################
sub sendPresence {
######################################################################
        my ($pres, $to, $from, $type, $show, $status) = @_;

=head2 sendPresence()

Send presence information to user

=cut

        $pres->SetType($type);
        $pres->SetShow($show);
        $pres->SetStatus($status);
        $pres->SetTo($to);
        $pres->SetFrom($from);
        $Querymodule::Con->Send($pres);

}


sub systemcommands {

my $from	= shift;
my $barejid	= get_barejid($from);
my $message 	= shift;
my $type	= shift;
my $thread	= shift;

=head2 systemcommands()

=over 2

=item * If user type '!help` send a help instruction to the user

=back

=cut

if (($message eq '!help') or ($message eq 'help'))
{
  my $help_msg = $bot_description;
  $help_msg .= "\n
Bot admin: 
!help
!status
!query off
!query on
!penalty workload 5
!shutdown

Bot support contact xmpp: $bot_admin";
  querybot_log("info","InMessage($barejid): Help description --  delivered ");
  return ($help_msg);
} ### if (($ret_msg eq '!help') or ($ret_msg eq 'help'))

=head2

=over 2

=item * Send statistic information to the user if he types '!status`

=back

=cut

if ($message eq '!status')
{
  calculate_stats();
  my $ret_bot_status ="$ident
---
Uptime: $uptime_hours h 
Status: `$bot_status`
Queries: $msg_per/h 
";
if ($barejid eq $bot_admin)
{
  $ret_bot_status.="
Query status: $querystatus
queries total: $querycount 
Idle timer: $timer_idle secs 
Reconnect timer: $timer_reconnect secs 
Presence timer: $timer_presence secs

Total message stanzas: $stanza_counter_message stanzas
Total presence stanzas: $stanza_counter_presence stanzas
Total iq stanzas: $stanza_counter_iq stanzas
Total stanzas: $stanza_counter_total stanzas
Total message stanzas/h	: $stanza_counter_message_q stanzas/h
Total presence stanzas/h: $stanza_counter_presence_q stanzas/h
Total iq stanzas/h: $stanza_counter_iq_q stanzas/h
Total stanzas/h: $stanza_counter_total_q stanzas/h

Penalty status: `$penalty_status`
Penalty timer: $timer_penalty waits
Stanza penalty calc : $stanza_penalty_calc / $stanza_penalty_calc_default
Counter penalties: $penalty_counter_penalties
Counter penalty normal: $penalty_counter_normal
";
} ### END of if ($barejid eq $bot_admin)
return ($ret_bot_status);
}

=head2

=over 2

=item * Is it a bot command?

=item * Is the bot command from the bot admin? If not, send "not allowed"

=item * '!shutdown` will shutdown your bot via jabber invoke.

=item * '!query off` turn off queries

=item * '!query on` turn on queries

=back

=cut

if($message =~ /!/)
 {
 unless ($bot_admin eq $barejid)
  {
   return ("$message -- not allowed command");
  }
 }

if ($message eq '!shutdown')
	{
	disconnect_server();
	exit(0);
	}

if ($message eq '!query off')
        {
	$querystatus = 0;
	#jabber_set_presence("dnd","$bot_msg_maintenance");
	return ("$ident -- Queries blocked");
        }

if ($message eq '!query on')
        {
        $querystatus = 1;
	#jabber_set_presence(undef,"Back at work...");
        return ("$ident -- Queries enabled");
        }

if ($message =~ /!penalty workload/)
        {
	my @parameter         = split(/\s/,$message);
	$stanza_penalty_calc_default = $parameter[2];
	return ("$ident -- penalty default set to: $stanza_penalty_calc_default");
        }
 return $message;
} ### systemcommands()

sub get_barejid
 {

   my $user = shift;

=head2 get_barejid()

Remove resource id from a jabber id.

=cut

   my ($barejid)                 = split (/\//, $user);

   return $barejid;

 } # END of get_barejid

sub calculate_stats
 {

=head2 calcualte_stats()

Calculate message statistics

=cut

  $msg_per 		= $querycount / ($uptime/3600);

  $stanza_counter_total 	= $stanza_counter_message + 
	     				$stanza_counter_iq +
					$stanza_counter_presence;

  $stanza_counter_total_q 	= $stanza_counter_message_q + 
	     				$stanza_counter_iq_q +
					$stanza_counter_presence_q;

  $stanza_counter_total_q	= $stanza_counter_total/($uptime/3600);
  $stanza_counter_message_q	= $stanza_counter_message/($uptime/3600);
  $stanza_counter_presence_q	= $stanza_counter_presence/($uptime/3600);
  $stanza_counter_iq_q		= $stanza_counter_iq/($uptime/3600);

  $msg_per		= sprintf("%.3f",$msg_per);

  $stanza_counter_message_q	
	     = sprintf("%.3f",$stanza_counter_message_q);

  $stanza_counter_presence_q	
	     = sprintf("%.3f",$stanza_counter_presence_q);

  $stanza_counter_iq_q	
	     = sprintf("%.3f",$stanza_counter_iq_q);

  $stanza_counter_total_q
	     = sprintf("%.3f",$stanza_counter_total_q);

  $uptime_hours 	= $uptime / 3600;
  $uptime_hours 	= sprintf("%.3f",$uptime_hours);

  $stanza_penalty_calc = 	$stanza_counter_total - 
    				$stanza_counter_total_penalty;

  if((	$stanza_penalty_calc >= $stanza_penalty_calc_default) 
  	and $penalty_status eq "normal")
     {
      $penalty_counter_penalties++;
      $penalty_status="penalty";  
      querybot_log("info","calculate_stats(): Sorry, $ident has heavy workload, please try again later...");
     } ### if($stanza_penalty_calc >= $stanza_penalty_calc_default)

  if((	$stanza_penalty_calc < $stanza_penalty_calc_default) 
  	and $penalty_status eq "penalty"
	and $timer_penalty >=15)
     {
      $penalty_counter_normal++;
      $penalty_status="normal";
      querybot_log("info","calculate_stats(): Workload is back to normal...");
     } ### if((  $stanza_penalty_calc < $stanza_penalty_calc_default) ...
  
  return 0;
 } ### calculate_stats

sub jabber_set_presence
 {
  my $v_bot_status	= shift;
  my $status		= shift;
  my $force		= shift;

  unless($force eq "force")
   { $force = "none"; }

=head2 jabber_set_presence()

Set new presence if we have another presence status or we have set the force 
flag (for transport presence).

=cut

  if(($v_bot_status ne $bot_status) or $force eq "force")
   {
    $bot_status	= $v_bot_status;
    if($v_bot_status)
     {
      $Querymodule::Con->PresenceSend(	show	=> $v_bot_status,
					status	=> $status);
     } ### if($v_bot_status)
    else
     {
      $Querymodule::Con->PresenceSend(status	=> $status);
     } ### if($v_bot_status)
   querybot_log("info","jabber_set_presence($v_bot_status,$force): $status");
   return 0;
  } ### if($v_bot_status eq $bot_status)
  return 1;
 }

sub jabber_send_message
 {
  my $from	= shift;
  my $type	= shift;
  my $thread	= shift; 
  my $body	= shift;

=head2 jabber_send_message()

This function send all jabber messages which are outgoing from the 
jabber-querybot.

=cut

  $Querymodule::Con->MessageSend(	to	=>$from,
  					type 	=>$type,
                      			body	=>$body,
					thread	=>$thread,
                      			priority=>10);
  return 0;
 }

sub set_wakeup_mode
 {

=head2 set_wakeup_mode()

Set jabber presence via jabber_set_presence()

=cut

  querybot_log("info","set_wakeup_mode(): Send presence $bot_status");

  jabber_set_presence("xa","Morning baby, just a minute, then I will work for you... ;)");

  querybot_log("info","set_wakeup_mode(): Get roster");

  $Querymodule::Con->RosterGet();
  querybot_log("info","set_wakeup_mode(): Wait and see a moment!");


 } ### set_wakeup_mode

sub penalty_handler
 {
  my $user	= shift;

=head2 penalty_handler()

This function checks if the bot is overloaded with incoming mesages and 
reject if it is. Two times that's ok so 2:1 because while in main.

=cut

  if($querystatus == 1 and $penalty_status eq "penalty")
   {

=head2

Bot admin got everytime an answer.

=cut

    if($user eq $bot_admin)
     { return 0; }
    else
     { 
      querybot_log("info","penalty_handler($user): Query not proceeded ($stanza_penalty_calc >= " .
		"$stanza_penalty_calc_default)");
      return 1; 
     } ### if($user eq $bot_admin)
   return 0;
  } ### if($querystatus == 1 and $penalty_status eq "penalty")
 } ### penalty_handler

sub jabber_add_footer
 {
  my $msg 	= shift;
  my $elapsed 	= shift;

=head2 jabber_add_footer()

Add footer to the processed message.

=cut

$msg .= "
---
$ident ($release) proceeded in $elapsed sec";
  return $msg;
 } ### jabber_add_footer

sub calculate_elapsed_time
 {
  my $t0	= shift;
  my $t1 	= gettimeofday;

=head2 calcualte_elapsed_time()

Calculate elapsed worktime for a query

=cut

  my $elapsed = $t1 - $t0;
  $elapsed = sprintf("%.3f",$elapsed);
  return $elapsed;
 } ### calculate_elapsed_time

sub sendError 
 {
  
  my ($msg, $to, $from, $code, $text) = @_;

=head2 sendError()

sendError($message, $from, $to, 404, "text");

=cut


  $msg->SetType('error');
  $msg->SetFrom($from);
  $msg->SetTo($to);
  $msg->SetErrorCode($code);
  $msg->SetError($text);
  $Querymodule::Con->Send($msg);
 } ### sendError

sub system_load_check
# check the system load, if it is to high, shutdown connection and wait.
 {
  my $ret 	= 2;
  my $load_ok	= 5;
  my $load_high	= 6;

=head2 system_load_check()

=over 2

=item * Calculate system load

=item * If load is too high shutdown bot.

=item * If load is ok, starting up bot

=back

=cut

  # Calculate system load
  @system_load  = split (/,/,Sys::CpuLoad::load());
  my $load_15min = $system_load[0];

  if ($load_15min >= $load_high)
    {
    # Load is too high
    $ret = 1;
    $system_load_status = 1;
    }
  if ($load_15min <= $load_ok  and $system_load_status == 1 or
  	$system_load_status == 0)
    {
    # Load is ok for starting up bot
    $ret = 0;
    $system_load_status = 0;
    }

  querybot_log("debug","system_load_check(): Load 15min: $load_15min return $ret");
  return $ret;
 } ### END of system_load_check()

sub connect_bot
{

=head2 connect_bot()

Connect bot and initialize all timers.

=cut

   querybot_log("info","connect_bot();");
   connect_server();
   set_wakeup_mode();
   $timer_reconnect 	= 0;
   $timer_presence  	= -10;
   $timer_idle		= 0;
}


sub check_before_start 
# Check necessary settings before starting up
{


=head2 check_before_start()

Check configuration variables in Querybotmodule.pm and give answer if anything
does not match or is missing.

=cut

unless ($service_name and 
	$ident and
	$hostname and
	$port and
	$user and
	$password) 
{ print "Please check settings (service_name, ident, hostname, port, user
or password at /etc/jabber-querybot/Querymodule.pm\n"; 
  exit(1); }
}

=head1 COPYRIGHT AND LICENSE

Copyright (C) 2009-2012 Marco Balmer <marco@balmer.name>

The Debian packaging is licensed under the 
GPL, see `/usr/share/common-licenses/GPL-3'.

=cut
