--- /dev/null
+<?php
+
+namespace App\Console\Commands;
+
+use App\Models\DiscordGuild;
+use App\Models\Episode;
+use Illuminate\Console\Command;
+
+class DiscordEpisodeSubscriptionsCommand extends Command
+{
+       /**
+        * The name and signature of the console command.
+        *
+        * @var string
+        */
+       protected $signature = 'discord:episode-subscriptions';
+
+       /**
+        * The console command description.
+        *
+        * @var string
+        */
+       protected $description = 'Update episode subscriptions and create or update guild events accordingly';
+
+       /**
+        * Execute the console command.
+        *
+        * @return int
+        */
+       public function handle() {
+               $guilds = DiscordGuild::all();
+               foreach ($guilds as $guild) {
+                       try {
+                               $this->handleGuild($guild);
+                       } catch (\Exception $e) {
+                               $this->error('error handling guild '.$guild->name.': '.$e->getMessage());
+                       }
+               }
+               return 0;
+       }
+
+       private function handleGuild(DiscordGuild $guild) {
+               $from = now()->sub(1, 'hour');
+               $eventIDs = $guild->event_subscriptions->pluck('event_id')->toArray();
+               $userIDs = $guild->user_subscriptions->pluck('user_id')->toArray();
+               if (empty($eventIDs) && empty($userIDs)) return;
+
+               $query = Episode::with(['channels', 'event', 'players'])
+                       ->where('episodes.start', '>', $from)
+                       ->orderBy('episodes.start', 'ASC')
+                       ->limit(20);
+               $query->where(function ($subquery) use ($eventIDs, $userIDs) {
+                       if (!empty($eventIDs)) {
+                               $subquery->whereIn('episodes.event_id', $eventIDs);
+                       }
+                       if (!empty($userIDs)) {
+                               $subquery->orWhereHas('players', function ($builder) use ($userIDs) {
+                                       $builder->whereIn('episode_players.user_id', $userIDs);
+                               });
+                       }
+               });
+               $episodes = $query->get();
+               foreach ($episodes as $episode) {
+                       $this->handleEpisode($episode);
+               }
+       }
+
+       private function handleEpisode(Episode $episode) {
+               $this->line($episode->start.' '.$episode->event->title.' ' .$episode->title);
+               foreach ($episode->players as $player) {
+                       $this->line(' - '.$player->name_override);
+               }
+       }
+
+}
 
--- /dev/null
+<?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_event_subscriptions', function (Blueprint $table) {
+            $table->id();
+                       $table->foreignId('discord_guild_id')->constrained();
+                       $table->foreignId('event_id')->constrained();
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('discord_guild_event_subscriptions');
+    }
+};
 
--- /dev/null
+<?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_user_subscriptions', function (Blueprint $table) {
+            $table->id();
+                       $table->foreignId('discord_guild_id')->constrained();
+                       $table->foreignId('user_id')->constrained();
+            $table->timestamps();
+        });
+    }
+
+    /**
+     * Reverse the migrations.
+     */
+    public function down(): void
+    {
+        Schema::dropIfExists('discord_guild_user_subscriptions');
+    }
+};