]> git.localhorst.tv Git - alttp.git/blobdiff - app/TwitchBot/TwitchBot.php
respond to whispers
[alttp.git] / app / TwitchBot / TwitchBot.php
index fd283789b9878187907ef60baed3cc30bc3d350b..242ac927021dfd6edace3e196f95f3f36ec87448 100644 (file)
@@ -1,7 +1,10 @@
 <?php
 
 namespace App\TwitchBot;
-use Illuminate\Support\Facades\Http;
+
+use App\Models\Channel;
+use App\Models\TwitchBotCommand;
+use App\Models\TwitchToken;
 use Monolog\Handler\StreamHandler;
 use Monolog\Logger;
 use Ratchet\Client\Connector;
@@ -11,14 +14,22 @@ use React\EventLoop\Loop;
 
 class TwitchBot {
 
-       public function __construct() {
+       public function __construct($nick) {
+               $this->nick = $nick;
                $this->logger = new Logger('TwitchBot');
                $this->logger->pushHandler(new StreamHandler('php://stdout', Logger::INFO));
 
-               $this->fetchToken();
+               $this->token = TwitchToken::firstWhere('nick', $nick);
+               if (!$this->token) {
+                       throw new \Exception('unable to find access token');
+               }
+               if ($this->token->hasExpired()) {
+                       $this->token->refresh();
+               }
 
                $this->connector = new Connector();
                $this->connect();
+               $this->startPinger();
        }
 
        public function getLogger() {
@@ -29,31 +40,23 @@ class TwitchBot {
                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 fetchToken() {
-               $this->logger->info('acquiring token');
-               $rsp = Http::post('https://id.twitch.tv/oauth2/token', [
-                       'client_id' => config('twitch.client_id'),
-                       'client_secret' => config('twitch.client_secret'),
-                       'code' => config('twitch.code'),
-                       'grant_type' => 'authorization_code',
-                       'redirect_uri' => config('twitch.redirect_uri'),
-               ]);
-               var_dump($rsp);
-               var_dump($rsp->body());
-               $this->token = $rsp->json();
-       }
-
-
        public function connect() {
                ($this->connector)('wss://irc-ws.chat.twitch.tv:443')->done(
                        [$this, 'handleWsConnect'],
@@ -66,14 +69,13 @@ class TwitchBot {
        }
 
        public function handleWsConnect(WebSocket $ws) {
-               $this->logger->info('websocket connection estblished');
+               $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->on('message', [$this, 'handleWsMessage']);
+               $ws->on('close', [$this, 'handleWsClose']);
+               $ws->on('error', [$this, 'handleWsError']);
                $ws->send('CAP REQ :twitch.tv/tags twitch.tv/commands');
-               $ws->send('PASS oauth:'.$this->token->access_token);
-               $ws->send('NICK localhorsttv');
+               $this->login();
        }
 
        public function handleWsConnectError(WebSocket $ws) {
@@ -81,26 +83,154 @@ class TwitchBot {
        }
 
        public function handleWsMessage(Message $message, WebSocket $ws) {
-               $this->logger->info('websocket message received');
-               var_dump($message->getPayload());
+               $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) {
+       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) {
+       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->isWhisper()) {
+                       $this->handleWhisper($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;
+               }
+               if ($msg->command == 'GLOBALUSERSTATE') {
+                       // receive own user metadata
+                       $this->handleUserState($msg);
+                       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 handleUserState(IRCMessage $msg) {
+               if (isset($msg->tags['user-id'])) {
+                       $this->user_id = $msg->tags['user-id'];
+               }
+       }
+
+       public function handleWhisper(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();
+       }
+
+       public function sendWhisper($to, $msg) {
+               $this->logger->info('sending whisper to '.$to.': '.$msg);
+               try {
+                       $response = $this->token->request()->post('/whispers?from_user_id='.$this->user_id.'&to_user_id='.$to, [
+                               'message' => $msg,
+                       ]);
+                       if (!$response->successful()) {
+                               $this->logger->error('sending whisper to '.$to.': '.$response->status());
+                       }
+               } catch (\Exception $e) {
+                       $this->logger->error('sending whisper to '.$to.': '.$e->getMessage());
+               }
+       }
+
+
+       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 $loop;
 
+       private $nick;
        private $token;
+       private $user_id = '';
 
        private $connector;
        private $ws;
+       private $ready = false;
+       private $shutting_down = false;
+
+       private $last_contact;
 
 }