From: Daniel Karbach Date: Wed, 24 Sep 2025 12:02:46 +0000 (+0200) Subject: sync HTH crew X-Git-Url: https://git.localhorst.tv/?a=commitdiff_plain;h=fe3c863a0bf3028f17e0155c6ad86929aa5219e6;p=alttp.git sync HTH crew --- diff --git a/app/Console/Commands/SyncHTH.php b/app/Console/Commands/SyncHTH.php index deab332..a75a697 100644 --- a/app/Console/Commands/SyncHTH.php +++ b/app/Console/Commands/SyncHTH.php @@ -2,8 +2,10 @@ namespace App\Console\Commands; +use App\Models\Channel; use App\Models\DiscordBotCommand; use App\Models\Episode; +use App\Models\EpisodeCrew; use App\Models\EpisodePlayer; use App\Models\Event; use App\Models\User; @@ -11,6 +13,7 @@ use Carbon\Carbon; use Illuminate\Console\Command; use Illuminate\Database\Eloquent\Builder; use Illuminate\Support\Facades\Http; +use Illuminate\Support\Str; class SyncHTH extends Command { @@ -34,7 +37,7 @@ class SyncHTH extends Command { * @return int */ public function handle() { - $events = Event::where('external_schedule', 'LIKE', 'hth:%') + $events = Event::query()->where('external_schedule', 'LIKE', 'hth:%') ->where(function (Builder $query) { $query->whereNull('end'); $query->orWhere('end', '>', now()); @@ -60,7 +63,7 @@ class SyncHTH extends Command { $hthEvent = $parts[1]; $this->line('series: "'.$hthSeries.'", event: "'.$hthEvent.'"'); $hthSchedule = Http::withHeaders([ - 'X-API-Key', config('hth.api_key') + 'X-API-Key' => config('hth.api_key') ])->post('https://hth.zeldaspeedruns.com/api/v1/graphql', [ 'operation_name' => 'my_schedule', 'variables' => [ @@ -71,29 +74,40 @@ class SyncHTH extends Command { query my_schedule(\$my_series: String, \$my_event: String) { series(name: \$my_series) { event(name: \$my_event) { - races { - id - start - room - phase - round - game - teams { - name - members { - role - user { + races { id - displayName - racetimeId - discordId + start + room + phase + round + game + teams { + name + members { + role + user { + id + displayName + discordId + } + } + } + restreamUrls { + url + language + } + confirmedVolunteers { + roleTypeName + user { + id + displayName + discordId + } } - } } } } } - } GRAPHQL, ])->throw()->json(); foreach ($hthSchedule['data']['series']['event']['races'] as $hthEntry) { @@ -107,7 +121,7 @@ class SyncHTH extends Command { private function syncSchedule(Event $event, $hthEntry): void { $ext_id = 'hth:'.$hthEntry['id']; - $episode = Episode::firstWhere('ext_id', '=', $ext_id); + $episode = Episode::query()->firstWhere('ext_id', '=', $ext_id); if (!$hthEntry['start']) { if ($episode) { $episode->callOff(); @@ -115,6 +129,10 @@ class SyncHTH extends Command { } return; } + if ($hthEntry['start'] < now()->sub(7, 'day')) { + // skip if too old + return; + } if (!$episode) { $episode = new Episode(); $episode->ext_id = $ext_id; @@ -145,6 +163,23 @@ class SyncHTH extends Command { } } } + + $first_channel = null; + if (!empty($hthEntry['restreamUrls'])) { + foreach ($hthEntry['restreamUrls'] as $restream) { + $channel = $this->syncChannel($episode, $restream); + $episode->channels()->syncWithoutDetaching([$channel->id]); + if (is_null($first_channel)) { + $first_channel = $channel; + } + } + } + $this->purgeCrew($episode, $hthEntry['confirmedVolunteers']); + if (!empty($hthEntry['confirmedVolunteers'])) { + foreach ($hthEntry['confirmedVolunteers'] as $hthCrew) { + $this->syncCrew($episode, $hthCrew, $first_channel); + } + } } private function purgePlayers(Episode $episode, $hthEntry): void { @@ -177,24 +212,79 @@ class SyncHTH extends Command { $player->save(); } + private function syncChannel(Episode $episode, $hthChannel): Channel { + $normalized_url = Str::rtrim(Str::lower($hthChannel['url']), '/'); + $normalized_url = str_replace('http://', 'https://', $normalized_url); + $normalized_url = str_replace('www.twitch.tv', 'twitch.tv', $normalized_url); + $channel = Channel::query()->firstWhere('stream_link', 'LIKE', $normalized_url); + if (!$channel) { + $channel = new Channel(); + $channel->stream_link = $normalized_url; + $channel->title = Str::afterLast($normalized_url, '/'); + $channel->languages = [static::$LANGUAGE_MAP[$hthChannel['language']]]; + $channel->twitch_chat = '#'.Str::afterLast($normalized_url, '/'); + $channel->access_key = Str::uuid(); + $channel->save(); + } + return $channel; + } + + private function purgeCrew(Episode $episode, $hthCrews) { + $ext_ids = []; + foreach ($hthCrews as $hthCrew) { + $ext_ids[] = 'hth:'.$hthCrew['roleTypeName'].':'.$hthCrew['user']['id']; + } + $episode->crew()->where('ext_id', 'LIKE', 'hth:%')->whereNotIn('ext_id', $ext_ids)->delete(); + } + + private function syncCrew(Episode $episode, $hthCrew, Channel|null $channel): void { + $ext_id = 'hth:'.$hthCrew['roleTypeName'].':'.$hthCrew['user']['id']; + $crew = $episode->crew()->firstWhere('ext_id', '=', $ext_id); + if (!$crew) { + $crew = new EpisodeCrew(); + $crew->ext_id = $ext_id; + $crew->episode()->associate($episode); + } + $user = $this->getUserByHTHPlayer($hthCrew); + if ($user) { + $crew->user()->associate($user); + } else { + $crew->user()->disassociate(); + } + $role = static::$ROLE_MAP[$hthCrew['roleTypeName']]; + if ($role == 'commentary') { + if ($channel) { + $crew->channel()->associate($channel); + } else { + $crew->channel()->disassociate(); + } + } + $crew->role = $role; + $crew->confirmed = true; + if (!empty($hthCrew['user']['displayName'])) { + $crew->name_override = $hthCrew['user']['displayName']; + } + $crew->save(); + } + private function getUserByHTHPlayer($player): User|null { if (!empty($player['user']['discordId'])) { - $user = User::find($player['user']['discordId']); + $user = User::query()->find($player['user']['discordId']); if ($user) { return $user; } DiscordBotCommand::syncUser($player['user']['discordId']); } if (!empty($player['user']['displayName'])) { - $user = User::firstWhere('discord_nickname', 'LIKE', $player['user']['displayName']); + $user = User::query()->firstWhere('discord_nickname', 'LIKE', $player['user']['displayName']); if ($user) { return $user; } - $user = User::firstWhere('global_name', 'LIKE', $player['user']['displayName']); + $user = User::query()->firstWhere('global_name', 'LIKE', $player['user']['displayName']); if ($user) { return $user; } - $user = User::firstWhere('username', 'LIKE', $player['user']['displayName']); + $user = User::query()->firstWhere('username', 'LIKE', $player['user']['displayName']); if ($user) { return $user; } @@ -202,4 +292,17 @@ class SyncHTH extends Command { return null; } + private static $LANGUAGE_MAP = [ + 'ENGLISH' => 'en', + 'FRENCH' => 'fr', + 'GERMAN' => 'de', + 'PORTUGUESE' => 'pt', + ]; + + private static $ROLE_MAP = [ + 'Commentary' => 'commentary', + 'Race Monitoring' => 'setup', + 'Tracking' => 'tracking', + ]; + } diff --git a/app/Console/Commands/SyncNmgLeague.php b/app/Console/Commands/SyncNmgLeague.php index cbf6653..7c109c2 100644 --- a/app/Console/Commands/SyncNmgLeague.php +++ b/app/Console/Commands/SyncNmgLeague.php @@ -164,14 +164,17 @@ class SyncNmgLeague extends Command { } private function syncChannel(Episode $episode, string $channel_url): Channel { - $normalized_url = str_replace('http://', 'https://', strtolower($channel_url)); + $normalized_url = Str::rtrim(Str::lower($channel_url), '/'); + $normalized_url = str_replace('http://', 'https://', $normalized_url); $normalized_url = str_replace('www.twitch.tv', 'twitch.tv', $normalized_url); - $channel = Channel::firstWhere('stream_link', 'LIKE', $normalized_url); + $channel = Channel::query()->firstWhere('stream_link', 'LIKE', $normalized_url); if (!$channel) { $channel = new Channel(); $channel->stream_link = $normalized_url; - $channel->title = Str::afterLast($channel_url, '/'); + $channel->title = Str::afterLast($normalized_url, '/'); $channel->languages = ['en']; + $channel->twitch_chat = '#'.Str::afterLast($normalized_url, '/'); + $channel->access_key = Str::uuid(); $channel->save(); } return $channel; diff --git a/database/migrations/2025_09_24_112139_default_channel_short_name_to_empty_string.php b/database/migrations/2025_09_24_112139_default_channel_short_name_to_empty_string.php new file mode 100644 index 0000000..d991f3a --- /dev/null +++ b/database/migrations/2025_09_24_112139_default_channel_short_name_to_empty_string.php @@ -0,0 +1,25 @@ +string('short_name')->default('')->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + } +};