use App\Models\DiscordBotCommand;
use Discord\Discord;
use Discord\Parts\Channel\Channel;
-use Discord\Parts\Channel\Message;
-use Discord\Parts\User\Member;
+use React\Promise\PromiseInterface;
class MessageCommand extends BaseCommand {
parent::__construct($discord, $cmd);
}
- public function execute() {
+ public function execute(): PromiseInterface {
if (!$this->hasParameter('text')) {
throw new \Exception('missing text parameter');
}
use Discord\Discord;
use Discord\Parts\User\Activity;
use React\Promise\Promise;
+use React\Promise\PromiseInterface;
class PresenceCommand extends BaseCommand {
parent::__construct($discord, $cmd);
}
- public function execute() {
- return new Promise(function($resolve) {
+ public function execute(): PromiseInterface {
+ return new Promise(function ($resolve) {
$activity = null;
$idle = false;
$status = 'online';
use App\Models\DiscordBotCommand;
use Discord\Discord;
use Discord\Parts\Channel\Channel;
-use Discord\Parts\Channel\Message;
use Discord\Parts\User\Member;
+use React\Promise\PromiseInterface;
class ResultCommand extends BaseCommand {
parent::__construct($discord, $cmd);
}
- public function execute() {
+ public function execute(): PromiseInterface {
if (!$this->hasRoundChannels()) {
return \React\Promise\resolve();
}
use Discord\Parts\User\User as DiscordUser;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Storage;
+use React\Promise\PromiseInterface;
class SyncUserCommand extends BaseCommand {
parent::__construct($discord, $cmd);
}
- public function execute() {
+ public function execute(): PromiseInterface {
return $this->fetchUser()
->then(function (DiscordUser $discordUser) {
$user = User::find($discordUser->id);
class DiscordBotController extends Controller
{
+ public function recentCommands(DiscordGuild $guild) {
+ $this->authorize('manage', $guild);
+ return $guild->bot_commands()->limit(10)->get();
+ }
+
public function sendMessage(Request $request, DiscordGuild $guild) {
$this->authorize('manage', $guild);
$validatedData = $request->validate([
use App\DiscordBotCommands\BaseCommand;
use Discord\Discord;
-use Discord\Parts\Channel\Channel;
-use Discord\Parts\Guild\Guild;
+use Illuminate\Broadcasting\PrivateChannel;
+use Illuminate\Database\Eloquent\BroadcastsEvents;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
-class DiscordBotCommand extends Model
-{
+class DiscordBotCommand extends Model {
+
+ use BroadcastsEvents;
use HasFactory;
+ public function broadcastOn(string $event): array {
+ $channels = [];
+ if ($this->discord_guild_id) {
+ $channels[] = new PrivateChannel('DiscordGuild.'.$this->discord_guild_id);
+ }
+ return $channels;
+ }
+
public static function queueResult(Result $result) {
$cmd = new DiscordBotCommand();
$cmd->tournament_id = $result->round->tournament_id;
}, [channel, guild]);
return <section className="mt-5">
- <h3>{t('discordBot.channelControls')}</h3>
+ <h2>{t('discordBot.channelControls')}</h2>
<Row>
<Col md={6}>
<Form.Group>
import { useTranslation } from 'react-i18next';
import ChannelControls from './ChannelControls';
+import GuildControls from './GuildControls';
+import GuildProtocol from './GuildProtocol';
import DiscordChannelSelect from '../common/DiscordChannelSelect';
import DiscordSelect from '../common/DiscordSelect';
+import ErrorBoundary from '../common/ErrorBoundary';
const Controls = () => {
const [channel, setChannel] = React.useState(null);
</Form.Group>
</Row>
{guild && channel ?
- <ChannelControls channel={channel} guild={guild} />
+ <ErrorBoundary>
+ <ChannelControls channel={channel} guild={guild} />
+ </ErrorBoundary>
+ : null}
+ {guild ?
+ <ErrorBoundary>
+ <GuildControls guild={guild} />
+ <GuildProtocol guild={guild} />
+ </ErrorBoundary>
: null}
</>;
};
--- /dev/null
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+const GuildControls = ({ guild }) => {
+ const { t } = useTranslation();
+
+ return <section className="mt-5">
+ <h2>{t('discordBot.guildControls')}</h2>
+ </section>;
+};
+
+GuildControls.propTypes = {
+ guild: PropTypes.shape({
+ }),
+};
+
+export default GuildControls;
--- /dev/null
+import axios from 'axios';
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+import Loading from '../common/Loading';
+
+const GuildProtocol = ({ guild }) => {
+ const [loading, setLoading] = React.useState(true);
+ const [protocol, setProtocol] = React.useState([]);
+
+ const { t } = useTranslation();
+
+ React.useEffect(() => {
+ const ctrl = new AbortController();
+ axios
+ .get(`/api/discord-bot/${guild.id}/commands`, { signal: ctrl.signal })
+ .then(response => {
+ setLoading(false);
+ setProtocol(response.data);
+ });
+ window.Echo.private(`DiscordGuild.${guild.id}`)
+ .listen('.DiscordBotCommandCreated', e => {
+ if (e.model) {
+ setProtocol(protocol => [e.model, ...protocol]);
+ }
+ })
+ .listen('.DiscordBotCommandUpdated', e => {
+ if (e.model) {
+ setProtocol(protocol => protocol.map(p => p.id === e.model.id ? { ...p, ...e.mode } : p));
+ }
+ });
+ return () => {
+ ctrl.abort();
+ window.Echo.leave(`DiscordGuild.${guild.id}`);
+ };
+ }, [guild.id]);
+
+ return <section className="mt-5">
+ <h2>{t('discordBot.guildProtocol')}</h2>
+ {loading ?
+ <Loading />
+ :
+ protocol.map((entry) =>
+ <div className="discord-bot-protocol border-top" key={entry.id}>
+ <div className="d-flex justify-content-between">
+ <span>{t(`discordBot.commandType.${entry.command}`)}</span>
+ <span>{t(`discordBot.commandStatus.${entry.status}`)}</span>
+ </div>
+ <div className="d-flex justify-content-between">
+ <span className="text-muted">
+ {t('discordBot.commandTime', { time: new Date(entry.created_at) })}
+ </span>
+ <span className="text-muted">
+ {entry.executed_at
+ ? t('discordBot.commandTime', { time: new Date(entry.executed_at) })
+ : t('discordBot.commandPending')
+ }
+ </span>
+ </div>
+ </div>
+ )
+ }
+ </section>;
+};
+
+GuildProtocol.propTypes = {
+ guild: PropTypes.shape({
+ id: PropTypes.number,
+ }).isRequired,
+};
+
+export default GuildProtocol;
discordBot: {
channel: 'Kanal',
channelControls: 'Kanal-Steuerung',
+ commandStatus: {
+ done: 'Abgeschlossen',
+ exception: 'Fehler',
+ executing: 'Ausführen',
+ hold: 'Pausiert',
+ pending: 'Ausstehend',
+ },
+ commandTime: '{{ time, L HH:mm:ss }}',
+ commandType: {
+ 'episode-event': 'Episoden-Event Synchronisation',
+ message: 'Nachricht',
+ result: 'Ergebnis',
+ },
controls: 'Steuerung',
guild: 'Server',
+ guildControls: 'Server-Steuerung',
+ guildProtocol: 'Command Protokoll',
heading: 'Discord Bot',
invite: 'Bot einladen',
message: 'Nachricht',
discordBot: {
channel: 'Channel',
channelControls: 'Channel controls',
+ commandStatus: {
+ done: 'Done',
+ exception: 'Error',
+ executing: 'Executing',
+ hold: 'Hold',
+ pending: 'Pending',
+ },
+ commandTime: '{{ time, L HH:mm:ss }}',
+ commandType: {
+ 'episode-event': 'Episode event synchronization',
+ message: 'Message',
+ result: 'Result',
+ },
controls: 'Controls',
guild: 'Server',
+ guildControls: 'Server controls',
+ guildProtocol: 'Command protocol',
heading: 'Discord Bot',
invite: 'Invite bot',
message: 'Message',
import { Helmet } from 'react-helmet';
import { useTranslation } from 'react-i18next';
+import ErrorBoundary from '../components/common/ErrorBoundary';
import Icon from '../components/common/Icon';
import Controls from '../components/discord-bot/Controls';
</Button>
</span>
</p>
- <h2>{t('discordBot.controls')}</h2>
- <Controls />
+ <ErrorBoundary>
+ <Controls />
+ </ErrorBoundary>
</Container>;
};
Route::get('content/{tech:name}', 'App\Http\Controllers\TechniqueController@single');
Route::put('content/{content}', 'App\Http\Controllers\TechniqueController@update');
+Route::get('discord-bot/{guild}/commands', 'App\Http\Controllers\DiscordBotController@recentCommands');
Route::post('discord-bot/{guild}/send-message', 'App\Http\Controllers\DiscordBotController@sendMessage');
Route::get('discord-channels/{channel_id}', 'App\Http\Controllers\DiscordChannelController@single');
<?php
use App\Models\Channel;
+use App\Models\DiscordGuild;
use App\Models\Tournament;
use Illuminate\Support\Facades\Broadcast;
return $user->can('editRestream', $channel);
});
+Broadcast::channel('DiscordGuild.{id}', function ($user, $id) {
+ $guild = DiscordGuild::findOrFail($id);
+ return $user->can('manage', $guild);
+});
+
Broadcast::channel('Protocol.{id}', function ($user, $id) {
$tournament = Tournament::findOrFail($id);
return $user->can('viewProtocol', $tournament);