]> git.localhorst.tv Git - alttp.git/commitdiff
guessing game settings
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Thu, 29 Feb 2024 14:47:14 +0000 (15:47 +0100)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Thu, 29 Feb 2024 15:30:40 +0000 (16:30 +0100)
17 files changed:
app/Http/Controllers/ChannelController.php
app/Models/Channel.php
app/TwitchBot/ChatCommand.php
app/TwitchBot/GuessingCancelCommand.php
app/TwitchBot/GuessingSolveCommand.php
app/TwitchBot/GuessingStartCommand.php
app/TwitchBot/GuessingStopCommand.php
database/migrations/2024_02_28_155113_guessing_settings.php [new file with mode: 0644]
resources/js/components/twitch-bot/Command.js [new file with mode: 0644]
resources/js/components/twitch-bot/CommandDialog.js [new file with mode: 0644]
resources/js/components/twitch-bot/CommandForm.js [new file with mode: 0644]
resources/js/components/twitch-bot/Commands.js [new file with mode: 0644]
resources/js/components/twitch-bot/Controls.js
resources/js/components/twitch-bot/GuessingSettingsForm.js [new file with mode: 0644]
resources/js/i18n/de.js
resources/js/i18n/en.js
routes/api.php

index f1f3cdbeb4038da5cc8b12a7dc9b2b6060645ea2..cbd01104cb3e7cd497d42a44fd33206fdf214909 100644 (file)
@@ -110,4 +110,59 @@ class ChannelController extends Controller {
                return $channel->toJson();
        }
 
+       public function deleteCommand(Channel $channel, $command) {
+               $this->authorize('editRestream', $channel);
+               if (isset($channel->chat_commands[$command])) {
+                       $cmds = $channel->chat_commands;
+                       unset($cmds[$command]);
+                       $channel->chat_commands = $cmds;
+                       $channel->save();
+               }
+               return $channel->toJson();
+       }
+
+       public function saveCommand(Request $request, Channel $channel, $command) {
+               $this->authorize('editRestream', $channel);
+
+               $validatedData = $request->validate([
+                       'command' => 'string',
+                       'restrict' => 'string',
+                       'type' => 'string',
+               ]);
+
+               $cmds = $channel->chat_commands;
+               $cmds[$command] = $validatedData;
+               $channel->chat_commands = $cmds;
+               $channel->save();
+
+               return $channel->toJson();
+       }
+
+       public function saveGuessingGame(Request $request, Channel $channel, $name) {
+               $this->authorize('editRestream', $channel);
+
+               $validatedData = $request->validate([
+                       'active_message' => 'string',
+                       'cancel_message' => 'string',
+                       'invalid_solution_message' => 'string',
+                       'no_winners_message' => 'string',
+                       'not_active_message' => 'string',
+                       'points_exact_first' => 'numeric|min:1|max:5',
+                       'points_exact_other' => 'numeric|min:0|max:5',
+                       'points_close_first' => 'numeric|min:0|max:5',
+                       'points_close_min' => 'integer|min:0',
+                       'points_close_other' => 'numeric|min:0|max:5',
+                       'start_message' => 'string',
+                       'stop_message' => 'string',
+                       'winners_message' => 'string',
+               ]);
+
+               $settings = $channel->guessing_settings;
+               $settings[$name] = $validatedData;
+               $channel->guessing_settings = $settings;
+               $channel->save();
+
+               return $channel->toJson();
+       }
+
 }
index 32ac875bb707c0760f9e75d2362a64416db7da61..738312dc3e82a60d6725a0289f2886a11b3dcc2e 100644 (file)
@@ -35,6 +35,17 @@ class Channel extends Model
                $this->save();
        }
 
