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');
protected $casts = [
'chat' => 'boolean',
'chat_commands' => 'array',
+ 'chat_settings' => 'array',
'languages' => 'array',
'join' => 'boolean',
];
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();
}
$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;
}
$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)
}
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);
--- /dev/null
+<?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');
+ });
+ }
+};
--- /dev/null
+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);
import { useTranslation } from 'react-i18next';
import toastr from 'toastr';
+import ChatSettingsForm from './ChatSettingsForm';
import ChannelSelect from '../common/ChannelSelect';
import ToggleSwitch from '../common/ToggleSwitch';
}
}, [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}>
</Button>
</div>
</Form.Group>
+ <Col md={6}>
+ <h3>{t('twitchBot.chatSettings')}</h3>
+ <ChatSettingsForm channel={channel} onSubmit={saveChatSettings} />
+ </Col>
</Row>
:
<Alert variant="info">
},
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',
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',
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',
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',
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');