From: Daniel Karbach Date: Sun, 21 Jan 2024 15:39:12 +0000 (+0100) Subject: chat bot settings X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=e49b130505f5712075dca2ff990e5a63fc90ce3c;p=alttp.git chat bot settings --- diff --git a/app/Http/Controllers/ChannelController.php b/app/Http/Controllers/ChannelController.php index b735a6f..f1f3cdb 100644 --- a/app/Http/Controllers/ChannelController.php +++ b/app/Http/Controllers/ChannelController.php @@ -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'); diff --git a/app/Models/Channel.php b/app/Models/Channel.php index 255b52c..d1f0fdc 100644 --- a/app/Models/Channel.php +++ b/app/Models/Channel.php @@ -33,6 +33,7 @@ class Channel extends Model protected $casts = [ 'chat' => 'boolean', 'chat_commands' => 'array', + 'chat_settings' => 'array', 'languages' => 'array', 'join' => 'boolean', ]; diff --git a/app/TwitchBot/TwitchChatBot.php b/app/TwitchBot/TwitchChatBot.php index 7a6eb1c..0896c2b 100644 --- a/app/TwitchBot/TwitchChatBot.php +++ b/app/TwitchBot/TwitchChatBot.php @@ -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 index 0000000..755aecd --- /dev/null +++ b/database/migrations/2024_01_21_134505_channel_chat_settings.php @@ -0,0 +1,32 @@ +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 index 0000000..d9c03d7 --- /dev/null +++ b/resources/js/components/twitch-bot/ChatSettingsForm.js @@ -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
+ + + {t('twitchBot.chatWaitMsgsMin')} + + + + {t('twitchBot.chatWaitMsgsMax')} + + + + {t('twitchBot.chatWaitTimeMin')} + + {touched.wait_time_min && errors.wait_time_min ? + + {t(errors.wait_time_min)} + + : + + {formatTime({ time: parseTime(values.wait_time_min)})} + + } + + + {t('twitchBot.chatWaitTimeMax')} + + {touched.wait_time_max && errors.wait_time_max ? + + {t(errors.wait_time_max)} + + : + + {formatTime({ time: parseTime(values.wait_time_max)})} + + } + + +
+ +
+
; +}; + +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); diff --git a/resources/js/components/twitch-bot/Controls.js b/resources/js/components/twitch-bot/Controls.js index cc062e8..bdc8938 100644 --- a/resources/js/components/twitch-bot/Controls.js +++ b/resources/js/components/twitch-bot/Controls.js @@ -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 <> @@ -126,6 +137,10 @@ const Controls = () => { + +

{t('twitchBot.chatSettings')}

+ +
: diff --git a/resources/js/i18n/de.js b/resources/js/i18n/de.js index bb5c08c..907ab3d 100644 --- a/resources/js/i18n/de.js +++ b/resources/js/i18n/de.js @@ -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', diff --git a/resources/js/i18n/en.js b/resources/js/i18n/en.js index 669c56e..401a7e4 100644 --- a/resources/js/i18n/en.js +++ b/resources/js/i18n/en.js @@ -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', diff --git a/routes/api.php b/routes/api.php index 4333494..2b8b682 100644 --- a/routes/api.php +++ b/routes/api.php @@ -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');