]> git.localhorst.tv Git - alttp.git/blob - app/TwitchBot/TwitchChatBot.php
more classification
[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                         'number' => 2,
144                         'pog' => 2,
145                         'o7' => 2,
146                 ];
147                 $time_quotas = [
148                         'gg' => 600,
149                         'gl' => 900,
150                         'hi' => 60,
151                         'hype' => 60,
152                         'lol' => 60,
153                         'number' => 300,
154                         'pog' => 60,
155                         'o7' => 300,
156                 ];
157                 foreach ($classifications as $classification => $count) {
158                         if ($classification == $last) continue;
159                         if (!isset($count_quotas[$classification]) || $count < $count_quotas[$classification]) continue;
160                         if (!isset($time_quotas[$classification]) || $this->getTimeSinceSpecial($channel, $classification) < $time_quotas[$classification]) continue;
161                         $this->tagChannelSpecialSent($channel, $classification);
162                         if ($classification == 'number') {
163                                 return $this->randomContextualNumber($channel);
164                         }
165                         if ($classification == 'lol') {
166                                 return $this->randomLaughter($channel);
167                         }
168                         return $channel->randomOfClass($classification);
169                 }
170                 return false;
171         }
172
173         private function randomChat(Channel $channel) {
174                 return $channel->queryChatlog()
175                         ->whereIn('classification', ['hi', 'hype', 'lol', 'pog', 'unclassified'])
176                         ->first();
177         }
178
179         private function randomContextualNumber(Channel $channel) {
180                 $notes = $this->getNotes($channel);
181                 $min = 100000;
182                 $max = 0;
183                 foreach ($notes['latest_msgs'] as $msg) {
184                         if ($msg->classify() == 'number') {
185                                 $number = $msg->getNumericValue();
186                                 $min = min($min, $number);
187                                 $max = max($max, $number);
188                         }
189                 }
190                 return random_int($min, $max);
191         }
192
193         private function randomLaughter(Channel $channel) {
194                 if (!random_int(0, 2)) {
195                         return $channel->randomOfClass('lol');
196                 }
197                 return Arr::random([
198                         ':tf:',
199                         '4Head',
200                         'CarlSmile',
201                         'CruW',
202                         'DendiFace',
203                         'EleGiggle',
204                         'GunRun',
205                         'heh',
206                         'Hhhehehe',
207                         'Jebaited',
208                         'Jebasted',
209                         'KEKW',
210                         'KEKHeim',
211                         'KKona',
212                         'KomodoHype',
213                         'MaxLOL',
214                         'MingLee',
215                         'lol',
216                         'LOL!',
217                         'LUL',
218                         'OneHand',
219                         'SeemsGood',
220                         'ShadyLulu',
221                         'SoonerLater',
222                         'SUBprise',
223                         'xD',
224                         'YouDontSay',
225                 ]);
226         }
227
228         private function randomMsg(Channel $channel) {
229                 return $channel->queryChatlog()->first();
230         }
231
232         private function randomWaitMsgs(Channel $channel) {
233                 $min = $channel->getChatSetting('wait_msgs_min', 1);
234                 $max = $channel->getChatSetting('wait_msgs_max', 10);
235                 return random_int($min, $max);
236         }
237
238         private function randomWaitTime(Channel $channel) {
239                 $min = $channel->getChatSetting('wait_time_min', 1);
240                 $max = $channel->getChatSetting('wait_time_max', 900);
241                 return random_int($min, $max);
242         }
243
244         private function tagChannelRead(Channel $channel, IRCMessage $msg) {
245                 $this->getNotes($channel);
246                 $this->notes[$channel->id]['last_read'] = time();
247                 ++$this->notes[$channel->id]['read_since_last_write'];
248
249                 $tokenized = $msg->tokenize();
250                 if (!ChatLog::isKnownBot($msg->nick) && !$tokenized->isSpammy()) {
251                         $this->notes[$channel->id]['latest_msgs'][] = $tokenized;
252                         if (count($this->notes[$channel->id]['latest_msgs']) > 10) {
253                                 array_shift($this->notes[$channel->id]['latest_msgs']);
254                         }
255                 }
256                 if ($this->isDirectedAtMe($msg->getText()) && $this->shouldRespond($channel)) {
257                         $this->notes[$channel->id]['wait_msgs'] = 0;
258                         $this->notes[$channel->id]['wait_time'] = 0;
259                 }
260         }
261
262         private function tagChannelWrite(Channel $channel) {
263                 $this->getNotes($channel);
264                 $this->notes[$channel->id]['last_write'] = time();
265                 $this->notes[$channel->id]['read_since_last_write'] = 0;
266                 $this->notes[$channel->id]['wait_msgs'] = $this->randomWaitMsgs($channel);
267                 $this->notes[$channel->id]['wait_time'] = $this->randomWaitTime($channel);
268         }
269
270         private function tagChannelSpecialSent(Channel $channel, $classification) {
271                 $this->getNotes($channel);
272                 $this->notes[$channel->id]['last_special'][$classification] = time();
273         }
274
275         private function getLastSpecialSent(Channel $channel) {
276                 $notes = $this->getNotes($channel);
277                 $max_time = 0;
278                 $max_classification = '';
279                 foreach ($notes['last_special'] as $classification => $time) {
280                         if ($time > $max_time) {
281                                 $max_time = $time;
282                                 $max_classification = $classification;
283                         }
284                 }
285                 return $max_classification;
286         }
287
288         private function getTimeSinceSpecial(Channel $channel, $classification) {
289                 $notes = $this->getNotes($channel);
290                 if (isset($notes['last_special'][$classification])) {
291                         return time() - $notes['last_special'][$classification];
292                 }
293                 return 999999;
294         }
295
296         private function isDirectedAtMe($raw_text) {
297                 $text = strtolower($raw_text);
298                 if (strpos($text, 'horsti') !== false) {
299                         return true;
300                 }
301                 return false;
302         }
303
304         private function shouldRespond(Channel $channel) {
305                 $setting = $channel->getChatSetting('respond', 'yes');
306                 if ($setting == 'yes') {
307                         return true;
308                 }
309                 if ($setting == '50') {
310                         return random_int(0, 1);
311                 }
312                 return false;
313         }
314
315         private $channels;
316         private $notes = [];
317
318 }