From d99e74afa59ee6e6d55c3c46b624d24f5318f10c Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Sat, 29 Nov 2025 16:05:52 +0100 Subject: [PATCH] preliminary tfl sync command --- app/Console/Commands/SyncNmgLeague.php | 4 +- app/Console/Commands/SyncTFL.php | 189 +++++++++++++++++++++++++ 2 files changed, 190 insertions(+), 3 deletions(-) create mode 100644 app/Console/Commands/SyncTFL.php diff --git a/app/Console/Commands/SyncNmgLeague.php b/app/Console/Commands/SyncNmgLeague.php index 7c109c2..49e6a1a 100644 --- a/app/Console/Commands/SyncNmgLeague.php +++ b/app/Console/Commands/SyncNmgLeague.php @@ -84,9 +84,7 @@ class SyncNmgLeague extends Command { ->whereNotIn('ext_id', $ext_ids) ->where('ext_id', 'LIKE', 'nmgleague:%'); foreach ($to_purge->get() as $episode) { - $episode->channels()->detach(); - $episode->crew()->delete(); - $episode->players()->delete(); + $episode->callOff(); } $to_purge->delete(); } diff --git a/app/Console/Commands/SyncTFL.php b/app/Console/Commands/SyncTFL.php new file mode 100644 index 0000000..320178e --- /dev/null +++ b/app/Console/Commands/SyncTFL.php @@ -0,0 +1,189 @@ +where(function (Builder $query) { + $query->whereNull('end'); + $query->orWhere('end', '>', now()); + }) + ->get(); + + foreach ($events as $event) { + try { + $this->line('syncing '.$event->name); + $this->syncEvent($event); + } catch (\Exception $e) { + $this->error('error syncing event '.$event->name.': '.$e->getMessage()); + } + } + + return 0; + } + + private function syncEvent(Event $event) { + $tflSchedule = Http::get('https://tfl-discord-api.onrender.com/api/upcoming?n=20')->json(); + if (!$tflSchedule) { + return; + } + $this->purgeSchedule($event, $tflSchedule['items']); + foreach ($tflSchedule['items'] as $tflEntry) { + try { + $this->syncSchedule($event, $tflEntry); + } catch (\Exception $e) { + $this->error('error syncing episode '.$tflEntry['id'].': '.$e->getMessage()); + } + } + } + + private function purgeSchedule(Event $event, $tflSchedule): void { + $ext_ids = []; + foreach ($tflSchedule as $tflEntry) { + $ext_ids[] = 'tfl:'.$tflEntry['id']; + } + $to_purge = $event->episodes() + ->whereNotIn('ext_id', $ext_ids) + ->where('ext_id', 'LIKE', 'tfl:%') + ->where('start', '>', 'NOW()'); + foreach ($to_purge->get() as $episode) { + $episode->callOff(); + } + $to_purge->delete(); + } + + private function syncSchedule(Event $event, $tflEntry): void { + $ext_id = 'tfl:'.$tflEntry['id']; + $episode = $event->episodes()->firstWhere('ext_id', '=', $ext_id); + if (!$episode) { + $episode = new Episode(); + $episode->ext_id = $ext_id; + $episode->event()->associate($event); + } + $episode->title = $tflEntry['name']; + $start = Carbon::createFromFormat('Y-m-d\\T H:i:sP', $tflEntry['start']); + if (!$episode->start || $start->ne($episode->start)) { + $episode->start = $start; + $episode->estimate = $start->diffInSeconds(Carbon::createFromFormat('Y-m-d\\T H:i:sP', $tflEntry['end'])); + } + $episode->confirmed = true; + $episode->save(); + + if (!preg_match('/\\| (.*) vs\\. (.*) \\|/', $tflEntry['name'], $matches)) { + return; + } + $tflPlayers = [$matches[1], $matches[2]]; + + $this->purgePlayers($episode, $tflPlayers); + foreach ($tflPlayers as $tflPlayer) { + $this->syncPlayer($episode, $tflPlayer); + } + + if (strpos($tflEntry['location'], 'twitch.tv/') !== false) { + $channel = $this->syncChannel($episode, $tflEntry['location']); + $episode->channels()->syncWithoutDetaching([$channel->id]); + } + } + + private function purgePlayers(Episode $episode, $tflPlayers): void { + $ext_ids = []; + foreach ($tflPlayers as $tflPlayer) { + $ext_ids[] = 'tfl:'.$tflPlayer; + } + $episode->players()->whereNotIn('ext_id', $ext_ids)->delete(); + } + + private function syncPlayer(Episode $episode, $tflPlayer) { + $ext_id = 'tfl:'.$tflPlayer; + $player = $episode->players()->firstWhere('ext_id', '=', $ext_id); + if (!$player) { + $player = new EpisodePlayer(); + $player->ext_id = $ext_id; + $player->name_override = $tflPlayer; + $player->episode()->associate($episode); + } + $user = $this->getUser($tflPlayer); + if ($user) { + $player->user()->associate($user); + } else { + $player->user()->disassociate(); + } + $player->save(); + } + + private function syncChannel(Episode $episode, string $channel_url): Channel { + $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::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 = ['de']; + $channel->twitch_chat = '#'.Str::afterLast($normalized_url, '/'); + $channel->access_key = Str::uuid(); + $channel->save(); + } + return $channel; + } + + private function getUser(string $codename): User|null { + if (isset($this->codename_table[$codename])) { + $user = User::query()->firstWhere('username', 'LIKE', $this->codename_table[$codename]); + if ($user) { + return $user; + } + } + $user = User::query()->firstWhere('discord_nickname', 'LIKE', $codename); + if ($user) { + return $user; + } + $user = User::query()->firstWhere('username', 'LIKE', $codename); + if ($user) { + return $user; + } + $user = User::query()->firstWhere('stream_link', 'LIKE', '%/'.$codename); + if ($user) { + return $user; + } + return null; + } + + private $codename_table = [ + ]; + +} -- 2.47.3