[ Index ]

PHP Cross Reference of Eventum

title

Body

[close]

/misc/irc/ -> bot.php (source)

   1  <?php
   2  /* vim: set expandtab tabstop=4 shiftwidth=4 encoding=utf-8: */
   3  // +----------------------------------------------------------------------+
   4  // | Eventum - Issue Tracking System                                      |
   5  // +----------------------------------------------------------------------+
   6  // | Copyright (c) 2003, 2004, 2005, 2006, 2007 MySQL AB                              |
   7  // |                                                                      |
   8  // | This program is free software; you can redistribute it and/or modify |
   9  // | it under the terms of the GNU General Public License as published by |
  10  // | the Free Software Foundation; either version 2 of the License, or    |
  11  // | (at your option) any later version.                                  |
  12  // |                                                                      |
  13  // | This program is distributed in the hope that it will be useful,      |
  14  // | but WITHOUT ANY WARRANTY; without even the implied warranty of       |
  15  // | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        |
  16  // | GNU General Public License for more details.                         |
  17  // |                                                                      |
  18  // | You should have received a copy of the GNU General Public License    |
  19  // | along with this program; if not, write to:                           |
  20  // |                                                                      |
  21  // | Free Software Foundation, Inc.                                       |
  22  // | 59 Temple Place - Suite 330                                          |
  23  // | Boston, MA 02111-1307, USA.                                          |
  24  // +----------------------------------------------------------------------+
  25  // | Authors: João Prado Maia <jpm@mysql.com>                             |
  26  // +----------------------------------------------------------------------+
  27  //
  28  // @(#) $Id: bot.php 3266 2007-03-06 20:18:51Z glen $
  29  //
  30  
  31  ini_set('memory_limit', '256M');
  32  
  33  require_once(dirname(__FILE__) . '/../../init.php');
  34  
  35  if (!file_exists(APP_CONFIG_PATH . 'irc_config.php')) {
  36      echo "ERROR: No config specified. Please see setup/irc_config.php for config information.\n\n";
  37      exit;
  38  }
  39  
  40  require_once(APP_CONFIG_PATH . 'irc_config.php');
  41  require_once (APP_INC_PATH . 'db_access.php');
  42  require_once (APP_INC_PATH . 'class.auth.php');
  43  require_once (APP_INC_PATH . 'class.lock.php');
  44  require_once (APP_INC_PATH . 'class.issue.php');
  45  require_once (APP_INC_PATH . 'class.user.php');
  46  require_once(APP_PEAR_PATH . 'Net/SmartIRC.php');
  47  
  48  // if requested, clear the lock
  49  if (in_array('--fix-lock', $argv)) {
  50      Lock::release('irc_bot');
  51      echo "The lock file was removed successfully.\n";
  52      exit;
  53  }
  54  
  55  // acquire a lock to prevent multiple scripts from
  56  // running at the same time
  57  if (!Lock::acquire('irc_bot')) {
  58      echo 'Error: Another instance of the script is still running. ',
  59                  "If this is not accurate, you may fix it by running this script with '--fix-lock' ",
  60                  "as the only parameter.\n";
  61      exit;
  62  }
  63  
  64  $auth = array();
  65  
  66  // map project_id => channel(s)
  67  $channels = array();
  68  foreach ($irc_channels as $proj => $chan) {
  69      $proj_id = Project::getID($proj);
  70      $channels[$proj_id] = is_array($chan) ? $chan : array($chan);
  71  }
  72  
  73  class Eventum_Bot
  74  {
  75      function _isAuthenticated(&$irc, &$data)
  76      {
  77          global $auth;
  78  
  79          if (in_array($data->nick, array_keys($auth))) {
  80              return true;
  81          } else {
  82              $this->sendResponse($irc, $data->nick, 'Error: You need to be authenticated to run this command.');
  83              return false;
  84          }
  85      }
  86  
  87  
  88      function _getEmailByNickname($nickname)
  89      {
  90          global $auth;
  91  
  92          if (in_array($nickname, array_keys($auth))) {
  93              return $auth[$nickname];
  94          } else {
  95              return '';
  96          }
  97      }
  98  
  99  
 100      function clockUser(&$irc, &$data)
 101      {
 102          if (!$this->_isAuthenticated($irc, $data)) {
 103              return;
 104          }
 105          $email = $this->_getEmailByNickname($data->nick);
 106  
 107          $pieces = explode(' ', $data->message);
 108          if ((count($pieces) == 2) && ($pieces[1] != 'in') && ($pieces[1] != 'out')) {
 109              $this->sendResponse($irc, $data->nick, 'Error: wrong parameter count for "CLOCK" command. Format is "!clock [in|out]".');
 110              return;
 111          }
 112          if (@$pieces[1] == 'in') {
 113              $res = User::clockIn(User::getUserIDByEmail($email));
 114          } elseif (@$pieces[1] == 'out') {
 115              $res = User::clockOut(User::getUserIDByEmail($email));
 116          } else {
 117              if (User::isClockedIn(User::getUserIDByEmail($email))) {
 118                  $msg = "clocked in";
 119              } else {
 120                  $msg = "clocked out";
 121              }
 122              $this->sendResponse($irc, $data->nick, "You are currently $msg.");
 123              return;
 124          }
 125          if ($res == 1) {
 126              $this->sendResponse($irc, $data->nick, 'Thank you, you are now clocked ' . $pieces[1] . '.');
 127          } else {
 128              $this->sendResponse($irc, $data->nick, 'Error clocking ' . $pieces[1] . '.');
 129          }
 130      }
 131  
 132  
 133      function listClockedInUsers(&$irc, &$data)
 134      {
 135          if (!$this->_isAuthenticated($irc, $data)) {
 136              return;
 137          }
 138  
 139          $list = User::getClockedInList();
 140          if (count($list) == 0) {
 141              $this->sendResponse($irc, $data->nick, 'There are no clocked-in users as of now.');
 142          } else {
 143              $this->sendResponse($irc, $data->nick, 'The following is the list of clocked-in users:');
 144              foreach ($list as $name => $email) {
 145                  $this->sendResponse($irc, $data->nick, "$name: $email");
 146              }
 147          }
 148      }
 149  
 150  
 151      function listQuarantinedIssues(&$irc, &$data)
 152      {
 153          if (!$this->_isAuthenticated($irc, $data)) {
 154              return;
 155          }
 156  
 157          $list = Issue::getQuarantinedIssueList();
 158          if (count($list) == 0) {
 159              $this->sendResponse($irc, $data->nick, 'There are no quarantined issues as of now.');
 160          } else {
 161              $this->sendResponse($irc, $data->nick, 'The following are the details of the ' . count($list) . ' quarantined issue(s):');
 162              for ($i = 0; $i < count($list); $i++) {
 163                  $url = APP_BASE_URL . 'view.php?id=' . $list[$i]['iss_id'];
 164                  $msg = sprintf('Issue #%d: %s, Assignment: %s, %s', $list[$i]['iss_id'], $list[$i]['iss_summary'], $list[$i]['assigned_users'], $url);
 165                  $this->sendResponse($irc, $data->nick, $msg);
 166              }
 167          }
 168      }
 169  
 170  
 171      function listAvailableCommands(&$irc, &$data)
 172      {
 173          $commands = array(
 174              'auth'             => 'Format is "auth user@example.com password"',
 175              'clock'            => 'Format is "clock [in|out]"',
 176              'list-clocked-in'  => 'Format is "list-clocked-in"',
 177              'list-quarantined' => 'Format is "list-quarantined"'
 178          );
 179          $this->sendResponse($irc, $data->nick, "This is the list of available commands:");
 180          foreach ($commands as $command => $description) {
 181              $this->sendResponse($irc, $data->nick, "$command: $description");
 182          }
 183      }
 184  
 185  
 186      function _updateAuthenticatedUser(&$irc, &$data)
 187      {
 188          global $auth;
 189  
 190          $old_nick = $data->nick;
 191          $new_nick = $data->message;
 192          if (in_array($data->nick, array_keys($auth))) {
 193              $auth[$new_nick] = $auth[$old_nick];
 194              unset($auth[$old_nick]);
 195          }
 196      }
 197  
 198  
 199      function _removeAuthenticatedUser(&$irc, &$data)
 200      {
 201          global $auth;
 202  
 203          if (in_array($data->nick, array_keys($auth))) {
 204              unset($auth[$data->nick]);
 205          }
 206      }
 207  
 208  
 209      function listAuthenticatedUsers(&$irc, &$data)
 210      {
 211          global $auth;
 212  
 213          foreach ($auth as $nickname => $email) {
 214              $this->sendResponse($irc, $data->nick, "$nickname => $email");
 215          }
 216      }
 217  
 218  
 219      function authenticate(&$irc, &$data)
 220      {
 221          global $auth;
 222  
 223          $pieces = explode(' ', $data->message);
 224          if (count($pieces) != 3) {
 225              $this->sendResponse($irc, $data->nick, 'Error: wrong parameter count for "AUTH" command. Format is "!auth user@example.com password".');
 226              return;
 227          }
 228          $email = $pieces[1];
 229          $password = $pieces[2];
 230          // check if the email exists
 231          if (!Auth::userExists($email)) {
 232              $this->sendResponse($irc, $data->nick, 'Error: could not find a user account for the given email address "$email".');
 233              return;
 234          }
 235          // check if the given password is correct
 236          if (!Auth::isCorrectPassword($email, $password)) {
 237              $this->sendResponse($irc, $data->nick, 'Error: The email address / password combination could not be found in the system.');
 238              return;
 239          }
 240          // check if the user account is activated
 241          if (!Auth::isActiveUser($email)) {
 242              $this->sendResponse($irc, $data->nick, 'Error: Your user status is currently set as inactive. Please contact your local system administrator for further information.');
 243              return;
 244          } else {
 245              $auth[$data->nick] = $email;
 246              $this->sendResponse($irc, $data->nick, 'Thank you, you have been successfully authenticated.');
 247              return;
 248          }
 249      }
 250  
 251  
 252      /**
 253       * Helper method to get the list of channels that should be used in the
 254       * notifications
 255       *
 256       * @access  private
 257       * @param   integer $prj_id The project ID
 258       * @return  array The list of channels
 259       */
 260      function _getChannels($prj_id)
 261      {
 262          global $channels;
 263          return $channels[$prj_id];
 264      }
 265  
 266  
 267      /**
 268       * Helper method to the projects a channel displays messages for.
 269       *
 270       * @access  private
 271       * @param   string $channel The name of the channel
 272       * @return  array The projects displayed in the channel
 273       */
 274      function _getProjectsForChannel($channel)
 275      {
 276          global $channels;
 277          $projects = array();
 278          foreach ($channels as $prj_id => $prj_channels) {
 279              foreach ($prj_channels as $prj_channel) {
 280                  if ($prj_channel == $channel) {
 281                      $projects[] = $prj_id;
 282                  }
 283              }
 284          }
 285          return $projects;
 286      }
 287  
 288  
 289      /**
 290       * Method used as a callback to send notification events to the proper
 291       * recipients.
 292       *
 293       * @access  public
 294       * @param   resource $irc The IRC connection handle
 295       * @return  void
 296       */
 297      function notifyEvents(&$irc)
 298      {
 299          // check the message table
 300          $stmt = "SELECT
 301                      ino_id,
 302                      ino_iss_id,
 303                      ino_prj_id,
 304                      ino_message
 305                   FROM
 306                      " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "irc_notice
 307                   LEFT JOIN
 308                      " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "issue
 309                   ON
 310                      iss_id=ino_iss_id
 311                   WHERE
 312                      ino_status='pending'";
 313          $res = $GLOBALS["db_api"]->dbh->getAll($stmt, DB_FETCHMODE_ASSOC);
 314          for ($i = 0; $i < count($res); $i++) {
 315              $channels = $this->_getChannels($res[$i]['ino_prj_id']);
 316              if (count($channels) > 0) {
 317                  foreach ($channels as $channel) {
 318                      if ($res[$i]['ino_iss_id'] > 0) {
 319                          $res[$i]['ino_message'] .= ' - ' . APP_BASE_URL . 'view.php?id=' . $res[$i]['ino_iss_id'];
 320                      } elseif (substr($res[$i]['ino_message'], 0, strlen('New Pending Email')) == 'New Pending Email') {
 321                          $res[$i]['ino_message'] .= ' - ' . APP_BASE_URL . 'emails.php';
 322                      }
 323                      if (count($this->_getProjectsForChannel($channel)) > 1) {
 324                          // if multiple projects display in the same channel, display project in message
 325                          $res[$i]['ino_message'] = "[" . Project::getName($res[$i]['ino_prj_id']) . "] " . $res[$i]['ino_message'];
 326                      }
 327                      $this->sendResponse($irc, $channel, $res[$i]['ino_message']);
 328                  }
 329                  // mark message as sent
 330                  $stmt = "UPDATE
 331                              " . APP_DEFAULT_DB . "." . APP_TABLE_PREFIX . "irc_notice
 332                           SET
 333                              ino_status='sent'
 334                           WHERE
 335                              ino_id=" . $res[$i]['ino_id'];
 336                  $GLOBALS["db_api"]->dbh->query($stmt);
 337              }
 338          }
 339      }
 340  
 341  
 342      /**
 343       * Method used to send a message to the given target.
 344       *
 345       * @access  public
 346       * @param   resource $irc The IRC connection handle
 347       * @param   string $target The target for this message
 348       * @param   string $response The message to send
 349       * @return  void
 350       */
 351      function sendResponse(&$irc, $target, $response)
 352      {
 353          // XXX: need way to handle messages with length bigger than 255 chars
 354          if (!is_array($response)) {
 355              $response = array($response);
 356          }
 357          foreach ($response as $line) {
 358              if (substr($target, 0, 1) != '#') {
 359                  $type = SMARTIRC_TYPE_QUERY;
 360              } else {
 361                  $type = SMARTIRC_TYPE_CHANNEL;
 362              }
 363              $irc->message($type, $target, $line, SMARTIRC_CRITICAL);
 364              sleep(1);
 365          }
 366      }
 367  
 368  
 369      function _joinChannels(&$irc)
 370      {
 371          foreach ($GLOBALS['channels'] as $prj_id => $channel_list) {
 372              $irc->join($channel_list);
 373          }
 374      }
 375  }
 376  
 377  $bot = &new Eventum_Bot();
 378  $irc = &new Net_SmartIRC();
 379  $irc->setDebug(SMARTIRC_DEBUG_ALL);
 380  $irc->setLogdestination(SMARTIRC_FILE);
 381  $irc->setLogfile(APP_IRC_LOG);
 382  $irc->setUseSockets(TRUE);
 383  $irc->setAutoReconnect(TRUE);
 384  $irc->setAutoRetry(TRUE);
 385  $irc->setReceiveTimeout(600);
 386  $irc->setTransmitTimeout(600);
 387  
 388  $irc->registerTimehandler(3000, $bot, 'notifyEvents');
 389  
 390  // methods that keep track of who is authenticated
 391  $irc->registerActionhandler(SMARTIRC_TYPE_QUERY, '^!?list-auth', $bot, 'listAuthenticatedUsers');
 392  $irc->registerActionhandler(SMARTIRC_TYPE_NICKCHANGE, '.*', $bot, '_updateAuthenticatedUser');
 393  $irc->registerActionhandler(SMARTIRC_TYPE_KICK|SMARTIRC_TYPE_QUIT|SMARTIRC_TYPE_PART, '.*', $bot, '_removeAuthenticatedUser');
 394  $irc->registerActionhandler(SMARTIRC_TYPE_LOGIN, '.*', $bot, '_joinChannels');
 395  
 396  // real bot commands
 397  $irc->registerActionhandler(SMARTIRC_TYPE_QUERY, '^!?help', $bot, 'listAvailableCommands');
 398  $irc->registerActionhandler(SMARTIRC_TYPE_QUERY, '^!?auth ', $bot, 'authenticate');
 399  $irc->registerActionhandler(SMARTIRC_TYPE_QUERY, '^!?clock', $bot, 'clockUser');
 400  $irc->registerActionhandler(SMARTIRC_TYPE_QUERY, '^!?list-clocked-in', $bot, 'listClockedInUsers');
 401  $irc->registerActionhandler(SMARTIRC_TYPE_QUERY, '^!?list-quarantined', $bot, 'listQuarantinedIssues');
 402  
 403  $irc->connect($irc_server_hostname, $irc_server_port);
 404  if (empty($username)) {
 405      $irc->login($nickname, $realname);
 406  } elseif (empty($password)) {
 407      $irc->login($nickname, $realname, 0, $username);
 408  } else {
 409      $irc->login($nickname, $realname, 0, $username, $password);
 410  }
 411  $irc->listen();
 412  $irc->disconnect();
 413  
 414  // release the lock
 415  Lock::release('irc_bot');


Generated: Wed Dec 19 21:21:33 2007 Cross-referenced by PHPXref 0.7