| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561 |
- <?php
- class imBot extends model
- {
- /**
- * Bot code list.
- *
- * @var array
- * @access public
- */
- public $bots = array();
- /**
- * Bot instance map.
- *
- * @var object
- * @access public
- */
- public $botInstances;
- /**
- * Inited bot list.
- *
- * @var array
- * @access public
- */
- public $initedBots = array();
- /**
- * Load and create bot instances.
- *
- * @param string $appName
- * @param object $im imModel instance
- * @access public
- * @return void
- */
- public function __construct($appName, $im)
- {
- parent::__construct($appName);
- /* Save imModel for further uses. */
- $this->im = $im;
- /* Load bot classes and init. */
- $modulePaths = $im->app->getModuleExtPath('im', 'bot');
- $modulePath = array_key_exists('xuan', $modulePaths) ? $modulePaths['xuan'] : current($modulePaths);
- helper::import($modulePath . 'xuanbot.php');
- $botFiles = helper::ls($modulePath, '*.bot.php');
- foreach($botFiles as $botFile) helper::import($botFile);
- $botClasses = array_filter(get_declared_classes(), function($className)
- {
- return is_subclass_of($className, xuanBot::className);
- });
- $this->botInstances = new stdclass();
- foreach($botClasses as $botClass)
- {
- $vars = get_class_vars($botClass);
- if(empty($vars['code']) || !preg_match('/^[a-zA-Z]\\w*$/', $vars['code'])) continue;
- $code = $vars['code'];
- $this->bots[] = $code;
- /* Init bots with im module. */
- $bot = new $botClass();
- $this->botInstances->$code = $bot;
- $this->botInstances->$code->im = $im;
- }
- }
- /**
- * Init a bot.
- *
- * @param string $botCode
- * @access public
- * @return bool
- */
- public function initBot($botCode)
- {
- if(!in_array($botCode, $this->bots)) return false;
- if(in_array($botCode, $this->initedBots)) return true;
- $this->botInstances->$botCode->init();
- $this->initedBots[] = $botCode;
- return true;
- }
- /**
- * Process message with bots.
- *
- * @param object $message
- * @param int $userID
- * @access public
- * @return array messages from bots
- */
- public function processMessage($message, $userID = 0)
- {
- $replies = array();
- /* Convert characters from "zen-kaku"(full-width) to "han-kaku"(half-width). */
- $message->content = mb_convert_kana($message->content, "rnas", 'UTF-8');
- /* Check if message is command. */
- $isCommand = $message->type == 'botcommand' || ($message->type == 'normal' && in_array($message->contentType, array('text', 'plain')) && strpos($message->content, '/') === 0);
- if($isCommand)
- {
- /* Call bot commands. */
- $commandLine = ltrim($message->content, '/');
- $commandLine = explode(' ', $commandLine);
- $command = array_shift($commandLine);
- $args = $commandLine;
- $result = $this->executeCommand($command, $args, $message, $userID);
- if(isset($result->executedBot))
- {
- $executedBot = $result->executedBot;
- $replies[] = $result->result;
- }
- else
- {
- $replies[] = $result;
- }
- }
- else
- {
- /* Call onMessage on bots. */
- foreach($this->bots as $bot)
- {
- if(!method_exists($this->botInstances->$bot, 'onMessage')) continue;
- $this->initBot($bot);
- $replies[] = $this->botInstances->$bot->onMessage($message->content, $userID);
- }
- }
- $messages = array();
- $responses = array();
- $replies = array_filter($replies, function($reply)
- {
- return !is_null($reply) && $reply !== false;
- });
- foreach($replies as $reply)
- {
- /* Use reply as is if is properly set. E.g., (object)array('messages' => array(), 'responses' => array()) */
- if(is_object($reply) && isset($reply->messages) && isset($reply->responses))
- {
- $messages = array_merge($messages, $reply->messages);
- $responses = array_merge($responses, $reply->responses);
- continue;
- }
- /* Non-array / non-object values are treated as message contents. */
- if(!is_array($reply))
- {
- $messages[] = $reply;
- continue;
- }
- /* Flatten and extract array of message contents or responses. */
- if(is_array($reply) && array_keys($reply) === range(0, count($reply) - 1))
- {
- foreach($reply as $item)
- {
- if(is_array($item) || (is_object($item) && isset($item->method) && isset($item->result)))
- {
- $responses[] = $item;
- }
- else
- {
- $messages[] = $item;
- }
- }
- continue;
- }
- /* Otherwise it's a response. */
- $responses[] = $reply;
- }
- $sender = new stdclass();
- $sender->id = 0;
- $sender->displayName = isset($executedBot->code) && $executedBot->code != 'default' ? $this->lang->im->bot->commonName . '@' . $executedBot->name : $this->lang->im->bot->commonName;
- $sender->avatar = commonModel::getSysURL() . $this->config->webRoot . 'data/image/xuanbot.png';
- $messageData = new stdclass();
- $messageData->sender = $sender;
- $messages = array_map(function($reply) use ($message, $messageData)
- {
- $botUserID = str_replace("$message->user&", '', $message->cgid);
- $replyMessage = new stdclass();
- $replyMessage->gid = imModel::createGID();
- $replyMessage->cgid = $message->cgid;
- $replyMessage->user = $botUserID;
- $replyMessage->content = is_object($reply) ? json_encode($reply) : $reply;
- $replyMessage->type = 'normal';
- $replyMessage->contentType = is_object($reply) ? 'object' : 'text';
- $replyMessage->data = json_encode($messageData);
- return $replyMessage;
- }, $messages);
- return (object)array('messages' => $messages, 'responses' => $responses);
- }
- /**
- * Execute command with bot.
- *
- * @param string $command
- * @param array $args
- * @param object $message
- * @param int $userID
- * @access public
- * @return object
- */
- public function executeCommand($command, $args, $message, $userID)
- {
- $user = $this->im->userGetByID($userID);
- $currentBot = $this->getUserCurrentBot($userID);
- /* Handle built-in command. */
- if(empty($args))
- {
- foreach($this->lang->im->bot->help->commands as $helpCommand)
- {
- if($helpCommand == mb_strtolower($command) || mb_strpos(mb_strtolower($command), "{$helpCommand}@") !== false)
- {
- if($currentBot != 'default' && !strpos($command, '@')) $command .= '@' . $currentBot;
- if($currentBot == 'default') return $this->genHelpContentReply();
- return $this->helpCommand($command, $user);
- }
- }
- if(in_array(mb_strtolower($command), $this->lang->im->bot->show->commands)) return $this->showCommand($command, $user);
- $isSwitchBot = false;
- $botCode = 'default';
- if(in_array(mb_strtolower($command), $this->lang->im->bot->exit->commands))
- {
- $isSwitchBot = true;
- }
- else
- {
- foreach($this->bots as $bot)
- {
- $botClass = $this->botInstances->$bot;
- if($botClass->code == $command || $botClass->name == $command || (isset($botClass->alias) && in_array($command, $botClass->alias)))
- {
- $isSwitchBot = true;
- $botCode = $botClass->code;
- break;
- }
- }
- }
- if($isSwitchBot)
- {
- $reply = new stdClass();
- $reply->messages = array();
- $reply->responses = array();
- $this->setUserCurrentBot($botCode, $userID);
- if($botCode == 'default' && $currentBot != 'default')
- {
- $reply->messages[] = sprintf($this->lang->im->bot->exit->tips, $this->botInstances->$currentBot->name);
- return $reply;
- }
- $reply->messages[] = sprintf($this->lang->im->bot->switch->tips, $this->botInstances->$botCode->name);
- if($botCode == 'default' && $currentBot == 'default') return $reply;
- $help = $this->helpCommand($botCode, $user);
- if(isset($help->result)) $reply->messages[] = $help->result;
- return $reply;
- }
- }
- $botsToCall = $this->getListByCommand($command, $currentBot != 'default' ? $currentBot : '');
- if(empty($botsToCall)) return $this->lang->im->bot->error->noSuchCommand;
- $bot = current($botsToCall);
- $this->initBot($bot->code);
- /* Check permission. */
- $commandToCheck = array();
- foreach($this->botInstances->{$bot->bot}->commands as $cmd)
- {
- if(is_array($cmd) && $bot->command === $cmd['command']) $commandToCheck = $cmd;
- if(is_string($cmd) && $bot->command === $cmd) $commandToCheck = $cmd;
- }
- if(empty($commandToCheck)) return $this->lang->im->bot->error->noSuchCommand;
- $executedBot = (object)array('name' => $bot->name, 'code' => $bot->code);
- if(isset($commandToCheck['adminOnly']) && $commandToCheck['adminOnly'] && $user->admin == 'no') return $this->lang->im->bot->error->unauthorized;
- if(isset($commandToCheck['validator']))
- {
- $validationResult = $this->botInstances->{$bot->bot}->{$commandToCheck['validator']}($args);
- if(!$validationResult) return (object)array('executedBot' => $executedBot, 'result' => $this->lang->im->bot->error->badArguments);
- }
- if(!method_exists($this->botInstances->{$bot->bot}, $bot->command)) return $this->lang->im->bot->error->noSuchCommand;
- return (object)array('executedBot' => $executedBot, 'result' => $this->botInstances->{$bot->bot}->{$bot->command}($args, $userID, $user));
- }
- /**
- * Get the configured bot for the user.
- *
- * @param int $userID
- * @access public
- * @return string
- */
- public function getUserCurrentBot($userID)
- {
- $user = $this->im->userGetByID($userID);
- $botCode = $this->dao->select('value')->from(TABLE_CONFIG)
- ->where('owner')->eq($user->account)
- ->andWhere('module')->eq('xuanxuan')
- ->andWhere('section')->eq('bot')
- ->andWhere('`key`')->eq('context')
- ->fetch('value');
- if(empty($botCode))
- {
- $data = new stdclass();
- $data->owner = $user->account;
- $data->module = 'xuanxuan';
- $data->section = 'bot';
- $data->key = 'context';
- $data->value = 'default';
- $this->dao->insert(TABLE_CONFIG)->data($data)->exec();
- return 'default';
- }
- return $botCode;
- }
- /**
- * Set the configured bot for the user.
- * @param $botCode
- * @param $userID
- * @return void
- */
- public function setUserCurrentBot($botCode, $userID)
- {
- $user = $this->im->userGetByID($userID);
- $this->dao->update(TABLE_CONFIG)->set('value')->eq($botCode)->where('owner')->eq($user->account)->andWhere('module')->eq('xuanxuan')->andWhere('section')->eq('bot')->andWhere('`key`')->eq('context')->exec();
- }
- /**
- * Handle show command.
- *
- * @param string $command
- * @param object $user
- * @return object
- */
- public function showCommand($command, $user)
- {
- $bots = array_values($this->bots);
- $result = array();
- $reply = new stdClass();
- $reply->messages = array();
- $reply->responses = array();
- foreach($bots as $bot) $result[] = array($this->botInstances->$bot->name, sprintf($this->lang->im->bot->help->sendCommandLink, $bot, $bot));
- $reply->messages[] = xuanBot::printMarkdownTable(array($this->lang->im->bot->name, $this->lang->im->bot->code), $result);
- return $reply;
- }
- /**
- * Get all command with bot.
- *
- * @param string $command
- * @param object $user
- * @access public
- * @return string|object
- */
- public function helpCommand($command, $user)
- {
- $executedBot = new stdClass();
- $helpContent = array();
- $botCodeOffset = -1;
- if(isset($this->botInstances->{$command}))
- {
- $bot = $command;
- $this->initBot($bot);
- $executedBot = $this->getExecutedBot($bot);
- if(!empty($this->botInstances->$bot->help)) return (object)array('executedBot' => $executedBot, 'result' => $this->botInstances->$bot->getHelp());
- }
- if(!isset($this->botInstances->{$command})) $botCodeOffset = mb_strpos($command, "@");
- if($botCodeOffset === false) $bots = $this->bots;
- if($botCodeOffset !== false)
- {
- $bot = mb_substr($command, $botCodeOffset + 1);
- if(!isset($this->botInstances->$bot)) return $this->lang->im->bot->error->noSuchBot;
- $bots = array($bot);
- $executedBot = $this->getExecutedBot($bot);
- }
- foreach($bots as $bot)
- {
- if(!isset($this->botInstances->$bot) || !isset($this->botInstances->$bot->commands)) continue;
- $this->initBot($bot);
- if(!empty($this->botInstances->$bot->help) && count($bots) == 1)
- {
- return (object)array('executedBot' => $executedBot, 'result' => $this->botInstances->$bot->getHelp());
- }
- foreach($this->botInstances->$bot->commands as $botCommand)
- {
- if(is_array($botCommand) && !empty($botCommand['adminOnly']) && $user->admin == 'no') continue;
- $botCode = $this->botInstances->$bot->code;
- $command = is_array($botCommand) ? $botCommand['command'] : $botCommand;
- $description = is_array($botCommand) && isset($botCommand['description']) ? $botCommand['description'] : '';
- $isClickToSend = is_array($botCommand) && (!isset($botCommand['argsRequired']) || !$botCommand['argsRequired']);
- $commandLink = sprintf($this->lang->im->bot->help->{$isClickToSend ? 'sendBotCommandLink' : 'botCommandLink'}, $command, $command, $botCode);
- $helpContent[] = array($commandLink, $description, $botCode);
- }
- }
- $result = xuanBot::printMarkdownTable($this->lang->im->bot->help->header, $helpContent);
- return (object)array('executedBot' => $executedBot, 'result' => $result);
- }
- /**
- * Get Executed Bot.
- *
- * @param string $bot
- * @access public
- * @return object
- */
- public function getExecutedBot($bot)
- {
- if(!isset($this->botInstances->$bot)) return new stdClass();
- return (object)array('name' => $this->botInstances->$bot->name, 'code' => $this->botInstances->$bot->code, 'avatar' => $this->botInstances->$bot->avatar);
- }
- /**
- * Get bot list by command, returns list of bot with such command.
- *
- * @param string $command
- * @access public
- * @return array
- */
- public function getListByCommand($command, $bot = '')
- {
- /* Get command from specified bot. */
- if(strpos($command, '@') !== false)
- {
- list($command, $bot) = explode('@', $command);
- if(!isset($this->botInstances->$bot) || !isset($this->botInstances->$bot->commands)) return array();
- return $this->getListByCommand($command, $bot);
- }
- $currentBot = $bot;
- $command = mb_strtolower($command); // Ignore case of command.
- /* Get command bot pairs. */
- $bots = array();
- $botsToCheck = empty($bot) ? $this->bots : array($bot);
- foreach($botsToCheck as $bot)
- {
- if(!isset($this->botInstances->$bot) || !isset($this->botInstances->$bot->commands)) continue;
- $this->initBot($bot); // init bot on demand.
- /* Check command array of bot. */
- foreach($this->botInstances->$bot->commands as $botCommand)
- {
- $isThisCommand = false;
- /* Command is array, check for properties. */
- if(is_array($botCommand))
- {
- /* Check if command equal (ignore case). */
- if(!empty($botCommand['command']) && mb_strtolower($botCommand['command']) == $command)
- {
- $isThisCommand = true;
- }
- elseif(!empty($botCommand['alias'])) // Check for alias (ignore case).
- {
- foreach($botCommand['alias'] as $alias)
- {
- if(mb_strtolower($alias) == $command)
- {
- $isThisCommand = true;
- break;
- }
- }
- }
- /* Check if command is internal (can only be called within the bot app). */
- if($isThisCommand && (!isset($botCommand['internal']) || !$botCommand['internal'] || $bot == $currentBot))
- {
- $bots[] = (object)array('bot' => $bot, 'command' => $botCommand['command'], 'name' => $this->botInstances->$bot->name, 'code' => $this->botInstances->$bot->code, 'avatar' => $this->botInstances->$bot->avatar);
- break;
- }
- }
- elseif(mb_strtolower($botCommand) == $command) // Command is string, check if equal (ignore case).
- {
- $bots[] = (object)array('bot' => $bot, 'command' => $command, 'name' => $this->botInstances->$bot->name, 'code' => $this->botInstances->$bot->code, 'avatar' => $this->botInstances->$bot->avatar);
- break;
- }
- }
- }
- return $bots;
- }
- /**
- * Gen help content reply.
- *
- * @return object
- */
- public function genHelpContentReply()
- {
- $reply = new stdClass();
- $reply->messages = array();
- $reply->responses = array();
- $reply->messages[] = $this->lang->im->bot->help->welcome;
- $reply->messages[] = $this->lang->im->bot->help->global . $this->lang->im->bot->help->system;
- return $reply;
- }
- /**
- * Create default bot sender.
- *
- * @param string $extraName
- * @access public
- * @return object
- */
- public function createDefaultBotSender($extraName = '')
- {
- $sender = new stdclass();
- $sender->id = 0;
- $sender->displayName = $this->lang->im->bot->commonName;
- $sender->avatar = commonModel::getSysURL() . $this->config->webRoot . 'data/image/xuanbot.png';
- if(!empty($extraName)) $sender->displayName .= '@' . $extraName;
- return $sender;
- }
- }
|