]> git.localhorst.tv Git - alttp.git/blob - app/TwitchBot/TwitchBot.php
3ca2db9283e6651e521fa2fe4aeaa596d94f9e2e
[alttp.git] / app / TwitchBot / TwitchBot.php
1 <?php
2
3 namespace App\TwitchBot;
4
5 use App\Models\Channel;
6 use App\Models\TwitchToken;
7 use Monolog\Handler\StreamHandler;
8 use Monolog\Logger;
9 use Ratchet\Client\Connector;
10 use Ratchet\Client\WebSocket;
11 use Ratchet\RFC6455\Messaging\Message;
12 use React\EventLoop\Loop;
13
14 class TwitchBot {
15
16         public function __construct() {
17                 $this->logger = new Logger('TwitchBot');
18                 $this->logger->pushHandler(new StreamHandler('php://stdout', Logger::DEBUG));
19
20                 $this->token = TwitchToken::firstWhere('nick', 'localhorsttv');
21                 if (!$this->token) {
22                         throw new \Exception('unable to find access token');
23                 }
24
25                 $this->connector = new Connector();
26                 $this->connect();
27         }
28
29         public function getLogger() {
30                 return $this->logger;
31         }
32
33         public function getLoop() {
34                 return Loop::get();
35         }
36
37         public function run() {
38                 $this->shutting_down = false;
39                 $this->getLoop()->run();
40         }
41
42         public function stop() {
43                 $this->logger->info('shutting down');
44                 $this->shutting_down = true;
45                 $this->disconnect();
46                 $this->getLoop()->stop();
47         }
48
49
50         public function connect() {
51                 ($this->connector)('wss://irc-ws.chat.twitch.tv:443')->done(
52                         [$this, 'handleWsConnect'],
53                         [$this, 'handleWsConnectError'],
54                 );
55         }
56
57         public function disconnect() {
58                 $this->ws->close();
59         }
60
61         public function handleWsConnect(WebSocket $ws) {
62                 $this->logger->info('websocket connection established');
63                 $this->ws = $ws;
64                 $ws->on('message', [$this, 'handleWsMessage']);
65                 $ws->on('close', [$this, 'handleWsClose']);
66                 $ws->on('error', [$this, 'handleWsError']);
67                 $ws->send('CAP REQ :twitch.tv/tags twitch.tv/commands');
68                 $this->login();
69         }
70
71         public function handleWsConnectError(WebSocket $ws) {
72                 $this->logger->error('failed to establish websocket connection');
73         }
74
75         public function handleWsMessage(Message $message, WebSocket $ws) {
76                 $irc_messages = explode("\r\n", rtrim($message->getPayload(), "\r\n"));
77                 foreach ($irc_messages as $irc_message) {
78                         $this->logger->debug('received IRC message '.$irc_message);
79                         $this->handleIRCMessage(IRCMessage::fromString($irc_message));
80                 }
81         }
82
83         public function handleWsClose(int $op, string $reason) {
84                 $this->logger->info('websocket connection closed: '.$reason.' ['.$op.']');
85                 if (!$this->shutting_down) {
86                         $this->logger->info('reconnecting in 10 seconds');
87                         Loop::addTimer(10, [$this, 'connect']);
88                 }
89         }
90
91         public function handleWsError(\Exception $e, WebSocket $ws) {
92                 $this->logger->error('websocket error '.$e->getMessage());
93         }
94
95
96         public function handleIRCMessage(IRCMessage $msg) {
97                 if ($msg->isPrivMsg()) {
98                         $this->handlePrivMsg($msg);
99                         return;
100                 }
101                 if ($msg->isPing()) {
102                         $this->sendIRCMessage($msg->makePong());
103                         return;
104                 }
105                 if ($msg->isNotice() && $msg->getText() == 'Login authentication failed') {
106                         $this->logger->notice('login failed, refreshing access token');
107                         $this->token->refresh();
108                         $this->login();
109                         return;
110                 }
111                 if ($msg->command == '001') {
112                         // successful login
113                         $this->joinChannels();
114                         return;
115                 }
116         }
117
118         public function handlePrivMsg(IRCMessage $msg) {
119                 $target = $msg->getPrivMsgTarget();
120                 if ($target[0] != '#') return;
121                 $text = $msg->getText();
122                 if ($text[0] != '!') return;
123                 $channel = Channel::firstWhere('twitch_chat', '=', $target);
124                 if (!$channel) return;
125                 $this->logger->info('got command '.$text.' on channel '.$channel->title);
126         }
127
128         public function login() {
129                 $this->ws->send('PASS oauth:'.$this->token->access);
130                 $this->ws->send('NICK localhorsttv');
131         }
132
133         public function joinChannels() {
134                 $this->logger->info('joining channels');
135                 $channels = Channel::where('twitch_chat', '!=', '')->get();
136                 $names = [];
137                 foreach ($channels as $channel) {
138                         $names[] = $channel->twitch_chat;
139                 }
140                 $chunks = array_chunk($names, 10);
141                 foreach ($chunks as $chunk) {
142                         $this->sendIRCMessage(IRCMessage::join($chunk));
143                 }
144         }
145
146         public function sendIRCMessage(IRCMessage $msg) {
147                 $irc_message = $msg->encode();
148                 $this->logger->debug('sending IRC message '.$irc_message);
149                 $this->ws->send($irc_message);
150         }
151
152
153         private $logger;
154
155         private $token;
156
157         private $connector;
158         private $ws;
159         private $shutting_down = false;
160
161 }
162
163 ?>