mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-10 08:52:39 +01:00
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
This commit is contained in:
parent
0e7382b102
commit
5fb56f859c
4 changed files with 249 additions and 140 deletions
|
@ -713,10 +713,12 @@ phutil_register_library_map(array(
|
|||
'PhabricatorBaseEnglishTranslation' => 'infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php',
|
||||
'PhabricatorBaseProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorBaseProtocolAdapter.php',
|
||||
'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php',
|
||||
'PhabricatorBotBaseStreamingProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorBotBaseStreamingProtocolAdapter.php',
|
||||
'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php',
|
||||
'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php',
|
||||
'PhabricatorBotDifferentialNotificationHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDifferentialNotificationHandler.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',
|
||||
|
@ -2197,10 +2199,12 @@ phutil_register_library_map(array(
|
|||
'PhabricatorBarePageView' => 'AphrontPageView',
|
||||
'PhabricatorBaseEnglishTranslation' => 'PhabricatorTranslation',
|
||||
'PhabricatorBot' => 'PhabricatorDaemon',
|
||||
'PhabricatorBotBaseStreamingProtocolAdapter' => 'PhabricatorBaseProtocolAdapter',
|
||||
'PhabricatorBotChannel' => 'PhabricatorBotTarget',
|
||||
'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler',
|
||||
'PhabricatorBotDifferentialNotificationHandler' => 'PhabricatorBotHandler',
|
||||
'PhabricatorBotFeedNotificationHandler' => 'PhabricatorBotHandler',
|
||||
'PhabricatorBotFlowdockProtocolAdapter' => 'PhabricatorBotBaseStreamingProtocolAdapter',
|
||||
'PhabricatorBotLogHandler' => 'PhabricatorBotHandler',
|
||||
'PhabricatorBotMacroHandler' => 'PhabricatorBotHandler',
|
||||
'PhabricatorBotObjectNameHandler' => 'PhabricatorBotHandler',
|
||||
|
|
|
@ -0,0 +1,158 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorBotBaseStreamingProtocolAdapter
|
||||
extends PhabricatorBaseProtocolAdapter {
|
||||
|
||||
private $readBuffers;
|
||||
private $authtoken;
|
||||
private $server;
|
||||
private $readHandles;
|
||||
private $multiHandle;
|
||||
private $active;
|
||||
private $inRooms = array();
|
||||
|
||||
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("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);
|
||||
$this->readHandle[$url] = curl_init();
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_URL, $url);
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_setopt(
|
||||
$this->readHandle[$url],
|
||||
CURLOPT_USERPWD,
|
||||
$this->authtoken.':x');
|
||||
curl_setopt(
|
||||
$this->readHandle[$url],
|
||||
CURLOPT_HTTPHEADER,
|
||||
array("Content-type: application/json"));
|
||||
curl_setopt(
|
||||
$this->readHandle[$url],
|
||||
CURLOPT_WRITEFUNCTION,
|
||||
array($this, 'read'));
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_BUFFERSIZE, 128);
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_TIMEOUT, 0);
|
||||
|
||||
curl_multi_add_handle($this->multiHandle, $this->readHandle[$url]);
|
||||
|
||||
// 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(
|
||||
"Phabricator Bot had a problem reading from stream.");
|
||||
}
|
||||
}
|
||||
|
||||
public function getNextMessages($poll_frequency) {
|
||||
$messages = array();
|
||||
|
||||
if (!$this->active) {
|
||||
throw new Exception("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 = json_decode($message, true);
|
||||
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 json_decode($output, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function getAuthorizationHeader() {
|
||||
return 'Basic '.base64_encode($this->authtoken.':x');
|
||||
}
|
||||
|
||||
abstract protected function buildStreamingUrl($channel);
|
||||
|
||||
abstract protected function processMessage($raw_object);
|
||||
}
|
||||
|
|
@ -0,0 +1,78 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorBotFlowdockProtocolAdapter
|
||||
extends PhabricatorBotBaseStreamingProtocolAdapter {
|
||||
|
||||
protected function buildStreamingUrl($channel) {
|
||||
$organization = $this->getConfig('organization');
|
||||
$ssl = $this->getConfig('ssl');
|
||||
|
||||
$url = ($ssl) ? "https://" : "http://";
|
||||
$url .= "stream.flowdock.com/flows/{$organization}/{$channel}";
|
||||
|
||||
return $url;
|
||||
}
|
||||
|
||||
protected function processMessage($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) {
|
||||
|
||||
list($organization, $room_id) = explode(":", $flow->getName());
|
||||
|
||||
$this->performPost(
|
||||
"/flows/{$organization}/{$room_id}/messages",
|
||||
array(
|
||||
'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);
|
||||
}
|
||||
}
|
|
@ -1,120 +1,18 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCampfireProtocolAdapter
|
||||
extends PhabricatorBaseProtocolAdapter {
|
||||
extends PhabricatorBotBaseStreamingProtocolAdapter {
|
||||
|
||||
private $readBuffers;
|
||||
private $authtoken;
|
||||
private $server;
|
||||
private $readHandles;
|
||||
private $multiHandle;
|
||||
private $active;
|
||||
private $inRooms = array();
|
||||
protected function buildStreamingUrl($channel) {
|
||||
$ssl = $this->getConfig('ssl');
|
||||
|
||||
public function connect() {
|
||||
$this->server = $this->getConfig('server');
|
||||
$this->authtoken = $this->getConfig('authtoken');
|
||||
$ssl = $this->getConfig('ssl', false);
|
||||
$rooms = $this->getConfig('join');
|
||||
$url = ($ssl) ? "https://" : "http://";
|
||||
$url .= "streaming.campfirenow.com/room/{$channel}/live.json";
|
||||
|
||||
// First, join the room
|
||||
if (!$rooms) {
|
||||
throw new Exception("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 = ($ssl) ? "https://" : "http://";
|
||||
$url .= "streaming.campfirenow.com/room/{$room_id}/live.json";
|
||||
$this->readHandle[$url] = curl_init();
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_URL, $url);
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_FOLLOWLOCATION, 1);
|
||||
curl_setopt(
|
||||
$this->readHandle[$url],
|
||||
CURLOPT_USERPWD,
|
||||
$this->authtoken.':x');
|
||||
curl_setopt(
|
||||
$this->readHandle[$url],
|
||||
CURLOPT_HTTPHEADER,
|
||||
array("Content-type: application/json"));
|
||||
curl_setopt(
|
||||
$this->readHandle[$url],
|
||||
CURLOPT_WRITEFUNCTION,
|
||||
array($this, 'read'));
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_BUFFERSIZE, 128);
|
||||
curl_setopt($this->readHandle[$url], CURLOPT_TIMEOUT, 0);
|
||||
|
||||
curl_multi_add_handle($this->multiHandle, $this->readHandle[$url]);
|
||||
|
||||
// Initialize read buffer
|
||||
$this->readBuffers[$url] = '';
|
||||
}
|
||||
|
||||
$this->active = null;
|
||||
$this->blockingMultiExec();
|
||||
return $url;
|
||||
}
|
||||
|
||||
// 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(
|
||||
"Phabricator Bot had a problem reading from campfire.");
|
||||
}
|
||||
}
|
||||
|
||||
public function getNextMessages($poll_frequency) {
|
||||
$messages = array();
|
||||
|
||||
if (!$this->active) {
|
||||
throw new Exception("Phabricator Bot stopped reading from campfire.");
|
||||
}
|
||||
|
||||
// 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 = json_decode($message, true);
|
||||
protected function processMessage($m_obj) {
|
||||
$command = null;
|
||||
switch ($m_obj['type']) {
|
||||
case 'TextMessage':
|
||||
|
@ -129,7 +27,7 @@ final class PhabricatorCampfireProtocolAdapter
|
|||
}
|
||||
|
||||
if ($command === null) {
|
||||
continue;
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: These should be usernames, not user IDs.
|
||||
|
@ -144,10 +42,6 @@ final class PhabricatorCampfireProtocolAdapter
|
|||
->setSender($sender)
|
||||
->setTarget($target)
|
||||
->setBody($m_obj['body']);
|
||||
}
|
||||
|
||||
// If we're here, there's nothing to process
|
||||
return false;
|
||||
}
|
||||
|
||||
public function writeMessage(PhabricatorBotMessage $message) {
|
||||
|
@ -172,7 +66,7 @@ final class PhabricatorCampfireProtocolAdapter
|
|||
}
|
||||
}
|
||||
|
||||
private function joinRoom($room_id) {
|
||||
protected function joinRoom($room_id) {
|
||||
$this->performPost("/room/{$room_id}/join.json");
|
||||
$this->inRooms[$room_id] = true;
|
||||
}
|
||||
|
@ -197,27 +91,6 @@ final class PhabricatorCampfireProtocolAdapter
|
|||
'body' => $message)));
|
||||
}
|
||||
|
||||
private 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 json_decode($output, true);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public function __destruct() {
|
||||
foreach ($this->inRooms as $room_id => $ignored) {
|
||||
$this->leaveRoom($room_id);
|
||||
|
@ -232,8 +105,4 @@ final class PhabricatorCampfireProtocolAdapter
|
|||
|
||||
curl_multi_close($this->multiHandle);
|
||||
}
|
||||
|
||||
private function getAuthorizationHeader() {
|
||||
return 'Basic '.base64_encode($this->authtoken.':x');
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue