/** * Called when the worker is ready to go. * @return void */ public function onReady() { $appInstance = $this; // a reference to this application instance for ExampleWebSocketRoute \PHPDaemon\Servers\WebSocket\Pool::getInstance()->addRoute('ExamplePubSub', function ($client) use($appInstance) { return new ExamplePubSubWebSocketRoute($client, $appInstance); }); $this->sql = \PHPDaemon\Clients\MySQL\Pool::getInstance(); $this->pubsub = new \PHPDaemon\PubSub\PubSub(); $this->pubsub->addEvent('usersNum', \PHPDaemon\PubSub\PubSubEvent::init()->onActivation(function ($pubsub) use($appInstance) { \PHPDaemon\Core\Daemon::log('onActivation'); if (isset($pubsub->event)) { \PHPDaemon\Core\Timer::setTimeout($pubsub->event, 0); return; } $pubsub->event = setTimeout(function ($timer) use($pubsub, $appInstance) { $appInstance->sql->getConnection(function ($sql) use($pubsub) { if (!$sql->connected) { return; } $sql->query('SELECT COUNT(*) `num` FROM `dle_users`', function ($sql, $success) use($pubsub) { $pubsub->pub(sizeof($sql->resultRows) ? $sql->resultRows[0]['num'] : 'null'); }); }); $timer->timeout(5000000.0); // 5 seconds }, 0); })->onDeactivation(function ($pubsub) { if (isset($pubsub->event)) { \PHPDaemon\Core\Timer::cancelTimeout($pubsub->event); } })); }
/** * Bind given socket * @param string $uri Address to bind * @return boolean Success */ public function bindSocket($uri) { $u = \PHPDaemon\Config\Object::parseCfgUri($uri); $scheme = $u['scheme']; if ($scheme === 'unix') { $socket = new \PHPDaemon\BoundSocket\UNIX($u); } elseif ($scheme === 'udp') { $socket = new \PHPDaemon\BoundSocket\UDP($u); if (isset($this->config->port->value)) { $socket->setDefaultPort($this->config->port->value); } } elseif ($scheme === 'tcp') { $socket = new \PHPDaemon\BoundSocket\TCP($u); if (isset($this->config->port->value)) { $socket->setDefaultPort($this->config->port->value); } } else { Daemon::log(get_class($this) . ': enable to bind \'' . $uri . '\': scheme \'' . $scheme . '\' is not supported'); return false; } $socket->attachTo($this); if ($socket->bindSocket()) { if ($this->enabled) { $socket->enable(); } return true; } return false; }
public function run() { $this->proxy = $this->orm->appInstance->proxy; $this->params = $this['args'][0]; try { $this->orm->appInstance->httpclient->post('https://account.fineproxy.org/proxy/download/http_auth/txt/', ['log' => $this->params['username'], 'pass' => $this->params['password'], 'logsub' => 'Войти'], function ($conn) { $proxies = []; foreach (explode("\n", $conn->body) as $addr) { $addr = trim($addr); if (!preg_match('~^\\d+\\.\\d+\\.\\d+\\.\\d+:\\d+$~', $addr)) { continue; } $proxies[] = ['type' => 'http', 'addr' => $addr, 'auth' => ['username' => $this->params['username'], 'password' => $this->params['password']]]; } $source = 'Fineproxy-' . $this->params['username']; $itime = time(); $j = (new ComplexJob(function ($j) use($source, $itime) { $this->sendResult(true); $this->proxy->removeProxyServer(['source' => $source, 'itime' => ['$lt' => $itime]]); Daemon::log('complete'); }))->maxConcurrency(5)->more(function () use(&$proxies, $source, $itime) { foreach ($proxies as $k => $proxy) { (yield 'proxy_' . $k => function ($jobname, $j) use($proxy, $itime) { $this->proxy->newProxyServer($proxy)->setOnInsertMode(false)->attr(['itime' => $itime])->save(function ($o) use($jobname, $j) { $j->setResult($jobname, $o->lastError()); }); }); } }); $j(); }); } catch (Exception $e) { $this->sendResult(false); } }
/** * Called when socket is bound * @return boolean Success */ protected function onBound() { touch($this->path); chmod($this->path, 0770); if ($this->group === null && !empty($this->uri['pass'])) { $this->group = $this->uri['pass']; } if ($this->group === null && isset(Daemon::$config->group->value)) { $this->group = Daemon::$config->group->value; } if ($this->group !== null) { if (!@chgrp($this->path, $this->group)) { unlink($this->path); Daemon::log('Couldn\'t change group of the socket \'' . $this->path . '\' to \'' . $this->group . '\'.'); return false; } } if ($this->user === null && !empty($this->uri['user'])) { $this->user = $this->uri['user']; } if ($this->user === null && isset(Daemon::$config->user->value)) { $this->user = Daemon::$config->user->value; } if ($this->user !== null) { if (!@chown($this->path, $this->user)) { unlink($this->path); Daemon::log('Couldn\'t change owner of the socket \'' . $this->path . '\' to \'' . $this->user . '\'.'); return false; } } return true; }
/** * @TODO DESCR * @return void */ public function onReady() { if ($this->user === null) { $this->connected = true; } if ($this->connected) { parent::onReady(); return; } $this->dbname = $this->path; $this->pool->getNonce(['dbname' => $this->dbname], function ($result) { if (isset($result['$err'])) { Daemon::log('MongoClient: getNonce() error with ' . $this->url . ': ' . $result['$err']); $this->finish(); } $this->pool->auth(['user' => $this->user, 'password' => $this->password, 'nonce' => $result['nonce'], 'dbname' => $this->dbname], function ($result) { if (!isset($result['ok']) || !$result['ok']) { Daemon::log('MongoClient: authentication error with ' . $this->url . ': ' . $result['errmsg']); $this->finish(); return; } $this->connected = true; $this->onReady(); }, $this); }, $this); }
/** * Called when new frame received. * @param string Frame's contents. * @param integer Frame's type. * @return void */ public function onFrame($data, $type) { if ($data === 'ping') { $this->client->sendFrame('pong', 'STRING', function ($client) { // optional. called when the frame is transmitted to the client \PHPDaemon\Core\Daemon::log('ExampleWebSocket: \'pong\' received by client.'); }); } }
public function getObject($type, $cond = null, $objOrCb = null) { $class = ClassFinder::find($type, $this->name, $this->ns); if (!class_exists($class)) { Daemon::log(get_class($this) . ': undefined class: ' . $class); return false; } return new $class($cond, $objOrCb, $this); }
public function init() { if ($this->config->enable->value) { Daemon::log(__CLASS__ . ' up.'); $this->db = \PHPDaemon\Clients\Mongo\Pool::getInstance(); $this->tags = array(); $this->minMsgInterval = 1; } }
/** * Called when the worker is ready to go * * @return void */ public function onReady() { // Adding listener // ComplexJob - STATE_WAITING $job = new \PHPDaemon\Core\ComplexJob(function ($job) { // ComplexJob - STATE_DONE /*array ( 'bar' => array ( 'job' => 'bar', 'success' => false, 'line' => 63, ), 'foo' => array ( 'job' => 'foo', 'success' => true, 'line' => 84, 'arg' => array ( 'param' => 'value', ), ), 'baz' => array ( 'job' => 'baz', 'success' => false, 'line' => 94, ), )*/ \PHPDaemon\Core\Daemon::log($job->results); }); // Adding listener // ComplexJob - STATE_WAITING $job->addListener(function ($job) { // ComplexJob - STATE_DONE }); // Adding async job foo $job('foo', $this->foo(['param' => 'value'])); // Adding with 1 sec delay \PHPDaemon\Core\Timer::add(function ($event) use($job) { // Adding async job bar $job('bar', function ($jobname, $job) { \PHPDaemon\Core\Timer::add(function ($event) use($jobname, $job) { // Job done $job->setResult($jobname, ['job' => 'bar', 'success' => false, 'line' => __LINE__]); $event->finish(); }, 1000.0 * 50); }); // Adding async job baz. Equal $job('baz', $this->baz()); $job->addJob('baz', $this->baz()); // Run jobs. All listeners will be called when the jobs done // ComplexJob - STATE_RUNNING $job(); $event->finish(); }, 1000000.0 * 1); }
/** * @param string $name * @return bool */ public function __get($name) { $class = '\\WakePHP\\Components\\' . $name; if (!class_exists($class)) { Daemon::log(get_class($this) . ': undefined class: ' . $class); return false; } return $this->{$name} = new $class($this->req); }
/** * Called when the worker is ready to go. * @return void */ public function onReady() { $_ws = \PHPDaemon\Servers\WebSocket\Pool::getInstance(); $_ws->addRoute('/sockjs', function ($client) { Daemon::log('call route at WebSocket Server'); //Daemon::log($client); return new \SockJsAppRoute($client, $this); }); $this->log('onReady at SockJsApp'); }
/** * Adds a route if it doesn't exist already. * @param string $path Route name. * @param callable $cb Route's callback. * @callback $cb ( ) * @return boolean Success. */ public function addRoute($path, $cb) { $routeName = ltrim($path, '/'); if (isset($this->routes[$routeName])) { Daemon::log(__METHOD__ . ': Route \'' . $path . '\' is already defined.'); return false; } $this->routes[$routeName] = $cb; return true; }
/** * Returns a proxy callback function with logging for debugging purposes * @param callable $cb Callback * @param mixed $name Data * @return callable */ public static function proxy($cb, $name = null) { static $i = 0; $n = ++$i; Daemon::log('Debug::proxy #' . $n . ': SPAWNED (' . json_encode($name) . ')'); return function () use($cb, $name, $n) { Daemon::log('Debug::proxy #' . $n . ': CALLED (' . json_encode($name) . ')'); call_user_func_array($cb, func_get_args()); }; }
public function onHandshake() { $this->defineLocalMethods(['serverTest' => function () { $this->callRemote('clientTest', 'foobar', function () { Daemon::log('callback called'); }); }]); $this->onFrame('{"method":"methods","arguments":[{"clientTest":{}}],"callbacks":{"1":[0,"clientTest"]},"links":[]} ', 'STRING'); parent::onHandshake(); }
/** * Called when new frame received. * @param string Frame's contents. * @param integer Frame's type. * @return void */ public function onFrame($data, $type) { D($data); Daemon::log('SockJsAppRoute'); Daemon::log($data); if ($data === 'ping') { $this->client->sendFrame('pong'); } else { $this->client->sendFrame($data); } }
public function redirect() { if (!$this->checkReferer($this->appInstance->config->domain->value)) { $this->req->setResult(['error' => 'Wrong referer']); return; } $code = Request::getString($_GET['code']); if ($code === '') { Daemon::log('Authentication failed'); $this->req->status(401); $this->req->setResult(['error' => 'Authenticaion failed']); return; } $this->appInstance->httpclient->get(['https://graph.facebook.com/oauth/access_token', 'client_id' => $this->cmp->config->facebook_app_key->value, 'redirect_uri' => $this->req->getBaseUrl() . $_SERVER['REQUEST_URI'], 'client_secret' => $this->cmp->config->facebook_app_secret->value, 'code' => $code], function ($conn, $success) { if (!$success) { $this->req->status(400); $this->req->setResult(['error' => 'request declined']); return; } parse_str($conn->body, $response); if (!isset($response['access_token'])) { $json_response = json_decode($conn->body, true); $err_message = 'no access_token'; if (isset($json_response['error']['message'])) { $err_message = $json_response['error']['message']; } $this->req->status(403); $this->req->setResult(['error' => $err_message]); return; } $this->appInstance->httpclient->get(['https://graph.facebook.com/me', 'fields' => 'id,name,email', 'format' => 'json', 'access_token' => $response['access_token']], function ($conn, $success) { $response = json_decode($conn->body, true); $id = Request::getString($response['id']); if (!$success || !is_array($response) || empty($id)) { $this->req->redirectTo('/'); return; } $data = []; if (isset($response['name'])) { $data['username'] = Request::getString($response['name']); } if (isset($response['email'])) { $data['email'] = Request::getString($response['email']); } if (isset($_REQUEST['external_token'])) { $data['external_token'] = Request::getString($_REQUEST['external_token']); } $this->req->components->account->acceptUserAuthentication('facebook', $id, $data, function () { $this->finalRedirect(); }); }); }); }
/** * Bind the socket * @return null|boolean Success. */ public function bindSocket() { if ($this->erroneous) { return false; } $port = $this->getPort(); if (!is_int($port)) { Daemon::log(get_class($this) . ' (' . get_class($this->pool) . '): no port defined for \'' . $this->uri['uri'] . '\''); return; } if ($port < 1024 && Daemon::$config->user->value !== 'root') { $this->listenerMode = false; } if ($this->listenerMode) { $this->setFd($this->host . ':' . $port); return true; } $sock = socket_create(AF_INET, SOCK_STREAM, SOL_TCP); if (!$sock) { $errno = socket_last_error(); Daemon::$process->log(get_class($this->pool) . ': Couldn\'t create TCP-socket (' . $errno . ' - ' . socket_strerror($errno) . ').'); return false; } if ($this->reuse) { if (!socket_set_option($sock, SOL_SOCKET, SO_REUSEADDR, 1)) { $errno = socket_last_error(); Daemon::$process->log(get_class($this->pool) . ': Couldn\'t set option REUSEADDR to socket (' . $errno . ' - ' . socket_strerror($errno) . ').'); return false; } if (defined('SO_REUSEPORT') && !@socket_set_option($sock, SOL_SOCKET, SO_REUSEPORT, 1)) { $errno = socket_last_error(); Daemon::$process->log(get_class($this->pool) . ': Couldn\'t set option REUSEPORT to socket (' . $errno . ' - ' . socket_strerror($errno) . ').'); return false; } } if (!@socket_bind($sock, $this->host, $port)) { $errno = socket_last_error(); Daemon::$process->log(get_class($this->pool) . ': Couldn\'t bind TCP-socket \'' . $this->host . ':' . $port . '\' (' . $errno . ' - ' . socket_strerror($errno) . ').'); return false; } socket_getsockname($sock, $this->host, $this->port); socket_set_nonblock($sock); if (!$this->listenerMode) { if (!socket_listen($sock, SOMAXCONN)) { $errno = socket_last_error(); Daemon::$process->log(get_class($this->pool) . ': Couldn\'t listen TCP-socket \'' . $this->host . ':' . $port . '\' (' . $errno . ' - ' . socket_strerror($errno) . ')'); return false; } } $this->setFd($sock); return true; }
/** * Initialize FS driver * @return void */ public static function init() { if (!Daemon::$config->eioenabled->value) { self::$supported = false; return; } if (!(self::$supported = Daemon::loadModuleIfAbsent('eio', self::$eioVer))) { Daemon::log('FS: missing pecl-eio >= ' . self::$eioVer . '. Filesystem I/O performance compromised. Consider installing pecl-eio. `pecl install http://pecl.php.net/get/eio`'); return; } self::$fdCache = new CappedStorageHits(self::$fdCacheSize); eio_init(); }
/** * Get real frame type identificator * @param $type * @return integer */ public function getFrameType($type) { if (is_int($type)) { return $type; } if ($type === null) { $type = 'STRING'; } $frametype = @constant($a = get_class($this) . '::' . $type); if ($frametype === null) { Daemon::log(__METHOD__ . ' : Undefined frametype "' . $type . '"'); } return $frametype; }
/** * Called when file $path is changed * @param string $path Path * @return void */ public function onFileChanged($path) { if (!Daemon::lintFile($path)) { Daemon::log(__METHOD__ . ': Detected parse error in ' . $path); return; } foreach ($this->files[$path] as $cb) { if (is_callable($cb) || is_array($cb)) { $cb($path); } elseif (!Daemon::$process->IPCManager->importFile($cb, $path)) { $this->rmWatch($path, $cb); } } }
public function connect() { $this->asteriskclient->getConnection($this->config->url->value, function ($conn) { $this->asteriskconn = $conn; if ($conn->connected) { $conn->bind('disconnect', function ($conn) { \PHPDaemon\Core\Daemon::log('Connection lost... Reconnect in ' . $this->config->reconnect->value . ' sec'); $this->connect(); }); } else { \PHPDaemon\Core\Daemon::log(get_class($this) . ': couldn\'t connect to ' . $this->config->url->value); } }); }
/** * Returns string of pseudo random characters * @param integer $len Length of desired string * @param string $chars String of allowed characters * @param callable $cb Callback * @param integer $pri Priority of EIO operation * @param boolean $hang If true, we shall use /dev/random instead of /dev/urandom and it may cause a delay * @return string */ public static function randomString($len = null, $chars = null, $cb = null, $pri = 0, $hang = false) { if ($len === null) { $len = 64; } if ($chars === null) { $chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789_-.'; } if ($cb === null) { Daemon::log('[CODE WARN] \\PHPDaemon\\Utils\\Crypt::randomString: non-callback way is not secure.' . ' Please rewrite your code with callback function in third argument' . PHP_EOL . Debug::backtrace()); $r = ''; $m = strlen($chars) - 1; for ($i = 0; $i < $len; ++$i) { $r .= $chars[mt_rand(0, $m)]; } return $r; } $charsLen = strlen($chars); $mask = static::getMinimalBitMask($charsLen - 1); $iterLimit = max($len, $len * 64); static::randomInts(2 * $len, function ($ints) use($cb, $chars, $charsLen, $len, $mask, &$iterLimit) { if ($ints === false) { call_user_func($cb, false); return; } $r = ''; for ($i = 0, $s = sizeof($ints); $i < $s; ++$i) { // This is wasteful, but RNGs are fast and doing otherwise adds complexity and bias $c = $ints[$i] & $mask; // Only use the random number if it is in range, otherwise try another (next iteration) if ($c < $charsLen) { $r .= static::stringIdx($chars, $c); } // Guarantee termination if (--$iterLimit <= 0) { return false; } } $d = $len - strlen($r); if ($d > 0) { static::randomString($d, $chars, function ($r2) use($r, $cb) { call_user_func($cb, $r . $r2); }); return; } call_user_func($cb, $r); }, $pri, $hang); }
/** * Constructor * @param string $path Path * @param integer $segsize Segment size * @param string $name Name * @param boolean $create Create */ public function __construct($path, $segsize, $name, $create = false) { $this->path = $path; $this->segsize = $segsize; $this->name = $name; if ($create && !touch($this->path)) { Daemon::log('Couldn\'t touch IPC file \'' . $this->path . '\'.'); exit(0); } if (!is_file($this->path) || ($this->key = ftok($this->path, 't')) === false) { Daemon::log('Couldn\'t ftok() IPC file \'' . $this->path . '\'.'); exit(0); } if (!$this->open(0, $create) && $create) { Daemon::log('Couldn\'t open IPC-' . $this->name . ' shared memory segment (key=' . $this->key . ', segsize=' . $this->segsize . ', uid=' . posix_getuid() . ', path = ' . $this->path . ').'); exit(0); } }
public function redirect() { if (!$this->checkReferer($this->appInstance->config->domain->value)) { $this->req->setResult(['error' => 'Wrong referer']); return; } $code = Request::getString($_GET['code']); if ($code === '') { Daemon::log('Authentication failed'); $this->req->status(401); $this->req->setResult(['error' => 'Authenticaion failed']); return; } $this->appInstance->httpclient->get($get = ['https://api.vk.com/oauth/access_token', 'client_id' => $this->cmp->config->vk_app_key->value, 'redirect_uri' => HTTPClient::buildUrl([$this->req->getBaseUrl() . '/component/Account/ExternalAuthRedirect/json', 'agent' => 'VK', 'backurl' => $this->getBackurl(true)]), 'client_secret' => $this->cmp->config->vk_app_secret->value, 'code' => $code], function ($conn, $success) use(&$get) { if (!$success) { $this->req->status(400); $this->req->setResult(['error' => 'request declined']); return; } Daemon::log(Debug::dump($get)); Daemon::log(Debug::dump($conn->body)); $response = json_decode(rtrim($conn->body), true); $user_id = isset($response['user_id']) ? (int) $response['user_id'] : 0; $access_token = Request::getString($response['access_token']); if ($user_id === 0 || $access_token === '') { $this->req->status(403); $this->req->setResult(['error' => 'no access token or user id']); return; } $this->appInstance->httpclient->get(['https://api.vk.com/method/users.get', 'uids' => $user_id, 'fields' => 'screen_name', 'access_token' => $access_token], function ($conn, $success) use($user_id) { $response = json_decode($conn->body, true); if (!$success || !is_array($response) || empty($user_id)) { $this->req->redirectTo($this->req->getBaseUrl(), false); $this->req->setResult(['error' => 'Unrecognized response']); return; } $data = []; if (isset($response['screen_name'])) { $data['username'] = Request::getString($response['screen_name']); } $this->req->components->account->acceptUserAuthentication('VK', $user_id, $data, [$this, 'finalRedirect']); }); }); }
public static function checkJob($req, $invalidate = true) { $token = Request::getString($req->attrs->request['captcha_token']); $text = Request::getString($req->attrs->request['captcha_text']); return function ($jobname, $job) use($token, $text, $req, $invalidate) { Daemon::log(Debug::dump([$token, $text, $invalidate])); if ($token === '') { $job->setResult($jobname, ['captcha' => 'need']); return; } $req->appInstance->captcha->check($token, $text, $invalidate, function ($result) use($jobname, $job) { $errors = []; if ($result !== 'ok') { $errors['captcha'] = $result; } $job->setResult($jobname, $errors); }); }; }
/** * @TODO DESCR * @return void */ public function onReady() { if ($this->user === null) { $this->connected = true; } if ($this->connected) { parent::onReady(); return; } $this->dbname = $this->path; $this->pool->sasl_scrum_sha1_auth(['user' => $this->user, 'password' => $this->password, 'dbname' => $this->dbname, 'conn' => $this], function ($result) { if (!isset($result['ok']) || !$result['ok']) { Daemon::log('MongoClient: authentication error with ' . $this->url . ': ' . $result['errmsg']); $this->finish(); return; } $this->connected = true; $this->onReady(); }, $this); }
public function connect() { $app = $this; $this->xmppclient->getConnection($this->config->url->value, function ($conn) use($app) { $app->xmppconn = $conn; if ($conn->connected) { \PHPDaemon\Core\Daemon::log('Jabberbot connected at ' . $this->config->url->value); $conn->presence('I\'m a robot.', 'chat'); $conn->bind('message', function ($conn, $msg) { \PHPDaemon\Core\Daemon::log('JabberBot: got message \'' . $msg['body'] . '\''); $conn->message($msg['from'], 'You just wrote: ' . $msg['body']); // send the message back }); $conn->bind('disconnect', function () use($app) { $app->connect(); }); } else { \PHPDaemon\Core\Daemon::log('Jabberbot: unable to connect (' . $this->config->url->value . ')'); } }); }
/** * Constructor. * @return void */ protected function init() { if ($this->isEnabled()) { list($class, $name) = explode(':', $this->name . ':'); $realclass = ClassFinder::find($class); $e = explode('\\', $realclass); if ($e[sizeof($e) - 1] !== 'Pool' && class_exists($realclass . '\\Pool')) { $realclass .= '\\Pool'; } if ($realclass !== $class) { $base = '\\PHPDaemon\\Core\\Pool:'; Daemon::$config->renameSection($base . $class . ($name !== '' ? ':' . $name : ''), $base . $realclass . ($name !== '' ? ':' . $name : '')); } if (!class_exists($realclass)) { Daemon::log($realclass . ' class not exists.'); return; } $this->pool = call_user_func([$realclass, 'getInstance'], $name); $this->pool->appInstance = $this; } }
/** * @param Request $req */ public function __construct($req) { $this->req = $req; $this->appInstance = $req->appInstance; $this->dbname =& $this->appInstance->dbname; Daemon::log(__CLASS__ . ' up.'); $this->db = $this->appInstance->db; $this->ORM = new MUChat($this); $this->tags = array(); $this->minMsgInterval = 1; $this->cache = \PHPDaemon\Clients\Memcache\Pool::getInstance(); $this->ipcId = sprintf('%x', crc32(Daemon::$process->pid . '-' . microtime(true))); $my_class = ClassFinder::getClassBasename($this); $this->config = isset($this->appInstance->config->{$my_class}) ? $this->appInstance->config->{$my_class} : null; $defaults = $this->getConfigDefaults(); if ($defaults) { $this->processDefaultConfig($defaults); } $this->dbname = $this->config->dbname->value; $this->init(); $this->onReady(); }
public function connect() { $app = $this; $r = $this->client->getConnection($this->config->url->value, function ($conn) use($app) { $app->conn = $conn; if ($conn->connected) { \PHPDaemon\Core\Daemon::log('IRC bot connected at ' . $this->config->url->value); $conn->join('#botwar_phpdaemon'); $conn->bind('motd', function ($conn) { //\PHPDaemon\Daemon::log($conn->motd); }); $conn->bind('privateMsg', function ($conn, $msg) { \PHPDaemon\Core\Daemon::log('IRCBot: got private message \'' . $msg['body'] . '\' from \'' . $msg['from']['orig'] . '\''); $conn->message($msg['from']['nick'], 'You just wrote: ' . $msg['body']); // send the message back }); $conn->bind('disconnect', function () use($app) { $app->connect(); }); } else { \PHPDaemon\Core\Daemon::log('IRCBot: unable to connect (' . $this->config->url->value . ')'); } }); }