From: Daniel Karbach Date: Tue, 9 Apr 2024 13:34:06 +0000 (+0200) Subject: chat bot protocol ui X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=147c5f43c5d41fa18e82edb6651fe5a37c789353;p=alttp.git chat bot protocol ui --- diff --git a/app/Http/Controllers/ChannelController.php b/app/Http/Controllers/ChannelController.php index 883ca65..15213b4 100644 --- a/app/Http/Controllers/ChannelController.php +++ b/app/Http/Controllers/ChannelController.php @@ -68,6 +68,16 @@ class ChannelController extends Controller { return $this->sendChannel($channel); } + public function chatBotLog(Request $request, Channel $channel) { + $this->authorize('editRestream', $channel); + $log = $channel->chat_bot_logs() + ->with('origin') + ->orderBy('created_at', 'DESC') + ->limit(150) + ->get(); + return $log->values()->toJson(); + } + public function chatSettings(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 46155cd..8af6a7d 100644 --- a/app/Models/Channel.php +++ b/app/Models/Channel.php @@ -240,6 +240,10 @@ class Channel extends Model { return Arr::join($entries, ', ', ' and '); } + public function chat_bot_logs() { + return $this->hasMany(ChatBotLog::class); + } + public function crews() { return $this->hasMany(ChannelCrew::class); } diff --git a/resources/js/components/chat-bot-logs/ChatBotLog.js b/resources/js/components/chat-bot-logs/ChatBotLog.js new file mode 100644 index 0000000..45c8027 --- /dev/null +++ b/resources/js/components/chat-bot-logs/ChatBotLog.js @@ -0,0 +1,51 @@ +import axios from 'axios'; +import PropTypes from 'prop-types'; +import React, { useEffect, useState } from 'react'; +import { Button } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +import Dialog from './Dialog'; +import Icon from '../common/Icon'; + +const ChatBotLog = ({ id }) => { + const [showDialog, setShowDialog] = useState(false); + const [log, setLog] = useState([]); + + const { t } = useTranslation(); + + useEffect(() => { + if (!showDialog) return; + const ctrl = new AbortController(); + axios + .get(`/api/channels/${id}/chat-bot-log`, { signal: ctrl.signal }) + .then(response => { + setLog(response.data); + }); + return () => { + ctrl.abort(); + }; + }, [id, showDialog]); + + return ( + <> + + setShowDialog(false)} + show={showDialog} + /> + + ); +}; + +ChatBotLog.propTypes = { + id: PropTypes.number, +}; + +export default ChatBotLog; diff --git a/resources/js/components/chat-bot-logs/Dialog.js b/resources/js/components/chat-bot-logs/Dialog.js new file mode 100644 index 0000000..ffe457c --- /dev/null +++ b/resources/js/components/chat-bot-logs/Dialog.js @@ -0,0 +1,65 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { Alert, Button, Modal } from 'react-bootstrap'; +import { withTranslation } from 'react-i18next'; + +import List from './List'; +import i18n from '../../i18n'; + +class Dialog extends React.Component { + + componentDidMount() { + this.timer = setInterval(() => { + this.forceUpdate(); + }, 30000); + } + + componentWillUnmount() { + clearInterval(this.timer); + } + + render() { + const { + log, + onHide, + show, + } = this.props; + return + + + {i18n.t('chatBotLog.heading')} + + + {log && log.length ? + + : + + + {i18n.t('chatBotLog.empty')} + + + } + + + + ; + } + +} + +Dialog.propTypes = { + log: PropTypes.arrayOf(PropTypes.shape({ + })), + onHide: PropTypes.func, + show: PropTypes.bool, +}; + +Dialog.defaultProps = { + log: null, + onHide: null, + show: false, +}; + +export default withTranslation()(Dialog); diff --git a/resources/js/components/chat-bot-logs/Item.js b/resources/js/components/chat-bot-logs/Item.js new file mode 100644 index 0000000..8340e4e --- /dev/null +++ b/resources/js/components/chat-bot-logs/Item.js @@ -0,0 +1,59 @@ +import moment from 'moment'; +import PropTypes from 'prop-types'; +import React from 'react'; +import { ListGroup } from 'react-bootstrap'; +import { useTranslation } from 'react-i18next'; + +const getEntryDate = entry => { + const dateStr = moment(entry.created_at).fromNow(); + return entry.user + ? `${entry.user.username} ${dateStr}` + : dateStr; +}; + +const getEntryOrigin = (entry, t) => { + return t('chatBotLog.origin.chatLog', { + channel: entry.origin.params[0], + date: new Date(entry.origin.created_at), + nick: entry.origin.nick, + }); +}; + +const Item = ({ entry }) => { + const { t } = useTranslation(); + + return +
+
+ {entry.text} +
+ {entry.origin ? +
+ {getEntryOrigin(entry, t)} +
+ : null} +
+ {getEntryDate(entry)} +
+
+
; +}; + +Item.propTypes = { + entry: PropTypes.shape({ + created_at: PropTypes.string, + origin: PropTypes.shape({}), + text: PropTypes.string, + }), +}; + +Item.defaultProps = { + entry: {}, +}; + +export default Item; diff --git a/resources/js/components/chat-bot-logs/List.js b/resources/js/components/chat-bot-logs/List.js new file mode 100644 index 0000000..9838b8e --- /dev/null +++ b/resources/js/components/chat-bot-logs/List.js @@ -0,0 +1,23 @@ +import PropTypes from 'prop-types'; +import React from 'react'; +import { ListGroup } from 'react-bootstrap'; + +import Item from './Item'; + +const List = ({ log }) => + + {log ? log.map(entry => + + ) : null} + ; + +List.propTypes = { + log: PropTypes.arrayOf(PropTypes.shape({ + })), +}; + +List.defaultProps = { + log: [], +}; + +export default List; diff --git a/resources/js/components/twitch-bot/Controls.js b/resources/js/components/twitch-bot/Controls.js index 496ff11..51f0633 100644 --- a/resources/js/components/twitch-bot/Controls.js +++ b/resources/js/components/twitch-bot/Controls.js @@ -8,6 +8,7 @@ import ChatSettingsForm from './ChatSettingsForm'; import CommandDialog from './CommandDialog'; import Commands from './Commands'; import GuessingSettingsForm from './GuessingSettingsForm'; +import ChatBotLog from '../chat-bot-logs/ChatBotLog'; import ChannelSelect from '../common/ChannelSelect'; import Icon from '../common/Icon'; import ToggleSwitch from '../common/ToggleSwitch'; @@ -228,7 +229,12 @@ const Controls = () => { -

{t('twitchBot.chatSettings')}

+
+

{t('twitchBot.chatSettings')}

+
+ +
+
diff --git a/resources/js/i18n/de.js b/resources/js/i18n/de.js index c663747..8a24991 100644 --- a/resources/js/i18n/de.js +++ b/resources/js/i18n/de.js @@ -97,6 +97,13 @@ export default { unconfirm: 'Zurückziehen', unset: 'Zurücksetzen', }, + chatBotLog: { + empty: 'Noch keine Nachrichten erfasst', + heading: 'Chat Bot Protokoll', + origin: { + chatLog: '{{ nick }} in {{ channel }} am {{ date, L LT }}', + }, + }, content: { attribution: 'Attribution', description: 'Beschreibung', diff --git a/resources/js/i18n/en.js b/resources/js/i18n/en.js index 0bc85e8..f33d4f0 100644 --- a/resources/js/i18n/en.js +++ b/resources/js/i18n/en.js @@ -97,6 +97,13 @@ export default { unconfirm: 'Retract', unset: 'Unset', }, + chatBotLog: { + empty: 'No messages on protocol yet', + heading: 'Chat Bot Log', + origin: { + chatLog: '{{ nick }} in {{ channel }} on {{ date, L LT }}', + }, + }, content: { attribution: 'Attribution', description: 'Description', diff --git a/routes/api.php b/routes/api.php index 0c897af..69a4bbf 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::get('channels/{channel}/chat-bot-log', 'App\Http\Controllers\ChannelController@chatBotLog'); 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');