From 87ab0a1f3f0abebe078ba3972794627153352081 Mon Sep 17 00:00:00 2001 From: Daniel Karbach Date: Tue, 22 Aug 2023 11:05:22 +0200 Subject: [PATCH] racetime sync --- app/Console/Commands/SyncRacetime.php | 142 ++++++++++++++++++ app/Console/Kernel.php | 3 +- .../2023_08_21_073535_add_event_racetime.php | 32 ++++ ...2023_08_22_075105_add_episode_raceroom.php | 32 ++++ resources/js/components/common/Icon.js | 1 + resources/js/components/episodes/Item.js | 15 ++ resources/js/i18n/de.js | 1 + resources/js/i18n/en.js | 1 + resources/sass/_variables.scss | 2 + 9 files changed, 228 insertions(+), 1 deletion(-) create mode 100644 app/Console/Commands/SyncRacetime.php create mode 100644 database/migrations/2023_08_21_073535_add_event_racetime.php create mode 100644 database/migrations/2023_08_22_075105_add_episode_raceroom.php diff --git a/app/Console/Commands/SyncRacetime.php b/app/Console/Commands/SyncRacetime.php new file mode 100644 index 0000000..34e41c4 --- /dev/null +++ b/app/Console/Commands/SyncRacetime.php @@ -0,0 +1,142 @@ +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 Command::SUCCESS; + } + + private function syncEvent(Event $event) { + $episodes = $event->episodes()->whereBetween('start', [now()->sub(1, 'hour'), now()->add(30, 'minute')])->get(); + if ($episodes->isEmpty()) return; + $racerooms = $this->getRacetime($event->racetime_category); + foreach ($episodes as $episode) { + $rooms = $this->filterID($episode, $racerooms); + if (count($rooms) > 1) { + $rooms = $this->filterStartTime($episode, $rooms); + } + if (count($rooms) > 1) { + $rooms = $this->filterPlayerNames($episode, $rooms); + } + if (count($rooms) == 1) { + $this->line(' - linking episode '.$episode->id.' with room '.$rooms[0]['name']); + $episode->raceroom = 'https://racetime.gg'.$rooms[0]['url']; + $episode->save(); + } + } + } + + private function getRacetime($category) { + if (isset($this->cat_cache[$category])) { + return $this->cat_cache[$category]; + } + $category_data = HTTP::get('https://racetime.gg/'.$category.'/data'); + $races_data = HTTP::get('https://racetime.gg/'.$category.'/races/data'); + $this->cat_cache[$category] = array_merge(array_values($category_data['current_races']), array_values($races_data['races'])); + return $this->cat_cache[$category]; + } + + private function filterID(Episode $episode, $racerooms) { + if (empty($episode->ext_id) || substr($episode->ext_id, 0, 3) != 'sg:') { + return $racerooms; + } + $rooms = []; + $id = substr($episode->ext_id, 3); + foreach ($racerooms as $room) { + if (strpos($room['info'], $id) !== false) { + $rooms[] = $room; + } + } + return empty($rooms) ? $racerooms : $rooms; + } + + private function filterStartTime(Episode $episode, $racerooms) { + if (empty($episode->start)) { + return $racerooms; + } + $rooms = []; + $from = Carbon::createFromFormat('Y-m-d H:i:s', $episode->start, 'UTC')->sub(1, 'hour'); + $till = Carbon::createFromFormat('Y-m-d H:i:s', $episode->start, 'UTC')->add(1, 'hour'); + foreach ($racerooms as $room) { + $created = Carbon::createFromFormat('Y-m-d\TH:i:s.uP', $room['opened_at']); + if ($created->isAfter($from) && $created->isBefore($till)) { + $rooms[] = $room; + } + } + return empty($rooms) ? $racerooms : $rooms; + } + + private function filterPlayerNames(Episode $episode, $racerooms) { + $pnames = []; + foreach ($episode->players as $player) { + $pname = trim($player->getName()); + if (!empty($pname)) { + $pnames[] = $pname; + } + } + if (empty($pnames)) { + return $racerooms; + } + $rooms = []; + foreach ($racerooms as $room) { + $missing = false; + foreach ($pnames as $pname) { + if (stripos($room['info'], $pname) === false) { + $missing = true; + break; + } + } + if (!$missing) { + $rooms[] = $room; + } + } + return empty($rooms) ? $racerooms : $rooms; + } + + private $cat_cache = []; + +} diff --git a/app/Console/Kernel.php b/app/Console/Kernel.php index 904bfd1..c3d9aff 100644 --- a/app/Console/Kernel.php +++ b/app/Console/Kernel.php @@ -15,9 +15,10 @@ class Kernel extends ConsoleKernel */ protected function schedule(Schedule $schedule) { - $schedule->command('sync:avatars')->everyFiveMinutes(); $schedule->command('sync:speedgaming')->everyFiveMinutes(); $schedule->command('sync:sra')->everyFifteenMinutes(); + $schedule->command('sync:avatars')->everyFiveMinutes(); + $schedule->command('sync:racetime')->everyFiveMinutes(); } /** diff --git a/database/migrations/2023_08_21_073535_add_event_racetime.php b/database/migrations/2023_08_21_073535_add_event_racetime.php new file mode 100644 index 0000000..21b05fb --- /dev/null +++ b/database/migrations/2023_08_21_073535_add_event_racetime.php @@ -0,0 +1,32 @@ +string('racetime_category')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('events', function(Blueprint $table) { + $table->dropColumn('racetime_category'); + }); + } +}; diff --git a/database/migrations/2023_08_22_075105_add_episode_raceroom.php b/database/migrations/2023_08_22_075105_add_episode_raceroom.php new file mode 100644 index 0000000..7eb5006 --- /dev/null +++ b/database/migrations/2023_08_22_075105_add_episode_raceroom.php @@ -0,0 +1,32 @@ +string('raceroom')->nullable()->default(null); + }); + } + + /** + * Reverse the migrations. + * + * @return void + */ + public function down() + { + Schema::table('episodes', function(Blueprint $table) { + $table->dropColumn('raceroom'); + }); + } +}; diff --git a/resources/js/components/common/Icon.js b/resources/js/components/common/Icon.js index e1c4d46..6415770 100644 --- a/resources/js/components/common/Icon.js +++ b/resources/js/components/common/Icon.js @@ -85,6 +85,7 @@ Icon.PENDING = makePreset('PendingIcon', 'clock'); Icon.PIN = makePreset('PinIcon', 'location-pin'); Icon.PLAY = makePreset('PlayIcon', 'play'); Icon.PROTOCOL = makePreset('ProtocolIcon', 'file-alt'); +Icon.RACETIME = makePreset('RacetimeIcon', 'stopwatch'); Icon.REJECT = makePreset('RejectIcon', 'square-xmark'); Icon.REMOVE = makePreset('RemoveIcon', 'square-xmark'); Icon.RESULT = makePreset('ResultIcon', 'clock'); diff --git a/resources/js/components/episodes/Item.js b/resources/js/components/episodes/Item.js index adb7109..4fb4666 100644 --- a/resources/js/components/episodes/Item.js +++ b/resources/js/components/episodes/Item.js @@ -67,6 +67,20 @@ const Item = ({ episode, onAddRestream, onApply, onEditRestream, user }) => { {!hasChannels && hasPlayers ? : null} + {episode.raceroom ? +
+ +
+ : null} {onAddRestream && canRestreamEpisode(user, episode) ?