+       public function getGuessingSetting($name, $default = null) {
+               if (empty($this->guessing_settings) ||
+                       empty($this->guessing_type) ||
+                       !array_key_exists($this->guessing_type, $this->guessing_settings) ||
+                       !array_key_exists($name, $this->guessing_settings[$this->guessing_type])
+               ) {
+                       return $default;
+               }
+               return $this->guessing_settings[$this->guessing_type][$name];
+       }
+
        public function solveGuessing($solution) {
                $start = $this->guessing_start;
                $end = is_null($this->guessing_end) ? now() : $this->guessing_end;
@@ -96,7 +107,20 @@ class Channel extends Model
        }
 
        public function scoreGuessing($solution, $guess, $first) {
-               return 1;
+               if ($guess == $solution) {
+                       if ($first) {
+                               return $this->getGuessingSetting('points_exact_first', 1);
+                       }
+                       return $this->getGuessingSetting('points_exact_other', 1);
+               }
+               $distance = abs(intval($guess) - intval($solution));
+               if ($distance <= $this->getGuessingSetting('points_close_max', 3)) {
+                       if ($first) {
+                               return $this->getGuessingSetting('points_close_first', 1);
+                       }
+                       return $this->getGuessingSetting('points_close_other', 1);
+               }
+               return 0;
        }
 
        public function isValidGuess($solution) {
@@ -129,6 +153,7 @@ class Channel extends Model
                'chat' => 'boolean',
                'chat_commands' => 'array',
                'chat_settings' => 'array',
+               'guessing_settings' => 'array',
                'guessing_start' => 'datetime',
                'guessing_end' => 'datetime',
                'languages' => 'array',
index d03f7829e2704923f3b72780a8b3f1fd12596fb2..8e6fbe1e7f1fa9c7b1c09b2e6e1552417c9079df 100644 (file)
@@ -59,10 +59,15 @@ abstract class ChatCommand {
        }
 
        protected function listAnd($entries) {
+               $lang = empty($this->channels->languages) ? 'en' : $this->channel->languages[0];
+               if ($lang == 'de') {
+                       return Arr::join($entries, ', ', ' und ');
+               }
                return Arr::join($entries, ', ', ' and ');
        }
 
        protected function messageChannel($str) {
+               if (empty($str)) return;
                $msg = IRCMessage::privmsg($this->channel->twitch_chat, $str);
                $this->bot->sendIRCMessage($msg);
        }
index c510a324f3aa1d07dc2772b150960587532f82cc..19f995345756a4ca00f2a333ff817815090c841e 100644 (file)
@@ -7,8 +7,9 @@ class GuessingCancelCommand extends ChatCommand {
        public function execute($args) {
                if ($this->chanel->hasActiveGuessing()) {
                        $this->channel->clearGuessing();
-                       $this->messageChannel('Guessing game cancelled');
                }
+               $msg = $this->channel->getGuessingSetting('cancel_message');
+               $this->messageChannel($msg);
        }
 
 }
index 57ee5f840424640a7f54e9031a650dfc44700ecb..b4a877d5b45d8b9378bb0cab431830872d46f6ec 100644 (file)
@@ -6,15 +6,13 @@ class GuessingSolveCommand extends ChatCommand {
 
        public function execute($args) {
                if (!$this->channel->hasActiveGuessing()) {
-                       $this->messageChannel('Channel has no active guessing game');
+                       $msg = $this->channel->getGuessingSetting('not_active_message');
+                       $this->messageChannel($msg);
                        return;
                }
-               if (empty($args)) {
-                       $this->messageChannel('Please provide a solution to the guessing game');
-                       return;
-               }
-               if (!$this->channel->isValidGuess($args)) {
-                       $this->messageChannel('Please provide a valid solution to the guessing game');
+               if (empty($args) || !$this->channel->isValidGuess($args)) {
+                       $msg = $this->channel->getGuessingSetting('invalid_solution_message');
+                       $this->messageChannel($msg);
                        return;
                }
                $winners = $this->channel->solveGuessing($args);
@@ -25,9 +23,12 @@ class GuessingSolveCommand extends ChatCommand {
                        }
                }
                if (empty($names)) {
-                       $this->messageChannel('nobody wins :(');
+                       $msg = $this->channel->getGuessingSetting('no_winners_message');
+                       $this->messageChannel($msg);
                } else {
-                       $this->messageChannel('Congrats '.$this->listAnd($names));
+                       $msg = $this->channel->getGuessingSetting('winners_message');
+                       $msg = str_replace('{names}', $this->listAnd($names), $msg);
+                       $this->messageChannel($msg);
                }
                $this->channel->clearGuessing();
        }
index d2b216843714dc603ecffb397081dcb7ac6df3b1..64a57292c5162666093d004525b8ba24d53bcf04 100644 (file)
@@ -6,12 +6,14 @@ class GuessingStartCommand extends ChatCommand {
 
        public function execute($args) {
                if ($this->channel->hasActiveGuessing()) {
-                       $this->messageChannel('Channel already has an active guessing game');
+                       $msg = $this->channel->getGuessingSetting('active_message');
+                       $this->messageChannel($msg);
                        return;
                }
                $type = $this->getStringConfig('type', 'gtbk');
                $this->channel->startGuessing($type);
-               $this->messageChannel('Get your guesses in');
+               $msg = $this->channel->getGuessingSetting('start_message');
+               $this->messageChannel($msg);
        }
 
 }
index cc4cd08c4593ff87769c93f85289761ba42d3bf0..898ece7ed343c1407dc60999f5d251dddd43cc30 100644 (file)
@@ -6,11 +6,13 @@ class GuessingStopCommand extends ChatCommand {
 
        public function execute($args) {
                if (!$this->channel->hasActiveGuessing()) {
-                       $this->messageChannel('Channel has no active guessing game');
+                       $msg = $this->channel->getGuessingSetting('not_active_message');
+                       $this->messageChannel($msg);
                        return;
                }
                $this->channel->stopGuessing();
-               $this->messageChannel('Guessing closed');
+               $msg = $this->channel->getGuessingSetting('stop_message');
+               $this->messageChannel($msg);
        }
 
 }
diff --git a/database/migrations/2024_02_28_155113_guessing_settings.php b/database/migrations/2024_02_28_155113_guessing_settings.php
new file mode 100644 (file)
index 0000000..953ed1d
--- /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('guessing_settings')->default('{}');
+               });
+       }
+
+       /**
+        * Reverse the migrations.
+        *
+        * @return void
+        */
+       public function down()
+       {
+               Schema::table('channels', function (Blueprint $table) {
+                       $table->dropColumn('guessing_settings');
+               });
+       }
+};
diff --git a/resources/js/components/twitch-bot/Command.js b/resources/js/components/twitch-bot/Command.js
new file mode 100644 (file)
index 0000000..9122086
--- /dev/null
@@ -0,0 +1,57 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Icon from '../common/Icon';
+
+const Command = ({
+       name,
+       onEditCommand,
+       onRemoveCommand,
+       settings,
+}) => {
+       const { t } = useTranslation();
+
+       const type = (settings && settings.command) || 'none';
+
+       return <tr>
+               <td>{`!${name}`}</td>
+               <td>{t(`twitchBot.commandRestrictions.${(settings && settings.restrict) || 'none'}`)}</td>
+               <td>{t(`twitchBot.commandTypes.${type}`)}</td>
+               <td className="text-end">
+                       <div className="button-bar">
+                               {onEditCommand ?
+                                       <Button
+                                               onClick={() => onEditCommand(name, settings)}
+                                               title={t('button.edit')}
+                                               variant="outline-secondary"
+                                       >
+                                               <Icon.EDIT title="" />
+                                       </Button>
+                               : null}
+                               {onRemoveCommand ?
+                                       <Button
+                                               onClick={() => onRemoveCommand(name)}
+                                               title={t('button.remove')}
+                                               variant="outline-danger"
+                                       >
+                                               <Icon.REMOVE title="" />
+                                       </Button>
+                               : null}
+                       </div>
+               </td>
+       </tr>;
+};
+
+Command.propTypes = {
+       name: PropTypes.string,
+       onEditCommand: PropTypes.func,
+       onRemoveCommand: PropTypes.func,
+       settings: PropTypes.shape({
+               command: PropTypes.string,
+               restrict: PropTypes.string,
+       }),
+};
+
+export default Command;
diff --git a/resources/js/components/twitch-bot/CommandDialog.js b/resources/js/components/twitch-bot/CommandDialog.js
new file mode 100644 (file)
index 0000000..7e8ce16
--- /dev/null
@@ -0,0 +1,41 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Modal } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import CommandForm from './CommandForm';
+
+const CommandDialog = ({
+       name,
+       onHide,
+       onSubmit,
+       settings,
+       show,
+}) => {
+       const { t } = useTranslation();
+
+       return <Modal className="report-dialog" onHide={onHide} show={show}>
+               <Modal.Header closeButton>
+                       <Modal.Title>
+                               {t(name ? 'twitchBot.commandDialog' : 'twitchBot.addCommand')}
+                       </Modal.Title>
+               </Modal.Header>
+               <CommandForm
+                       name={name}
+                       onCancel={onHide}
+                       onSubmit={onSubmit}
+                       settings={settings}
+               />
+       </Modal>;
+};
+
+CommandDialog.propTypes = {
+       name: PropTypes.string,
+       onHide: PropTypes.func,
+       onSubmit: PropTypes.func,
+       settings: PropTypes.shape({
+       }),
+       show: PropTypes.bool,
+};
+
+export default CommandDialog;
diff --git a/resources/js/components/twitch-bot/CommandForm.js b/resources/js/components/twitch-bot/CommandForm.js
new file mode 100644 (file)
index 0000000..9e9586a
--- /dev/null
@@ -0,0 +1,163 @@
+import { withFormik } from 'formik';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Button, Form, Modal } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
+import yup from '../../schema/yup';
+
+const CommandForm = ({
+       errors,
+       handleBlur,
+       handleChange,
+       handleSubmit,
+       name,
+       onCancel,
+       touched,
+       values,
+}) => {
+       const { t } = useTranslation();
+
+       const COMMANDS = [
+               'none',
+               'runner',
+               'crew',
+               'guessing-start',
+               'guessing-stop',
+               'guessing-solve',
+               'guessing-cancel',
+       ];
+       const RESTRICTIONS = [
+               'none',
+               'mod',
+               'owner',
+       ];
+
+       return <Form noValidate onSubmit={handleSubmit}>
+               <Modal.Body>
+                       <Form.Group controlId="command.name">
+                               <Form.Label>{t('twitchBot.commandName')}</Form.Label>
+                               <Form.Control
+                                       disabled={!!name}
+                                       isInvalid={!!(touched.name && errors.name)}
+                                       name="name"
+                                       onBlur={handleBlur}
+                                       onChange={handleChange}
+                                       plaintext={!!name}
+                                       readOnly={!!name}
+                                       type="text"
+                                       value={values.name || ''}
+                               />
+                               {touched.name && errors.name ?
+                                       <Form.Control.Feedback type="invalid">
+                                               {t(errors.name)}
+                                       </Form.Control.Feedback>
+                               : null}
+                       </Form.Group>
+                       <Form.Group controlId="command.restrict">
+                               <Form.Label>{t('twitchBot.commandRestriction')}</Form.Label>
+                               <Form.Select
+                                       isInvalid={!!(touched.restrict && errors.restrict)}
+                                       name="restrict"
+                                       onBlur={handleBlur}
+                                       onChange={handleChange}
+                                       value={values.restrict || 'none'}
+                               >
+                                       {RESTRICTIONS.map(r =>
+                                               <option key={r} value={r}>
+                                                       {t(`twitchBot.commandRestrictions.${r}`)}
+                                               </option>
+                                       )}
+                               </Form.Select>
+                               {touched.restrict && errors.restrict ?
+                                       <Form.Control.Feedback type="invalid">
+                                               {t(errors.restrict)}
+                                       </Form.Control.Feedback>
+                               : null}
+                       </Form.Group>
+                       <Form.Group controlId="command.command">
+                               <Form.Label>{t('twitchBot.commandType')}</Form.Label>
+                               <Form.Select
+                                       isInvalid={!!(touched.command && errors.command)}
+                                       name="command"
+                                       onBlur={handleBlur}
+                                       onChange={handleChange}
+                                       value={values.command || 'none'}
+                               >
+                                       {COMMANDS.map(c =>
+                                               <option key={c} value={c}>
+                                                       {t(`twitchBot.commandTypes.${c}`)}
+                                               </option>
+                                       )}
+                               </Form.Select>
+                               {touched.command && errors.command ?
+                                       <Form.Control.Feedback type="invalid">
+                                               {t(errors.command)}
+                                       </Form.Control.Feedback>
+                               : null}
+                       </Form.Group>
+               </Modal.Body>
+               <Modal.Footer>
+                       {onCancel ?
+                               <Button onClick={onCancel} variant="secondary">
+                                       {t('button.cancel')}
+                               </Button>
+                       : null}
+                       <Button type="submit" variant="primary">
+                               {t('button.save')}
+                       </Button>
+               </Modal.Footer>
+       </Form>;
+};
+
+CommandForm.propTypes = {
+       errors: PropTypes.shape({
+               command: PropTypes.string,
+               name: PropTypes.string,
+               restrict: PropTypes.string,
+       }),
+       handleBlur: PropTypes.func,
+       handleChange: PropTypes.func,
+       handleSubmit: PropTypes.func,
+       name: PropTypes.string,
+       onCancel: PropTypes.func,
+       touched: PropTypes.shape({
+               command: PropTypes.bool,
+               name: PropTypes.bool,
+               restrict: PropTypes.bool,
+       }),
+       values: PropTypes.shape({
+               command: PropTypes.string,
+               name: PropTypes.string,
+               restrict: PropTypes.string,
+       }),
+};
+
+export default withFormik({
+       displayName: 'CommandForm',
+       enableReinitialize: true,
+       handleSubmit: async (values, actions) => {
+               const { setErrors } = actions;
+               const { onSubmit } = actions.props;
+               try {
+                       await onSubmit(values);
+               } catch (e) {
+                       if (e.response && e.response.data && e.response.data.errors) {
+                               setErrors(laravelErrorsToFormik(e.response.data.errors));
+                       }
+               }
+       },
+       mapPropsToValues: ({ name, settings }) => {
+               return {
+                       command: (settings && settings.command) || 'none',
+                       name: name || '',
+                       restrict: (settings && settings.restrict) || 'none',
+               };
+       },
+       validationSchema: yup.object().shape({
+               command: yup.string(),
+               name: yup.string().required(),
+               restrict: yup.string(),
+       }),
+})(CommandForm);
diff --git a/resources/js/components/twitch-bot/Commands.js b/resources/js/components/twitch-bot/Commands.js
new file mode 100644 (file)
index 0000000..98600d0
--- /dev/null
@@ -0,0 +1,49 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { Table } from 'react-bootstrap';
+import { useTranslation } from 'react-i18next';
+
+import Command from './Command';
+
+const Commands = ({
+       channel,
+       onEditCommand,
+       onRemoveCommand,
+}) => {
+       const { t } = useTranslation();
+
+       return channel.chat_commands ?
+               <Table>
+                       <thead>
+                               <tr>
+                                       <th>{t('twitchBot.commandName')}</th>
+                                       <th>{t('twitchBot.commandRestriction')}</th>
+                                       <th>{t('twitchBot.commandType')}</th>
+                                       <th className="text-end">{t('general.actions')}</th>
+                               </tr>
+                       </thead>
+                       <tbody>
+                               {Object.entries(channel.chat_commands).map(([name, settings]) =>
+                                       <Command
+                                               key={name}
+                                               name={name}
+                                               onEditCommand={onEditCommand}
+                                               onRemoveCommand={onRemoveCommand}
+                                               settings={settings}
+                                       />
+                               )}
+                       </tbody>
+               </Table>
+       : null;
+};
+
+Commands.propTypes = {
+       channel: PropTypes.shape({
+               chat_commands: PropTypes.shape({
+               }),
+       }),
+       onEditCommand: PropTypes.func,
+       onRemoveCommand: PropTypes.func,
+};
+
+export default Commands;
index bdc89380fe75c07a4a1ad22e8dd2587661d1ac1b..822c201b8fde5504b97c5553dbd484fe6bc536d6 100644 (file)
@@ -5,12 +5,18 @@ import { useTranslation } from 'react-i18next';
 import toastr from 'toastr';
 
 import ChatSettingsForm from './ChatSettingsForm';
