namespace App\Http\Controllers;
use App\Models\ChatBotLog;
+use App\Models\ChatLog;
+use Carbon\Carbon;
use Illuminate\Http\Request;
class ChatBotLogController extends Controller {
return $logs->get()->toJson();
}
+ public function getContext(ChatBotLog $entry) {
+ $log = ChatLog::where('command', '=', 'PRIVMSG')
+ ->where('channel_id', '=', $entry->channel_id)
+ ->where('created_at', '<=', $entry->created_at)
+ ->orderBy('created_at', 'DESC')
+ ->limit(10)
+ ->get()
+ ->reverse()
+ ->values();
+ $original = null;
+ if ($entry->origin_id) {
+ try {
+ $original = ChatLog::where('command', '=', 'PRIVMSG')
+ ->where('channel_id', '=', $entry->origin->channel_id)
+ ->where('id', '<', $entry->origin_id)
+ ->orderBy('created_at', 'DESC')
+ ->limit(10)
+ ->get()
+ ->reverse()
+ ->values();
+ } catch (\Exception $e) {
+ // original was deleted perhaps
+ }
+ }
+ return [
+ 'current' => $log,
+ 'original' => $original,
+ ];
+ }
+
}
+import axios from 'axios';
import moment from 'moment';
import PropTypes from 'prop-types';
import React from 'react';
-import { ListGroup } from 'react-bootstrap';
+import { Button, Col, ListGroup, Row } from 'react-bootstrap';
import { useTranslation } from 'react-i18next';
import ChannelLink from '../channel/Link';
+import List from '../chat-logs/List';
+import Icon from '../common/Icon';
+import Loading from '../common/Loading';
import { getUserName } from '../../helpers/User';
const getEntryDate = entry => moment(entry.created_at).fromNow();
};
const Item = ({ entry = {} }) => {
+ const [context, setContext] = React.useState(null);
+ const [contextLoading, setContextLoading] = React.useState(true);
+ const [showContext, setShowContext] = React.useState(false);
+
const { t } = useTranslation();
- return <ListGroup.Item className="d-flex justify-content-between">
- <div>
+ React.useEffect(() => {
+ if (context || !showContext) return;
+ const ctrl = new AbortController();
+ axios
+ .get(`/api/chatbotlogs/${entry.id}/context`, {
+ signal: ctrl.signal
+ })
+ .then(response => {
+ setContextLoading(false);
+ setContext(response.data);
+ })
+ .catch(error => {
+ if (!axios.isCancel(error)) {
+ setContextLoading(false);
+ setContext(null);
+ }
+ });
+ return () => {
+ ctrl.abort();
+ };
+ }, [context, showContext]);
+
+ return <ListGroup.Item>
+ <div className="d-flex justify-content-between">
<div>
- {entry.text}
- </div>
- {entry.origin ?
+ <div>
+ {entry.text}
+ </div>
+ {entry.origin ?
+ <div
+ className="text-muted"
+ >
+ {getEntryOrigin(entry, t)}
+ </div>
+ : null}
<div
className="text-muted"
+ title={moment(entry.created_at).format('LLLL')}
>
- {getEntryOrigin(entry, t)}
+ {getEntryInfo(entry, t)}
</div>
- : null}
- <div
- className="text-muted"
- title={moment(entry.created_at).format('LLLL')}
- >
- {getEntryInfo(entry, t)}
</div>
- </div>
- {entry.channel ?
<div>
- <ChannelLink channel={entry.channel} />
+ {entry.channel ?
+ <ChannelLink channel={entry.channel} />
+ : null}
+ <Button
+ className="ms-2"
+ onClick={() => { setShowContext(c => !c); }}
+ title={t('chatBotLog.showContext')}
+ variant={showContext ? 'secondary' : 'outline-secondary'}
+ >
+ <Icon.PROTOCOL title="" />
+ </Button>
+ </div>
+ </div>
+ {showContext ?
+ <div className="chat-bot-log-context mt-2">
+ {contextLoading ?
+ <Loading />
+ : null}
+ {context ?
+ <Row>
+ <Col sm={6}>
+ <h3 className="fs-6">{t('chatBotLog.context')}</h3>
+ <List log={context.current} />
+ </Col>
+ {context.original ?
+ <Col sm={6}>
+ <h3 className="fs-6">{t('chatBotLog.originalContext')}</h3>
+ <List log={context.original} />
+ </Col>
+ : null}
+ </Row>
+ : null}
</div>
: null}
</ListGroup.Item>;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+const getChatterColor = entry => {
+ if (entry.tags && entry.tags['color']) {
+ return entry.tags['color'];
+ }
+ return 'inherit';
+};
+
+const getChatterNick = entry => {
+ if (entry.tags && entry.tags['display-name']) {
+ return entry.tags['display-name'];
+ }
+ return entry.nick;
+};
+
+const getTextContent = entry => {
+ if (entry.params && entry.params.length >= 2) {
+ return entry.params[1];
+ }
+ return entry.text_content;
+};
+
+const getTimestamp = entry => {
+ if (entry.tags && entry.tags['tmi-sent-ts']) {
+ return new Date(parseInt(entry.tags['tmi-sent-ts'], 10));
+ }
+ return new Date(entry.created_at);
+};
+
+const Item = ({ entry }) => {
+ const { t } = useTranslation();
+
+ return <div className="chat-log-item">
+ <div>
+ <span className="text-muted me-2">
+ {t('chatBotLog.shortTimestamp', { date: getTimestamp(entry) })}
+ </span>
+ <strong style={{ color: getChatterColor(entry) }}>{getChatterNick(entry)}</strong>
+ </div>
+ <div>{getTextContent(entry)}</div>
+ </div>;
+};
+
+Item.propTypes = {
+ entry: PropTypes.shape({
+ text_content: PropTypes.string,
+ }).isRequired,
+};
+
+export default Item;
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+
+import Item from './Item';
+
+const List = ({ log = [] }) => {
+ return <div className="chat-log-list">
+ {log.map(entry =>
+ <Item key={entry.id} entry={entry} />
+ )}
+ </div>;
+};
+
+List.propTypes = {
+ log: PropTypes.arrayOf(PropTypes.shape({
+ })),
+};
+
+export default List;
unset: 'Zurücksetzen',
},
chatBotLog: {
+ context: 'Kontext',
empty: 'Noch keine Nachrichten erfasst',
heading: 'Chat Bot Protokoll',
info: {
origin: {
chatLog: 'Quelle: {{ nick }} in {{ channel }} am {{ date, L LT }}',
},
+ originalContext: 'Ursprünglicher Kontext',
+ shortTimestamp: '{{ date, HH:mm:ss }}',
+ showContext: 'Kontext zeigen',
},
content: {
attribution: 'Attribution',
unset: 'Unset',
},
chatBotLog: {
+ context: 'Context',
empty: 'No messages on protocol yet',
heading: 'Chat Bot Log',
info: {
origin: {
chatLog: 'Source: {{ nick }} in {{ channel }} on {{ date, L LT }}',
},
+ originalContext: 'Original context',
+ shortTimestamp: '{{ date, hh:mm:ss }}',
+ showContext: 'Show context',
},
content: {
attribution: 'Attribution',
// Custom
@import 'common';
@import 'channels';
+@import 'chatlog';
@import 'discord';
@import 'doors';
@import 'episodes';
--- /dev/null
+.chat-log-list {
+ padding: 0.5ex 1ex;
+ background: $dark;
+}
Route::get('guessing-game-monitor/{key}', 'App\Http\Controllers\ChannelController@getGuessingGameMonitor');
Route::get('chatbotlogs', 'App\Http\Controllers\ChatBotLogController@search');
+Route::get('chatbotlogs/{entry}/context', 'App\Http\Controllers\ChatBotLogController@getContext');
Route::get('content', 'App\Http\Controllers\TechniqueController@search');
Route::get('content/{tech:name}', 'App\Http\Controllers\TechniqueController@single');