]> git.localhorst.tv Git - alttp.git/commitdiff
discord scheduled event command
authorDaniel Karbach <daniel.karbach@localhorst.tv>
Sat, 5 Jul 2025 14:41:28 +0000 (16:41 +0200)
committerDaniel Karbach <daniel.karbach@localhorst.tv>
Sat, 5 Jul 2025 14:41:28 +0000 (16:41 +0200)
app/DiscordBotCommands/BaseCommand.php
app/DiscordBotCommands/EpisodeEventCommand.php [new file with mode: 0644]
app/Http/Controllers/ChatBotLogController.php
app/Models/DiscordBotCommand.php
app/Models/DiscordGuildEpisode.php [new file with mode: 0644]
app/Models/Episode.php
app/Models/EpisodePlayer.php
database/migrations/2025_07_05_130428_create_discord_guild_episodes_table.php [new file with mode: 0644]

index 61152c1280d97be5455993c6053e07404333bea0..259176d51b0e2848fad03df3d58798fd3cbfcfa7 100644 (file)
@@ -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 (file)
index 0000000..3dc03bb
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+
+namespace App\DiscordBotCommands;
+
+use App\Models\DiscordBotCommand;
+use App\Models\DiscordGuildEpisode;
+use App\Models\Episode;
+use Carbon\Carbon;
+use Discord\Discord;
+use Discord\Parts\Guild\Guild;
+use Discord\Parts\Guild\ScheduledEvent;
+use React\Promise\PromiseInterface;
+
+class EpisodeEventCommand extends BaseCommand {
+
+       public function __construct(Discord $discord, DiscordBotCommand $cmd) {
+               parent::__construct($discord, $cmd);
+       }
+
+       public function execute(): PromiseInterface {
+               return $this->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 = '';
+       }
+
+}
index 62c28855ab3bb4af816cb1141b23af832cc9aed9..041be3ce21042f934cd47c107af15f1144dcd438 100644 (file)
@@ -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 {
index 8a8b81bc184092cfab9d2eaebdd95f60e4cbcede..9397e5b30e63d0c25007cf2ebc8e4544d7d08adf 100644 (file)
@@ -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 (file)
index 0000000..8752993
--- /dev/null
@@ -0,0 +1,22 @@
+<?php
+
+namespace App\Models;
+
+use Illuminate\Database\Eloquent\Model;
+
+class DiscordGuildEpisode extends Model {
+
+       public function discord_guild() {
+               return $this->belongsTo(DiscordGuild::class);
+       }
+
+       public function episode() {
+               return $this->belongsTo(Episode::class);
+       }
+
+       protected $fillable = [
+               'discord_guild_id',
+               'episode_id',
+       ];
+
+}
index 4b8a96a53e2061f527db4d88d089ac411f90e1d9..e0789a818014f9c41a2b3e0572f4b208448ee083 100644 (file)
@@ -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',
index 26ccc044950d9bedd1dcc08203c9f4e58ea09bd6..e2f927e33c0c88ff4781cf405baa5fe8820b97cc 100644 (file)
@@ -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 (file)
index 0000000..e28eabf
--- /dev/null
@@ -0,0 +1,29 @@
+<?php
+
+use Illuminate\Database\Migrations\Migration;
+use Illuminate\Database\Schema\Blueprint;
+use Illuminate\Support\Facades\Schema;
+
+return new class extends Migration
+{
+       /**
+        * Run the migrations.
+        */
+       public function up(): void {
+               Schema::create('discord_guild_episodes', function (Blueprint $table) {
+                       $table->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');
+       }
+};