@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Added Flowdock protocol adapter for the bot. Refactored campfire bot into a base streaming protocol adapter for common functionality.

Summary: First pass. Flowdock supports interesting message types (like replies to messages), but for now implementing a standard messaging interface.

Test Plan: Ran both a Flowdock bot and a Campfire bot. Made sure both still connected and responded properly to the Object Handler.

Reviewers: epriestley

Reviewed By: epriestley

CC: aran, Korvin

Differential Revision: https://secure.phabricator.com/D4983

authored by

indiefan and committed by
epriestley
5fb56f85 0e7382b1

+249 -140
+4
src/__phutil_library_map__.php
··· 713 713 'PhabricatorBaseEnglishTranslation' => 'infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php', 714 714 'PhabricatorBaseProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorBaseProtocolAdapter.php', 715 715 'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php', 716 + 'PhabricatorBotBaseStreamingProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorBotBaseStreamingProtocolAdapter.php', 716 717 'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php', 717 718 'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php', 718 719 'PhabricatorBotDifferentialNotificationHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDifferentialNotificationHandler.php', 719 720 'PhabricatorBotFeedNotificationHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotFeedNotificationHandler.php', 721 + 'PhabricatorBotFlowdockProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php', 720 722 'PhabricatorBotHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotHandler.php', 721 723 'PhabricatorBotLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotLogHandler.php', 722 724 'PhabricatorBotMacroHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotMacroHandler.php', ··· 2197 2199 'PhabricatorBarePageView' => 'AphrontPageView', 2198 2200 'PhabricatorBaseEnglishTranslation' => 'PhabricatorTranslation', 2199 2201 'PhabricatorBot' => 'PhabricatorDaemon', 2202 + 'PhabricatorBotBaseStreamingProtocolAdapter' => 'PhabricatorBaseProtocolAdapter', 2200 2203 'PhabricatorBotChannel' => 'PhabricatorBotTarget', 2201 2204 'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler', 2202 2205 'PhabricatorBotDifferentialNotificationHandler' => 'PhabricatorBotHandler', 2203 2206 'PhabricatorBotFeedNotificationHandler' => 'PhabricatorBotHandler', 2207 + 'PhabricatorBotFlowdockProtocolAdapter' => 'PhabricatorBotBaseStreamingProtocolAdapter', 2204 2208 'PhabricatorBotLogHandler' => 'PhabricatorBotHandler', 2205 2209 'PhabricatorBotMacroHandler' => 'PhabricatorBotHandler', 2206 2210 'PhabricatorBotObjectNameHandler' => 'PhabricatorBotHandler',
+158
src/infrastructure/daemon/bot/adapter/PhabricatorBotBaseStreamingProtocolAdapter.php
··· 1 + <?php 2 + 3 + abstract class PhabricatorBotBaseStreamingProtocolAdapter 4 + extends PhabricatorBaseProtocolAdapter { 5 + 6 + private $readBuffers; 7 + private $authtoken; 8 + private $server; 9 + private $readHandles; 10 + private $multiHandle; 11 + private $active; 12 + private $inRooms = array(); 13 + 14 + public function connect() { 15 + $this->server = $this->getConfig('server'); 16 + $this->authtoken = $this->getConfig('authtoken'); 17 + $rooms = $this->getConfig('join'); 18 + 19 + // First, join the room 20 + if (!$rooms) { 21 + throw new Exception("Not configured to join any rooms!"); 22 + } 23 + 24 + $this->readBuffers = array(); 25 + 26 + // Set up our long poll in a curl multi request so we can 27 + // continue running while it executes in the background 28 + $this->multiHandle = curl_multi_init(); 29 + $this->readHandles = array(); 30 + 31 + foreach ($rooms as $room_id) { 32 + $this->joinRoom($room_id); 33 + 34 + // Set up the curl stream for reading 35 + $url = $this->buildStreamingUrl($room_id); 36 + $this->readHandle[$url] = curl_init(); 37 + curl_setopt($this->readHandle[$url], CURLOPT_URL, $url); 38 + curl_setopt($this->readHandle[$url], CURLOPT_RETURNTRANSFER, true); 39 + curl_setopt($this->readHandle[$url], CURLOPT_FOLLOWLOCATION, 1); 40 + curl_setopt( 41 + $this->readHandle[$url], 42 + CURLOPT_USERPWD, 43 + $this->authtoken.':x'); 44 + curl_setopt( 45 + $this->readHandle[$url], 46 + CURLOPT_HTTPHEADER, 47 + array("Content-type: application/json")); 48 + curl_setopt( 49 + $this->readHandle[$url], 50 + CURLOPT_WRITEFUNCTION, 51 + array($this, 'read')); 52 + curl_setopt($this->readHandle[$url], CURLOPT_BUFFERSIZE, 128); 53 + curl_setopt($this->readHandle[$url], CURLOPT_TIMEOUT, 0); 54 + 55 + curl_multi_add_handle($this->multiHandle, $this->readHandle[$url]); 56 + 57 + // Initialize read buffer 58 + $this->readBuffers[$url] = ''; 59 + } 60 + 61 + $this->active = null; 62 + $this->blockingMultiExec(); 63 + } 64 + 65 + protected function joinRoom($room_id) { 66 + // Optional hook, by default, do nothing 67 + } 68 + 69 + // This is our callback for the background curl multi-request. 70 + // Puts the data read in on the readBuffer for processing. 71 + private function read($ch, $data) { 72 + $info = curl_getinfo($ch); 73 + $length = strlen($data); 74 + $this->readBuffers[$info['url']] .= $data; 75 + return $length; 76 + } 77 + 78 + private function blockingMultiExec() { 79 + do { 80 + $status = curl_multi_exec($this->multiHandle, $this->active); 81 + } while ($status == CURLM_CALL_MULTI_PERFORM); 82 + 83 + // Check for errors 84 + if ($status != CURLM_OK) { 85 + throw new Exception( 86 + "Phabricator Bot had a problem reading from stream."); 87 + } 88 + } 89 + 90 + public function getNextMessages($poll_frequency) { 91 + $messages = array(); 92 + 93 + if (!$this->active) { 94 + throw new Exception("Phabricator Bot stopped reading from stream."); 95 + } 96 + 97 + // Prod our http request 98 + curl_multi_select($this->multiHandle, $poll_frequency); 99 + $this->blockingMultiExec(); 100 + 101 + // Process anything waiting on the read buffer 102 + while ($m = $this->processReadBuffer()) { 103 + $messages[] = $m; 104 + } 105 + 106 + return $messages; 107 + } 108 + 109 + private function processReadBuffer() { 110 + foreach ($this->readBuffers as $url => &$buffer) { 111 + $until = strpos($buffer, "}\r"); 112 + if ($until == false) { 113 + continue; 114 + } 115 + 116 + $message = substr($buffer, 0, $until + 1); 117 + $buffer = substr($buffer, $until + 2); 118 + 119 + $m_obj = json_decode($message, true); 120 + if ($message = $this->processMessage($m_obj)) { 121 + return $message; 122 + } 123 + } 124 + 125 + // If we're here, there's nothing to process 126 + return false; 127 + } 128 + 129 + protected function performPost($endpoint, $data = Null) { 130 + $uri = new PhutilURI($this->server); 131 + $uri->setPath($endpoint); 132 + 133 + $payload = json_encode($data); 134 + 135 + list($output) = id(new HTTPSFuture($uri)) 136 + ->setMethod('POST') 137 + ->addHeader('Content-Type', 'application/json') 138 + ->addHeader('Authorization', $this->getAuthorizationHeader()) 139 + ->setData($payload) 140 + ->resolvex(); 141 + 142 + $output = trim($output); 143 + if (strlen($output)) { 144 + return json_decode($output, true); 145 + } 146 + 147 + return true; 148 + } 149 + 150 + protected function getAuthorizationHeader() { 151 + return 'Basic '.base64_encode($this->authtoken.':x'); 152 + } 153 + 154 + abstract protected function buildStreamingUrl($channel); 155 + 156 + abstract protected function processMessage($raw_object); 157 + } 158 +
+78
src/infrastructure/daemon/bot/adapter/PhabricatorBotFlowdockProtocolAdapter.php
··· 1 + <?php 2 + 3 + final class PhabricatorBotFlowdockProtocolAdapter 4 + extends PhabricatorBotBaseStreamingProtocolAdapter { 5 + 6 + protected function buildStreamingUrl($channel) { 7 + $organization = $this->getConfig('organization'); 8 + $ssl = $this->getConfig('ssl'); 9 + 10 + $url = ($ssl) ? "https://" : "http://"; 11 + $url .= "stream.flowdock.com/flows/{$organization}/{$channel}"; 12 + 13 + return $url; 14 + } 15 + 16 + protected function processMessage($m_obj) { 17 + $command = null; 18 + switch ($m_obj['event']) { 19 + case 'message': 20 + $command = 'MESSAGE'; 21 + break; 22 + default: 23 + // For now, ignore anything which we don't otherwise know about. 24 + break; 25 + } 26 + 27 + if ($command === null) { 28 + return false; 29 + } 30 + 31 + // TODO: These should be usernames, not user IDs. 32 + $sender = id(new PhabricatorBotUser()) 33 + ->setName($m_obj['user']); 34 + 35 + $target = id(new PhabricatorBotChannel()) 36 + ->setName($m_obj['flow']); 37 + 38 + return id(new PhabricatorBotMessage()) 39 + ->setCommand($command) 40 + ->setSender($sender) 41 + ->setTarget($target) 42 + ->setBody($m_obj['content']); 43 + } 44 + 45 + public function writeMessage(PhabricatorBotMessage $message) { 46 + switch ($message->getCommand()) { 47 + case 'MESSAGE': 48 + $this->speak( 49 + $message->getBody(), 50 + $message->getTarget()); 51 + break; 52 + } 53 + } 54 + 55 + private function speak( 56 + $body, 57 + PhabricatorBotTarget $flow) { 58 + 59 + list($organization, $room_id) = explode(":", $flow->getName()); 60 + 61 + $this->performPost( 62 + "/flows/{$organization}/{$room_id}/messages", 63 + array( 64 + 'event' => 'message', 65 + 'content' => $body)); 66 + } 67 + 68 + public function __destruct() { 69 + if ($this->readHandles) { 70 + foreach ($this->readHandles as $read_handle) { 71 + curl_multi_remove_handle($this->multiHandle, $read_handle); 72 + curl_close($read_handle); 73 + } 74 + } 75 + 76 + curl_multi_close($this->multiHandle); 77 + } 78 + }
+9 -140
src/infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php
··· 1 1 <?php 2 2 3 3 final class PhabricatorCampfireProtocolAdapter 4 - extends PhabricatorBaseProtocolAdapter { 5 - 6 - private $readBuffers; 7 - private $authtoken; 8 - private $server; 9 - private $readHandles; 10 - private $multiHandle; 11 - private $active; 12 - private $inRooms = array(); 13 - 14 - public function connect() { 15 - $this->server = $this->getConfig('server'); 16 - $this->authtoken = $this->getConfig('authtoken'); 17 - $ssl = $this->getConfig('ssl', false); 18 - $rooms = $this->getConfig('join'); 19 - 20 - // First, join the room 21 - if (!$rooms) { 22 - throw new Exception("Not configured to join any rooms!"); 23 - } 4 + extends PhabricatorBotBaseStreamingProtocolAdapter { 24 5 25 - $this->readBuffers = array(); 6 + protected function buildStreamingUrl($channel) { 7 + $ssl = $this->getConfig('ssl'); 26 8 27 - // Set up our long poll in a curl multi request so we can 28 - // continue running while it executes in the background 29 - $this->multiHandle = curl_multi_init(); 30 - $this->readHandles = array(); 9 + $url = ($ssl) ? "https://" : "http://"; 10 + $url .= "streaming.campfirenow.com/room/{$channel}/live.json"; 31 11 32 - foreach ($rooms as $room_id) { 33 - $this->joinRoom($room_id); 34 - 35 - // Set up the curl stream for reading 36 - $url = ($ssl) ? "https://" : "http://"; 37 - $url .= "streaming.campfirenow.com/room/{$room_id}/live.json"; 38 - $this->readHandle[$url] = curl_init(); 39 - curl_setopt($this->readHandle[$url], CURLOPT_URL, $url); 40 - curl_setopt($this->readHandle[$url], CURLOPT_RETURNTRANSFER, true); 41 - curl_setopt($this->readHandle[$url], CURLOPT_FOLLOWLOCATION, 1); 42 - curl_setopt( 43 - $this->readHandle[$url], 44 - CURLOPT_USERPWD, 45 - $this->authtoken.':x'); 46 - curl_setopt( 47 - $this->readHandle[$url], 48 - CURLOPT_HTTPHEADER, 49 - array("Content-type: application/json")); 50 - curl_setopt( 51 - $this->readHandle[$url], 52 - CURLOPT_WRITEFUNCTION, 53 - array($this, 'read')); 54 - curl_setopt($this->readHandle[$url], CURLOPT_BUFFERSIZE, 128); 55 - curl_setopt($this->readHandle[$url], CURLOPT_TIMEOUT, 0); 56 - 57 - curl_multi_add_handle($this->multiHandle, $this->readHandle[$url]); 58 - 59 - // Initialize read buffer 60 - $this->readBuffers[$url] = ''; 61 - } 62 - 63 - $this->active = null; 64 - $this->blockingMultiExec(); 12 + return $url; 65 13 } 66 14 67 - // This is our callback for the background curl multi-request. 68 - // Puts the data read in on the readBuffer for processing. 69 - private function read($ch, $data) { 70 - $info = curl_getinfo($ch); 71 - $length = strlen($data); 72 - $this->readBuffers[$info['url']] .= $data; 73 - return $length; 74 - } 75 - 76 - private function blockingMultiExec() { 77 - do { 78 - $status = curl_multi_exec($this->multiHandle, $this->active); 79 - } while ($status == CURLM_CALL_MULTI_PERFORM); 80 - 81 - // Check for errors 82 - if ($status != CURLM_OK) { 83 - throw new Exception( 84 - "Phabricator Bot had a problem reading from campfire."); 85 - } 86 - } 87 - 88 - public function getNextMessages($poll_frequency) { 89 - $messages = array(); 90 - 91 - if (!$this->active) { 92 - throw new Exception("Phabricator Bot stopped reading from campfire."); 93 - } 94 - 95 - // Prod our http request 96 - curl_multi_select($this->multiHandle, $poll_frequency); 97 - $this->blockingMultiExec(); 98 - 99 - // Process anything waiting on the read buffer 100 - while ($m = $this->processReadBuffer()) { 101 - $messages[] = $m; 102 - } 103 - 104 - return $messages; 105 - } 106 - 107 - private function processReadBuffer() { 108 - foreach ($this->readBuffers as $url => &$buffer) { 109 - $until = strpos($buffer, "}\r"); 110 - if ($until == false) { 111 - continue; 112 - } 113 - 114 - $message = substr($buffer, 0, $until + 1); 115 - $buffer = substr($buffer, $until + 2); 116 - 117 - $m_obj = json_decode($message, true); 15 + protected function processMessage($m_obj) { 118 16 $command = null; 119 17 switch ($m_obj['type']) { 120 18 case 'TextMessage': ··· 129 27 } 130 28 131 29 if ($command === null) { 132 - continue; 30 + return false; 133 31 } 134 32 135 33 // TODO: These should be usernames, not user IDs. ··· 144 42 ->setSender($sender) 145 43 ->setTarget($target) 146 44 ->setBody($m_obj['body']); 147 - } 148 - 149 - // If we're here, there's nothing to process 150 - return false; 151 45 } 152 46 153 47 public function writeMessage(PhabricatorBotMessage $message) { ··· 172 66 } 173 67 } 174 68 175 - private function joinRoom($room_id) { 69 + protected function joinRoom($room_id) { 176 70 $this->performPost("/room/{$room_id}/join.json"); 177 71 $this->inRooms[$room_id] = true; 178 72 } ··· 197 91 'body' => $message))); 198 92 } 199 93 200 - private function performPost($endpoint, $data = Null) { 201 - $uri = new PhutilURI($this->server); 202 - $uri->setPath($endpoint); 203 - 204 - $payload = json_encode($data); 205 - 206 - list($output) = id(new HTTPSFuture($uri)) 207 - ->setMethod('POST') 208 - ->addHeader('Content-Type', 'application/json') 209 - ->addHeader('Authorization', $this->getAuthorizationHeader()) 210 - ->setData($payload) 211 - ->resolvex(); 212 - 213 - $output = trim($output); 214 - if (strlen($output)) { 215 - return json_decode($output, true); 216 - } 217 - 218 - return true; 219 - } 220 - 221 94 public function __destruct() { 222 95 foreach ($this->inRooms as $room_id => $ignored) { 223 96 $this->leaveRoom($room_id); ··· 231 104 } 232 105 233 106 curl_multi_close($this->multiHandle); 234 - } 235 - 236 - private function getAuthorizationHeader() { 237 - return 'Basic '.base64_encode($this->authtoken.':x'); 238 107 } 239 108 }