91 \Erebot\Interfaces\
Core $bot,
92 \Erebot\Interfaces\Config\Server $config = null,
95 $this->config = $config;
97 $this->channelModules = array();
98 $this->plainModules = array();
99 $this->numerics = array();
100 $this->events = array();
101 $this->connected =
false;
102 $this->io = new \Erebot\LineIO(\Erebot\LineIO::EOL_WIN);
103 $this->collator = new \Erebot\IrcCollator\RFC1459();
104 $this->eventsProducer = new \Erebot\IrcParser($this);
106 $this->eventsProducer->setEventClasses($events);
107 $this->setURIFactory(
'\\Erebot\\URI');
108 $this->setNumericProfile(
new \Erebot\NumericProfile\RFC2812());
110 $this->addEventHandler(
112 array($this,
'handleCapabilities'),
113 new \Erebot\Event\Match\Type(
114 '\\Erebot\\Event\\ServerCapabilities'
119 $this->addEventHandler(
121 array($this,
'handleConnect'),
122 new \Erebot\Event\Match\Type(
123 '\\Erebot\\Interface\\Event\\Connect'
134 $this->socket = null;
140 $this->channelModules,
155 return $this->uriFactory;
167 $reflector = new \ReflectionClass($factory);
168 if (!$reflector->implementsInterface(
'\\Erebot\\URIInterface')) {
169 throw new \Erebot\InvalidValueException(
170 'The factory must implement \\Erebot\\URIInterface'
173 $this->uriFactory = $factory;
176 public function getNumericProfile()
178 return $this->numericProfile;
181 public function setNumericProfile(\Erebot\NumericProfile\Base $profile)
183 $this->numericProfile = $profile;
192 public function reload(\Erebot\Interfaces\Config\Server $config)
196 \Erebot\Module\Base::RELOAD_ALL
198 $this->config = $config;
215 \Erebot\Interfaces\Config\Server $config,
218 $logger = \Plop\Plop::getInstance();
220 $channelModules = $this->channelModules;
221 $plainModules = $this->plainModules;
223 $newNetCfg = $config->getNetworkCfg();
224 $newChannels = $newNetCfg->getChannels();
226 $oldNetCfg = $this->config->getNetworkCfg();
227 $oldChannels = $oldNetCfg->getChannels();
231 foreach ($oldChannels as $chan => $oldChanCfg) {
233 $newChanCfg = $newNetCfg->getChannelCfg($chan);
234 $newModules = $newChanCfg->getModules(
false);
235 foreach ($oldChanCfg->getModules(
false) as $module) {
236 if (!in_array($module, $newModules)) {
237 unset($this->channelModules[$chan][$module]);
238 } elseif (isset($this->channelModules[$chan][$module])) {
239 $this->channelModules[$chan][$module] =
240 clone $this->channelModules[$chan][$module];
243 }
catch (\Erebot\NotFoundException $e) {
244 unset($this->channelModules[$chan]);
250 $newModules = $config->getModules(
true);
251 foreach ($this->config->getModules(
true) as $module) {
252 if (!in_array($module, $newModules)) {
253 unset($this->plainModules[$module]);
254 } elseif (isset($this->plainModules[$module])) {
255 $this->plainModules[$module] =
256 clone $this->plainModules[$module];
262 foreach ($newChannels as $chanCfg) {
263 $modules = $chanCfg->getModules(
false);
264 $chan = $chanCfg->getName();
265 foreach ($modules as $module) {
272 $this->channelModules
274 }
catch (\Erebot\StopException $e) {
276 }
catch (\Exception $e) {
277 $logger->warning($e->getMessage());
282 foreach ($newModules as $module) {
289 $this->channelModules
291 }
catch (\Erebot\StopException $e) {
293 }
catch (\Exception $e) {
294 $logger->warning($e->getMessage());
299 foreach ($channelModules as $modules) {
300 foreach ($modules as $module) {
301 $module->unloadModule();
304 foreach ($plainModules as $module) {
305 $module->unloadModule();
309 public function isConnected()
311 return $this->connected;
314 public function connect()
316 if ($this->connected) {
320 $logger = \Plop\Plop::getInstance();
321 $uris = $this->config->getConnectionURI();
322 $serverUri = new \Erebot\URI($uris[count($uris) - 1]);
323 $this->socket = null;
326 $this->bot->gettext(
'Loading required modules for "%(uri)s"...'),
327 array(
'uri' => $serverUri)
331 \Erebot\Module\Base::RELOAD_ALL |
332 \Erebot\Module\Base::RELOAD_INIT
336 $nbTunnels = count($uris);
337 $factory = $this->uriFactory;
338 for ($i = 0; $i < $nbTunnels; $i++) {
339 $uri =
new $factory($uris[$i]);
340 $scheme = $uri->getScheme();
341 $upScheme = strtoupper($scheme);
343 if ($i + 1 == $nbTunnels) {
344 $cls =
'\\Erebot\\Proxy\\EndPoint\\'.$upScheme;
346 $cls =
'\\Erebot\\Proxy\\'.$upScheme;
349 if ($scheme ==
'base' || !class_exists($cls)) {
350 throw new \Erebot\InvalidValueException(
'Invalid class');
353 $port = $uri->getPort();
354 if ($port === null) {
355 $port = getservbyname($scheme,
'tcp');
357 if (!is_int($port) || $port <= 0 || $port > 65535) {
358 throw new \Erebot\InvalidValueException(
'Invalid port');
361 if ($this->socket === null) {
362 $this->socket = @stream_socket_client(
363 'tcp://' . $uri->getHost() .
':' . $port,
366 ini_get(
'default_socket_timeout'),
367 STREAM_CLIENT_CONNECT
370 if ($this->socket ===
false) {
371 throw new \Erebot\Exception(
'Could not connect');
376 if ($i + 1 < $nbTunnels) {
377 $proxy =
new $cls($this->socket);
378 if (!($proxy instanceof \Erebot\Proxy\Base)) {
379 throw new \Erebot\InvalidValueException(
'Invalid class');
382 $next =
new $factory($uris[$i + 1]);
383 $proxy->proxify($uri, $next);
385 "Successfully established connection ".
386 "through proxy '%(uri)s'",
387 array(
'uri' => $uri->toURI(
false,
false))
391 $endPoint =
new $cls();
392 if (!($endPoint instanceof \Erebot\Interfaces\Proxy\EndPoint)) {
393 throw new \Erebot\InvalidValueException(
'Invalid class');
396 $query = $uri->getQuery();
398 if ($query !== null) {
399 parse_str($query, $params);
402 stream_context_set_option(
406 isset($params[
'verify_peer'])
407 ? \Erebot\Config\Proxy::parseBoolHelper(
408 $params[
'verify_peer']
413 stream_context_set_option(
417 isset($params[
'allow_self_signed'])
418 ? \Erebot\Config\Proxy::parseBoolHelper(
419 $params[
'allow_self_signed']
424 stream_context_set_option(
428 isset($params[
'ciphers'])
435 stream_set_write_buffer($this->socket, 0);
436 if ($endPoint->requiresSSL()) {
437 stream_socket_enable_crypto(
440 STREAM_CRYPTO_METHOD_TLS_CLIENT
445 }
catch (\Exception $e) {
447 fclose($this->socket);
450 throw new \Erebot\ConnectionFailureException(
452 "Unable to connect to '%s' (%s)",
453 $uris[count($uris) - 1],
459 $this->io->setSocket($this->socket);
460 $this->dispatch($this->eventsProducer->makeEvent(
'!Logon'));
464 public function disconnect($quitMessage = null)
466 $logger = \Plop\Plop::getInstance();
467 $uris = $this->config->getConnectionURI();
469 "Disconnecting from '%(uri)s' ...",
470 array(
'uri' => $uris[count($uris) - 1])
474 $this->io->setSocket($this->socket);
476 \Erebot\Utils::stringifiable($quitMessage)
479 $this->io->push(
'QUIT'.$quitMessage);
482 while ($this->io->inWriteQueue()) {
488 $this->bot->removeConnection($this);
489 if (is_resource($this->socket)) {
490 fclose($this->socket);
493 $this->io->setSocket(null);
494 $this->socket = null;
495 $this->connected =
false;
498 public function getConfig($chan)
500 if ($chan === null) {
501 return $this->config;
505 $netCfg = $this->config->getNetworkCfg();
506 $chanCfg = $netCfg->getChannelCfg($chan);
509 }
catch (\Erebot\NotFoundException $e) {
510 return $this->config;
514 public function getSocket()
516 return $this->socket;
519 public function getBot()
524 public function getIO()
529 public function read()
531 $res = $this->io->read();
532 if ($res ===
false) {
533 $event = $this->eventsProducer->makeEvent(
'!Disconnect');
534 $this->dispatch($event);
536 if (!$event->preventDefault()) {
537 $logger = \Plop\Plop::getInstance();
538 $logger->error(
'Disconnected');
539 throw new \Erebot\ConnectionFailureException(
'Disconnected');
548 for ($i = $this->io->inReadQueue(); $i > 0; $i--) {
549 $this->eventsProducer->parseLine($this->io->pop());
553 public function write()
555 if (!$this->io->inWriteQueue()) {
556 throw new \Erebot\NotFoundException(
557 'No outgoing data needs to be handled'
561 $logger = \Plop\Plop::getInstance();
566 $rateLimiter = $this->getModule(
'\\Erebot\\Module\\RateLimiter', null,
false);
570 if (!$rateLimiter->canSend()) {
573 }
catch (\Exception $e) {
576 'Got an exception from the rate-limiter module. '.
577 'Assuming implicit approval to send the message.'
582 }
catch (\Erebot\NotFoundException $e) {
586 return $this->io->write();
628 if ($chan !== null) {
629 if (isset($channelModules[$chan][$module])) {
630 return $channelModules[$chan][$module];
632 } elseif (isset($plainModules[$module])) {
633 return $plainModules[$module];
636 if (!class_exists($module,
true)) {
637 throw new \Erebot\InvalidValueException(
"No such class '$module'");
640 if (!is_subclass_of($module,
'\\Erebot\\Module\\Base')) {
641 throw new \Erebot\InvalidValueException(
642 "Invalid module! Not a subclass of \\Erebot\\Module\\Base."
646 $reflector = new \ReflectionClass($module);
647 $instance =
new $module($chan);
648 if ($chan === null) {
649 $plainModules[$module] = $instance;
651 $channelModules[$chan][$module] = $instance;
654 $instance->reloadModule($this, $flags);
655 $logger = \Plop\Plop::getInstance();
657 $this->bot->gettext(
"Successfully loaded module '%(module)s' [%(source)s]"),
660 'source' => (substr($reflector->getFileName(), 0, 7) ==
'phar://')
661 ? $this->bot->gettext(
'PHP archive')
662 : $this->bot->gettext(
'regular file'),
668 public function loadModule($module, $chan = null)
670 return $this->realLoadModule(
673 \Erebot\Module\Base::RELOAD_ALL,
675 $this->channelModules
679 public function getModules($chan = null)
681 if ($chan !== null) {
682 $chanModules = isset($this->channelModules[$chan])
683 ? $this->channelModules[$chan]
685 return $chanModules + $this->plainModules;
687 return $this->plainModules;
690 public function getModule($name, $chan = null, $autoload =
true)
692 if ($chan !== null) {
693 if (isset($this->channelModules[$chan][$name])) {
694 return $this->channelModules[$chan][$name];
697 $netCfg = $this->config->getNetworkCfg();
698 $chanCfg = $netCfg->getChannelCfg($chan);
699 $modules = $chanCfg->getModules(
false);
700 if (in_array($name, $modules,
true)) {
702 throw new \Erebot\NotFoundException(
'No instance found');
704 return $this->loadModule($name, $chan);
708 if (isset($this->plainModules[$name])) {
709 return $this->plainModules[$name];
712 $modules = $this->config->getModules(
true);
713 if (!in_array($name, $modules,
true) || !$autoload) {
714 throw new \Erebot\NotFoundException(
'No instance found');
717 return $this->loadModule($name, null);
720 public function addNumericHandler(\Erebot\Interfaces\NumericHandler $handler)
722 $this->numerics[] = $handler;
725 public function removeNumericHandler(\Erebot\Interfaces\NumericHandler $handler)
727 $key = array_search($handler, $this->numerics);
728 if ($key ===
false) {
729 throw new \Erebot\NotFoundException(
'No such numeric handler');
731 unset($this->numerics[$key]);
734 public function addEventHandler(\Erebot\Interfaces\EventHandler $handler)
736 $this->events[] = $handler;
739 public function removeEventHandler(\Erebot\Interfaces\EventHandler $handler)
741 $key = array_search($handler, $this->events);
742 if ($key ===
false) {
743 throw new \Erebot\NotFoundException(
'No such event handler');
745 unset($this->events[$key]);
755 protected function dispatchEvent(\Erebot\Interfaces\Event\Base\Generic $event)
757 $logger = \Plop\Plop::getInstance();
759 $this->bot->gettext(
'Dispatching "%(type)s" event.'),
760 array(
'type' => get_class($event))
763 foreach ($this->events as $handler) {
764 if ($handler->handleEvent($event) ===
false) {
768 }
catch (\Erebot\ErrorReportingException $e) {
770 $logger->exception($this->bot->gettext(
'Code is not clean!'), $e);
771 $this->disconnect($e->getMessage());
784 $logger = \Plop\Plop::getInstance();
786 $this->bot->gettext(
'Dispatching numeric %(code)s.'),
787 array(
'code' => sprintf(
'%03d', $numeric->getCode()))
790 foreach ($this->numerics as $handler) {
791 if ($handler->handleNumeric($numeric) ===
false) {
795 }
catch (\Erebot\ErrorReportingException $e) {
797 $logger->exception($this->bot->gettext(
'Code is not clean!'), $e);
798 $this->disconnect($e->getMessage());
802 public function dispatch(\Erebot\Interfaces\Event\Base\Generic $event)
804 if ($event instanceof \Erebot\Interfaces\Event\Numeric) {
805 return $this->dispatchNumeric($event);
807 return $this->dispatchEvent($event);
810 public function isChannel($chan)
813 $capabilities = $this->getModule(
'\\Erebot\\Module\\ServerCapabilities', null,
false);
814 return $capabilities->isChannel($chan);
815 }
catch (\Erebot\NotFoundException $e) {
819 if (!\Erebot\Utils::stringifiable($chan)) {
820 throw new \Erebot\InvalidValueException(
821 $this->bot->gettext(
'Bad channel name')
825 $chan = (string) $chan;
826 if (!strlen($chan)) {
832 foreach (array(
' ',
',',
"\x07",
':') as $token) {
833 if (strpos($token, $chan) !==
false) {
838 if (strlen($chan) > 50) {
843 return (strpos(
'#&+!', $chan[0]) !==
false);
858 \Erebot\Event\ServerCapabilities $event
860 $module = $event->getModule();
861 $validMappings = array(
864 'rfc1459' =>
'\\Erebot\\IrcCollator\\RFC1459',
865 'strict-rfc1459' =>
'\\Erebot\\IrcCollator\\StrictRFC1459',
866 'ascii' =>
'\\Erebot\\IrcCollator\\ASCII',
868 $caseMapping = strtolower($module->getCaseMapping());
869 if (in_array($caseMapping, array_keys($validMappings))) {
870 $cls = $validMappings[$caseMapping];
871 $this->collator =
new $cls();
887 \Erebot\Interfaces\Event\Connect $event
889 $this->connected =
true;
898 public function setCollator(\Erebot\Interfaces\IrcCollator $collator)
900 $this->collator = $collator;
912 return $this->collator;
915 public function getEventsProducer()
917 return $this->eventsProducer;
$numerics
A list of numeric handlers.
setCollator(\Erebot\Interfaces\IrcCollator $collator)
Provides core functionalities for Erebot.
$connected
Whether this connection is actually... well, connected.
Handles a (possibly encrypted) connection to an IRC server.
$socket
The underlying socket, represented as a stream.
handleCapabilities(\Erebot\Interfaces\EventHandler $handler,\Erebot\Event\ServerCapabilities $event)
reload(\Erebot\Interfaces\Config\Server $config)
handleConnect(\Erebot\Interfaces\EventHandler $handler,\Erebot\Interfaces\Event\Connect $event)
$events
A list of event handlers.
$numericProfile
Numeric profile.
$plainModules
Maps modules names to modules instances.
__construct(\Erebot\Interfaces\Core $bot,\Erebot\Interfaces\Config\Server $config=null, $events=array())
loadModules(\Erebot\Interfaces\Config\Server $config, $flags)
$uriFactory
Factory to use to parse URI.
dispatchEvent(\Erebot\Interfaces\Event\Base\Generic $event)
process()
Processes commands queued in the input buffer.
$bot
A bot object implementing the Erebot::Interfaces::Core interface.
$collator
Collator for IRC nicknames.
dispatchNumeric(\Erebot\Interfaces\Event\Numeric $numeric)
An event handler which will call a callback function/method whenever a set of conditions are met...
$io
I/O manager for the socket.
realLoadModule($module, $chan, $flags, &$plainModules, &$channelModules)
$eventsProducer
Class to use to parse IRC messages and produce events from them.
$channelModules
Maps channels to their loaded modules.