+import CommandDialog from './CommandDialog';
+import Commands from './Commands';
+import GuessingSettingsForm from './GuessingSettingsForm';
 import ChannelSelect from '../common/ChannelSelect';
 import ToggleSwitch from '../common/ToggleSwitch';
 
 const Controls = () => {
        const [channel, setChannel] = React.useState(null);
        const [chatText, setChatText] = React.useState('');
+       const [editCommand, setEditCommand] = React.useState('');
+       const [editCommandSettings, setEditCommandSettings] = React.useState({});
+       const [showCommandDialog, setShowCommandDialog] = React.useState(false);
 
        const { t } = useTranslation();
 
@@ -56,6 +62,59 @@ const Controls = () => {
                }
        }, [channel, t]);
 
+       const onAddCommand = React.useCallback(() => {
+               setEditCommand('');
+               setEditCommandSettings({});
+               setShowCommandDialog(true);
+       }, [channel]);
+
+       const onEditCommand = React.useCallback((name, settings) => {
+               setEditCommand(name);
+               setEditCommandSettings(settings);
+               setShowCommandDialog(true);
+       }, [channel]);
+
+       const onRemoveCommand = React.useCallback(async (name) => {
+               try {
+                       const rsp = await axios.delete(`/api/channels/${channel.id}/commands/${name}`);
+                       setChannel(rsp.data);
+                       toastr.success(t('twitchBot.saveSuccess'));
+               } catch (e) {
+                       toastr.error(t('twitchBot.saveError'));
+               }
+       }, [channel]);
+
+       const saveCommand = React.useCallback(async (values) => {
+               try {
+                       const rsp = await axios.put(
+                               `/api/channels/${channel.id}/commands/${values.name}`,
+                               values,
+                       );
+                       setChannel(rsp.data);
+                       setShowCommandDialog(false);
+                       setEditCommand('');
+                       setEditCommandSettings({});
+                       toastr.success(t('twitchBot.saveSuccess'));
+               } catch (e) {
+                       toastr.error(t('twitchBot.saveError'));
+                       throw e;
+               }
+       }, [channel]);
+
+       const saveGuessingGame = React.useCallback(async (values) => {
+               try {
+                       const rsp = await axios.put(
+                               `/api/channels/${channel.id}/guessing-game/${values.name}`,
+                               values,
+                       );
+                       setChannel(rsp.data);
+                       toastr.success(t('twitchBot.saveSuccess'));
+               } catch (e) {
+                       toastr.error(t('twitchBot.saveError'));
+                       throw e;
+               }
+       }, [channel]);
+
        return <>
                <Row className="mb-4">
                        <Form.Group as={Col} md={6}>
