3 namespace App\TwitchBot;
5 use App\Models\Channel;
6 use App\Models\ChatLog;
7 use Illuminate\Support\Arr;
8 use Illuminate\Support\Str;
10 class TwitchChatBot extends TwitchBot {
12 public function __construct() {
13 parent::__construct('horstiebot');
14 $this->updateChannels();
16 $this->listenCommands();
19 public function joinChannels() {
20 $this->getLogger()->info('joining channels');
22 foreach ($this->channels as $channel) {
23 $names[] = $channel->twitch_chat;
25 $chunks = array_chunk($names, 10);
26 foreach ($chunks as $chunk) {
27 $this->sendIRCMessage(IRCMessage::join($chunk));
31 public function logMessage(IRCMessage $msg) {
32 $channel = $this->getMessageChannel($msg);
33 if ($channel && !$channel->join) {
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);
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);
53 $this->getLoop()->addPeriodicTimer(60, function () {
54 $this->updateChannels();
58 private function updateChannels() {
59 $this->channels = Channel::where('twitch_chat', '!=', '')->where('chat', '=', true)->get();
62 private function decideSend(Channel $channel) {
63 $notes = $this->getNotes($channel);
64 if ($notes['read_since_last_write'] < $notes['wait_msgs']) {
67 if (time() - $notes['last_write'] < $notes['wait_time']) {
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
74 $text = $this->contextualMsg($channel);
75 if (!$text) $text = $this->randomMsg($channel);
77 $this->tagChannelWrite($channel);
78 $this->sendIRCMessage(IRCMessage::privmsg($channel->twitch_chat, $text));
81 private function getChatSetting(Channel $channel, $name, $default = null) {
82 if (array_key_exists($name, $channel->chat_settings)) {
83 return $channel->chat_settings[$name];
88 private function getNotes(Channel $channel) {
89 if (!isset($this->notes[$channel->id])) {
90 $this->notes[$channel->id] = [
93 'last_write' => time(),
95 'read_since_last_write' => 0,
96 'wait_msgs' => $this->randomWaitMsgs($channel),
97 'wait_time' => $this->randomWaitTime($channel),
100 return $this->notes[$channel->id];
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];
111 private function setNote(Channel $channel, $name, $value) {
112 $this->getNotes($channel);
113 $this->notes[$channel->id][$name] = $value;
116 private function checkForGG(Channel $channel) {
117 $notes = $this->getNotes($channel);
119 foreach ($notes['latest_msgs'] as $text) {
120 $rawText = strtolower(preg_replace('/[^\w]/', '', $text));
121 if (Str::startsWith($rawText, 'gg') || Str::endsWith($rawText, 'gg')) {
128 private function checkForGLHF(Channel $channel) {
129 $notes = $this->getNotes($channel);
131 foreach ($notes['latest_msgs'] as $text) {
132 $rawText = strtolower(preg_replace('/[^\w]/', '', $text));
133 if (Str::contains($rawText, ['glgl', 'glhf', 'hfgl'])) {
140 private function checkForLaughter(Channel $channel) {
141 $notes = $this->getNotes($channel);
143 foreach ($notes['latest_msgs'] as $text) {
144 $rawText = strtolower(preg_replace('/[^\w]/', '', $text));
145 if (Str::contains($rawText, ['haha', 'kekw', 'lol', 'lul', 'xd'])) {
152 private function checkForNumbers(Channel $channel) {
153 $notes = $this->getNotes($channel);
155 foreach ($notes['latest_msgs'] as $text) {
156 if (is_numeric(trim($text))) {
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);
169 if ($last != 'number' && $this->checkForNumbers($channel)) {
170 $this->setNote($channel, 'last_special', 'number');
171 return $this->randomContextualNumber($channel);
173 if ($last != 'lol' && $this->checkForLaughter($channel)) {
174 $this->setNote($channel, 'last_special', 'lol');
175 return $this->randomLaughter($channel);
177 if ($last != 'glhf' && $this->checkForGLHF($channel)) {
178 $this->setNote($channel, 'last_special', 'glhf');
179 return $this->randomGLHF($channel);
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);
195 private function randomContextualNumber(Channel $channel) {
196 $notes = $this->getNotes($channel);
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);
206 return random_int($min, $max);
209 private function randomGG(Channel $channel) {
210 $line = $this->queryChatlog($channel)
211 ->where('text_content', 'LIKE', '%gg')
212 ->whereRaw('LENGTH(`text_content`) < 30')
214 return $line->text_content;
217 private function randomGLHF(Channel $channel) {
218 $line = $this->queryChatlog($channel)
219 ->where('text_content', 'LIKE', '%glhf%')
220 ->whereRaw('LENGTH(`text_content`) < 30')
222 return $line->text_content;
225 private function randomLaughter(Channel $channel) {
258 private function randomMsg(Channel $channel) {
259 $line = $this->queryChatlog($channel)->first();
260 return $line->text_content;
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);
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);
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']);
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);