@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.

Formalize targets (users and channel) into objects

Summary:
Make users/channels/rooms into objects, so we can later sort out stuff like Campfire user IDs, Phabricator vs chat accounts, etc.

The only change here is that I removed output buffering from the macro handler. We should move throttling/buffering to adapters instead and have it apply globally.

Test Plan: Ran IRC and Campfire bots and interacted with them.

Reviewers: indiefan

Reviewed By: indiefan

CC: aran

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

+147 -111
+5
src/__phutil_library_map__.php
··· 707 707 'PhabricatorBaseEnglishTranslation' => 'infrastructure/internationalization/PhabricatorBaseEnglishTranslation.php', 708 708 'PhabricatorBaseProtocolAdapter' => 'infrastructure/daemon/bot/adapter/PhabricatorBaseProtocolAdapter.php', 709 709 'PhabricatorBot' => 'infrastructure/daemon/bot/PhabricatorBot.php', 710 + 'PhabricatorBotChannel' => 'infrastructure/daemon/bot/target/PhabricatorBotChannel.php', 710 711 'PhabricatorBotDebugLogHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDebugLogHandler.php', 711 712 'PhabricatorBotDifferentialNotificationHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotDifferentialNotificationHandler.php', 712 713 'PhabricatorBotFeedNotificationHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotFeedNotificationHandler.php', ··· 716 717 'PhabricatorBotMessage' => 'infrastructure/daemon/bot/PhabricatorBotMessage.php', 717 718 'PhabricatorBotObjectNameHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php', 718 719 'PhabricatorBotSymbolHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotSymbolHandler.php', 720 + 'PhabricatorBotTarget' => 'infrastructure/daemon/bot/target/PhabricatorBotTarget.php', 721 + 'PhabricatorBotUser' => 'infrastructure/daemon/bot/target/PhabricatorBotUser.php', 719 722 'PhabricatorBotWhatsNewHandler' => 'infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php', 720 723 'PhabricatorBuiltinPatchList' => 'infrastructure/storage/patch/PhabricatorBuiltinPatchList.php', 721 724 'PhabricatorButtonsExample' => 'applications/uiexample/examples/PhabricatorButtonsExample.php', ··· 2163 2166 'PhabricatorBarePageView' => 'AphrontPageView', 2164 2167 'PhabricatorBaseEnglishTranslation' => 'PhabricatorTranslation', 2165 2168 'PhabricatorBot' => 'PhabricatorDaemon', 2169 + 'PhabricatorBotChannel' => 'PhabricatorBotTarget', 2166 2170 'PhabricatorBotDebugLogHandler' => 'PhabricatorBotHandler', 2167 2171 'PhabricatorBotDifferentialNotificationHandler' => 'PhabricatorBotHandler', 2168 2172 'PhabricatorBotFeedNotificationHandler' => 'PhabricatorBotHandler', ··· 2170 2174 'PhabricatorBotMacroHandler' => 'PhabricatorBotHandler', 2171 2175 'PhabricatorBotObjectNameHandler' => 'PhabricatorBotHandler', 2172 2176 'PhabricatorBotSymbolHandler' => 'PhabricatorBotHandler', 2177 + 'PhabricatorBotUser' => 'PhabricatorBotTarget', 2173 2178 'PhabricatorBotWhatsNewHandler' => 'PhabricatorBotHandler', 2174 2179 'PhabricatorBuiltinPatchList' => 'PhabricatorSQLPatchList', 2175 2180 'PhabricatorButtonsExample' => 'PhabricatorUIExample',
+2 -18
src/infrastructure/daemon/bot/PhabricatorBotMessage.php
··· 13 13 $this->public = true; 14 14 } 15 15 16 - public function setSender($sender) { 16 + public function setSender(PhabricatorBotTarget $sender = null) { 17 17 $this->sender = $sender; 18 18 return $this; 19 19 } ··· 40 40 return $this->body; 41 41 } 42 42 43 - public function setTarget($target) { 43 + public function setTarget(PhabricatorBotTarget $target = null) { 44 44 $this->target = $target; 45 45 return $this; 46 46 } ··· 49 49 return $this->target; 50 50 } 51 51 52 - public function isPublic() { 53 - return $this->public; 54 - } 55 - 56 - public function setPublic($is_public) { 57 - $this->public = $is_public; 58 - return $this; 59 - } 60 - 61 - public function getReplyTo() { 62 - if ($this->public) { 63 - return $this->target; 64 - } else { 65 - return $this->sender; 66 - } 67 - } 68 52 }
+33 -4
src/infrastructure/daemon/bot/adapter/PhabricatorCampfireProtocolAdapter.php
··· 115 115 $buffer = substr($buffer, $until + 2); 116 116 117 117 $m_obj = json_decode($message, true); 118 + $command = null; 119 + switch ($m_obj['type']) { 120 + case 'TextMessage': 121 + $command = 'MESSAGE'; 122 + break; 123 + case 'PasteMessage': 124 + $command = 'PASTE'; 125 + break; 126 + default: 127 + // For now, ignore anything which we don't otherwise know about. 128 + break; 129 + } 130 + 131 + if ($command === null) { 132 + continue; 133 + } 134 + 135 + // TODO: These should be usernames, not user IDs. 136 + $sender = id(new PhabricatorBotUser()) 137 + ->setName($m_obj['user_id']); 138 + 139 + $target = id(new PhabricatorBotChannel()) 140 + ->setName($m_obj['room_id']); 118 141 119 142 return id(new PhabricatorBotMessage()) 120 - ->setCommand('MESSAGE') 121 - ->setSender($m_obj['user_id']) 122 - ->setTarget($m_obj['room_id']) 143 + ->setCommand($command) 144 + ->setSender($sender) 145 + ->setTarget($target) 123 146 ->setBody($m_obj['body']); 124 147 } 125 148 ··· 159 182 unset($this->inRooms[$room_id]); 160 183 } 161 184 162 - private function speak($message, $room_id, $type='TextMessage') { 185 + private function speak( 186 + $message, 187 + PhabricatorBotTarget $channel, 188 + $type = 'TextMessage') { 189 + 190 + $room_id = $channel->getName(); 191 + 163 192 $this->performPost( 164 193 "/room/{$room_id}/speak.json", 165 194 array(
+20 -7
src/infrastructure/daemon/bot/adapter/PhabricatorIRCProtocolAdapter.php
··· 128 128 switch ($message->getCommand()) { 129 129 case 'MESSAGE': 130 130 $data = $irc_command.' '. 131 - $message->getTarget().' :'. 131 + $message->getTarget()->getName().' :'. 132 132 $message->getBody()."\r\n"; 133 133 break; 134 134 default: ··· 164 164 $command = $this->getBotCommand($matches['command']); 165 165 list($target, $body) = $this->parseMessageData($command, $matches['data']); 166 166 167 + if (!strlen($matches['sender'])) { 168 + $sender = null; 169 + } else { 170 + $sender = id(new PhabricatorBotUser()) 171 + ->setName($matches['sender']); 172 + } 173 + 167 174 $bot_message = id(new PhabricatorBotMessage()) 168 - ->setSender(idx($matches, 'sender')) 175 + ->setSender($sender) 169 176 ->setCommand($command) 170 177 ->setTarget($target) 171 178 ->setBody($body); 172 179 173 - if (!empty($target) && strncmp($target, '#', 1) !== 0) { 174 - $bot_message->setPublic(false); 175 - } 176 - 177 180 return $bot_message; 178 181 } 179 182 ··· 201 204 case 'MESSAGE': 202 205 $matches = null; 203 206 if (preg_match('/^(\S+)\s+:?(.*)$/', $data, $matches)) { 207 + 208 + $target_name = $matches[1]; 209 + if (strncmp($target_name, '#', 1) === 0) { 210 + $target = id(new PhabricatorBotChannel()) 211 + ->setName($target_name); 212 + } else { 213 + $target = id(new PhabricatorBotUser()) 214 + ->setName($target_name); 215 + } 216 + 204 217 return array( 205 - $matches[1], 218 + $target, 206 219 rtrim($matches[2], "\r\n")); 207 220 } 208 221 break;
+2 -3
src/infrastructure/daemon/bot/handler/PhabricatorBotHandler.php
··· 48 48 $reply = id(new PhabricatorBotMessage()) 49 49 ->setCommand('MESSAGE'); 50 50 51 - if ($original_message->isPublic()) { 51 + if ($original_message->getTarget()->isPublic()) { 52 52 // This is a public target, like a chatroom. Send the response to the 53 53 // chatroom. 54 54 $reply->setTarget($original_message->getTarget()); 55 55 } else { 56 56 // This is a private target, like a private message. Send the response 57 57 // back to the sender (presumably, we are the target). 58 - $reply->setTarget($original_message->getSender()) 59 - ->setPublic(false); 58 + $reply->setTarget($original_message->getSender()); 60 59 } 61 60 62 61 $reply->setBody($body);
+7 -8
src/infrastructure/daemon/bot/handler/PhabricatorBotLogHandler.php
··· 13 13 14 14 switch ($message->getCommand()) { 15 15 case 'MESSAGE': 16 - $reply_to = $message->getReplyTo(); 17 - if (!$reply_to) { 18 - break; 19 - } 20 - if (!$message->isPublic()) { 16 + $target = $message->getTarget(); 17 + if (!$target->isPublic()) { 21 18 // Don't log private messages, although maybe we should for debugging? 22 19 break; 23 20 } 24 21 22 + $target_name = $target->getName(); 23 + 25 24 $logs = array( 26 25 array( 27 - 'channel' => $reply_to, 26 + 'channel' => $target_name, 28 27 'type' => 'mesg', 29 28 'epoch' => time(), 30 - 'author' => $message->getSender(), 29 + 'author' => $message->getSender()->getName(), 31 30 'message' => $message->getBody(), 32 31 ), 33 32 ); ··· 54 53 55 54 if ($tell) { 56 55 $response = $this->getURI( 57 - '/chatlog/channel/'.phutil_escape_uri($reply_to).'/'); 56 + '/chatlog/channel/'.phutil_escape_uri($target_name).'/'); 58 57 59 58 $this->replyTo($message, $response); 60 59 }
+23 -53
src/infrastructure/daemon/bot/handler/PhabricatorBotMacroHandler.php
··· 8 8 private $macros; 9 9 private $regexp; 10 10 11 - private $buffer = array(); 12 11 private $next = 0; 13 12 14 13 private function init() { ··· 46 45 } 47 46 48 47 switch ($message->getCommand()) { 49 - case 'MESSAGE': 50 - $reply_to = $message->getReplyTo(); 51 - if (!$reply_to) { 52 - break; 53 - } 48 + case 'MESSAGE': 49 + $message_body = $message->getBody(); 54 50 55 - $message_body = $message->getBody(); 51 + $matches = null; 52 + if (!preg_match($this->regexp, $message_body, $matches)) { 53 + return; 54 + } 56 55 57 - $matches = null; 58 - if (!preg_match($this->regexp, $message_body, $matches)) { 59 - return; 60 - } 61 - 62 - $macro = $matches[1]; 56 + $macro = $matches[1]; 63 57 64 - $ascii = idx($this->macros[$macro], 'ascii'); 65 - if ($ascii === false) { 66 - return; 67 - } 68 - 69 - if (!$ascii) { 70 - $this->macros[$macro]['ascii'] = $this->rasterize( 71 - $this->macros[$macro], 72 - $this->getConfig('macro.size', 48), 73 - $this->getConfig('macro.aspect', 0.66)); 74 - $ascii = $this->macros[$macro]['ascii']; 75 - } 76 - 77 - foreach ($ascii as $line) { 78 - $this->buffer[$reply_to][] = $line; 79 - } 80 - break; 81 - } 82 - } 58 + $ascii = idx($this->macros[$macro], 'ascii'); 59 + if ($ascii === false) { 60 + return; 61 + } 83 62 84 - public function runBackgroundTasks() { 85 - if (microtime(true) < $this->next) { 86 - return; 87 - } 63 + if (!$ascii) { 64 + $this->macros[$macro]['ascii'] = $this->rasterize( 65 + $this->macros[$macro], 66 + $this->getConfig('macro.size', 48), 67 + $this->getConfig('macro.aspect', 0.66)); 68 + $ascii = $this->macros[$macro]['ascii']; 69 + } 88 70 89 - foreach ($this->buffer as $channel => $lines) { 90 - if (empty($lines)) { 91 - unset($this->buffer[$channel]); 92 - continue; 93 - } 94 - foreach ($lines as $key => $line) { 95 - $this->writeMessage( 96 - id(new PhabricatorBotMessage()) 97 - ->setCommand('MESSAGE') 98 - ->setTarget($channel) 99 - ->setBody($line)); 100 - unset($this->buffer[$channel][$key]); 101 - break 2; 102 - } 71 + $target_name = $message->getTarget()->getName(); 72 + foreach ($ascii as $line) { 73 + $this->replyTo($message, $line); 74 + } 75 + break; 103 76 } 104 - 105 - $sleep = $this->getConfig('macro.sleep', 0.25); 106 - $this->next = microtime(true) + ((mt_rand(75, 150) / 100) * $sleep); 107 77 } 108 78 109 79 public function rasterize($macro, $size, $aspect) {
+5 -5
src/infrastructure/daemon/bot/handler/PhabricatorBotObjectNameHandler.php
··· 170 170 // in public channels, so we avoid spamming the chat over and over 171 171 // again for discsussions of a specific revision, for example. 172 172 173 - $reply_to = $original_message->getReplyTo(); 174 - if (empty($this->recentlyMentioned[$reply_to])) { 175 - $this->recentlyMentioned[$reply_to] = array(); 173 + $target_name = $original_message->getTarget()->getName(); 174 + if (empty($this->recentlyMentioned[$target_name])) { 175 + $this->recentlyMentioned[$target_name] = array(); 176 176 } 177 177 178 178 $quiet_until = idx( 179 - $this->recentlyMentioned[$reply_to], 179 + $this->recentlyMentioned[$target_name], 180 180 $phid, 181 181 0) + (60 * 10); 182 182 ··· 185 185 continue; 186 186 } 187 187 188 - $this->recentlyMentioned[$reply_to][$phid] = time(); 188 + $this->recentlyMentioned[$target_name][$phid] = time(); 189 189 $this->replyTo($original_message, $description); 190 190 } 191 191 break;
+4 -13
src/infrastructure/daemon/bot/handler/PhabricatorBotWhatsNewHandler.php
··· 13 13 14 14 switch ($message->getCommand()) { 15 15 case 'MESSAGE': 16 - $reply_to = $message->getReplyTo(); 17 - if (!$reply_to) { 18 - break; 19 - } 20 - 21 16 $message_body = $message->getBody(); 22 17 23 18 $prompt = '~what( i|\')?s new\?~i'; ··· 27 22 } 28 23 $this->floodblock = time() + 60; 29 24 30 - $this->getLatest($reply_to); 25 + $this->getLatest($message); 31 26 } 32 27 break; 33 28 } 34 29 } 35 30 36 - public function getLatest($reply_to) { 31 + public function getLatest(PhabricatorBotMessage $message) { 37 32 $latest = $this->getConduit()->callMethodSynchronous( 38 33 'feed.query', 39 34 array( 40 - 'limit'=>5 35 + 'limit' => 5 41 36 )); 42 37 43 38 $phids = array(); ··· 112 107 // $content = "{$bold}{$user}{$reset} {$gray}{$action} {$blue}{$bold}". 113 108 // "{$title}{$reset} - {$gray}{$uri}{$reset}"; 114 109 $content = "{$user} {$action} {$title} - {$uri}"; 115 - $this->writeMessage( 116 - id(new PhabricatorBotMessage()) 117 - ->setCommand('MESSAGE') 118 - ->setTarget($reply_to) 119 - ->setBody($content)); 110 + $this->replyTo($message, $content); 120 111 } 121 112 return; 122 113 }
+12
src/infrastructure/daemon/bot/target/PhabricatorBotChannel.php
··· 1 + <?php 2 + 3 + /** 4 + * Represents a group/public space, like an IRC channel or a Campfire room. 5 + */ 6 + final class PhabricatorBotChannel extends PhabricatorBotTarget { 7 + 8 + public function isPublic() { 9 + return true; 10 + } 11 + 12 + }
+22
src/infrastructure/daemon/bot/target/PhabricatorBotTarget.php
··· 1 + <?php 2 + 3 + /** 4 + * Represents something which can be the target of messages, like a user or 5 + * channel. 6 + */ 7 + abstract class PhabricatorBotTarget { 8 + 9 + private $name; 10 + 11 + public function setName($name) { 12 + $this->name = $name; 13 + return $this; 14 + } 15 + 16 + public function getName() { 17 + return $this->name; 18 + } 19 + 20 + abstract public function isPublic(); 21 + 22 + }
+12
src/infrastructure/daemon/bot/target/PhabricatorBotUser.php
··· 1 + <?php 2 + 3 + /** 4 + * Represents an individual user. 5 + */ 6 + final class PhabricatorBotUser extends PhabricatorBotTarget { 7 + 8 + public function isPublic() { 9 + return false; 10 + } 11 + 12 + }