]> git.localhorst.tv Git - alttp.git/blob - app/TwitchBot/TwitchChatBot.php
add some contextual reactions to chat bot
[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                         $rawText = strtolower(preg_replace('/[^\w]/', '', $text));
121                         if (Str::startsWith($rawText, 'gg') || Str::endsWith($rawText, 'gg')) {
122                                 ++$ggs;
123                         }
124                 }
125                 return $ggs > 2;
126         }
127
128         private function checkForGLHF(Channel $channel) {
129                 $notes = $this->getNotes($channel);
130                 $gls = 0;
131                 foreach ($notes['latest_msgs'] as $text) {
132                         $rawText = strtolower(preg_replace('/[^\w]/', '', $text));
133                         if (Str::contains($rawText, ['glgl', 'glhf', 'hfgl'])) {
134                                 ++$gls;
135                         }
136                 }
137                 return $gls > 2;
138         }
139
140         private function checkForLaughter(Channel $channel) {
141                 $notes = $this->getNotes($channel);
142                 $lulz = 0;
143                 foreach ($notes['latest_msgs'] as $text) {
144                         $rawText = strtolower(preg_replace('/[^\w]/', '', $text));
145                         if (Str::contains($rawText, ['haha', 'kekw', 'lol', 'lul', 'xd'])) {
146                                 ++$lulz;
147                         }
148                 }
149                 return $lulz > 2;
150         }
151
152         private function checkForNumbers(Channel $channel) {
153                 $notes = $this->getNotes($channel);
154                 $numbers = 0;
155                 foreach ($notes['latest_msgs'] as $text) {
156                         if (is_numeric(trim($text))) {
157                                 ++$numbers;
158                         }
159                 }
160                 return $numbers > 2;
161         }
162
163         private function contextualMsg(Channel $channel) {
164                 $last = $this->getNote($channel, 'last_special');
165                 if ($last != 'gg' && $this->checkForGG($channel)) {
166                         $this->setNote($channel, 'last_special', 'gg');
167                         return $this->randomGG($channel);
168                 }
169                 if ($last != 'number' && $this->checkForNumbers($channel)) {
170                         $this->setNote($channel, 'last_special', 'number');
171                         return $this->randomContextualNumber($channel);
172                 }
173                 if ($last != 'lol' && $this->checkForLaughter($channel)) {
174                         $this->setNote($channel, 'last_special', 'lol');
175                         return $this->randomLaughter($channel);
176                 }
177                 if ($last != 'glhf' && $this->checkForGLHF($channel)) {
178                         $this->setNote($channel, 'last_special', 'glhf');
179                         return $this->randomGLHF($channel);
180                 }
181                 return false;
182         }
183
184         private function queryChatlog(Channel $channel) {
185                 return ChatLog::where('type', '=', 'chat')
186                         ->where('banned', '=', false)
187                         ->where('created_at', '<', now()->sub(1, 'day'))
188                         ->where(function ($query) use ($channel) {
189                                 $query->whereNull('detected_language');
190                                 $query->orWhereIn('detected_language', $channel->languages);
191                         })
192                         ->inRandomOrder();
193         }
194
195         private function randomContextualNumber(Channel $channel) {
196                 $notes = $this->getNotes($channel);
197                 $min = 100000;
198                 $max = 0;
199                 foreach ($notes['latest_msgs'] as $text) {
200                         if (is_numeric(trim($text))) {
201                                 $number = intval(trim($text));
202                                 $min = min($min, $number);
203                                 $max = max($max, $number);
204                         }
205                 }
206                 return random_int($min, $max);
207         }
208
209         private function randomGG(Channel $channel) {
210                 $line = $this->queryChatlog($channel)
211                         ->where('text_content', 'LIKE', '%gg')
212                         ->whereRaw('LENGTH(`text_content`) < 30')
213                         ->first();
214                 return $line->text_content;
215         }
216
217         private function randomGLHF(Channel $channel) {
218                 $line = $this->queryChatlog($channel)
219                         ->where('text_content', 'LIKE', '%glhf%')
220                         ->whereRaw('LENGTH(`text_content`) < 30')
221                         ->first();
222                 return $line->text_content;
223         }
224
225         private function randomLaughter(Channel $channel) {
226                 return Arr::random([
227                         ':tf:',
228                         '4Head',
229                         'CarlSmile',
230                         'CruW',
231                         'DendiFace',
232                         'EleGiggle',
233                         'GunRun',
234                         'heh',
235                         'Hhhehehe',
236                         'HypeLUL',
237                         'Jebaited',
238                         'Jebasted',
239                         'KEKW',
240                         'KEKHeim',
241                         'KKona',
242                         'KomodoHype',
243                         'MaxLOL',
244                         'MingLee',
245                         'lol',
246                         'LOL!',
247                         'LUL',
248                         'OneHand',
249                         'SeemsGood',
250                         'ShadyLulu',
251                         'SoonerLater',
252                         'SUBprise',
253                         'xD',
254                         'YouDontSay',
255                 ]);
256         }
257
258         private function randomMsg(Channel $channel) {
259                 $line = $this->queryChatlog($channel)->first();
260                 return $line->text_content;
261         }
262
263         private function randomWaitMsgs(Channel $channel) {
264                 $min = $this->getChatSetting($channel, 'wait_msgs_min', 1);
265                 $max = $this->getChatSetting($channel, 'wait_msgs_max', 10);
266                 return random_int($min, $max);
267         }
268
269         private function randomWaitTime(Channel $channel) {
270                 $min = $this->getChatSetting($channel, 'wait_time_min', 1);
271                 $max = $this->getChatSetting($channel, 'wait_time_max', 900);
272                 return random_int($min, $max);
273         }
274
275         private function tagChannelRead(Channel $channel, IRCMessage $msg) {
276                 $this->getNotes($channel);
277                 $this->notes[$channel->id]['last_read'] = time();
278                 ++$this->notes[$channel->id]['read_since_last_write'];
279                 $this->notes[$channel->id]['latest_msgs'][] = $msg->getText();
280                 if (count($this->notes[$channel->id]['latest_msgs']) > 10) {
281                         array_shift($this->notes[$channel->id]['latest_msgs']);
282                 }
283         }
284
285         private function tagChannelWrite(Channel $channel) {
286                 $this->getNotes($channel);
287                 $this->notes[$channel->id]['last_write'] = time();
288                 $this->notes[$channel->id]['read_since_last_write'] = 0;
289                 $this->notes[$channel->id]['wait_msgs'] = $this->randomWaitMsgs($channel);
290                 $this->notes[$channel->id]['wait_time'] = $this->randomWaitTime($channel);
291         }
292
293         private $channels;
294         private $notes = [];
295
296 }