<?php
/*
* This is a more complex and complete example.
*
* This bot is an announcement bot where users may subscribe
* to an AIM-hosted message list. IM it to use it once it
* is running. The list of subscribers will be written to disk,
* as well as the archive of messages. Users that are offline
* will still receive the message (up to three are stored).
* This is mostly proof of concept (I think) due to the rate
* limiting issues with AIM.
*
* There is currently no way to administrate the list of
* subscribers or the archive.
*
* This is not too well documented yet.
*
* To get it working:
* (1) Configure username and password
* (2) Non-Windows: Chmod the current directory writable
* (3) Start up script
* (4) Talk to bot
*
* Only one instance of this application can be running.
* It has not been written to be safe to run with
* multiple threads.
*/
// We must include the BlueToc libraries
require_once "bluetoc/EventHandlers/ObjectBased.php";
require_once "bluetoc/TocProtocol.php";
require_once "bluetoc/AimClient.php";
// We define our own class, extending AimClient
class AnnouncementsBot extends AimClient
{
var $name = '';
var $subscribers_file = '';
var $messages_file = '';
var $backlog_file = '';
var $max_subscribers = 20;
var $max_message_length = 60;
var $archive_size = 5;
var $backlog_user_size = 3;
var $backlog_cutoff_time = 1209600; // 14 days = 2 weeks
var $admins = array();
var $subscribers = array();
var $messages = array();
var $backlog = array();
var $online = array();
function AnnouncementsBot($user, $pass, $name, $subscribers_file, $messages_file, $backlog_file, $admins)
{
// Debug mode is by default off
$this->debug_mode = false;
$this->aim_user = $user;
$this->aim_pass = $pass;
$this->name = $name;
$this->subscribers_file = $subscribers_file;
$this->messages_file = $messages_file;
$this->backlog_file = $backlog_file;
foreach($admins as $user)
{
$this->admins[] = $this->normalize_string($user);
}
$this->subscribers = $this->read_archive($this->subscribers_file);
$this->messages = $this->read_archive($this->messages_file);
$this->backlog = $this->read_archive($this->backlog_file);
// Backlog cleanup
foreach($this->backlog as $user => $bl)
{
foreach((array) $this->backlog[$user] as $k => $msg)
{
if($msg[0] < time() - $this->backlog_cutoff_time)
{
unset($this->backlog[$user][$k]);
}
}
}
foreach($this->backlog as $user => $bl)
{
if(!$this->backlog[$user]) unset($this->backlog[$user]);
}
$this->write_archive($this->backlog_file, $this->backlog);
echo "* The IM list '{$this->name}' is signing on...\n";
$this->connect();
}
function read_archive($file)
{
return (array) @unserialize(@file_get_contents($file));
}
function write_archive($file, $data)
{
$data = serialize($data);
$fp = fopen($file, "w");
flock($fp, LOCK_EX);
fwrite($fp, $data);
flock($fp, LOCK_UN);
fclose($fp);
}
function build_reply($message)
{
return <<<EOB
<font face=Arial><b><u>{$this->name} IM List</u></b></font><br>
$message
EOB;
}
// Handle once we've signed on
function event_sign_on($args)
{
// Let's re-add all the subscribers anyway
foreach($this->subscribers as $user)
{
$list .= "\nb:$user";
}
if($list)
{
$this->add_buddies("g:Subscribers$list");
}
echo "* The IM list '{$this->name}' has signed online as {$this->aim_user}\n";
echo "* Buddies:\n{$args['config']}\n";
}
// Handle when we get an instant message
function event_im($args)
{
$user = $this->normalize_string($args['user']);
// Remember that AIM IMs usually have HTML
// so we must strip it so that we can
// easily parse it
$message = strip_tags($args['message']);
echo "* Received an instant message from: {$args['user']} -> $message\n";
// Subscription command
if(strtolower($message) == "subscribe")
{
// Check whether the user is already subscribed or not
if(in_array($user, $this->subscribers))
{
$reply = "ERROR! You are already subscribed to this list.";
$this->send_im($args['user'], $this->build_reply($reply), false);
}
// Limit the number of subscribers allowed
else if(count($this->subscribers) >= $this->max_subscribers)
{
$reply = "ERROR! There are already too many people subscribed to this list (max: {$this->max_subscribers})";
$this->send_im($args['user'], $this->build_reply($reply), false);
}
// Subscribe
else
{
// We need to keep track of who is online to track them
$this->add_buddies("g:Subscribers\nb:$user");
$this->subscribers[] = $user;
$this->write_archive($this->subscribers_file, $this->subscribers);
$reply = "SUCCESS! You have been subscribed. Tell me <b>unsubscribe</b> to leave this list at any time.";
$this->send_im($args['user'], $this->build_reply($reply), false);
}
}
// Unsubscribe command
else if(strtolower($message) == "unsubscribe")
{
// Check whether the user is subscribed or not
if(!in_array($user, $this->subscribers))
{
$reply = "ERROR! You <i>aren't</i> subscribed.";
$this->send_im($args['user'], $this->build_reply($reply), false);
}
else
{
// Remove from tracking
$this->remove_buddy($user, "Subscribers");
$this->subscribers = array_diff($this->subscribers, array($user));
$this->write_archive($this->subscribers_file, $this->subscribers);
unset($this->online[$user]);
$reply = "SUCCESS! We are sorry for you to leave, but you have been removed.";
$this->send_im($args['user'], $this->build_reply($reply), false);
}
}
// Message archival
else if(preg_match("#^archive( (.*))?$#is", $message, $m))
{
if(!$m[2])
{
print_r($this->messages);
$messages = array();
foreach($this->messages as $msg)
{
// Command is archive DATETIMESTAMP
$messages[] = "{$msg[0]}:<br><strong>archive {$msg[2]}</strong>";
}
$reply = "Please type the bolded command to read:<br><br>" . implode("<br><br>", $messages);
$this->send_im($args['user'], $this->build_reply($reply), false);
}
else
{
// The date timestamp is stored in array key 2
// We need to look for that
$i = -1;
foreach($this->messages as $k => $msg)
{
if($msg[2] == $m[2])
{
$i = $k;
break;
}
}
if($this->messages[$i])
{
$msg = $this->messages[$i];
$reply = "Date: {$msg[0]}<br>{$msg[1]}";
$this->send_im($args['user'], $this->build_reply($reply), false);
}
else
{
$reply = "ERROR! The requested message does not eixst.";
$this->send_im($args['user'], $this->build_reply($reply), false);
}
}
}
// Posting command
else if(preg_match("#^post( (.*))?$#is", $message, $m))
{
if(!in_array($user, $this->admins))
{
$reply = "ERROR! You <i>aren't</i> an administrator. You cannot post to this list.";
$this->send_im($args['user'], $this->build_reply($reply), false);
}
else if(strlen($m[2]) > $this->max_message_length)
{
$reply = "ERROR! Your message is too long.";
$this->send_im($args['user'], $this->build_reply($reply), false);
}
else if(!$m[2])
{
$reply = "ERROR! You must enter a message.";
$this->send_im($args['user'], $this->build_reply($reply), false);
}
else
{
$time = time();
$this->messages[] = array(date("r", $time), "From: {$user}<br>{$m[2]}", $time);
while(count($this->messages) > $this->archive_size) array_shift($this->messages);
$this->write_archive($this->messages_file, $this->messages);
$reply = "SUCCESS! The message has been posted!";
$this->send_im($args['user'], $this->build_reply($reply), false);
// This loops through all the users...
// And locks up everything =/
$m = $this->build_reply("From: {$user}<br>{$m[2]}");
foreach($this->subscribers as $sub)
{
if($this->online[$sub])
{
$this->send_im($sub, $m, true);
usleep(1000000 / 2);
}
// Send when they're online!
else
{
$this->backlog[$sub][] = array($time, "Date: " . date("r", $time) . "<br>From: {$user}<br>{$m[2]}");
while(count($this->backlog[$sub]) > $this->backlog_user_size) array_shift($this->backlog[$sub]);
}
}
$this->write_archive($this->backlog_file, $this->backlog);
}
}
// About command
else if(strtolower($message) == "about")
{
$reply = "This announcement IM list is derived from the <i>ex_announcements.php</i> example from the <a href=\"http://www.therisenrealm.com/scripts/bluetoc/\">BlueTOC</a> PHP AIM connection class";
$this->send_im($args['user'], $this->build_reply($reply), false);
}
else
{
$reply = "Welcome to this list. Repeat back the follow commands to interact with me:<br>" .
"<b>subscribe</b> - Subscribe to this IM list<br>" .
"<b>unsubscribe</b> - Unsubscribe to this IM list<br>" .
"<b>archive</b> - View this list's archive<br>" .
"<b>post</b> - Post to this list (admins only)<br>" .
"<b>about</b> - About this list<br>";
$this->send_im($args['user'], $this->build_reply($reply), false);
}
}
function event_buddy_update($args)
{
$user = $this->normalize_string($args['user']);
echo "Online: {$user}: " . ($args['is_online'] ? "Online" : "Offline") . "\n";
if($args['is_online'])
{
$this->online[$user] = 1;
// Sends back log
if($this->backlog[$user])
{
// This loops through all the messages...
// And locks up everything =/
foreach($this->backlog[$user] as $msg)
{
if($msg[0] < time() - $this->backlog_cutoff_time) continue;
$this->send_im($user, $this->build_reply($msg[1]), true);
usleep(1000000 / 2);
}
unset($this->backlog[$user]);
$this->write_archive($this->backlog_file, $this->backlog);
}
}
else
{
unset($this->online[$user]);
}
}
function event_error($args)
{
// These are a list of errors in English
// Most, if not all, errors will return an error number
// and not the error description
$connection_errors = array(
100 => 'Data unable to be sent',
200 => 'Flapon',
201 => 'Data not received from server after FLAPON packet',
202 => 'Invalid FLAP SIGNON response from the server',
203 => 'Invalid response from the server' );
$aim_errors = array(
0 => 'Success',
1 => 'AOLIM Error: Unknown Error',
2 => 'AOLIM Error: Incorrect Arguments',
3 => 'AOLIM Error: Exceeded Max Packet Length (1024)',
4 => 'AOLIM Error: Reading from server',
5 => 'AOLIM Error: Sending to server',
6 => 'AOLIM Error: Login timeout',
901 => 'General Error: %s not currently available',
902 => 'General Error: Warning of %s not currently available',
903 => 'General Error: A message has been dropped, you are exceeding the
server speed limit',
950 => 'Chat Error: Chat in %s is unavailable',
960 => 'IM and Info Error: You are sending messages too fast to %s',
961 => 'IM and Info Error: You missed an IM from %s because it was too big',
962 => 'IM and Info Error: You missed an IM from %s because it was sent
too fast',
970 => 'Dir Error: Failure',
971 => 'Dir Error: Too many matches',
972 => 'Dir Error: Need more qualifiers',
973 => 'Dir Error: Dir service temporarily unavailble',
974 => 'Dir Error: Email lookup restricted',
975 => 'Dir Error: Keyword ignored',
976 => 'Dir Error: No keywords',
977 => 'Dir Error: Language not supported',
978 => 'Dir Error: Country not supported',
979 => 'Dir Error: Failure unknown %s',
980 => 'Auth Error: Incorrect nickname or password',
981 => 'Auth Error: The service is temporarily unavailable',
982 => 'Auth Error: Your warning level is too high to sign on',
983 => 'Auth Error: You have been connecting and disconnecting too frequently.
Wait 10 minutes and try again. If you continue to try, you will need to
wait even longer.',
989 => 'Auth Error: An unknown signon error has occurred %s' );
// Let's see what kind of error we are faced with
switch($args['type'])
{
// Connection error
case ERROR_CONNECTION:
echo "* Connection error: {$connection_errors[$args['number']]} ({$args['number']})\n";
break;
// AIM is giving us an error
case ERROR_AIM:
echo "* AIM error: {$aim_errors[$args['number']]} ({$args['number']})\n";
break;
}
}
}
// Create a new instance of the bot
$client = new AnnouncementsBot('username1', 'password1',
$name = "Latest Updates",
$subscribers_file = "ex_announce_subs.txt",
$messages_file = "ex_announce_msgs.txt",
$backlog_file = "ex_announce_backlog.txt",
$admins = array("username2", "username3", "username4"));
// Listen to the bot infinitely
while(true)
{
$client->listen();
usleep(150);
}
?>
|