From: Daniel Karbach Date: Sat, 5 Jul 2025 14:41:28 +0000 (+0200) Subject: discord scheduled event command X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=bcd9790fa3229ee6ad22f18351dd3a5080022958;p=alttp.git discord scheduled event command --- diff --git a/app/DiscordBotCommands/BaseCommand.php b/app/DiscordBotCommands/BaseCommand.php index 61152c1..259176d 100644 --- a/app/DiscordBotCommands/BaseCommand.php +++ b/app/DiscordBotCommands/BaseCommand.php @@ -4,6 +4,7 @@ namespace App\DiscordBotCommands; use App\Models\DiscordBotCommand; use App\Models\DiscordGuild; +use App\Models\Episode; use App\Models\Round; use App\Models\User; use Discord\Discord; @@ -12,11 +13,14 @@ use Discord\Parts\Guild\Guild; use Discord\Parts\User\Member; use Discord\Parts\User\User as DiscordUser; use Illuminate\Support\Facades\App; +use React\Promise\PromiseInterface; abstract class BaseCommand { - public static function resolve(Discord $discord, DiscordBotCommand $cmd) { + public static function resolve(Discord $discord, DiscordBotCommand $cmd): BaseCommand { switch ($cmd->command) { + case 'episode-event': + return new EpisodeEventCommand($discord, $cmd); case 'message': return new MessageCommand($discord, $cmd); case 'presence': @@ -26,11 +30,11 @@ abstract class BaseCommand { case 'sync-user': return new SyncUserCommand($discord, $cmd); default: - throw new Exception('unrecognized command'); + throw new \Exception('unrecognized command'); } } - public abstract function execute(); + abstract public function execute(): PromiseInterface; protected function __construct(Discord $discord, DiscordBotCommand $cmd) { @@ -41,17 +45,13 @@ abstract class BaseCommand { } } - protected function fetchGuild() { + protected function fetchGuild(): PromiseInterface { if (isset($this->guild)) { return \React\Promise\resolve($this->guild); } - if (is_null($this->command->discord_guild)) { - $g = DiscordGuild::where('guild_id', '=', $this->command->tournament->discord)->firstOrFail(); - $this->command->discord_guild()->associate($g); - $this->command->save(); - } + $cmd_guild = $this->getCommandGuild(); return $this->discord->guilds - ->fetch($this->command->discord_guild->guild_id) + ->fetch($cmd_guild->guild_id) ->then(function (Guild $guild) { $this->guild = $guild; if ($guild->preferred_locale && !($this->command->tournament && $this->command->tournament->locale)) { @@ -61,7 +61,16 @@ abstract class BaseCommand { }); } - protected function fetchMember() { + protected function getCommandGuild(): DiscordGuild { + if (is_null($this->command->discord_guild)) { + $g = DiscordGuild::where('guild_id', '=', $this->command->tournament->discord)->firstOrFail(); + $this->command->discord_guild()->associate($g); + $this->command->save(); + } + return $this->command->discord_guild; + } + + protected function fetchMember(): PromiseInterface { return $this->fetchGuild()->then(function (Guild $guild) { return $guild->members ->fetch($this->getParameter('user')) @@ -72,7 +81,7 @@ abstract class BaseCommand { }); } - protected function fetchParameterChannel() { + protected function fetchParameterChannel(): PromiseInterface { if (isset($this->parameterChannel)) { return \React\Promise\resolve($this->parameterChannel); } @@ -89,7 +98,7 @@ abstract class BaseCommand { }); } - protected function fetchRoundChannel() { + protected function fetchRoundChannel(): PromiseInterface { if (isset($this->roundChannel)) { return \React\Promise\resolve($this->roundChannel); } @@ -114,7 +123,7 @@ abstract class BaseCommand { }); } - protected function fetchUser() { + protected function fetchUser(): PromiseInterface { if (isset($this->user)) { return \React\Promise\resolve($this->user); } @@ -131,9 +140,16 @@ abstract class BaseCommand { return $this->command->parameters[$name]; } - protected function getRound() { + protected function getEpisode(): Episode { + if (!$this->hasParameter('episode')) { + throw new \Exception('no episode in parameters'); + } + return Episode::findOrFail($this->getParameter('episode')); + } + + protected function getRound(): Round { if (!$this->hasParameter('round')) { - throw new \Exception('no rounds in parameters'); + throw new \Exception('no round in parameters'); } return Round::findOrFail($this->getParameter('round')); } diff --git a/app/DiscordBotCommands/EpisodeEventCommand.php b/app/DiscordBotCommands/EpisodeEventCommand.php new file mode 100644 index 0000000..3dc03bb --- /dev/null +++ b/app/DiscordBotCommands/EpisodeEventCommand.php @@ -0,0 +1,72 @@ +fetchGuild() + ->then(function (Guild $guild) { + $episode = $this->getEpisode(); + $memo = $this->getMemo($episode); + if ($memo->scheduled_event_id) { + return $guild->guild_scheduled_events + ->fetch($memo->scheduled_event_id) + ->then(function ($event) use ($episode, $guild, $memo) { + $this->configureEvent($event, $episode); + $guild->guild_scheduled_events + ->save($event) + ->then(function (ScheduledEvent $savedEvent) use ($memo) { + $memo->synced_at = now(); + $memo->save(); + }); + }); + } else { + $event = $guild->guild_scheduled_events->create(); + $this->configureEvent($event, $episode); + return $guild->guild_scheduled_events + ->save($event) + ->then(function (ScheduledEvent $createdEvent) use ($memo) { + $memo->scheduled_event_id = $createdEvent->id; + $memo->synced_at = now(); + $memo->save(); + }); + } + }); + } + + private function getMemo(Episode $episode): DiscordGuildEpisode { + return DiscordGuildEpisode::firstOrCreate([ + 'discord_guild_id' => $this->getCommandGuild()->id, + 'episode_id' => $episode->id, + ]); + } + + private function configureEvent(ScheduledEvent $event, Episode $episode): void { + $end = Carbon::parse($episode->start)->add($episode->estimate, 'seconds')->toIso8601String(); + $event->name = $episode->getScheduledEventName(); + $event->description = $episode->getScheduledEventDescription(); + $event->scheduled_start_time = $episode->start; + $event->scheduled_end_time = $end; + $event->privacy_level = ScheduledEvent::PRIVACY_LEVEL_GUILD_ONLY; + $event->entity_type = ScheduledEvent::ENTITY_TYPE_EXTERNAL; + $event->entity_metadata = [ + 'location' => $episode->getRestreamLink(), + ]; + $event->image = ''; + } + +} diff --git a/app/Http/Controllers/ChatBotLogController.php b/app/Http/Controllers/ChatBotLogController.php index 62c2885..041be3c 100644 --- a/app/Http/Controllers/ChatBotLogController.php +++ b/app/Http/Controllers/ChatBotLogController.php @@ -4,7 +4,6 @@ namespace App\Http\Controllers; use App\Models\ChatBotLog; use App\Models\ChatLog; -use Carbon\Carbon; use Illuminate\Http\Request; class ChatBotLogController extends Controller { diff --git a/app/Models/DiscordBotCommand.php b/app/Models/DiscordBotCommand.php index 8a8b81b..9397e5b 100644 --- a/app/Models/DiscordBotCommand.php +++ b/app/Models/DiscordBotCommand.php @@ -65,13 +65,13 @@ class DiscordBotCommand extends Model return $this->belongsTo(User::class); } - public function execute(Discord $discord) { + public function execute(Discord $discord): void { $this->setExecuting(); try { BaseCommand::resolve($discord, $this) ->execute() - ->done(function($v = null) { + ->done(function ($v = null) { $this->setDone(); }, function (\Throwable $e) { $this->setException($e); diff --git a/app/Models/DiscordGuildEpisode.php b/app/Models/DiscordGuildEpisode.php new file mode 100644 index 0000000..8752993 --- /dev/null +++ b/app/Models/DiscordGuildEpisode.php @@ -0,0 +1,22 @@ +belongsTo(DiscordGuild::class); + } + + public function episode() { + return $this->belongsTo(Episode::class); + } + + protected $fillable = [ + 'discord_guild_id', + 'episode_id', + ]; + +} diff --git a/app/Models/Episode.php b/app/Models/Episode.php index 4b8a96a..e0789a8 100644 --- a/app/Models/Episode.php +++ b/app/Models/Episode.php @@ -36,6 +36,53 @@ class Episode extends Model return $this->hasMany(EpisodePlayer::class); } + public function getScheduledEventName(): string { + $parts = []; + if (count($this->players) > 1) { + $players = []; + foreach ($this->players as $player) { + $players[] = $player->getName(); + } + $parts[] = implode(' vs ', $players); + } + if ($this->title != '') { + $parts[] = $this->title; + } + if ($this->event->title != '') { + $parts[] = $this->event->title; + } + return implode(' - ', $parts); + } + + public function getScheduledEventDescription(): string { + if ($this->comment != '') { + return $this->comment; + } + return ''; + } + + public function getRestreamLink(string $preferred_lang = ''): string { + if (count($this->channels) > 0) { + foreach ($this->channels as $channel) { + if (in_array($preferred_lang, $channel->languages)) { + return $channel->stream_link; + } + } + return $this->channels[0]->stream_link; + } + if (count($this->players) == 1) { + return $this->players[0]->getStreamLink(); + } + if (count($this->players) > 0) { + $link = 'https://multistre.am'; + foreach ($this->players as $player) { + $link .= '/'.$player->getTwitchName(); + } + return $link; + } + return ''; + } + protected $casts = [ 'confirmed' => 'boolean', 'start' => 'datetime', diff --git a/app/Models/EpisodePlayer.php b/app/Models/EpisodePlayer.php index 26ccc04..e2f927e 100644 --- a/app/Models/EpisodePlayer.php +++ b/app/Models/EpisodePlayer.php @@ -4,6 +4,7 @@ namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; +use Illuminate\Support\Str; class EpisodePlayer extends Model { @@ -32,7 +33,7 @@ class EpisodePlayer extends Model return ''; } - public function getStreamLink() { + public function getStreamLink(): string { if (!empty($this->stream_override)) { return $this->stream_override; } @@ -42,6 +43,10 @@ class EpisodePlayer extends Model return ''; } + public function getTwitchName(): string { + return Str::afterLast($this->getStreamLink(), '/'); + } + protected $casts = [ 'user_id' => 'string', ]; diff --git a/database/migrations/2025_07_05_130428_create_discord_guild_episodes_table.php b/database/migrations/2025_07_05_130428_create_discord_guild_episodes_table.php new file mode 100644 index 0000000..e28eabf --- /dev/null +++ b/database/migrations/2025_07_05_130428_create_discord_guild_episodes_table.php @@ -0,0 +1,29 @@ +id(); + $table->foreignId('discord_guild_id')->constrained(); + $table->foreignId('episode_id')->constrained(); + $table->string('scheduled_event_id')->nullable()->default(null); + $table->timestamp('synced_at')->nullable()->default(null); + $table->timestamps(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void { + Schema::dropIfExists('discord_guild_episodes'); + } +};