@@ -105,41 +164,76 @@ const Controls = () => {
                </Row>
                {channel ?
                        <Row>
-                               <Form.Group as={Col} md={6}>
-                                       <Form.Label>{t('twitchBot.chat')}</Form.Label>
-                                       <Form.Control
-                                               as="textarea"
-                                               onChange={({ target: { value } }) => {
-                                                       setChatText(value);
+                               <Col className="mt-5" md={6}>
+                                       <h3>{t('twitchBot.chat')}</h3>
+                                       <Form.Group>
+                                               <Form.Label>{t('twitchBot.chat')}</Form.Label>
+                                               <Form.Control
+                                                       as="textarea"
+                                                       onChange={({ target: { value } }) => {
+                                                               setChatText(value);
+                                                       }}
+                                                       value={chatText}
+                                               />
+                                               <div className="button-bar">
+                                                       <Button
+                                                               className="mt-2"
+                                                               disabled={!chatText || !channel.join}
+                                                               onClick={() => {
+                                                                       if (chatText) chat(chatText, 'localhorsttv');
+                                                               }}
+                                                               variant="twitch"
+                                                       >
+                                                               {t('twitchBot.sendApp')}
+                                                       </Button>
+                                                       <Button
+                                                               className="mt-2"
+                                                               disabled={!chatText || !channel.chat}
+                                                               onClick={() => {
+                                                                       if (chatText) chat(chatText, 'horstiebot');
+                                                               }}
+                                                               variant="twitch"
+                                                       >
+                                                               {t('twitchBot.sendChat')}
+                                                       </Button>
+                                               </div>
+                                       </Form.Group>
+                               </Col>
+                               <Col className="mt-5" md={6}>
+                                       <h3>{t('twitchBot.chatSettings')}</h3>
+                                       <ChatSettingsForm channel={channel} onSubmit={saveChatSettings} />
+                               </Col>
+                               <Col className="mt-5" md={12}>
+                                       <h3>{t('twitchBot.commands')}</h3>
+                                       <Commands
+                                               channel={channel}
+                                               onEditCommand={onEditCommand}
+                                               onRemoveCommand={onRemoveCommand}
+                                       />
+                                       <CommandDialog
+                                               name={editCommand}
+                                               onHide={() => {
+                                                       setShowCommandDialog(false);
+                                                       setEditCommand('');
+                                                       setEditCommandSettings({});
                                                }}
-                                               value={chatText}
+                                               onSubmit={saveCommand}
+                                               settings={editCommandSettings}
+                                               show={showCommandDialog}
                                        />
-                                       <div className="button-bar">
-                                               <Button
-                                                       className="mt-2"
-                                                       disabled={!chatText || !channel.join}
-                                                       onClick={() => {
-                                                               if (chatText) chat(chatText, 'localhorsttv');
-                                                       }}
-                                                       variant="twitch"
-                                               >
-                                                       {t('twitchBot.sendApp')}
-                                               </Button>
-                                               <Button
-                                                       className="mt-2"
-                                                       disabled={!chatText || !channel.chat}
-                                                       onClick={() => {
-                                                               if (chatText) chat(chatText, 'horstiebot');
-                                                       }}
-                                                       variant="twitch"
-                                               >
-                                                       {t('twitchBot.sendChat')}
+                                       <div>
+                                               <Button onClick={onAddCommand} variant="primary">
+                                                       {t('twitchBot.addCommand')}
                                                </Button>
                                        </div>
-                               </Form.Group>
-                               <Col md={6}>
-                                       <h3>{t('twitchBot.chatSettings')}</h3>
-                                       <ChatSettingsForm channel={channel} onSubmit={saveChatSettings} />
+                               </Col>
+                               <Col className="mt-5" md={12}>
+                                       <h3>{t('twitchBot.guessingGame.settings')}</h3>
+                                       <GuessingSettingsForm
+                                               name="gtbk"
+                                               onSubmit={saveGuessingGame}
+                                               settings={channel.guessing_settings?.gtbk || {}}
+                                       />
                                </Col>
                        </Row>
                :
diff --git a/resources/js/components/twitch-bot/GuessingSettingsForm.js b/resources/js/components/twitch-bot/GuessingSettingsForm.js
new file mode 100644 (file)
index 0000000..128d6f5
--- /dev/null
@@ -0,0 +1,373 @@
+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 laravelErrorsToFormik from '../../helpers/laravelErrorsToFormik';
+import i18n from '../../i18n';
+import yup from '../../schema/yup';
+
+const GuessingSettingsForm = ({
+       errors,
+       handleBlur,
+       handleChange,
+       handleSubmit,
+       onCancel,
+       touched,
+       values,
+}) => {
+       const { t } = useTranslation();
+
+       return <Form noValidate onSubmit={handleSubmit}>
+               <Row>
+                       <Form.Group as={Col} controlId="gg.points_exact_first" md={6}>
+                               <Form.Label>{t('twitchBot.guessingGame.pointsExactFirst')}</Form.Label>
+                               <Form.Control
+                                       isInvalid={!!(touched.points_exact_first && errors.points_exact_first)}
+                                       max="5"
+                                       min="1"
+                                       name="points_exact_first"
+                                       onBlur={handleBlur}
+                                       onChange={handleChange}
+                                       step="1"
+                                       type="number"
+                                       value={values.points_exact_first || 0}
+                               />
+                               {touched.points_exact_first && errors.points_exact_first ?
+                                       <Form.Control.Feedback type="invalid">
+                                               {t(errors.points_exact_first)}
+                                       </Form.Control.Feedback>
+                               : null}
+                       </Form.Group>
+                       <Form.Group as={Col} controlId="gg.points_exact_other" md={6}>
+                               <Form.Label>{t('twitchBot.guessingGame.pointsExactOther')}</Form.Label>
+                               <Form.Control
+                                       isInvalid={!!(touched.points_exact_other && errors.points_exact_other)}
+                                       max="5"
+                                       min="0"
+                                       name="points_exact_other"
+                                       onBlur={handleBlur}
+                                       onChange={handleChange}
+                                       step="1"
+                                       type="number"
+                                       value={values.points_exact_other || 0}
+                               />
+                               {touched.points_exact_other && errors.points_exact_other ?
+                                       <Form.Control.Feedback type="invalid">
+                                               {t(errors.points_exact_other)}
+                                       </Form.Control.Feedback>
+                               : null}
+                       </Form.Group>
+                       <Form.Group as={Col} controlId="gg.points_close_first" md={6}>
+                               <Form.Label>{t('twitchBot.guessingGame.pointsCloseFirst')}</Form.Label>
+                               <Form.Control
+                                       isInvalid={!!(touched.points_close_first && errors.points_close_first)}
+                                       max="5"
+                                       min="0"
+                                       name="points_close_first"
+                                       onBlur={handleBlur}
+                                       onChange={handleChange}
+                                       step="0.5"
+                                       type="number"
+                                       value={values.points_close_first || 0}
+                               />
+                               {touched.points_close_first && errors.points_close_first ?
+                                       <Form.Control.Feedback type="invalid">
+                                               {t(errors.points_close_first)}
+                                       </Form.Control.Feedback>
+                               : null}
+                       </Form.Group>
+                       <Form.Group as={Col} controlId="gg.points_close_other" md={6}>
+                               <Form.Label>{t('twitchBot.guessingGame.pointsCloseOther')}</Form.Label>
+                               <Form.Control
+                                       isInvalid={!!(touched.points_close_other && errors.points_close_other)}
+                                       max="5"
+                                       min="0"
+                                       name="points_close_other"
+                                       onBlur={handleBlur}
+                                       onChange={handleChange}
+                                       step="0.5"
+                                       type="number"
+                                       value={values.points_close_other || 0}
+                               />
+                               {touched.points_close_other && errors.points_close_other ?
+                                       <Form.Control.Feedback type="invalid">
+                                               {t(errors.points_close_other)}
+                                       </Form.Control.Feedback>
+                               : null}
+                       </Form.Group>
+                       <Form.Group as={Col} controlId="gg.points_close_max" md={6}>
+                               <Form.Label>{t('twitchBot.guessingGame.pointsCloseMax')}</Form.Label>
+                               <Form.Control
+                                       isInvalid={!!(touched.points_close_max && errors.points_close_max)}
+                                       min="0"
+                                       name="points_close_max"
+                                       onBlur={handleBlur}
+                                       onChange={handleChange}
+                                       step="1"
+                                       type="number"
+                                       value={values.points_close_max || 0}
+                               />
+                               {touched.points_close_max && errors.points_close_max ?
+                                       <Form.Control.Feedback type="invalid">
+                                               {t(errors.points_close_max)}
+                                       </Form.Control.Feedback>
+                               : null}
+                       </Form.Group>
+               </Row>
+               <Form.Group controlId="gg.start_message">
+                       <Form.Label>{t('twitchBot.guessingGame.startMessage')}</Form.Label>
+                       <Form.Control
+                               isInvalid={!!(touched.start_message && errors.start_message)}
+                               name="start_message"
+                               onBlur={handleBlur}
+                               onChange={handleChange}
+                               type="text"
+                               value={values.start_message || ''}
+                       />
+                       {touched.start_message && errors.start_message ?
+                               <Form.Control.Feedback type="invalid">
+                                       {t(errors.start_message)}
+                               </Form.Control.Feedback>
+                       : null}
+               </Form.Group>
+               <Form.Group controlId="gg.stop_message">
+                       <Form.Label>{t('twitchBot.guessingGame.stopMessage')}</Form.Label>
+                       <Form.Control
+                               isInvalid={!!(touched.stop_message && errors.stop_message)}
+                               name="stop_message"
+                               onBlur={handleBlur}
+                               onChange={handleChange}
+                               type="text"
+                               value={values.stop_message || ''}
+                       />
+                       {touched.stop_message && errors.stop_message ?
+                               <Form.Control.Feedback type="invalid">
+                                       {t(errors.stop_message)}
+                               </Form.Control.Feedback>
+                       : null}
+               </Form.Group>
+               <Form.Group controlId="gg.winners_message">
+                       <Form.Label>{t('twitchBot.guessingGame.winnersMessage')}</Form.Label>
+                       <Form.Control
+                               isInvalid={!!(touched.winners_message && errors.winners_message)}
+                               name="winners_message"
+                               onBlur={handleBlur}
+                               onChange={handleChange}
+                               type="text"
+                               value={values.winners_message || ''}
+                       />
+                       {touched.winners_message && errors.winners_message ?
+                               <Form.Control.Feedback type="invalid">
+                                       {t(errors.winners_message)}
+                               </Form.Control.Feedback>
+                       :
+                               <Form.Text muted>
+                                       {t('twitchBot.guessingGame.winnersMessageHint')}
+                               </Form.Text>
+                       }
+               </Form.Group>
+               <Form.Group controlId="gg.no_winners_message">
+                       <Form.Label>{t('twitchBot.guessingGame.noWinnersMessage')}</Form.Label>
+                       <Form.Control
+                               isInvalid={!!(touched.no_winners_message && errors.no_winners_message)}
+                               name="no_winners_message"
+                               onBlur={handleBlur}
+                               onChange={handleChange}
+                               type="text"
+                               value={values.no_winners_message || ''}
+                       />
+                       {touched.no_winners_message && errors.no_winners_message ?
+                               <Form.Control.Feedback type="invalid">
+                                       {t(errors.no_winners_message)}
+                               </Form.Control.Feedback>
+                       : null}
+               </Form.Group>
+               <Form.Group controlId="gg.cancel_message">
+                       <Form.Label>{t('twitchBot.guessingGame.cancelMessage')}</Form.Label>
+                       <Form.Control
+                               isInvalid={!!(touched.cancel_message && errors.cancel_message)}
+                               name="cancel_message"
+                               onBlur={handleBlur}
+                               onChange={handleChange}
+                               type="text"
+                               value={values.cancel_message || ''}
+                       />
+                       {touched.cancel_message && errors.cancel_message ?
+                               <Form.Control.Feedback type="invalid">
+                                       {t(errors.cancel_message)}
+                               </Form.Control.Feedback>
+                       : null}
+               </Form.Group>
+               <Form.Group controlId="gg.invalid_solution_message">
+                       <Form.Label>{t('twitchBot.guessingGame.invalidSolutionMessage')}</Form.Label>
+                       <Form.Control
+                               isInvalid={!!(touched.invalid_solution_message && errors.invalid_solution_message)}
+                               name="invalid_solution_message"
+                               onBlur={handleBlur}
+                               onChange={handleChange}
+                               type="text"
+                               value={values.invalid_solution_message || ''}
+                       />
+                       {touched.invalid_solution_message && errors.invalid_solution_message ?
+                               <Form.Control.Feedback type="invalid">
+                                       {t(errors.invalid_solution_message)}
+                               </Form.Control.Feedback>
+                       : null}
+               </Form.Group>
+               <Form.Group controlId="gg.active_message">
+                       <Form.Label>{t('twitchBot.guessingGame.activeMessage')}</Form.Label>
+                       <Form.Control
+                               isInvalid={!!(touched.active_message && errors.active_message)}
+                               name="active_message"
+                               onBlur={handleBlur}
+                               onChange={handleChange}
+                               type="text"
+                               value={values.active_message || ''}
+                       />
+                       {touched.active_message && errors.active_message ?
+                               <Form.Control.Feedback type="invalid">
+                                       {t(errors.active_message)}
+                               </Form.Control.Feedback>
+                       : null}
+               </Form.Group>
+               <Form.Group controlId="gg.not_active_message">
+                       <Form.Label>{t('twitchBot.guessingGame.notActiveMessage')}</Form.Label>
+                       <Form.Control
+                               isInvalid={!!(touched.not_active_message && errors.not_active_message)}
+                               name="not_active_message"
+                               onBlur={handleBlur}
+                               onChange={handleChange}
+                               type="text"
+                               value={values.not_active_message || ''}
+                       />
+                       {touched.not_active_message && errors.not_active_message ?
+                               <Form.Control.Feedback type="invalid">
+                                       {t(errors.not_active_message)}
+                               </Form.Control.Feedback>
+                       : null}
+               </Form.Group>
+               <div className="button-bar mt-3">
+                       {onCancel ?
+                               <Button onClick={onCancel} variant="secondary">
+                                       {t('button.cancel')}
+                               </Button>
+                       : null}
+                       <Button type="submit" variant="primary">
+                               {t('button.save')}
+                       </Button>
+               </div>
+       </Form>;
+};
+
+GuessingSettingsForm.propTypes = {
+       errors: PropTypes.shape({
+               active_message: PropTypes.string,
+               cancel_message: PropTypes.string,
+               invalid_solution_message: PropTypes.string,
+               name: PropTypes.string,
+               no_winners_message: PropTypes.string,
+               not_active_message: PropTypes.string,
+               points_close_first: PropTypes.string,
+               points_close_max: PropTypes.string,
+               points_close_other: PropTypes.string,
+               points_exact_first: PropTypes.string,
+               points_exact_other: PropTypes.string,
+               start_message: PropTypes.string,
+               stop_message: PropTypes.string,
+               winners_message: PropTypes.string,
+       }),
+       handleBlur: PropTypes.func,
+       handleChange: PropTypes.func,
+       handleSubmit: PropTypes.func,
+       name: PropTypes.string,
+       onCancel: PropTypes.func,
+       touched: PropTypes.shape({
+               active_message: PropTypes.bool,
+               cancel_message: PropTypes.bool,
+               invalid_solution_message: PropTypes.bool,
+               name: PropTypes.bool,
+               no_winners_message: PropTypes.bool,
+               not_active_message: PropTypes.bool,
+               points_close_first: PropTypes.bool,
+               points_close_max: PropTypes.bool,
+               points_close_other: PropTypes.bool,
+               points_exact_first: PropTypes.bool,
+               points_exact_other: PropTypes.bool,
+               start_message: PropTypes.bool,
+               stop_message: PropTypes.bool,
+               winners_message: PropTypes.bool,
+       }),
+       values: PropTypes.shape({
+               active_message: PropTypes.string,
+               cancel_message: PropTypes.string,
+               invalid_solution_message: PropTypes.string,
+               name: PropTypes.string,
+               no_winners_message: PropTypes.string,
+               not_active_message: PropTypes.string,
+               points_close_first: PropTypes.number,
+               points_close_max: PropTypes.number,
+               points_close_other: PropTypes.number,
+               points_exact_first: PropTypes.number,
+               points_exact_other: PropTypes.number,
+               start_message: PropTypes.string,
+               stop_message: PropTypes.string,
+               winners_message: PropTypes.string,
+       }),
+};
+
+export default withFormik({
+       displayName: 'GuessingSettingsForm',
+       enableReinitialize: true,
+       handleSubmit: async (values, actions) => {
+               const { setErrors } = actions;
+               const { onSubmit } = actions.props;
+               try {
+                       await onSubmit(values);
+               } catch (e) {
+                       if (e.response && e.response.data && e.response.data.errors) {
+                               setErrors(laravelErrorsToFormik(e.response.data.errors));
+                       }
+               }
+       },
+       mapPropsToValues: ({ name, settings }) => {
+               const getNumericValue = (s, n, d) => s && Object.prototype.hasOwnProperty.call(s, n)
+                       ? s[n] : d;
+               const getStringValue = (s, n, d) => s && Object.prototype.hasOwnProperty.call(s, n)
+                       ? s[n] : i18n.t(`twitchBot.guessingGame.default${d}`);
+               return {
+                       active_message: getStringValue(settings, 'active_message', 'ActiveMessage'),
+                       cancel_message: getStringValue(settings, 'cancel_message', 'CancelMessage'),
+                       invalid_solution_message:
+                               getStringValue(settings, 'invalid_solution_message', 'InvalidSolutionMessage'),
+                       name: name || '',
+                       no_winners_message: getStringValue(settings, 'no_winners_message', 'NoWinnersMessage'),
+                       not_active_message: getStringValue(settings, 'not_active_message', 'NotActiveMessage'),
+                       points_close_first: getNumericValue(settings, 'points_close_first', 1),
+                       points_close_max: getNumericValue(settings, 'points_close_max', 3),
+                       points_close_other: getNumericValue(settings, 'points_close_other', 1),
+                       points_exact_first: getNumericValue(settings, 'points_exact_first', 1),
+                       points_exact_other: getNumericValue(settings, 'points_exact_other', 1),
+                       start_message: getStringValue(settings, 'start_message', 'StartMessage'),
+                       stop_message: getStringValue(settings, 'stop_message', 'StopMessage'),
+                       winners_message: getStringValue(settings, 'winners_message', 'WinnersMessage'),
+               };
+       },
+       validationSchema: yup.object().shape({
+               active_message: yup.string(),
+               cancel_message: yup.string(),
+               invalid_solution_message: yup.string(),
+               name: yup.string().required(),
+               no_winners_message: yup.string(),
+               not_active_message: yup.string(),
+               points_close_first: yup.number(),
+               points_close_max: yup.number(),
+               points_close_other: yup.number(),
+               points_exact_first: yup.number(),
+               points_exact_other: yup.number(),
+               start_message: yup.string(),
+               stop_message: yup.string(),
+               winners_message: yup.string(),
+       }),
+})(GuessingSettingsForm);
index 080a6dd13d90e9210ee99e8191d8d0c03fa106ab..b9496952887c60f601dfcc08d83f8c80d6c0eaa1 100644 (file)
@@ -182,6 +182,7 @@ export default {
                        tech: 'ALttP Techniken',
                },
                general: {
+                       actions: 'Aktionen',
                        anonymous: 'Anonym',
                        appDescription: 'Turniere und Tutorials für The Legend of Zelda: A Link to the Past Randomizer',
                        appName: 'ALttP',
@@ -491,6 +492,7 @@ export default {
                        unlockSuccess: 'Turnier entsperrt',
                },
                twitchBot: {
+                       addCommand: 'Command hinzufügen',
                        channel: 'Channel',
                        chat: 'Chat',
                        chatError: 'Fehler beim Senden',
@@ -500,7 +502,52 @@ export default {
                        chatWaitMsgsMax: 'Max. Messages',
                        chatWaitTimeMin: 'Min. Zeit',
                        chatWaitTimeMax: 'Max. Zeit',
+                       commandDialog: 'Command bearbeiten',
+                       commandName: 'Name',
+                       commandParameters: 'Parameter',
+                       commandRestriction: 'Einschränkung',
+                       commandRestrictions: {
+                               mod: 'Mods',
+                               none: 'keine',
+                               owner: 'Owner',
+                       },
+                       commands: 'Commands',
+                       commandType: 'Typ',
+                       commandTypes: {
+                               crew: 'Crew Liste',
+                               'guessing-cancel': 'Guessing Game abbrechen',
+                               'guessing-solve': 'Guessing Game auflösen',
+                               'guessing-start': 'Guessing Game starten',
+                               'guessing-stop': 'Guessing Game stoppen',
+                               none: 'keiner',
+                               runner: 'Runner Liste',
+                       },
                        controls: 'Controls',
+                       guessingGame: {
+                               activeMessage: 'Nachricht bei bereits laufendem Spiel',
+                               cancelMessage: 'Nachricht bei Spielabbruch',
+                               defaultActiveMessage: 'Es läuft bereits ein Spiel auf diesem Kanal',
+                               defaultCancelMessage: 'Spiel abgebrochen',
+                               defaultInvalidSolutionMessage: 'Bitte eine gültige Lösung für das Guessing Game angeben',
+                               defaultNoWinnersMessage: 'keiner gewonnen :(',
+                               defaultNotActiveMessage: 'Es läuft gerade kein Spiel auf diesem Kanal',
+                               defaultStartMessage: 'Haut jetzt eure Zahlen raus!',
+                               defaultStopMessage: 'Annahme geschlossen',
+                               defaultWinnersMessage: 'Glückwunsch {names}!',
+                               invalidSolutionMessage: 'Nachricht bei ungültiger (oder fehlender) Lösung',
+                               noWinnersMessage: 'Nachricht, falls keine Gewinner',
+                               notActiveMessage: 'Nachricht, wenn kein Spiel läuft',
+                               pointsCloseFirst: 'Punkte für den ersten nächsten Treffer',
+                               pointsCloseMax: 'Maximaler Abstand, um als nächster Treffer gewertet zu werden',
+                               pointsCloseOther: 'Punkte für weitere nächste Treffer',
+                               pointsExactFirst: 'Punkte für den ersten exakten Treffer',
+                               pointsExactOther: 'Punkte für weitere exakte Treffer',
+                               settings: 'Guessing Game Einstellungen',
+                               startMessage: 'Nachricht bei Start',
+                               stopMessage: 'Nachricht bei Einsendeschluss',
+                               winnersMessage: 'Ankündigung der Gewinner',
+                               winnersMessageHint: '{names} wird durch die Namen der Gewinner ersetzt',
+                       },
                        heading: 'Twitch Bot',
                        joinApp: 'Join als App Bot',
                        joinChat: 'Join als Chat Bot',
index f722332ba30bc65e218d7fdd603b79d414e4a9b6..695530d9ccb133051bd67e3cbd8e1a23a8e66ff1 100644 (file)
@@ -182,6 +182,7 @@ export default {
                        tech: 'ALttP Tech',
                },
                general: {
+                       actions: 'Actions',
                        anonymous: 'Anonym',
                        appDescription: 'Tournaments and tutorials for The Legend of Zelda: A Link to the Past Randomizer',
                        appName: 'ALttP',
@@ -491,6 +492,7 @@ export default {
                        unlockSuccess: 'Tournament unlocked',
                },
                twitchBot: {
+                       addCommand: 'Add command',
                        channel: 'Channel',
                        chat: 'Chat',
                        chatError: 'Error sending message',
@@ -500,7 +502,52 @@ export default {
                        chatWaitMsgsMax: 'Max. messages',
                        chatWaitTimeMin: 'Min. time',
                        chatWaitTimeMax: 'Max. time',
+                       commandDialog: 'Edit command',
+                       commandName: 'Name',
+                       commandParameters: 'Parameters',
+                       commandRestriction: 'Restriction',
+                       commandRestrictions: {
+                               mod: 'Mods',
+                               none: 'none',
+                               owner: 'Owner',
+                       },
+                       commands: 'Commands',
+                       commandType: 'Type',
+                       commandTypes: {
+                               crew: 'Crew list',
+                               'guessing-cancel': 'Cancel guessing game',
+                               'guessing-solve': 'Solve guessing game',
+                               'guessing-start': 'Start guessing game',
+                               'guessing-stop': 'Stop guessing game',
+                               none: 'keiner',
+                               runner: 'Runner list',
+                       },
                        controls: 'Controls',
+                       guessingGame: {
+                               activeMessage: 'Message when a game is already running',
+                               cancelMessage: 'Game cancellation announcement',
+                               defaultActiveMessage: 'Channel already has an active guessing game',
+                               defaultCancelMessage: 'Guessing game cancelled',
+                               defaultInvalidSolutionMessage: 'Please provide a valid solution to the guessing game',
+                               defaultNoWinnersMessage: 'nobody wins :(',
+                               defaultNotActiveMessage: 'Channel has no active guessing game',
+                               defaultStartMessage: 'Get your guesses in',
+                               defaultStopMessage: 'Guessing closed',
+                               defaultWinnersMessage: 'Congrats {names}!',
+                               invalidSolutionMessage: 'Message for invalid (or missing) solution',
+                               noWinnersMessage: 'Announcement for no winners',
+                               notActiveMessage: 'Message when no game is currently active',
+                               pointsCloseFirst: 'Points for first close match',
+                               pointsCloseMax: 'Maximum distance to count as close match',
+                               pointsCloseOther: 'Points for further close matches',
+                               pointsExactFirst: 'Points for first exact match',
+                               pointsExactOther: 'Points for further exact matches',
+                               settings: 'Guessing game settings',
+                               startMessage: 'Starting announcement',
+                               stopMessage: 'Closing announcement',
+                               winnersMessage: 'Winners announcement',
+                               winnersMessageHint: '{names} will be replaced with a list of winners\' names',
+                       },
                        heading: 'Twitch Bot',
                        joinApp: 'Join as App Bot',
                        joinChat: 'Join as Chat Bot',
index 84cbf733cc5e611d8d8d2556e1ddf3228987b15e..276ff634641dc6234d16d2e869b26be2bf35c211 100644 (file)
@@ -30,6 +30,9 @@ Route::post('channels/{channel}/chat', 'App\Http\Controllers\ChannelController@c
 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');
+Route::delete('channels/{channel}/commands/{command}', 'App\Http\Controllers\ChannelController@deleteCommand');
+Route::put('channels/{channel}/commands/{command}', 'App\Http\Controllers\ChannelController@saveCommand');
+Route::put('channels/{channel}/guessing-game/{name}', 'App\Http\Controllers\ChannelController@saveGuessingGame');
 
 Route::get('content', 'App\Http\Controllers\TechniqueController@search');
 Route::get('content/{tech:name}', 'App\Http\Controllers\TechniqueController@single');