]> git.localhorst.tv Git - alttp.git/blob - app/TwitchBot/TwitchChatBot.php
slightly better chat bot msg selection
[alttp.git] / app / TwitchBot / TwitchChatBot.php
1 <?php
2
3 namespace App\TwitchBot;
4
5 use App\Models\Channel;
6 use App\Models\ChatLog;
7 use Illuminate\Support\Arr;
8 use Illuminate\Support\Str;
9
10 class TwitchChatBot extends TwitchBot {
11
12         public function __construct() {
13                 parent::__construct('horstiebot');
14                 $this->updateChannels();
15                 $this->startTimer();
16                 $this->listenCommands();
17         }
18
19         public function joinChannels() {
20                 $this->getLogger()->info('joining channels');
21                 $names = [];
22                 foreach ($this->channels as $channel) {
23                         $names[] = $channel->twitch_chat;
24                 }
25                 $chunks = array_chunk($names, 10);
26                 foreach ($chunks as $chunk) {
27                         $this->sendIRCMessage(IRCMessage::join($chunk));
28                 }
29         }
30
31         public function logMessage(IRCMessage $msg) {
32                 $channel = $this->getMessageChannel($msg);
33                 if ($channel && !$channel->join) {
34                         $msg->log();
35                 }
36         }
37
38         public function handlePrivMsg(IRCMessage $msg) {
39                 if ($msg->nick == 'horstiebot') return;
40                 $channel = $this->getMessageChannel($msg);
41                 if (!$channel) return;
42                 $this->tagChannelRead($channel, $msg);
43         }
44
45
46         private function startTimer() {
47                 $this->getLoop()->addPeriodicTimer(1, function () {
48                         if (!$this->isReady()) return;
49                         foreach ($this->channels as $channel) {
50                                 $this->decideSend($channel);
51                         }
52                 });
53                 $this->getLoop()->addPeriodicTimer(60, function () {
54                         $this->updateChannels();
55                 });
56         }
57
58         private function updateChannels() {
59                 $this->channels = Channel::where('twitch_chat', '!=', '')->where('chat', '=', true)->get();
60         }
61
62         private function decideSend(Channel $channel) {
63                 $notes = $this->getNotes($channel);
64                 if ($notes['read_since_last_write'] < $notes['wait_msgs']) {
65                         return;
66                 }
67                 if (time() - $notes['last_write'] < $notes['wait_time']) {
68                         return;
69                 }
70                 if ($notes['read_since_last_write'] == $notes['wait_msgs'] && time() - $notes['last_read'] < 3) {
71                         // don't immediately respond if we crossed the msg threshold last
72                         return;
73                 }
74                 $text = $this->contextualMsg($channel);
75                 if (!$text) $text = $this->randomMsg($channel);
76                 if (!$text) return;
77                 $this->tagChannelWrite($channel);
78                 $this->sendIRCMessage(IRCMessage::privmsg($channel->twitch_chat, $text));
79         }
80
81         private function getChatSetting(Channel $channel, $name, $default = null) {
82                 if (array_key_exists($name, $channel->chat_settings)) {
83                         return $channel->chat_settings[$name];
84                 }
85                 return $default;
86         }
87
88         private function getNotes(Channel $channel) {
89                 if (!isset($this->notes[$channel->id])) {
90                         $this->notes[$channel->id] = [
91                                 'last_read' => 0,
92                                 'last_special' => '',
93                                 'last_write' => time(),
94                                 'latest_msgs' => [],
95                                 'read_since_last_write' => 0,
96                                 'wait_msgs' => $this->randomWaitMsgs($channel),
97                                 'wait_time' => $this->randomWaitTime($channel),
98                         ];
99                 }
100                 return $this->notes[$channel->id];
101         }
102
103         private function getNote(Channel $channel, $name, $default = null) {
104                 $notes = $this->getNotes($channel);
105                 if (array_key_exists($name, $notes)) {
106                         return $notes[$name];
107                 }
108                 return $default;
109         }
110
111         private function setNote(Channel $channel, $name, $value) {
112                 $this->getNotes($channel);
113                 $this->notes[$channel->id][$name] = $value;
114         }
115
116         private function checkForGG(Channel $channel) {
117                 $notes = $this->getNotes($channel);
118                 $ggs = 0;
119                 foreach ($notes['latest_msgs'] as $text) {
120                         if (ChatLog::classify($text) == 'gg') {
121                                 ++$ggs;
122                         }
123                 }
124                 return $ggs > 2;
125         }
126
127         private function checkForGLHF(Channel $channel) {
128                 $notes = $this->getNotes($channel);
129                 $gls = 0;
130                 foreach ($notes['latest_msgs'] as $text) {
131                         if (ChatLog::classify($text) == 'gl') {
132                                 ++$gls;
133                         }
134                 }
135                 return $gls > 2;
136         }
137
138         private function checkForGreeting(Channel $channel) {
139                 $notes = $this->getNotes($channel);
140                 $his = 0;
141                 foreach ($notes['latest_msgs'] as $text) {
142                         if (ChatLog::classify($text) == 'hi') {
143                                 ++$his;
144                         }
145                 }
146                 return $his > 2;
147         }
148
149         private function checkForHype(Channel $channel) {
150                 $notes = $this->getNotes($channel);
151                 $hypes = 0;
152                 foreach ($notes['latest_msgs'] as $text) {
153                         if (ChatLog::classify($text) == 'hype') {
154                                 ++$hypes;
155                         }
156                 }
157                 return $hypes > 2;
158         }
159
160         private function checkForLaughter(Channel $channel) {
161                 $notes = $this->getNotes($channel);
162                 $lulz = 0;
163                 foreach ($notes['latest_msgs'] as $text) {
164                         if (ChatLog::classify($text) == 'lol') {
165                                 ++$lulz;
166                         }
167                 }
168                 return $lulz > 2;
169         }
170
171         private function checkForNumbers(Channel $channel) {
172                 $notes = $this->getNotes($channel);
173                 $numbers = 0;
174                 foreach ($notes['latest_msgs'] as $text) {
175                         if (is_numeric(trim($text))) {
176                                 ++$numbers;
177                         }
178                 }
179                 return $numbers > 2;
180         }
181
182         private function checkForPog(Channel $channel) {
183                 $notes = $this->getNotes($channel);
184                 $pogs = 0;
185                 foreach ($notes['latest_msgs'] as $text) {
186                         if (ChatLog::classify($text) == 'pog') {
187                                 ++$pogs;
188                         }
189                 }
190                 return $pogs > 2;
191         }
192
193         private function checkForSalute(Channel $channel) {
194                 $notes = $this->getNotes($channel);
195                 $o7s = 0;
196                 foreach ($notes['latest_msgs'] as $text) {
197                         if (ChatLog::classify($text) == 'o7') {
198                                 ++$o7s;
199                         }
200                 }
201                 return $o7s > 2;
202         }
203
204         private function contextualMsg(Channel $channel) {
205                 $last = $this->getNote($channel, 'last_special');
206                 if ($last != 'gg' && $this->checkForGG($channel)) {
207                         $this->setNote($channel, 'last_special', 'gg');
208                         return $this->randomOfClass($channel, 'gg');
209                 }
210                 if ($last != 'number' && $this->checkForNumbers($channel)) {
211                         $this->setNote($channel, 'last_special', 'number');
212                         return $this->randomContextualNumber($channel);
213                 }
214                 if ($last != 'lol' && $this->checkForLaughter($channel)) {
215                         $this->setNote($channel, 'last_special', 'lol');
216                         return $this->randomLaughter($channel);
217                 }
218                 if ($last != 'glhf' && $this->checkForGLHF($channel)) {
219                         $this->setNote($channel, 'last_special', 'glhf');
220                         return $this->randomOfClass($channel, 'gl');
221                 }
222                 if ($last != 'hi' && $this->checkForGreeting($channel)) {
223                         $this->setNote($channel, 'last_special', 'hi');
224                         return $this->randomOfClass($channel, 'hi');
225                 }
226                 if ($last != 'hype' && $this->checkForHype($channel)) {
227                         $this->setNote($channel, 'last_special', 'hype');
228                         return $this->randomOfClass($channel, 'hype');
229                 }
230                 if ($last != 'pog' && $this->checkForPog($channel)) {
231                         $this->setNote($channel, 'last_special', 'pog');
232                         return $this->randomOfClass($channel, 'pog');
233                 }
234                 if ($last != 'o7' && $this->checkForSalute($channel)) {
235                         $this->setNote($channel, 'last_special', 'o7');
236                         return $this->randomOfClass($channel, 'o7');
237                 }
238                 return false;
239         }
240
241         private function queryChatlog(Channel $channel) {
242                 return ChatLog::where('type', '=', 'chat')
243                         ->where('banned', '=', false)
244                         ->where('created_at', '<', now()->sub(1, 'day'))
245                         ->where(function ($query) use ($channel) {
246                                 $query->whereNull('detected_language');
247                                 $query->orWhereIn('detected_language', $channel->languages);
248                         })
249                         ->inRandomOrder();
250         }
251
252         private function randomChat(Channel $channel) {
253                 $line = $this->queryChatlog($channel)
254                         ->whereIn('classification', ['hi', 'hype', 'lol', 'pog', 'unclassified'])
255                         ->first();
256                 return $line->text_content;
257         }
258
259         private function randomContextualNumber(Channel $channel) {
260                 $notes = $this->getNotes($channel);
261                 $min = 100000;
262                 $max = 0;
263                 foreach ($notes['latest_msgs'] as $text) {
264                         if (is_numeric(trim($text))) {
265                                 $number = intval(trim($text));
266                                 $min = min($min, $number);
267                                 $max = max($max, $number);
268                         }
269                 }
270                 return random_int($min, $max);
271         }
272
273         private function randomOfClass(Channel $channel, $class) {
274                 $line = $this->queryChatlog($channel)
275                         ->where('classification', '=', $class)
276                         ->first();
277                 return $line->text_content;
278         }
279
280         private function randomLaughter(Channel $channel) {
281                 return Arr::random([
282                         ':tf:',
283                         '4Head',
284                         'CarlSmile',
285                         'CruW',
286                         'DendiFace',
287                         'EleGiggle',
288                         'GunRun',
289                         'heh',
290                         'Hhhehehe',
291                         'HypeLUL',
292                         'Jebaited',
293                         'Jebasted',
294                         'KEKW',
295                         'KEKHeim',
296                         'KKona',
297                         'KomodoHype',
298                         'MaxLOL',
299                         'MingLee',
300                         'lol',
301                         'LOL!',
302                         'LUL',
303                         'OneHand',
304                         'SeemsGood',
305                         'ShadyLulu',
306                         'SoonerLater',
307                         'SUBprise',
308                         'xD',
309                         'YouDontSay',
310                         $this->randomOfClass($channel, 'lol'),
311                 ]);
312         }
313
314         private function randomMsg(Channel $channel) {
315                 $line = $this->queryChatlog($channel)->first();
316                 return $line->text_content;
317         }
318
319         private function randomWaitMsgs(Channel $channel) {
320                 $min = $this->getChatSetting($channel, 'wait_msgs_min', 1);
321                 $max = $this->getChatSetting($channel, 'wait_msgs_max', 10);
322                 return random_int($min, $max);
323         }
324
325         private function randomWaitTime(Channel $channel) {
326                 $min = $this->getChatSetting($channel, 'wait_time_min', 1);
327                 $max = $this->getChatSetting($channel, 'wait_time_max', 900);
328                 return random_int($min, $max);
329         }
330
331         private function tagChannelRead(Channel $channel, IRCMessage $msg) {
332                 $this->getNotes($channel);
333                 $this->notes[$channel->id]['last_read'] = time();
334                 ++$this->notes[$channel->id]['read_since_last_write'];
335                 if (!$msg->isKnownBot() && !$msg->scanForSpam()) {
336                         $this->notes[$channel->id]['latest_msgs'][] = $msg->getText();
337                         if (count($this->notes[$channel->id]['latest_msgs']) > 10) {
338                                 array_shift($this->notes[$channel->id]['latest_msgs']);
339                         }
340                 }
341         }
342
343         private function tagChannelWrite(Channel $channel) {
344                 $this->getNotes($channel);
345                 $this->notes[$channel->id]['last_write'] = time();
346                 $this->notes[$channel->id]['read_since_last_write'] = 0;
347                 $this->notes[$channel->id]['wait_msgs'] = $this->randomWaitMsgs($channel);
348                 $this->notes[$channel->id]['wait_time'] = $this->randomWaitTime($channel);
349         }
350
351         private $channels;
352         private $notes = [];
353
354 }