diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -2089,20 +2089,6 @@ 'PhabricatorBoardRenderingEngine' => 'applications/project/engine/PhabricatorBoardRenderingEngine.php', 'PhabricatorBoardResponseEngine' => 'applications/project/engine/PhabricatorBoardResponseEngine.php', 'PhabricatorBoolEditField' => 'applications/transactions/editfield/PhabricatorBoolEditField.php', - 'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php', - 'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php', - 'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php', - 'PhabricatorBotFeedNotificationHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotFeedNotificationHandler.php', - 'PhabricatorBotFlowdockProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php', - 'PhabricatorBotHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotHandler.php', - 'PhabricatorBotLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotLogHandler.php', - 'PhabricatorBotMacroHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotMacroHandler.php', - 'PhabricatorBotMessage' => 'infrastructure/daemon/bot/PhabricatorBotMessage.php', - 'PhabricatorBotObjectNameHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php', - 'PhabricatorBotSymbolHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotSymbolHandler.php', - 'PhabricatorBotTarget' => 'infrastructure/daemon/bot/target/PhabricatorBotTarget.php', - 'PhabricatorBotUser' => 'infrastructure/daemon/bot/target/PhabricatorBotUser.php', - 'PhabricatorBotWhatsNewHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php', 'PhabricatorBritishEnglishTranslation' => 'infrastructure/internationalization/translation/PhabricatorBritishEnglishTranslation.php', 'PhabricatorBuiltinDraftEngine' => 'applications/transactions/draft/PhabricatorBuiltinDraftEngine.php', 'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php', @@ -2261,7 +2247,6 @@ 'PhabricatorCalendarRemarkupRule' => 'applications/calendar/remarkup/PhabricatorCalendarRemarkupRule.php', 'PhabricatorCalendarReplyHandler' => 'applications/calendar/mail/PhabricatorCalendarReplyHandler.php', 'PhabricatorCalendarSchemaSpec' => 'applications/calendar/storage/PhabricatorCalendarSchemaSpec.php', - 'PhabricatorCampfireProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php', 'PhabricatorCelerityApplication' => 'applications/celerity/application/PhabricatorCelerityApplication.php', 'PhabricatorCelerityTestCase' => '__tests__/PhabricatorCelerityTestCase.php', 'PhabricatorChangeParserTestCase' => 'applications/repository/worker/__tests__/PhabricatorChangeParserTestCase.php', @@ -2921,7 +2906,6 @@ 'PhabricatorHovercardEngineExtensionModule' => 'applications/search/engineextension/PhabricatorHovercardEngineExtensionModule.php', 'PhabricatorIDsSearchEngineExtension' => 'applications/search/engineextension/PhabricatorIDsSearchEngineExtension.php', 'PhabricatorIDsSearchField' => 'applications/search/field/PhabricatorIDsSearchField.php', - 'PhabricatorIRCProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php', 'PhabricatorIconDatasource' => 'applications/files/typeahead/PhabricatorIconDatasource.php', 'PhabricatorIconRemarkupRule' => 'applications/macro/markup/PhabricatorIconRemarkupRule.php', 'PhabricatorIconSet' => 'applications/files/iconset/PhabricatorIconSet.php', @@ -3654,7 +3638,6 @@ 'PhabricatorProjectsSearchEngineExtension' => 'applications/project/engineextension/PhabricatorProjectsSearchEngineExtension.php', 'PhabricatorProjectsWatchersSearchEngineAttachment' => 'applications/project/engineextension/PhabricatorProjectsWatchersSearchEngineAttachment.php', 'PhabricatorPronounSetting' => 'applications/settings/setting/PhabricatorPronounSetting.php', - 'PhabricatorProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php', 'PhabricatorPygmentSetupCheck' => 'applications/config/check/PhabricatorPygmentSetupCheck.php', 'PhabricatorQuery' => 'infrastructure/query/PhabricatorQuery.php', 'PhabricatorQueryConstraint' => 'infrastructure/query/constraint/PhabricatorQueryConstraint.php', @@ -3978,7 +3961,6 @@ 'PhabricatorStoragePatch' => 'infrastructure/storage/management/PhabricatorStoragePatch.php', 'PhabricatorStorageSchemaSpec' => 'infrastructure/storage/schema/PhabricatorStorageSchemaSpec.php', 'PhabricatorStorageSetupCheck' => 'applications/config/check/PhabricatorStorageSetupCheck.php', - 'PhabricatorStreamingProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorStreamingProtocolAdapter.php', 'PhabricatorStringListEditField' => 'applications/transactions/editfield/PhabricatorStringListEditField.php', 'PhabricatorStringSetting' => 'applications/settings/setting/PhabricatorStringSetting.php', 'PhabricatorSubmitEditField' => 'applications/transactions/editfield/PhabricatorSubmitEditField.php', @@ -7150,20 +7132,6 @@ 'PhabricatorBoardRenderingEngine' => 'Phobject', 'PhabricatorBoardResponseEngine' => 'Phobject', 'PhabricatorBoolEditField' => 'PhabricatorEditField', - 'PhabricatorBot' => 'PhabricatorDaemon', - 'PhabricatorBotChannel' => 'PhabricatorBotTarget', - 'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler', - 'PhabricatorBotFeedNotificationHandler' => 'PhabricatorBotHandler', - 'PhabricatorBotFlowdockProtocolAdapter' => 'PhabricatorStreamingProtocolAdapter', - 'PhabricatorBotHandler' => 'Phobject', - 'PhabricatorBotLogHandler' => 'PhabricatorBotHandler', - 'PhabricatorBotMacroHandler' => 'PhabricatorBotHandler', - 'PhabricatorBotMessage' => 'Phobject', - 'PhabricatorBotObjectNameHandler' => 'PhabricatorBotHandler', - 'PhabricatorBotSymbolHandler' => 'PhabricatorBotHandler', - 'PhabricatorBotTarget' => 'Phobject', - 'PhabricatorBotUser' => 'PhabricatorBotTarget', - 'PhabricatorBotWhatsNewHandler' => 'PhabricatorBotHandler', 'PhabricatorBritishEnglishTranslation' => 'PhutilTranslation', 'PhabricatorBuiltinDraftEngine' => 'PhabricatorDraftEngine', 'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList', @@ -7358,7 +7326,6 @@ 'PhabricatorCalendarRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'PhabricatorCalendarReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhabricatorCalendarSchemaSpec' => 'PhabricatorConfigSchemaSpec', - 'PhabricatorCampfireProtocolAdapter' => 'PhabricatorStreamingProtocolAdapter', 'PhabricatorCelerityApplication' => 'PhabricatorApplication', 'PhabricatorCelerityTestCase' => 'PhabricatorTestCase', 'PhabricatorChangeParserTestCase' => 'PhabricatorWorkingCopyTestCase', @@ -8115,7 +8082,6 @@ 'PhabricatorHovercardEngineExtensionModule' => 'PhabricatorConfigModule', 'PhabricatorIDsSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'PhabricatorIDsSearchField' => 'PhabricatorSearchField', - 'PhabricatorIRCProtocolAdapter' => 'PhabricatorProtocolAdapter', 'PhabricatorIconDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorIconRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorIconSet' => 'Phobject', @@ -8973,7 +8939,6 @@ 'PhabricatorProjectsSearchEngineExtension' => 'PhabricatorSearchEngineExtension', 'PhabricatorProjectsWatchersSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'PhabricatorPronounSetting' => 'PhabricatorSelectSetting', - 'PhabricatorProtocolAdapter' => 'Phobject', 'PhabricatorPygmentSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorQuery' => 'Phobject', 'PhabricatorQueryConstraint' => 'Phobject', @@ -9377,7 +9342,6 @@ 'PhabricatorStoragePatch' => 'Phobject', 'PhabricatorStorageSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorStorageSetupCheck' => 'PhabricatorSetupCheck', - 'PhabricatorStreamingProtocolAdapter' => 'PhabricatorProtocolAdapter', 'PhabricatorStringListEditField' => 'PhabricatorEditField', 'PhabricatorStringSetting' => 'PhabricatorSetting', 'PhabricatorSubmitEditField' => 'PhabricatorEditField', diff --git a/src/docs/tech/chatbot.diviner b/src/docs/tech/chatbot.diviner deleted file mode 100644 --- a/src/docs/tech/chatbot.diviner +++ /dev/null @@ -1,87 +0,0 @@ -@title Chat Bot Technical Documentation -@group bot - -Configuring and extending the chat bot. - -= Overview = - -Phabricator includes a simple chat bot daemon, which is primarily intended as -an example of how you can write an external script that interfaces with -Phabricator over Conduit and does some kind of useful work. If you use IRC or -another supported chat protocol, you can also have the bot hang out in your -channel. - -NOTE: The chat bot is somewhat experimental and not very mature. - -= Configuring the Bot = - -The bot reads a JSON configuration file. You can find an example in: - - resources/chatbot/example_config.json - -These are the configuration values it reads: - - - `server` String, required, the server to connect to. - - `port` Int, optional, the port to connect to (defaults to 6667). - - `ssl` Bool, optional, whether to connect via SSL or not (defaults to - false). - - `nick` String, nickname to use. - - `user` String, optional, username to use (defaults to `nick`). - - `pass` String, optional, password for server. - - `nickpass` String, optional, password for NickServ. - - `join` Array, list of channels to join. - - `handlers` Array, list of handlers to run. These are like plugins for the - bot. - - `conduit.uri`, `conduit.token` Conduit configuration, - see below. - - `notification.channels` Notification configuration, see below. - -= Handlers = - -You specify a list of "handlers", which are basically plugins or modules for -the bot. These are the default handlers available: - - - @{class:PhabricatorBotObjectNameHandler} This handler looks for users - mentioning Phabricator objects like "T123" and "D345" in chat, looks them - up, and says their name with a link to the object. Requires conduit. - - @{class:PhabricatorBotFeedNotificationHandler} This handler posts - notifications about changes to revisions to the channels listed in - `notification.channels`. - - @{class:PhabricatorBotLogHandler} This handler records chatlogs which can - be browsed in the Phabricator web interface. - - @{class:PhabricatorBotSymbolHandler} This handler posts responses to lookups - for symbols in Diffusion - - @{class:PhabricatorBotMacroHandler} This handler looks for users mentioning - macros, if found will convert image to ASCII and output in chat. Configure - with `macro.size` and `macro.aspect` - -You can also write your own handlers, by extending -@{class:PhabricatorBotHandler}. - -= Conduit = - -Some handlers (e.g., @{class:PhabricatorBotObjectNameHandler}) need to read data -from Phabricator over Conduit, Phabricator's HTTP API. You can use this method -to allow other scripts or programs to access Phabricator's data from different -servers and in different languages. - -To allow the bot to access Conduit, you need to create a user that it can login -with. To do this, login to Phabricator as an administrator and go to -`People -> Create New Account`. Create a new account and flag them as a -"Bot/Script". Then in your configuration file, set these parameters: - - - `conduit.uri` The URI for your Phabricator install, like - `http://phabricator.example.com/` - - `conduit.token` The user's conduit API token, from the "Conduit API Tokens" - tab in the user's administrative view. - -Now the bot should be able to connect to Phabricator via Conduit. - -= Starting the Bot = - -The bot is a Phabricator daemon, so start it with `phd`: - - ./bin/phd launch phabricatorbot - -If you have issues you can try `debug` instead of `launch`, see -@{article:Managing Daemons with phd} for more information. diff --git a/src/infrastructure/daemon/bot/PhabricatorBot.php b/src/infrastructure/daemon/bot/PhabricatorBot.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/PhabricatorBot.php +++ /dev/null @@ -1,170 +0,0 @@ -getArgv(); - if (count($argv) !== 1) { - throw new Exception( - pht( - 'Usage: %s %s', - __CLASS__, - '')); - } - - $json_raw = Filesystem::readFile($argv[0]); - try { - $config = phutil_json_decode($json_raw); - } catch (PhutilJSONParserException $ex) { - throw new PhutilProxyException( - pht("File '%s' is not valid JSON!", $argv[0]), - $ex); - } - - $nick = idx($config, 'nick', 'phabot'); - $handlers = idx($config, 'handlers', array()); - $protocol_adapter_class = idx( - $config, - 'protocol-adapter', - 'PhabricatorIRCProtocolAdapter'); - $this->pollFrequency = idx($config, 'poll-frequency', 1); - - $this->config = $config; - - foreach ($handlers as $handler) { - $obj = newv($handler, array($this)); - $this->handlers[] = $obj; - } - - $ca_bundle = idx($config, 'https.cabundle'); - if ($ca_bundle) { - HTTPSFuture::setGlobalCABundleFromPath($ca_bundle); - } - - $conduit_uri = idx($config, 'conduit.uri'); - if ($conduit_uri) { - $conduit_token = idx($config, 'conduit.token'); - - // Normalize the path component of the URI so users can enter the - // domain without the "/api/" part. - $conduit_uri = new PhutilURI($conduit_uri); - - $conduit_host = (string)$conduit_uri->setPath('/'); - $conduit_uri = (string)$conduit_uri->setPath('/api/'); - - $conduit = new ConduitClient($conduit_uri); - if ($conduit_token) { - $conduit->setConduitToken($conduit_token); - } else { - $conduit_user = idx($config, 'conduit.user'); - $conduit_cert = idx($config, 'conduit.cert'); - - $response = $conduit->callMethodSynchronous( - 'conduit.connect', - array( - 'client' => __CLASS__, - 'clientVersion' => '1.0', - 'clientDescription' => php_uname('n').':'.$nick, - 'host' => $conduit_host, - 'user' => $conduit_user, - 'certificate' => $conduit_cert, - )); - } - - $this->conduit = $conduit; - } - - // Instantiate Protocol Adapter, for now follow same technique as - // handler instantiation - $this->protocolAdapter = newv($protocol_adapter_class, array()); - $this->protocolAdapter - ->setConfig($this->config) - ->connect(); - - $this->runLoop(); - - $this->protocolAdapter->disconnect(); - } - - public function getConfig($key, $default = null) { - return idx($this->config, $key, $default); - } - - private function runLoop() { - do { - PhabricatorCaches::destroyRequestCache(); - - $this->stillWorking(); - - $messages = $this->protocolAdapter->getNextMessages($this->pollFrequency); - if (count($messages) > 0) { - foreach ($messages as $message) { - $this->routeMessage($message); - } - } - - foreach ($this->handlers as $handler) { - $handler->runBackgroundTasks(); - } - } while (!$this->shouldExit()); - - } - - public function writeMessage(PhabricatorBotMessage $message) { - return $this->protocolAdapter->writeMessage($message); - } - - private function routeMessage(PhabricatorBotMessage $message) { - $ignore = $this->getConfig('ignore'); - if ($ignore) { - $sender = $message->getSender(); - if ($sender && in_array($sender->getName(), $ignore)) { - return; - } - } - - if ($message->getCommand() == 'LOG') { - $this->log('[LOG] '.$message->getBody()); - } - - foreach ($this->handlers as $handler) { - try { - $handler->receiveMessage($message); - } catch (Exception $ex) { - phlog($ex); - } - } - } - - public function getAdapter() { - return $this->protocolAdapter; - } - - public function getConduit() { - if (empty($this->conduit)) { - throw new Exception( - pht( - "This bot is not configured with a Conduit uplink. Set '%s' and ". - "'%s' in the configuration to connect.", - 'conduit.uri', - 'conduit.token')); - } - return $this->conduit; - } - -} diff --git a/src/infrastructure/daemon/bot/PhabricatorBotMessage.php b/src/infrastructure/daemon/bot/PhabricatorBotMessage.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/PhabricatorBotMessage.php +++ /dev/null @@ -1,52 +0,0 @@ -public = true; - } - - public function setSender(PhabricatorBotTarget $sender = null) { - $this->sender = $sender; - return $this; - } - - public function getSender() { - return $this->sender; - } - - public function setCommand($command) { - $this->command = $command; - return $this; - } - - public function getCommand() { - return $this->command; - } - - public function setBody($body) { - $this->body = $body; - return $this; - } - - public function getBody() { - return $this->body; - } - - public function setTarget(PhabricatorBotTarget $target = null) { - $this->target = $target; - return $this; - } - - public function getTarget() { - return $this->target; - } - -} diff --git a/src/infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php b/src/infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php +++ /dev/null @@ -1,93 +0,0 @@ -getConfig('flowdock.organization'); - if (empty($organization)) { - $this->getConfig('organization'); - } - if (empty($organization)) { - throw new Exception( - '"flowdock.organization" configuration variable not set'); - } - - - $ssl = $this->getConfig('ssl'); - - $url = ($ssl) ? 'https://' : 'http://'; - $url .= "{$this->authtoken}@stream.flowdock.com"; - $url .= "/flows/{$organization}/{$channel}"; - return $url; - } - - protected function processMessage(array $m_obj) { - $command = null; - switch ($m_obj['event']) { - case 'message': - $command = 'MESSAGE'; - break; - default: - // For now, ignore anything which we don't otherwise know about. - break; - } - - if ($command === null) { - return false; - } - - // TODO: These should be usernames, not user IDs. - $sender = id(new PhabricatorBotUser()) - ->setName($m_obj['user']); - - $target = id(new PhabricatorBotChannel()) - ->setName($m_obj['flow']); - - return id(new PhabricatorBotMessage()) - ->setCommand($command) - ->setSender($sender) - ->setTarget($target) - ->setBody($m_obj['content']); - } - - public function writeMessage(PhabricatorBotMessage $message) { - switch ($message->getCommand()) { - case 'MESSAGE': - $this->speak( - $message->getBody(), - $message->getTarget()); - break; - } - } - - private function speak( - $body, - PhabricatorBotTarget $flow) { - // The $flow->getName() returns the flow's UUID, - // as such, the Flowdock API does not require the organization - // to be specified in the URI - $this->performPost( - '/messages', - array( - 'flow' => $flow->getName(), - 'event' => 'message', - 'content' => $body, - )); - } - - public function __destruct() { - if ($this->readHandles) { - foreach ($this->readHandles as $read_handle) { - curl_multi_remove_handle($this->multiHandle, $read_handle); - curl_close($read_handle); - } - } - - curl_multi_close($this->multiHandle); - } -} diff --git a/src/infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php b/src/infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php +++ /dev/null @@ -1,114 +0,0 @@ -getConfig('ssl'); - - $url = ($ssl) ? 'https://' : 'http://'; - $url .= "streaming.campfirenow.com/room/{$channel}/live.json"; - - return $url; - } - - protected function processMessage(array $m_obj) { - $command = null; - switch ($m_obj['type']) { - case 'TextMessage': - $command = 'MESSAGE'; - break; - case 'PasteMessage': - $command = 'PASTE'; - break; - default: - // For now, ignore anything which we don't otherwise know about. - break; - } - - if ($command === null) { - return false; - } - - // TODO: These should be usernames, not user IDs. - $sender = id(new PhabricatorBotUser()) - ->setName($m_obj['user_id']); - - $target = id(new PhabricatorBotChannel()) - ->setName($m_obj['room_id']); - - return id(new PhabricatorBotMessage()) - ->setCommand($command) - ->setSender($sender) - ->setTarget($target) - ->setBody($m_obj['body']); - } - - public function writeMessage(PhabricatorBotMessage $message) { - switch ($message->getCommand()) { - case 'MESSAGE': - $this->speak( - $message->getBody(), - $message->getTarget()); - break; - case 'SOUND': - $this->speak( - $message->getBody(), - $message->getTarget(), - 'SoundMessage'); - break; - case 'PASTE': - $this->speak( - $message->getBody(), - $message->getTarget(), - 'PasteMessage'); - break; - } - } - - protected function joinRoom($room_id) { - $this->performPost("/room/{$room_id}/join.json"); - $this->inRooms[$room_id] = true; - } - - private function leaveRoom($room_id) { - $this->performPost("/room/{$room_id}/leave.json"); - unset($this->inRooms[$room_id]); - } - - private function speak( - $message, - PhabricatorBotTarget $channel, - $type = 'TextMessage') { - - $room_id = $channel->getName(); - - $this->performPost( - "/room/{$room_id}/speak.json", - array( - 'message' => array( - 'type' => $type, - 'body' => $message, - ), - )); - } - - public function __destruct() { - foreach ($this->inRooms as $room_id => $ignored) { - $this->leaveRoom($room_id); - } - - if ($this->readHandles) { - foreach ($this->readHandles as $read_handle) { - curl_multi_remove_handle($this->multiHandle, $read_handle); - curl_close($read_handle); - } - } - - curl_multi_close($this->multiHandle); - } -} diff --git a/src/infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php b/src/infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php +++ /dev/null @@ -1,282 +0,0 @@ -getConfig('network', $this->getConfig('server')); - } - - // Hash map of command translations - public static $commandTranslations = array( - 'PRIVMSG' => 'MESSAGE', - ); - - public function connect() { - $nick = $this->getConfig('nick', 'phabot'); - $server = $this->getConfig('server'); - $port = $this->getConfig('port', 6667); - $pass = $this->getConfig('pass'); - $ssl = $this->getConfig('ssl', false); - $user = $this->getConfig('user', $nick); - - if (!preg_match('/^[A-Za-z0-9_`[{}^|\]\\-]+$/', $nick)) { - throw new Exception( - pht( - "Nickname '%s' is invalid!", - $nick)); - } - - $errno = null; - $error = null; - if (!$ssl) { - $socket = fsockopen($server, $port, $errno, $error); - } else { - $socket = fsockopen('ssl://'.$server, $port, $errno, $error); - } - if (!$socket) { - throw new Exception(pht('Failed to connect, #%d: %s', $errno, $error)); - } - $ok = stream_set_blocking($socket, false); - if (!$ok) { - throw new Exception(pht('Failed to set stream nonblocking.')); - } - - $this->socket = $socket; - if ($pass) { - $this->write("PASS {$pass}"); - } - $this->write("NICK {$nick}"); - $this->write("USER {$user} 0 * :{$user}"); - } - - public function getNextMessages($poll_frequency) { - $messages = array(); - - $read = array($this->socket); - if (strlen($this->writeBuffer)) { - $write = array($this->socket); - } else { - $write = array(); - } - $except = array(); - - $ok = @stream_select($read, $write, $except, $timeout_sec = 1); - if ($ok === false) { - // We may have been interrupted by a signal, like a SIGINT. Try - // selecting again. If the second select works, conclude that the failure - // was most likely because we were signaled. - $ok = @stream_select($read, $write, $except, $timeout_sec = 0); - if ($ok === false) { - throw new Exception(pht('%s failed!', 'stream_select()')); - } - } - - if ($read) { - // Test for connection termination; in PHP, fread() off a nonblocking, - // closed socket is empty string. - if (feof($this->socket)) { - // This indicates the connection was terminated on the other side, - // just exit via exception and let the overseer restart us after a - // delay so we can reconnect. - throw new Exception(pht('Remote host closed connection.')); - } - do { - $data = fread($this->socket, 4096); - if ($data === false) { - throw new Exception(pht('%s failed!', 'fread()')); - } else { - $messages[] = id(new PhabricatorBotMessage()) - ->setCommand('LOG') - ->setBody('>>> '.$data); - $this->readBuffer .= $data; - } - } while (strlen($data)); - } - - if ($write) { - do { - $len = fwrite($this->socket, $this->writeBuffer); - if ($len === false) { - throw new Exception(pht('%s failed!', 'fwrite()')); - } else if ($len === 0) { - break; - } else { - $messages[] = id(new PhabricatorBotMessage()) - ->setCommand('LOG') - ->setBody('>>> '.substr($this->writeBuffer, 0, $len)); - $this->writeBuffer = substr($this->writeBuffer, $len); - } - } while (strlen($this->writeBuffer)); - } - - while (($m = $this->processReadBuffer()) !== false) { - if ($m !== null) { - $messages[] = $m; - } - } - - return $messages; - } - - private function write($message) { - $this->writeBuffer .= $message."\r\n"; - return $this; - } - - public function writeMessage(PhabricatorBotMessage $message) { - switch ($message->getCommand()) { - case 'MESSAGE': - case 'PASTE': - $name = $message->getTarget()->getName(); - $body = $message->getBody(); - $this->write("PRIVMSG {$name} :{$body}"); - return true; - default: - return false; - } - } - - private function processReadBuffer() { - $until = strpos($this->readBuffer, "\r\n"); - if ($until === false) { - return false; - } - - $message = substr($this->readBuffer, 0, $until); - $this->readBuffer = substr($this->readBuffer, $until + 2); - - $pattern = - '/^'. - '(?::(?P(\S+?))(?:!\S*)? )?'. // This may not be present. - '(?P[A-Z0-9]+) '. - '(?P.*)'. - '$/'; - - $matches = null; - if (!preg_match($pattern, $message, $matches)) { - throw new Exception("Unexpected message from server: {$message}"); - } - - if ($this->handleIRCProtocol($matches)) { - return null; - } - - $command = $this->getBotCommand($matches['command']); - list($target, $body) = $this->parseMessageData($command, $matches['data']); - - if (!strlen($matches['sender'])) { - $sender = null; - } else { - $sender = id(new PhabricatorBotUser()) - ->setName($matches['sender']); - } - - $bot_message = id(new PhabricatorBotMessage()) - ->setSender($sender) - ->setCommand($command) - ->setTarget($target) - ->setBody($body); - - return $bot_message; - } - - private function handleIRCProtocol(array $matches) { - $data = $matches['data']; - switch ($matches['command']) { - case '433': // Nickname already in use - // If we receive this error, try appending "-1", "-2", etc. to the nick - $this->nickIncrement++; - $nick = $this->getConfig('nick', 'phabot').'-'.$this->nickIncrement; - $this->write("NICK {$nick}"); - return true; - case '422': // Error - no MOTD - case '376': // End of MOTD - $nickpass = $this->getConfig('nickpass'); - if ($nickpass) { - $this->write("PRIVMSG nickserv :IDENTIFY {$nickpass}"); - } - $join = $this->getConfig('join'); - if (!$join) { - throw new Exception(pht('Not configured to join any channels!')); - } - foreach ($join as $channel) { - $this->write("JOIN {$channel}"); - } - return true; - case 'PING': - $this->write("PONG {$data}"); - return true; - } - - return false; - } - - private function getBotCommand($irc_command) { - if (isset(self::$commandTranslations[$irc_command])) { - return self::$commandTranslations[$irc_command]; - } - - // We have no translation for this command, use as-is - return $irc_command; - } - - private function parseMessageData($command, $data) { - switch ($command) { - case 'MESSAGE': - $matches = null; - if (preg_match('/^(\S+)\s+:?(.*)$/', $data, $matches)) { - - $target_name = $matches[1]; - if (strncmp($target_name, '#', 1) === 0) { - $target = id(new PhabricatorBotChannel()) - ->setName($target_name); - } else { - $target = id(new PhabricatorBotUser()) - ->setName($target_name); - } - - return array( - $target, - rtrim($matches[2], "\r\n"), - ); - } - break; - } - - // By default we assume there is no target, only a body - return array( - null, - $data, - ); - } - - public function disconnect() { - // NOTE: FreeNode doesn't show quit messages if you've recently joined a - // channel, presumably to prevent some kind of abuse. If you're testing - // this, you may need to stay connected to the network for a few minutes - // before it works. If you disconnect too quickly, the server will replace - // your message with a "Client Quit" message. - - $quit = $this->getConfig('quit', pht('Shutting down.')); - $this->write("QUIT :{$quit}"); - - // Flush the write buffer. - while (strlen($this->writeBuffer)) { - $this->getNextMessages(0); - } - - @fclose($this->socket); - $this->socket = null; - } -} diff --git a/src/infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php b/src/infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/adapter/PhabricatorProtocolAdapter.php +++ /dev/null @@ -1,62 +0,0 @@ -config = $config; - return $this; - } - - public function getConfig($key, $default = null) { - return idx($this->config, $key, $default); - } - - /** - * Performs any connection logic necessary for the protocol - */ - abstract public function connect(); - - /** - * Disconnect from the service. - */ - public function disconnect() { - return; - } - - /** - * This is the spout for messages coming in from the protocol. - * This will be called in the main event loop of the bot daemon - * So if if doesn't implement some sort of blocking timeout - * (e.g. select-based socket polling), it should at least sleep - * for some period of time in order to not overwhelm the processor. - * - * @param Int $poll_frequency The number of seconds between polls - */ - abstract public function getNextMessages($poll_frequency); - - /** - * This is the output mechanism for the protocol. - * - * @param PhabricatorBotMessage $message The message to write - */ - abstract public function writeMessage(PhabricatorBotMessage $message); - - /** - * String identifying the service type the adapter provides access to, like - * "irc", "campfire", "flowdock", "hipchat", etc. - */ - abstract public function getServiceType(); - - /** - * String identifying the service name the adapter is connecting to. This is - * used to distinguish between instances of a service. For example, for IRC, - * this should return the IRC network the client is connecting to. - */ - abstract public function getServiceName(); - -} diff --git a/src/infrastructure/daemon/bot/adapter/PhabricatorStreamingProtocolAdapter.php b/src/infrastructure/daemon/bot/adapter/PhabricatorStreamingProtocolAdapter.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/adapter/PhabricatorStreamingProtocolAdapter.php +++ /dev/null @@ -1,170 +0,0 @@ -server); - return $uri->getDomain(); - } - - public function connect() { - $this->server = $this->getConfig('server'); - $this->authtoken = $this->getConfig('authtoken'); - $rooms = $this->getConfig('join'); - - // First, join the room - if (!$rooms) { - throw new Exception(pht('Not configured to join any rooms!')); - } - - $this->readBuffers = array(); - - // Set up our long poll in a curl multi request so we can - // continue running while it executes in the background - $this->multiHandle = curl_multi_init(); - $this->readHandles = array(); - - foreach ($rooms as $room_id) { - $this->joinRoom($room_id); - - // Set up the curl stream for reading - $url = $this->buildStreamingUrl($room_id); - $ch = $this->readHandles[$url] = curl_init(); - - curl_setopt($ch, CURLOPT_URL, $url); - curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); - curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); - curl_setopt( - $ch, - CURLOPT_USERPWD, - $this->authtoken.':x'); - - curl_setopt( - $ch, - CURLOPT_HTTPHEADER, - array('Content-type: application/json')); - curl_setopt( - $ch, - CURLOPT_WRITEFUNCTION, - array($this, 'read')); - curl_setopt($ch, CURLOPT_BUFFERSIZE, 128); - curl_setopt($ch, CURLOPT_TIMEOUT, 0); - - curl_multi_add_handle($this->multiHandle, $ch); - - // Initialize read buffer - $this->readBuffers[$url] = ''; - } - - $this->active = null; - $this->blockingMultiExec(); - } - - protected function joinRoom($room_id) { - // Optional hook, by default, do nothing - } - - // This is our callback for the background curl multi-request. - // Puts the data read in on the readBuffer for processing. - private function read($ch, $data) { - $info = curl_getinfo($ch); - $length = strlen($data); - $this->readBuffers[$info['url']] .= $data; - return $length; - } - - private function blockingMultiExec() { - do { - $status = curl_multi_exec($this->multiHandle, $this->active); - } while ($status == CURLM_CALL_MULTI_PERFORM); - - // Check for errors - if ($status != CURLM_OK) { - throw new Exception( - pht('Phabricator Bot had a problem reading from stream.')); - } - } - - public function getNextMessages($poll_frequency) { - $messages = array(); - - if (!$this->active) { - throw new Exception(pht('Phabricator Bot stopped reading from stream.')); - } - - // Prod our http request - curl_multi_select($this->multiHandle, $poll_frequency); - $this->blockingMultiExec(); - - // Process anything waiting on the read buffer - while ($m = $this->processReadBuffer()) { - $messages[] = $m; - } - - return $messages; - } - - private function processReadBuffer() { - foreach ($this->readBuffers as $url => &$buffer) { - $until = strpos($buffer, "}\r"); - if ($until == false) { - continue; - } - - $message = substr($buffer, 0, $until + 1); - $buffer = substr($buffer, $until + 2); - - $m_obj = phutil_json_decode($message); - if ($message = $this->processMessage($m_obj)) { - return $message; - } - } - - // If we're here, there's nothing to process - return false; - } - - protected function performPost($endpoint, $data = null) { - $uri = new PhutilURI($this->server); - $uri->setPath($endpoint); - - $payload = json_encode($data); - - list($output) = id(new HTTPSFuture($uri)) - ->setMethod('POST') - ->addHeader('Content-Type', 'application/json') - ->addHeader('Authorization', $this->getAuthorizationHeader()) - ->setData($payload) - ->resolvex(); - - $output = trim($output); - if (strlen($output)) { - return phutil_json_decode($output); - } - - return true; - } - - protected function getAuthorizationHeader() { - return 'Basic '.$this->getEncodedAuthToken(); - } - - protected function getEncodedAuthToken() { - return base64_encode($this->authtoken.':x'); - } - - abstract protected function buildStreamingUrl($channel); - - abstract protected function processMessage(array $raw_object); - -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php +++ /dev/null @@ -1,17 +0,0 @@ -getCommand()) { - case 'LOG': - echo addcslashes( - $message->getBody(), - "\0..\37\177..\377"); - echo "\n"; - break; - } - } -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotFeedNotificationHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotFeedNotificationHandler.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotFeedNotificationHandler.php +++ /dev/null @@ -1,180 +0,0 @@ -getConfig('notification.types'); - - if ($show) { - $obj_type = phid_get_type($story_objectphid); - if (!in_array(strtolower($obj_type), $show)) { - return false; - } - } - - $verbosity = $this->getConfig('notification.verbosity', 3); - - $verbs = array(); - - switch ($verbosity) { - case 2: - $verbs[] = array( - 'commented', - 'added', - 'changed', - 'resigned', - 'explained', - 'modified', - 'attached', - 'edited', - 'joined', - 'left', - 'removed', - ); - // fallthrough - case 1: - $verbs[] = array( - 'updated', - 'accepted', - 'requested', - 'planned', - 'claimed', - 'summarized', - 'commandeered', - 'assigned', - ); - // fallthrough - case 0: - $verbs[] = array( - 'created', - 'closed', - 'raised', - 'committed', - 'abandoned', - 'reclaimed', - 'reopened', - 'deleted', - ); - break; - - case 3: - default: - return true; - } - - $verbs = '/('.implode('|', array_mergev($verbs)).')/'; - - if (preg_match($verbs, $story_text)) { - return true; - } - - return false; - } - - public function receiveMessage(PhabricatorBotMessage $message) { - return; - } - - public function runBackgroundTasks() { - if ($this->startupDelay > 0) { - // the event loop runs every 1s so delay enough to fully conenct - $this->startupDelay--; - - return; - } - if ($this->lastSeenChronoKey == 0) { - // Since we only want to post notifications about new stories, skip - // everything that's happened in the past when we start up so we'll - // only process real-time stories. - $latest = $this->getConduit()->callMethodSynchronous( - 'feed.query', - array( - 'limit' => 1, - )); - - foreach ($latest as $story) { - if ($story['chronologicalKey'] > $this->lastSeenChronoKey) { - $this->lastSeenChronoKey = $story['chronologicalKey']; - } - } - - return; - } - - $config_max_pages = $this->getConfig('notification.max_pages', 5); - $config_page_size = $this->getConfig('notification.page_size', 10); - - $last_seen_chrono_key = $this->lastSeenChronoKey; - $chrono_key_cursor = 0; - - // Not efficient but works due to feed.query API - for ($max_pages = $config_max_pages; $max_pages > 0; $max_pages--) { - $stories = $this->getConduit()->callMethodSynchronous( - 'feed.query', - array( - 'limit' => $config_page_size, - 'after' => $chrono_key_cursor, - 'view' => 'text', - )); - - foreach ($stories as $story) { - if ($story['chronologicalKey'] == $last_seen_chrono_key) { - // Caught up on feed - return; - } - if ($story['chronologicalKey'] > $this->lastSeenChronoKey) { - // Keep track of newest seen story - $this->lastSeenChronoKey = $story['chronologicalKey']; - } - if (!$chrono_key_cursor || - $story['chronologicalKey'] < $chrono_key_cursor) { - // Keep track of oldest story on this page - $chrono_key_cursor = $story['chronologicalKey']; - } - - if (!$story['text'] || - !$this->shouldShowStory($story)) { - continue; - } - - $message = $story['text']; - - $story_object_type = phid_get_type($story['objectPHID']); - if (in_array($story_object_type, $this->typesNeedURI)) { - $objects = $this->getConduit()->callMethodSynchronous( - 'phid.lookup', - array( - 'names' => array($story['objectPHID']), - )); - $message .= ' '.$objects[$story['objectPHID']]['uri']; - } - - $channels = $this->getConfig('join'); - foreach ($channels as $channel_name) { - - $channel = id(new PhabricatorBotChannel()) - ->setName($channel_name); - - $this->writeMessage( - id(new PhabricatorBotMessage()) - ->setCommand('MESSAGE') - ->setTarget($channel) - ->setBody($message)); - } - } - } - } - -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotHandler.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotHandler.php +++ /dev/null @@ -1,72 +0,0 @@ -bot = $irc_bot; - } - - final protected function writeMessage(PhabricatorBotMessage $message) { - $this->bot->writeMessage($message); - return $this; - } - - final protected function getConduit() { - return $this->bot->getConduit(); - } - - final protected function getConfig($key, $default = null) { - return $this->bot->getConfig($key, $default); - } - - final protected function getURI($path) { - $base_uri = new PhutilURI($this->bot->getConfig('conduit.uri')); - $base_uri->setPath($path); - return (string)$base_uri; - } - - final protected function getServiceName() { - return $this->bot->getAdapter()->getServiceName(); - } - - final protected function getServiceType() { - return $this->bot->getAdapter()->getServiceType(); - } - - abstract public function receiveMessage(PhabricatorBotMessage $message); - - public function runBackgroundTasks() { - return; - } - - public function replyTo(PhabricatorBotMessage $original_message, $body) { - if ($original_message->getCommand() != 'MESSAGE') { - throw new Exception( - pht('Handler is trying to reply to something which is not a message!')); - } - - $reply = id(new PhabricatorBotMessage()) - ->setCommand('MESSAGE'); - - if ($original_message->getTarget()->isPublic()) { - // This is a public target, like a chatroom. Send the response to the - // chatroom. - $reply->setTarget($original_message->getTarget()); - } else { - // This is a private target, like a private message. Send the response - // back to the sender (presumably, we are the target). - $reply->setTarget($original_message->getSender()); - } - - $reply->setBody($body); - - return $this->writeMessage($reply); - } - -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotLogHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotLogHandler.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotLogHandler.php +++ /dev/null @@ -1,77 +0,0 @@ -getCommand()) { - case 'MESSAGE': - $target = $message->getTarget(); - if (!$target->isPublic()) { - // Don't log private messages, although maybe we should for debugging? - break; - } - - $target_name = $target->getName(); - - $logs = array( - array( - 'channel' => $target_name, - 'type' => 'mesg', - 'epoch' => time(), - 'author' => $message->getSender()->getName(), - 'message' => $message->getBody(), - 'serviceName' => $this->getServiceName(), - 'serviceType' => $this->getServiceType(), - ), - ); - - $this->futures[] = $this->getConduit()->callMethod( - 'chatlog.record', - array( - 'logs' => $logs, - )); - - $prompts = array( - '/where is the (chat)?log\?/i', - '/where am i\?/i', - '/what year is (this|it)\?/i', - ); - - $tell = false; - foreach ($prompts as $prompt) { - if (preg_match($prompt, $message->getBody())) { - $tell = true; - break; - } - } - - if ($tell) { - $response = $this->getURI( - '/chatlog/channel/'.phutil_escape_uri($target_name).'/'); - - $this->replyTo($message, $response); - } - - break; - } - } - - public function runBackgroundTasks() { - foreach ($this->futures as $key => $future) { - try { - if ($future->isReady()) { - unset($this->futures[$key]); - } - } catch (Exception $ex) { - unset($this->futures[$key]); - phlog($ex); - } - } - } - -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotMacroHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotMacroHandler.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotMacroHandler.php +++ /dev/null @@ -1,176 +0,0 @@ -macros === false) { - return false; - } - - if ($this->macros !== null) { - return true; - } - - $macros = $this->getConduit()->callMethodSynchronous( - 'macro.query', - array()); - - // If we have no macros, cache `false` (meaning "no macros") and return - // immediately. - if (!$macros) { - $this->macros = false; - return false; - } - - $regexp = array(); - foreach ($macros as $macro_name => $macro) { - $regexp[] = preg_quote($macro_name, '/'); - } - $regexp = '/^('.implode('|', $regexp).')\z/'; - - $this->macros = $macros; - $this->regexp = $regexp; - - return true; - } - - public function receiveMessage(PhabricatorBotMessage $message) { - if (!$this->init()) { - return; - } - - switch ($message->getCommand()) { - case 'MESSAGE': - $message_body = $message->getBody(); - - $matches = null; - if (!preg_match($this->regexp, trim($message_body), $matches)) { - return; - } - - $macro = $matches[1]; - - $ascii = idx($this->macros[$macro], 'ascii'); - if ($ascii === false) { - return; - } - - if (!$ascii) { - $this->macros[$macro]['ascii'] = $this->rasterize( - $this->macros[$macro], - $this->getConfig('macro.size', 48), - $this->getConfig('macro.aspect', 0.66)); - $ascii = $this->macros[$macro]['ascii']; - } - - if ($ascii === false) { - // If we failed to rasterize the macro, bail out. - return; - } - - $target_name = $message->getTarget()->getName(); - foreach ($ascii as $line) { - $this->replyTo($message, $line); - } - break; - } - } - - public function rasterize($macro, $size, $aspect) { - try { - $image = $this->getConduit()->callMethodSynchronous( - 'file.download', - array( - 'phid' => $macro['filePHID'], - )); - $image = base64_decode($image); - } catch (Exception $ex) { - return false; - } - - if (!$image) { - return false; - } - - $img = @imagecreatefromstring($image); - if (!$img) { - return false; - } - - $sx = imagesx($img); - $sy = imagesy($img); - - if ($sx > $size || $sy > $size) { - $scale = max($sx, $sy) / $size; - $dx = floor($sx / $scale); - $dy = floor($sy / $scale); - } else { - $dx = $sx; - $dy = $sy; - } - - $dy = floor($dy * $aspect); - - $dst = imagecreatetruecolor($dx, $dy); - if (!$dst) { - return false; - } - imagealphablending($dst, false); - - $ok = imagecopyresampled( - $dst, $img, - 0, 0, - 0, 0, - $dx, $dy, - $sx, $sy); - - if (!$ok) { - return false; - } - - $map = array( - ' ', - '.', - ',', - ':', - ';', - '!', - '|', - '*', - '=', - '@', - '$', - '#', - ); - - $lines = array(); - - for ($ii = 0; $ii < $dy; $ii++) { - $buf = ''; - for ($jj = 0; $jj < $dx; $jj++) { - $c = imagecolorat($dst, $jj, $ii); - - $a = ($c >> 24) & 0xFF; - $r = ($c >> 16) & 0xFF; - $g = ($c >> 8) & 0xFF; - $b = ($c) & 0xFF; - - $luma = (255 - ((0.30 * $r) + (0.59 * $g) + (0.11 * $b))) / 256; - $luma *= ((127 - $a) / 127); - - $char = $map[max(0, floor($luma * count($map)))]; - $buf .= $char; - } - - $lines[] = $buf; - } - - return $lines; - } - -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php +++ /dev/null @@ -1,206 +0,0 @@ -getCommand()) { - case 'MESSAGE': - $message = $original_message->getBody(); - $matches = null; - - $paste_ids = array(); - $commit_names = array(); - $vote_ids = array(); - $file_ids = array(); - $object_names = array(); - $output = array(); - - $pattern = - '@'. - '(?getConduit()->callMethodSynchronous( - 'phid.lookup', - array( - 'names' => $object_names, - )); - foreach ($objects as $object) { - $output[$object['phid']] = $object['fullName'].' - '.$object['uri']; - } - } - - if ($vote_ids) { - foreach ($vote_ids as $vote_id) { - $vote = $this->getConduit()->callMethodSynchronous( - 'slowvote.info', - array( - 'poll_id' => $vote_id, - )); - $output[$vote['phid']] = 'V'.$vote['id'].': '.$vote['question']. - ' '.pht('Come Vote').' '.$vote['uri']; - } - } - - if ($file_ids) { - foreach ($file_ids as $file_id) { - $file = $this->getConduit()->callMethodSynchronous( - 'file.info', - array( - 'id' => $file_id, - )); - $output[$file['phid']] = $file['objectName'].': '. - $file['uri'].' - '.$file['name']; - } - } - - if ($paste_ids) { - foreach ($paste_ids as $paste_id) { - $paste = $this->getConduit()->callMethodSynchronous( - 'paste.query', - array( - 'ids' => array($paste_id), - )); - $paste = head($paste); - - $output[$paste['phid']] = 'P'.$paste['id'].': '.$paste['uri'].' - '. - $paste['title']; - - if ($paste['language']) { - $output[$paste['phid']] .= ' ('.$paste['language'].')'; - } - - $user = $this->getConduit()->callMethodSynchronous( - 'user.query', - array( - 'phids' => array($paste['authorPHID']), - )); - $user = head($user); - if ($user) { - $output[$paste['phid']] .= ' by '.$user['userName']; - } - } - } - - if ($commit_names) { - $commits = $this->getConduit()->callMethodSynchronous( - 'diffusion.querycommits', - array( - 'names' => $commit_names, - )); - foreach ($commits['data'] as $commit) { - $output[$commit['phid']] = $commit['uri']; - } - } - - foreach ($output as $phid => $description) { - - // Don't mention the same object more than once every 10 minutes - // in public channels, so we avoid spamming the chat over and over - // again for discussions of a specific revision, for example. - - $target_name = $original_message->getTarget()->getName(); - if (empty($this->recentlyMentioned[$target_name])) { - $this->recentlyMentioned[$target_name] = array(); - } - - $quiet_until = idx( - $this->recentlyMentioned[$target_name], - $phid, - 0) + (60 * 10); - - if (time() < $quiet_until) { - // Remain quiet on this channel. - continue; - } - - $this->recentlyMentioned[$target_name][$phid] = time(); - $this->replyTo($original_message, $description); - } - break; - } - } - -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotSymbolHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotSymbolHandler.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotSymbolHandler.php +++ /dev/null @@ -1,50 +0,0 @@ -?" - */ -final class PhabricatorBotSymbolHandler extends PhabricatorBotHandler { - - public function receiveMessage(PhabricatorBotMessage $message) { - switch ($message->getCommand()) { - case 'MESSAGE': - $text = $message->getBody(); - - $matches = null; - if (!preg_match('/where(?: in the world)? is (\S+?)\?/i', - $text, $matches)) { - break; - } - - $symbol = $matches[1]; - $results = $this->getConduit()->callMethodSynchronous( - 'diffusion.findsymbols', - array( - 'name' => $symbol, - )); - - $default_uri = $this->getURI('/diffusion/symbol/'.$symbol.'/'); - - if (count($results) > 1) { - $response = pht( - "Multiple symbols named '%s': %s", - $symbol, - $default_uri); - } else if (count($results) == 1) { - $result = head($results); - $response = - $result['type'].' '. - $result['name'].' '. - '('.$result['language'].'): '. - nonempty($result['uri'], $default_uri); - } else { - $response = pht("No symbol '%s' found anywhere.", $symbol); - } - - $this->replyTo($message, $response); - - break; - } - } - -} diff --git a/src/infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php b/src/infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php +++ /dev/null @@ -1,43 +0,0 @@ -getCommand()) { - case 'MESSAGE': - $message_body = $message->getBody(); - $now = time(); - - $prompt = '~what( i|\')?s new\?~i'; - if (preg_match($prompt, $message_body)) { - if ($now < $this->floodblock) { - return; - } - $this->floodblock = $now + 60; - $this->reportNew($message); - } - break; - } - } - - public function reportNew(PhabricatorBotMessage $message) { - $latest = $this->getConduit()->callMethodSynchronous( - 'feed.query', - array( - 'limit' => 5, - 'view' => 'text', - )); - - foreach ($latest as $feed_item) { - if (isset($feed_item['text'])) { - $this->replyTo($message, html_entity_decode($feed_item['text'])); - } - } - } - -} diff --git a/src/infrastructure/daemon/bot/target/PhabricatorBotChannel.php b/src/infrastructure/daemon/bot/target/PhabricatorBotChannel.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/target/PhabricatorBotChannel.php +++ /dev/null @@ -1,12 +0,0 @@ -name = $name; - return $this; - } - - public function getName() { - return $this->name; - } - - abstract public function isPublic(); - -} diff --git a/src/infrastructure/daemon/bot/target/PhabricatorBotUser.php b/src/infrastructure/daemon/bot/target/PhabricatorBotUser.php deleted file mode 100644 --- a/src/infrastructure/daemon/bot/target/PhabricatorBotUser.php +++ /dev/null @@ -1,12 +0,0 @@ -