nick = $nick; $this->logger = new Logger('TwitchBot'); $this->logger->pushHandler(new StreamHandler('php://stdout', Logger::INFO)); $this->token = TwitchToken::firstWhere('nick', $nick); if (!$this->token) { throw new \Exception('unable to find access token'); } $this->connector = new Connector(); $this->connect(); $this->startPinger(); } public function getLogger() { return $this->logger; } public function getLoop() { return Loop::get(); } public function isReady() { return $this->ready; } public function run() { $this->shutting_down = false; $this->getLoop()->run(); } public function stop() { $this->logger->info('shutting down'); $this->shutting_down = true; $this->disconnect(); $this->getLoop()->stop(); } public function connect() { ($this->connector)('wss://irc-ws.chat.twitch.tv:443')->done( [$this, 'handleWsConnect'], [$this, 'handleWsConnectError'], ); } public function disconnect() { $this->ws->close(); } public function handleWsConnect(WebSocket $ws) { $this->logger->info('websocket connection established'); $this->ws = $ws; $ws->on('message', [$this, 'handleWsMessage']); $ws->on('close', [$this, 'handleWsClose']); $ws->on('error', [$this, 'handleWsError']); $ws->send('CAP REQ :twitch.tv/tags twitch.tv/commands'); $this->login(); } public function handleWsConnectError(WebSocket $ws) { $this->logger->error('failed to establish websocket connection'); } public function handleWsMessage(Message $message, WebSocket $ws) { $irc_messages = explode("\r\n", rtrim($message->getPayload(), "\r\n")); foreach ($irc_messages as $irc_message) { $this->logger->info('received IRC message '.$irc_message); $this->handleIRCMessage(IRCMessage::fromString($irc_message)); } } public function handleWsClose(int $op, string $reason) { $this->ready = false; $this->logger->info('websocket connection closed: '.$reason.' ['.$op.']'); if (!$this->shutting_down) { $this->logger->info('reconnecting in 5 seconds'); Loop::addTimer(5, [$this, 'connect']); } } public function handleWsError(\Exception $e, WebSocket $ws) { $this->logger->error('websocket error '.$e->getMessage()); } public function handleIRCMessage(IRCMessage $msg) { $this->last_contact = time(); if ($msg->isPing()) { $this->sendIRCMessage($msg->makePong()); return; } if ($msg->isPong()) { return; } $this->logMessage($msg); if ($msg->isPrivMsg()) { $this->handlePrivMsg($msg); return; } if ($msg->isNotice() && $msg->getText() == 'Login authentication failed') { $this->logger->notice('login failed, refreshing access token'); $this->token->refresh(); $this->login(); return; } if ($msg->command == '001') { // successful login $this->joinChannels(); $this->ready = true; return; } } public function getMessageChannel(IRCMessage $msg) { $target = $msg->getPrivMsgTarget(); if (substr($target, 0, 1) !== '#') { $target = '#'.$target; } return Channel::firstWhere('twitch_chat', '=', $target); } public function logMessage(IRCMessage $msg) { } public function handlePrivMsg(IRCMessage $msg) { } public function login() { $this->ws->send('PASS oauth:'.$this->token->access); $this->ws->send('NICK '.$this->nick); } public function joinChannels() { } private function startPinger() { $this->getLoop()->addPeriodicTimer(15, function () { if (!$this->ready) return; if (time() - $this->last_contact < 60) return; try { $this->sendIRCMessage(IRCMessage::ping($this->nick)); } catch (\Exception $e) { } }); } public function sendIRCMessage(IRCMessage $msg) { $irc_message = $msg->encode(); $this->logger->info('sending IRC message '.$irc_message); $this->ws->send($irc_message); $this->last_contact = time(); } protected function listenCommands() { $this->getLoop()->addPeriodicTimer(1, function () { if (!$this->isReady()) return; $command = TwitchBotCommand::where('bot_nick', '=', $this->nick)->where('status', '=', 'pending')->oldest()->first(); if ($command) { try { $command->execute($this); } catch (\Exception $e) { } } }); } private $logger; private $nick; private $token; private $connector; private $ws; private $ready = false; private $shutting_down = false; private $last_contact; } ?>