]> git.localhorst.tv Git - alttp.git/commitdiff
chat bot settings
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Sun, 21 Jan 2024 15:39:12 +0000 (16:39 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Sun, 21 Jan 2024 15:39:12 +0000 (16:39 +0100)
app/Http/Controllers/ChannelController.php
app/Models/Channel.php
app/TwitchBot/TwitchChatBot.php
database/migrations/2024_01_21_134505_channel_chat_settings.php [new file with mode: 0644]
resources/js/components/twitch-bot/ChatSettingsForm.js [new file with mode: 0644]
resources/js/components/twitch-bot/Controls.js
resources/js/i18n/de.js
resources/js/i18n/en.js
routes/api.php

index b735a6f70b3e3e6fcc45c0df81dfd9446af970e9..f1f3cdbeb4038da5cc8b12a7dc9b2b6060645ea2 100644 (file)
@@ -56,6 +56,22 @@ class ChannelController extends Controller {
                return $channel->toJson();
        }
 
+       public function chatSettings(Request $request, Channel $channel) {
+               if (!$channel->twitch_chat) {
+                       throw new \Exception('channel has no twitch chat set');
+               }
+               $validatedData = $request->validate([
+                       'wait_msgs_min' => 'integer|min:1',
+                       'wait_msgs_max' => 'integer',
+                       'wait_time_min' => 'integer',
+                       'wait_time_max' => 'integer',
+               ]);
+               $this->authorize('editRestream', $channel);
+               $channel->chat_settings = $validatedData;
+               $channel->save();
+               return $channel->toJson();
+       }
+
        public function join(Request $request, Channel $channel) {
                if (!$channel->twitch_chat) {
                        throw new \Exception('channel has no twitch chat set');
index 255b52cef3537ceecfb14ac8a354b5f37af81fee..d1f0fdc784d4f4e0f4078be186322b4a54901bd8 100644 (file)
@@ -33,6 +33,7 @@ class Channel extends Model
        protected $casts = [
                'chat' => 'boolean',
                'chat_commands' => 'array',
+               'chat_settings' => 'array',
                'languages' => 'array',
                'join' => 'boolean',
        ];
index 7a6eb1c1357cb9c59d12c2bfc9af81467383ae53..0896c2b98017b77808630929ff4f47ce9c0b8852 100644 (file)
@@ -9,16 +9,7 @@ class TwitchChatBot extends TwitchBot {
 
        public function __construct() {
                parent::__construct('horstiebot');
-               $this->channels = Channel::where('twitch_chat', '!=', '')->where('chat', '=', true)->get();
-               foreach ($this->channels as $channel) {
-                       $this->notes[$channel->id] = [
-                               'last_read' => 0,
-                               'last_write' => time(),
-                               'read_since_last_write' => 0,
-                               'wait_msgs' => $this->randomWaitMsgs($channel),
-                               'wait_time' => $this->randomWaitTime($channel),
-                       ];
-               }
+               $this->updateChannels();
                $this->startTimer();
                $this->listenCommands();
        }
@@ -57,10 +48,17 @@ class TwitchChatBot extends TwitchBot {
                                $this->decideSend($channel);
                        }
                });
+               $this->getLoop()->addPeriodicTimer(60, function () {
+                       $this->updateChannels();
+               });
+       }
+
+       private function updateChannels() {
+               $this->channels = Channel::where('twitch_chat', '!=', '')->where('chat', '=', true)->get();
        }
 
        private function decideSend(Channel $channel) {
-               $notes = $this->notes[$channel->id];
+               $notes = $this->getNotes($channel);
                if ($notes['read_since_last_write'] < $notes['wait_msgs']) {
                        return;
                }
@@ -77,6 +75,26 @@ class TwitchChatBot extends TwitchBot {
                $this->sendIRCMessage(IRCMessage::privmsg($channel->twitch_chat, $text));
        }
 
+       private function getChatSetting(Channel $channel, $name, $default = null) {
+               if (array_key_exists($name, $channel->chat_settings)) {
+                       return $channel->chat_settings[$name];
+               }
+               return $default;
+       }
+
+       private function getNotes(Channel $channel) {
+               if (!isset($this->notes[$channel->id])) {
+                       $this->notes[$channel->id] = [
+                               'last_read' => 0,
+                               'last_write' => time(),
+                               'read_since_last_write' => 0,
+                               'wait_msgs' => $this->randomWaitMsgs($channel),
+                               'wait_time' => $this->randomWaitTime($channel),
+                       ];
+               }
+               return $this->notes[$channel->id];
+       }
+
        private function randomMsg(Channel $channel) {
                $line = ChatLog::where('type', '=', 'chat')
                        ->where('banned', '=', false)
@@ -91,19 +109,25 @@ class TwitchChatBot extends TwitchBot {
        }
 
        private function randomWaitMsgs(Channel $channel) {
-               return random_int(1, 10);
+               $min = $this->getChatSetting($channel, 'wait_msgs_min', 1);
+               $max = $this->getChatSetting($channel, 'wait_msgs_max', 10);
+               return random_int($min, $max);
        }
 
        private function randomWaitTime(Channel $channel) {
-               return random_int(1, 900);
+               $min = $this->getChatSetting($channel, 'wait_time_min', 1);
+               $max = $this->getChatSetting($channel, 'wait_time_max', 900);
+               return random_int($min, $max);
        }
 
        private function tagChannelRead(Channel $channel) {
+               $this->getNotes($channel);
                $this->notes[$channel->id]['last_read'] = time();
                ++$this->notes[$channel->id]['read_since_last_write'];
        }
 
        private function tagChannelWrite(Channel $channel) {
+               $this->getNotes($channel);
                $this->notes[$channel->id]['last_write'] = time();
                $this->notes[$channel->id]['read_since_last_write'] = 0;
                $this->notes[$channel->id]['wait_msgs'] = $this->randomWaitMsgs($channel);
diff --git a/database/migrations/2024_01_21_134505_channel_chat_settings.php b/database/migrations/2024_01_21_134505_channel_chat_settings.php
new file mode 100644 (file)
index 0000000..755aecd
--- /dev/null
@@ -0,0 +1,32 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+       /**
+        * Run the migrations.
+        *
+        * @return void
+        */
+       public function up()
+       {
+               Schema::table('channels', function (Blueprint $table) {
+                       $table->text('chat_settings')->default('{}');
+               });
+       }
+
+       /**
+        * Reverse the migrations.
+        *
+        * @return void
+        */
+       public function down()
+       {
+               Schema::table('channels', function (Blueprint $table) {
+                       $table->dropColumn('chat_settings');
+               });
+       }
+};
diff --git a/resources/js/components/twitch-bot/ChatSettingsForm.js b/resources/js/components/twitch-bot/ChatSettingsForm.js
new file mode 100644 (file)
index 0000000..d9c03d7
--- /dev/null
@@ -0,0 +1,148 @@
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Col, Form, Row } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import { formatTime, parseTime } from '../../helpers/Result';
+import yup from '../../schema/yup';
+
+const ChatSettingsForm = ({
+       dirty,
+       errors,
+       handleBlur,
+       handleChange,
+       handleSubmit,
+       isSubmitting,
+       touched,
+       values,
+}) => {
+       const { t } = useTranslation();
+
+       return <Form noValidate onSubmit={handleSubmit}>
+               <Row>
+                       <Form.Group as={Col} md={6} controlId="chatSettings.wait_msgs_min">
+                               <Form.Label>{t('twitchBot.chatWaitMsgsMin')}</Form.Label>
+                               <Form.Control
+                                       isInvalid={!!(touched.wait_msgs_min && errors.wait_msgs_min)}
+                                       name="wait_msgs_min"
+                                       min="1"
+                                       onBlur={handleBlur}
+                                       onChange={handleChange}
+                                       type="number"
+                                       value={values.wait_msgs_min || 1}
+                               />
+                       </Form.Group>
+                       <Form.Group as={Col} md={6} controlId="chatSettings.wait_msgs_max">
+                               <Form.Label>{t('twitchBot.chatWaitMsgsMax')}</Form.Label>
+                               <Form.Control
+                                       isInvalid={!!(touched.wait_msgs_max && errors.wait_msgs_max)}
+                                       name="wait_msgs_max"
+                                       min="1"
+                                       onBlur={handleBlur}
+                                       onChange={handleChange}
+                                       type="number"
+                                       value={values.wait_msgs_max || 10}
+                               />
+                       </Form.Group>
+                       <Form.Group as={Col} md={6} controlId="chatSettings.wait_time_min">
+                               <Form.Label>{t('twitchBot.chatWaitTimeMin')}</Form.Label>
+                               <Form.Control
+                                       isInvalid={!!(touched.wait_time_min && errors.wait_time_min)}
+                                       name="wait_time_min"
+                                       onBlur={handleBlur}
+                                       onChange={handleChange}
+                                       type="text"
+                                       value={values.wait_time_min || '0'}
+                               />
+                               {touched.wait_time_min && errors.wait_time_min ?
+                                       <Form.Control.Feedback type="invalid">
+                                               {t(errors.wait_time_min)}
+                                       </Form.Control.Feedback>
+                               :
+                                       <Form.Text muted>
+                                               {formatTime({ time: parseTime(values.wait_time_min)})}
+                                       </Form.Text>
+                               }
+                       </Form.Group>
+                       <Form.Group as={Col} md={6} controlId="chatSettings.wait_time_max">
+                               <Form.Label>{t('twitchBot.chatWaitTimeMax')}</Form.Label>
+                               <Form.Control
+                                       isInvalid={!!(touched.wait_time_max && errors.wait_time_max)}
+                                       name="wait_time_max"
+                                       onBlur={handleBlur}
+                                       onChange={handleChange}
+                                       type="text"
+                                       value={values.wait_time_max || '15:00'}
+                               />
+                               {touched.wait_time_max && errors.wait_time_max ?
+                                       <Form.Control.Feedback type="invalid">
+                                               {t(errors.wait_time_max)}
+                                       </Form.Control.Feedback>
+                               :
+                                       <Form.Text muted>
+                                               {formatTime({ time: parseTime(values.wait_time_max)})}
+                                       </Form.Text>
+                               }
+                       </Form.Group>
+               </Row>
+               <div className="button-bar mt-3">
+                       <Button disabled={!dirty || isSubmitting} type="submit" variant="primary">
+                               {t('button.save')}
+                       </Button>
+               </div>
+       </Form>;
+};
+
+ChatSettingsForm.propTypes = {
+       dirty: PropTypes.bool,
+       errors: PropTypes.shape({
+               wait_msgs_max: PropTypes.string,
+               wait_msgs_min: PropTypes.string,
+               wait_time_min: PropTypes.string,
+               wait_time_max: PropTypes.string,
+       }),
+       handleBlur: PropTypes.func,
+       handleChange: PropTypes.func,
+       handleSubmit: PropTypes.func,
+       isSubmitting: PropTypes.bool,
+       touched: PropTypes.shape({
+               wait_msgs_max: PropTypes.bool,
+               wait_msgs_min: PropTypes.bool,
+               wait_time_min: PropTypes.bool,
+               wait_time_max: PropTypes.bool,
+       }),
+       values: PropTypes.shape({
+               wait_msgs_max: PropTypes.number,
+               wait_msgs_min: PropTypes.number,
+               wait_time_min: PropTypes.string,
+               wait_time_max: PropTypes.string,
+       }),
+};
+
+export default withFormik({
+       displayName: 'ChatSettingsForm',
+       enableReinitialize: true,
+       handleSubmit: async (values, actions) => {
+               const { onSubmit } = actions.props;
+               await onSubmit({
+                       ...values,
+                       wait_time_min: parseTime(values.wait_time_min) || 0,
+                       wait_time_max: parseTime(values.wait_time_max) || 0,
+               });
+       },
+       mapPropsToValues: ({ channel }) => ({
+               wait_msgs_min: channel.chat_settings.wait_msgs_min || 1,
+               wait_msgs_max: channel.chat_settings.wait_msgs_max || 10,
+               wait_time_min: channel.chat_settings.wait_time_min
+                       ? formatTime({ time: channel.chat_settings.wait_time_min }) : '0',
+               wait_time_max: channel.chat_settings.wait_time_max
+                       ? formatTime({ time: channel.chat_settings.wait_time_max }) : '15:00',
+       }),
+       validationSchema: yup.object().shape({
+               wait_msgs_min: yup.number().min(1),
+               wait_msgs_max: yup.number().min(1),
+               wait_time_min: yup.string().time(),
+               wait_time_max: yup.string().time(),
+       }),
+})(ChatSettingsForm);
index cc062e8aff53805bf4a78cfa6499c4ab81915cf0..bdc89380fe75c07a4a1ad22e8dd2587661d1ac1b 100644 (file)
@@ -4,6 +4,7 @@ import { Alert, Button, Col, Form, Row } from 'react-bootstrap';
 import { useTranslation } from 'react-i18next';
 import toastr from 'toastr';
 
+import ChatSettingsForm from './ChatSettingsForm';
 import ChannelSelect from '../common/ChannelSelect';
 import ToggleSwitch from '../common/ToggleSwitch';
 
@@ -45,6 +46,16 @@ const Controls = () => {
                }
        }, [channel, t]);
 
