From 2cb04d6a86079b0dac91bce09e551004492fa601 Mon Sep 17 00:00:00 2001
From: Daniel Karbach 
Date: Sat, 5 Jul 2025 18:31:05 +0200
Subject: [PATCH] discord bot log
---
 app/DiscordBotCommands/MessageCommand.php     |  5 +-
 app/DiscordBotCommands/PresenceCommand.php    |  5 +-
 app/DiscordBotCommands/ResultCommand.php      |  4 +-
 app/DiscordBotCommands/SyncUserCommand.php    |  3 +-
 app/Http/Controllers/DiscordBotController.php |  5 ++
 app/Models/DiscordBotCommand.php              | 17 ++++-
 .../discord-bot/ChannelControls.jsx           |  2 +-
 .../js/components/discord-bot/Controls.jsx    | 13 +++-
 .../components/discord-bot/GuildControls.jsx  | 18 +++++
 .../components/discord-bot/GuildProtocol.jsx  | 73 +++++++++++++++++++
 resources/js/i18n/de.js                       | 15 ++++
 resources/js/i18n/en.js                       | 15 ++++
 resources/js/pages/DiscordBot.jsx             |  6 +-
 routes/api.php                                |  1 +
 routes/channels.php                           |  6 ++
 15 files changed, 172 insertions(+), 16 deletions(-)
 create mode 100644 resources/js/components/discord-bot/GuildControls.jsx
 create mode 100644 resources/js/components/discord-bot/GuildProtocol.jsx
diff --git a/app/DiscordBotCommands/MessageCommand.php b/app/DiscordBotCommands/MessageCommand.php
index 746bd5f..f147375 100644
--- a/app/DiscordBotCommands/MessageCommand.php
+++ b/app/DiscordBotCommands/MessageCommand.php
@@ -5,8 +5,7 @@ namespace App\DiscordBotCommands;
 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 {
 
@@ -14,7 +13,7 @@ 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');
 		}
diff --git a/app/DiscordBotCommands/PresenceCommand.php b/app/DiscordBotCommands/PresenceCommand.php
index 47c15a5..8fa249b 100644
--- a/app/DiscordBotCommands/PresenceCommand.php
+++ b/app/DiscordBotCommands/PresenceCommand.php
@@ -6,6 +6,7 @@ use App\Models\DiscordBotCommand;
 use Discord\Discord;
 use Discord\Parts\User\Activity;
 use React\Promise\Promise;
