]> git.localhorst.tv Git - alttp.git/blob - app/TwitchBot/TwitchBot.php
add chat bot
[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($nick) {
17                 $this->nick = $nick;
18                 $this->logger = new Logger('TwitchBot');
19                 $this->logger->pushHandler(new StreamHandler('php://stdout', Logger::INFO));
20
21                 $this->token = TwitchToken::firstWhere('nick', $nick);
22                 if (!$this->token) {
23                         throw new \Exception('unable to find access token');
24                 }
25
26                 $this->connector = new Connector();
27                 $this->connect();
28                 $this->startPinger();
29         }
30
31         public function getLogger() {
32                 return $this->logger;
33         }
34
35         public function getLoop() {
36                 return Loop::get();
37         }
38
39         public function isReady() {
40                 return $this->ready;
41         }
42
43         public function run() {
44                 $this->shutting_down = false;
45                 $this->getLoop()->run();
46         }
47
48         public function stop() {
49                 $this->logger->info('shutting down');
50                 $this->shutting_down = true;
51                 $this->disconnect();
52                 $this->getLoop()->stop();
53         }
54
55
56         public function connect() {
57                 ($this->connector)('wss://irc-ws.chat.twitch.tv:443')->done(
58                         [$this, 'handleWsConnect'],
59                         [$this, 'handleWsConnectError'],
60                 );
61         }
62
63         public function disconnect() {
64                 $this->ws->close();
65         }
66
67         public function handleWsConnect(WebSocket $ws) {
68                 $this->logger->info('websocket connection established');
69                 $this->ws = $ws;
70                 $ws->on('message', [$this, 'handleWsMessage']);
71                 $ws->on('close', [$this, 'handleWsClose']);
72                 $ws->on('error', [$this, 'handleWsError']);
73                 $ws->send('CAP REQ :twitch.tv/tags twitch.tv/commands');
74                 $this->login();
75         }
76
77         public function handleWsConnectError(WebSocket $ws) {
78                 $this->logger->error('failed to establish websocket connection');
79         }
80
81         public function handleWsMessage(Message $message, WebSocket $ws) {
82                 $irc_messages = explode("\r\n", rtrim($message->getPayload(), "\r\n"));
83                 foreach ($irc_messages as $irc_message) {
84                         $this->logger->info('received IRC message '.$irc_message);
85                         $this->handleIRCMessage(IRCMessage::fromString($irc_message));
86                 }
87         }
88
89         public function handleWsClose(int $op, string $reason) {
90                 $this->ready = false;
91                 $this->logger->info('websocket connection closed: '.$reason.' ['.$op.']');
92                 if (!$this->shutting_down) {
93                         $this->logger->info('reconnecting in 5 seconds');
94                         Loop::addTimer(5, [$this, 'connect']);
95                 }
96         }
97
98         public function handleWsError(\Exception $e, WebSocket $ws) {
99                 $this->logger->error('websocket error '.$e->getMessage());
100         }
101
102
103         public function handleIRCMessage(IRCMessage $msg) {
104                 $this->last_contact = time();
105                 if ($msg->isPing()) {
106                         $this->sendIRCMessage($msg->makePong());
107                         return;
108                 }
109                 if ($msg->isPong()) {
110                         return;
111                 }
112                 $this->logMessage($msg);
113                 if ($msg->isPrivMsg()) {
114                         $this->handlePrivMsg($msg);
115                         return;
116                 }
117                 if ($msg->isNotice() && $msg->getText() == 'Login authentication failed') {
118                         $this->logger->notice('login failed, refreshing access token');
119                         $this->token->refresh();
120                         $this->login();
121                         return;
122                 }
123                 if ($msg->command == '001') {
124                         // successful login
125                         $this->joinChannels();
126                         $this->ready = true;
127                         return;
128                 }
129         }
130
131         public function getMessageChannel(IRCMessage $msg) {
132                 $target = $msg->getPrivMsgTarget();
133                 if (substr($target, 0, 1) !== '#') {
134                         $target = '#'.$target;
135                 }
136                 return Channel::firstWhere('twitch_chat', '=', $target);
137         }
138
139         public function logMessage(IRCMessage $msg) {
140         }
141
142         public function handlePrivMsg(IRCMessage $msg) {
143         }
144
145         public function login() {
146                 $this->ws->send('PASS oauth:'.$this->token->access);
147                 $this->ws->send('NICK '.$this->nick);
148         }
149
150         public function joinChannels() {
151         }
152
153         private function startPinger() {
154                 $this->getLoop()->addPeriodicTimer(15, function () {
155                         if (!$this->ready) return;
156                         if (time() - $this->last_contact < 60) return;
157                         try {
158                                 $this->sendIRCMessage(IRCMessage::ping());
159                         } catch (\Exception $e) {
160                         }
161                 });
162         }
163
164         public function sendIRCMessage(IRCMessage $msg) {
165                 $irc_message = $msg->encode();
166                 $this->logger->info('sending IRC message '.$irc_message);
167                 $this->ws->send($irc_message);
168                 $this->last_contact = time();
169         }
170
171
172         private $logger;
173
174         private $nick;
175         private $token;
176
177         private $connector;
178         private $ws;
179         private $ready = false;
180         private $shutting_down = false;
181
182         private $last_contact;
183
184 }
185
186 ?>