--- /dev/null
+<?php
+
+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\Organization;
+use App\Models\User;
+use Carbon\Carbon;
+use Illuminate\Console\Command;
+use Illuminate\Database\Eloquent\Builder;
+use Illuminate\Support\Facades\Http;
+
+class SyncHTH extends Command {
+
+ /**
+ * The name and signature of the console command.
+ *
+ * @var string
+ */
+ protected $signature = 'sync:hth';
+
+ /**
+ * The console command description.
+ *
+ * @var string
+ */
+ protected $description = 'Synchronize Hyrule Town Hall schedule';
+
+ /**
+ * Execute the console command.
+ *
+ * @return int
+ */
+ public function handle() {
+ $events = Event::where('external_schedule', 'LIKE', 'hth:%')
+ ->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) {
+ $hthHandle = substr($event->external_schedule, 4);
+ $parts = explode('/', $hthHandle);
+ $hthSeries = $parts[0];
+ $hthEvent = $parts[1];
+ $this->line('series: "'.$hthSeries.'", evnet: "'.$hthEvent.'"');
+ $hthSchedule = HTTP::post('https://hth.zeldaspeedruns.com/api/v1/graphql', [
+ 'operation_name' => 'my_schedule',
+ 'variables' => [
+ 'my_series' => $hthSeries,
+ 'my_event' => $hthEvent,
+ ],
+ 'query' => <<<GRAPHQL
+ query my_schedule(\$my_series: String, \$my_event: String) {
+ series(name: \$my_series) {
+ event(name: \$my_event) {
+ races {
+ id
+ start
+ room
+ phase
+ round
+ teams {
+ name
+ members {
+ role
+ user {
+ id
+ displayName
+ racetimeId
+ discordId
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ GRAPHQL,
+ ])->throw()->json();
+ foreach ($hthSchedule['data']['series']['event']['races'] as $hthEntry) {
+ try {
+ $this->syncSchedule($event, $hthEntry);
+ } catch (\Exception $e) {
+ $this->error('error syncing episode '.$hthEntry['id'].': '.$e->getMessage());
+ }
+ }
+ }
+
+ private function syncSchedule(Event $event, $hthEntry) {
+ $ext_id = 'hth:'.$hthEntry['id'];
+ $episode = Episode::firstWhere('ext_id', '=', $ext_id);
+ if (!$hthEntry['start']) {
+ if ($episode) {
+ $episode->delete();
+ }
+ return;
+ }
+ if (!$episode) {
+ $episode = new Episode();
+ $episode->ext_id = $ext_id;
+ }
+ $episode->event()->associate($event);
+ $episode->title = $hthEntry['phase'].' '.$hthEntry['round'];
+ $start = Carbon::parse($hthEntry['start']);
+ if (!$episode->start || $start->ne($episode->start)) {
+ $episode->start = $start;
+ }
+ $episode->estimate = 90 * 60 * 60;
+ $episode->confirmed = true;
+ $episode->save();
+
+ $this->purgePlayers($episode, $hthEntry);
+ foreach ($hthEntry['teams'] as $hthTeam) {
+ foreach ($hthTeam['members'] as $hthPlayer) {
+ try {
+ $this->syncPlayer($episode, $hthTeam, $hthPlayer);
+ } catch (\Exception $e) {
+ $this->error('error syncing player '.$hthPlayer['user']['id'].': '.$e->getMessage());
+ }
+ }
+ }
+ }
+
+ private function purgePlayers(Episode $episode, $hthEntry) {
+ $ext_ids = [];
+ foreach ($hthEntry['teams'] as $hthTeam) {
+ foreach ($hthTeam['members'] as $hthPlayer) {
+ $ext_ids[] = 'hth:'.$hthPlayer['user']['id'];
+ }
+ }
+ $episode->players()->whereNotIn('ext_id', $ext_ids)->delete();
+ }
+
+ private function syncPlayer(Episode $episode, $hthTeam, $hthPlayer) {
+ $ext_id = 'hth:'.$hthPlayer['user']['id'];
+ $player = $episode->players()->firstWhere('ext_id', '=', $ext_id);
+ if (!$player) {
+ $player = new EpisodePlayer();
+ $player->ext_id = $ext_id;
+ $player->episode()->associate($episode);
+ }
+ $user = $this->getUserByHTHPlayer($hthPlayer);
+ if ($user) {
+ $player->user()->associate($user);
+ } else {
+ $player->user()->disassociate();
+ }
+ if (!empty($hthPlayer['user']['displayName'])) {
+ $player->name_override = $hthPlayer['user']['displayName'];
+ }
+ $player->save();
+ }
+
+ private function getUserByHTHPlayer($player) {
+ if (!empty($player['user']['discordId'])) {
+ $user = User::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']);
+ if ($user) {
+ return $user;
+ }
+ $user = User::firstWhere('global_name', 'LIKE', $player['user']['displayName']);
+ if ($user) {
+ return $user;
+ }
+ $user = User::firstWhere('username', 'LIKE', $player['user']['displayName']);
+ if ($user) {
+ return $user;
+ }
+ }
+ return null;
+ }
+
+}