+       const saveChatSettings = React.useCallback(async (values) => {
+               try {
+                       const rsp = await axios.post(`/api/channels/${channel.id}/chat-settings`, values);
+                       setChannel(rsp.data);
+                       toastr.success(t('twitchBot.saveSuccess'));
+               } catch (e) {
+                       toastr.error(t('twitchBot.saveError'));
+               }
+       }, [channel, t]);
+
        return <>
                <Row className="mb-4">
                        <Form.Group as={Col} md={6}>
@@ -126,6 +137,10 @@ const Controls = () => {
                                                </Button>
                                        </div>
                                </Form.Group>
+                               <Col md={6}>
+                                       <h3>{t('twitchBot.chatSettings')}</h3>
+                                       <ChatSettingsForm channel={channel} onSubmit={saveChatSettings} />
+                               </Col>
                        </Row>
                :
                        <Alert variant="info">
index bb5c08c8b0de74d72205908e0957bf42e2beb5fb..907ab3dc0589a2ce9ae59429715c7136110da953 100644 (file)
@@ -487,10 +487,13 @@ export default {
                },
                twitchBot: {
                        channel: 'Channel',
-                       chatApp: 'Chat als App Bot',
-                       chatChat: 'Chat als Chat Bot',
+                       chat: 'Chat',
                        chatError: 'Fehler beim Senden',
                        chatSuccess: 'Nachricht in Warteschlange',
+                       chatWaitMsgsMin: 'Min. Messages',
+                       chatWaitMsgsMax: 'Max. Messages',
+                       chatWaitTimeMin: 'Min. Zeit',
+                       chatWaitTimeMax: 'Max. Zeit',
                        controls: 'Controls',
                        heading: 'Twitch Bot',
                        joinApp: 'Join als App Bot',
@@ -500,6 +503,8 @@ export default {
                        noManagePermission: 'Du verfügst nicht über die notwendigen Berechtigungen, um den Twitch Bot zu administrieren.',
                        partError: 'Fehler beim Verlassen',
                        partSuccess: 'Verlassen',
+                       saveError: 'Fehler beim Speichern',
+                       saveSuccess: 'Gespeichert',
                        selectChannel: 'Bitte wählen einen Channel, den du verändern möchtest.',
                        sendApp: 'Als App Bot senden',
                        sendChat: 'Als Chat Bot senden',
index 669c56e8ed81b51fd8ef4953bc9b7664d11992b4..401a7e4453362865e1c7d49960d17469e7aea4ce 100644 (file)
@@ -490,6 +490,10 @@ export default {
                        chat: 'Chat',
                        chatError: 'Error sending message',
                        chatSuccess: 'Message queued',
+                       chatWaitMsgsMin: 'Min. messages',
+                       chatWaitMsgsMax: 'Max. messages',
+                       chatWaitTimeMin: 'Min. time',
+                       chatWaitTimeMax: 'Max. time',
                        controls: 'Controls',
                        heading: 'Twitch Bot',
                        joinApp: 'Join as App Bot',
@@ -499,6 +503,8 @@ export default {
                        noManagePermission: 'You lack the required privileges to manage the twitch bot.',
                        partError: 'Error parting channel',
                        partSuccess: 'Parted',
+                       saveError: 'Error saving',
+                       saveSuccess: 'Saved',
                        selectChannel: 'Please select a channel to manage.',
                        sendApp: 'Send as App Bot',
                        sendChat: 'Send as Chat Bot',
index 43334942606f668a68ba3dca5ee0dc564b509c37..2b8b6827113cf3fa5d0ffec411d35916d4f37475 100644 (file)
@@ -27,6 +27,7 @@ Route::post('application/{application}/reject', 'App\Http\Controllers\Applicatio
 Route::get('channels', 'App\Http\Controllers\ChannelController@search');
 Route::get('channels/{channel}', 'App\Http\Controllers\ChannelController@single');
 Route::post('channels/{channel}/chat', 'App\Http\Controllers\ChannelController@chat');
+Route::post('channels/{channel}/chat-settings', 'App\Http\Controllers\ChannelController@chatSettings');
 Route::post('channels/{channel}/join', 'App\Http\Controllers\ChannelController@join');
 Route::post('channels/{channel}/part', 'App\Http\Controllers\ChannelController@part');