]> git.localhorst.tv Git - alttp.git/blob - app/TwitchBot/TwitchChatBot.php
chat bot config
[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->randomChat($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', $this->getPreferredLanguages($channel));
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 (!ChatLog::isKnownBot($msg->nick) && !ChatLog::spammyText($msg->getText())) {
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                 if ($this->isDirectedAtMe($msg->getText()) && $this->shouldRespond($channel)) {
342                         $this->notes[$channel->id]['wait_msgs'] = 0;
343                         $this->notes[$channel->id]['wait_time'] = 0;
344                 }
345         }
346
347         private function tagChannelWrite(Channel $channel) {
348                 $this->getNotes($channel);
349                 $this->notes[$channel->id]['last_write'] = time();
350                 $this->notes[$channel->id]['read_since_last_write'] = 0;
351                 $this->notes[$channel->id]['wait_msgs'] = $this->randomWaitMsgs($channel);
352                 $this->notes[$channel->id]['wait_time'] = $this->randomWaitTime($channel);
353         }
354
355         private function getPreferredLanguages(Channel $channel) {
356                 $setting = $this->getChatSetting($channel, 'language');
357                 if ($setting) {
358                         return [$setting];
359                 }
360                 if (!empty($channel->languages)) {
361                         return $channel->languages;
362                 }
363                 return ['de'];
364         }
365
366         private function isDirectedAtMe($raw_text) {
367                 $text = strtolower($raw_text);
368                 if (strpos($text, 'horstie') !== false) {
369                         return true;
370                 }
371                 return false;
372         }
373
374         private function shouldRespond(Channel $channel) {
375                 $setting = $this->getChatSetting($channel, 'respond', 'yes');
376                 if ($setting == 'yes') {
377                         return true;
378                 }
379                 if ($setting == '50') {
380                         return random_int(0, 1);
381                 }
382                 return false;
383         }
384
385         private $channels;
386         private $notes = [];
387
388 }