Page MenuHomec4science

PhabricatorIRCProtocolAdapter.php
No OneTemporary

File Metadata

Created
Mon, Dec 23, 08:37

PhabricatorIRCProtocolAdapter.php

<?php
final class PhabricatorIRCProtocolAdapter
extends PhabricatorBaseProtocolAdapter {
private $socket;
private $writeBuffer;
private $readBuffer;
// Hash map of command translations
public static $commandTranslations = array(
'PRIVMSG' => 'MESSAGE');
public function connect() {
$nick = idx($this->config, 'nick', 'phabot');
$server = idx($this->config, 'server');
$port = idx($this->config, 'port', 6667);
$pass = idx($this->config, 'pass');
$ssl = idx($this->config, 'ssl', false);
$user = idx($this->config, 'user', $nick);
if (!preg_match('/^[A-Za-z0-9_`[{}^|\]\\-]+$/', $nick)) {
throw new Exception(
"Nickname '{$nick}' is invalid!");
}
$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("Failed to connect, #{$errno}: {$error}");
}
$ok = stream_set_blocking($socket, false);
if (!$ok) {
throw new Exception("Failed to set stream nonblocking.");
}
$this->socket = $socket;
$this->writeMessage(
id(new PhabricatorBotMessage())
->setCommand('USER')
->setBody("{$user} 0 * :{$user}"));
if ($pass) {
$this->writeMessage(
id(new PhabricatorBotMessage())
->setCommand('PASS')
->setBody("{$pass}"));
}
$this->writeMessage(
id(new PhabricatorBotMessage())
->setCommand('NICK')
->setBody("{$nick}"));
}
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) {
throw new Exception(
"socket_select() failed: ".socket_strerror(socket_last_error()));
}
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("Remote host closed connection.");
}
do {
$data = fread($this->socket, 4096);
if ($data === false) {
throw new Exception("fread() failed!");
} 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("fwrite() failed!");
} 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()) {
$messages[] = $m;
}
return $messages;
}
private function write($message) {
$this->writeBuffer .= $message;
return $this;
}
public function writeMessage(PhabricatorBotMessage $message) {
$irc_command = $this->getIRCCommand($message->getCommand());
switch ($message->getCommand()) {
case 'MESSAGE':
$data = $irc_command.' '.
$message->getTarget().' :'.
$message->getBody()."\r\n";
break;
default:
$data = $irc_command.' '.
$message->getBody()."\r\n";
break;
}
return $this->write($data);
}
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<sender>(\S+?))(?:!\S*)? )?'. // This may not be present.
'(?P<command>[A-Z0-9]+) '.
'(?P<data>.*)'.
'$/';
$matches = null;
if (!preg_match($pattern, $message, $matches)) {
throw new Exception("Unexpected message from server: {$message}");
}
$command = $this->getBotCommand($matches['command']);
list($target, $body) = $this->parseMessageData($command, $matches['data']);
$bot_message = id(new PhabricatorBotMessage())
->setSender(idx($matches, 'sender'))
->setCommand($command)
->setTarget($target)
->setBody($body);
if (!empty($target) && strncmp($target, '#', 1) !== 0) {
$bot_message->setPublic(false);
}
return $bot_message;
}
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 getIRCCommand($original_bot_command) {
foreach (self::$commandTranslations as $irc_command=>$bot_command) {
if ($bot_command === $original_bot_command) {
return $irc_command;
}
}
return $original_bot_command;
}
private function parseMessageData($command, $data) {
switch ($command) {
case 'MESSAGE':
$matches = null;
if (preg_match('/^(\S+)\s+:?(.*)$/', $data, $matches)) {
return array(
$matches[1],
rtrim($matches[2], "\r\n"));
}
break;
}
// By default we assume there is no target, only a body
return array(
null,
$data);
}
public function __destruct() {
$this->write("QUIT Goodbye.\r\n");
fclose($this->socket);
}
}

Event Timeline