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