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