+use React\Promise\PromiseInterface;
 
 class PresenceCommand extends BaseCommand {
 
@@ -13,8 +14,8 @@ 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';
diff --git a/app/DiscordBotCommands/ResultCommand.php b/app/DiscordBotCommands/ResultCommand.php
index bfe35bd..3f2030e 100644
--- a/app/DiscordBotCommands/ResultCommand.php
+++ b/app/DiscordBotCommands/ResultCommand.php
@@ -5,8 +5,8 @@ namespace App\DiscordBotCommands;
 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 {
 
@@ -14,7 +14,7 @@ class ResultCommand extends BaseCommand {
 		parent::__construct($discord, $cmd);
 	}
 
-	public function execute() {
+	public function execute(): PromiseInterface {
 		if (!$this->hasRoundChannels()) {
 			return \React\Promise\resolve();
 		}
diff --git a/app/DiscordBotCommands/SyncUserCommand.php b/app/DiscordBotCommands/SyncUserCommand.php
index 642c8c7..dc2f74e 100644
--- a/app/DiscordBotCommands/SyncUserCommand.php
+++ b/app/DiscordBotCommands/SyncUserCommand.php
@@ -8,6 +8,7 @@ use Discord\Discord;
 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 {
 
@@ -15,7 +16,7 @@ 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);
diff --git a/app/Http/Controllers/DiscordBotController.php b/app/Http/Controllers/DiscordBotController.php
index 99a1b4c..4e415a4 100644
--- a/app/Http/Controllers/DiscordBotController.php
+++ b/app/Http/Controllers/DiscordBotController.php
@@ -10,6 +10,11 @@ use Illuminate\Http\Request;
 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([
diff --git a/app/Models/DiscordBotCommand.php b/app/Models/DiscordBotCommand.php
index 9397e5b..d67ee9f 100644
--- a/app/Models/DiscordBotCommand.php
+++ b/app/Models/DiscordBotCommand.php
@@ -4,15 +4,24 @@ namespace App\Models;
 
 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;
diff --git a/resources/js/components/discord-bot/ChannelControls.jsx b/resources/js/components/discord-bot/ChannelControls.jsx
index ee06e66..66840d0 100644
--- a/resources/js/components/discord-bot/ChannelControls.jsx
+++ b/resources/js/components/discord-bot/ChannelControls.jsx
@@ -23,7 +23,7 @@ const ChannelControls = ({ channel, guild }) => {
 	}, [channel, guild]);
 
 	return 
-		{t('discordBot.channelControls')}
+		{t('discordBot.channelControls')}
 		
 			
 				
diff --git a/resources/js/components/discord-bot/Controls.jsx b/resources/js/components/discord-bot/Controls.jsx
index 12c2fb6..f001345 100644
--- a/resources/js/components/discord-bot/Controls.jsx
+++ b/resources/js/components/discord-bot/Controls.jsx
@@ -3,8 +3,11 @@ import { Col, Form, Row } from 'react-bootstrap';
 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);
@@ -38,7 +41,15 @@ const Controls = () => {
 			
 		
 		{guild && channel ?
-			
+			
+				
+			
+		: null}
+		{guild ?
+			
+				
+				
+			
 		: null}
 	>;
 };
diff --git a/resources/js/components/discord-bot/GuildControls.jsx b/resources/js/components/discord-bot/GuildControls.jsx
new file mode 100644
index 0000000..545c5ee
--- /dev/null
+++ b/resources/js/components/discord-bot/GuildControls.jsx
@@ -0,0 +1,18 @@
+import PropTypes from 'prop-types';
+import React from 'react';
+import { useTranslation } from 'react-i18next';
+
+const GuildControls = ({ guild }) => {
+	const { t } = useTranslation();
+
+	return 
+		{t('discordBot.guildControls')}
+	;
+};
+
+GuildControls.propTypes = {
+	guild: PropTypes.shape({
+	}),
+};
+
+export default GuildControls;
diff --git a/resources/js/components/discord-bot/GuildProtocol.jsx b/resources/js/components/discord-bot/GuildProtocol.jsx
new file mode 100644
index 0000000..1121720
--- /dev/null
+++ b/resources/js/components/discord-bot/GuildProtocol.jsx
@@ -0,0 +1,73 @@
+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 
+		{t('discordBot.guildProtocol')}
+		{loading ?
+			
+		:
+			protocol.map((entry) =>
+				
+					
+						{t(`discordBot.commandType.${entry.command}`)}
+						{t(`discordBot.commandStatus.${entry.status}`)}
+					
+					
+						
+							{t('discordBot.commandTime', { time: new Date(entry.created_at) })}
+						
+						
+							{entry.executed_at
+								? t('discordBot.commandTime', { time: new Date(entry.executed_at) })
+								: t('discordBot.commandPending')
+							}
+						
+					
+				
-		{t('discordBot.controls')}
-		
+		
+			
+		
 	;
 };
diff --git a/routes/api.php b/routes/api.php
index 9428065..81f35b8 100644
--- a/routes/api.php
+++ b/routes/api.php
@@ -46,6 +46,7 @@ Route::get('content', 'App\Http\Controllers\TechniqueController@search');
 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');
diff --git a/routes/channels.php b/routes/channels.php
index b0a2552..e04f537 100644
--- a/routes/channels.php
+++ b/routes/channels.php
@@ -1,6 +1,7 @@
 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);
-- 
2.47.2