From c4b789343de4f07e7c436fff7dff622a02e26ed1 Mon Sep 17 00:00:00 2001 From: Lee Peuker Date: Wed, 13 Jul 2022 20:43:30 +0200 Subject: [PATCH] Add trakt username to settings page --- README.md | 27 +++++++--- bin/console.php | 3 +- ...0713163724_AddTraktClientIdToUserTable.php | 4 +- src/Api/Trakt/Api.php | 13 +++-- .../Trakt/Exception/TraktUserNameNotSet.php | 7 +++ .../Service/Trakt/PlaysPerDateFetcher.php | 4 +- src/Application/Service/Trakt/SyncRatings.php | 8 ++- .../Service/Trakt/SyncWatchedMovies.php | 22 ++++++-- src/Application/User/Api.php | 10 ++++ src/Application/User/Repository.php | 24 +++++++++ ...raktId.php => ChangeUserTraktClientId.php} | 2 +- src/Command/ChangeUserTraktUserName.php | 53 +++++++++++++++++++ src/Command/SyncTrakt.php | 5 ++ src/Factory.php | 3 +- src/HttpController/SettingsController.php | 6 +++ templates/page/settings.html.twig | 11 ++-- 16 files changed, 172 insertions(+), 30 deletions(-) create mode 100644 src/Application/Service/Trakt/Exception/TraktUserNameNotSet.php rename src/Command/{ChangeUserTraktId.php => ChangeUserTraktClientId.php} (97%) create mode 100644 src/Command/ChangeUserTraktUserName.php diff --git a/README.md b/README.md index e8bea0ca..0eecd751 100644 --- a/README.md +++ b/README.md @@ -8,18 +8,22 @@ Demo installation can be found [here](https://movary-demo.leepeuker.dev/) (login 2. [Install via docker](#install-via-docker) 3. [Important: First steps](#important-first-steps) 4. [Features](#features) - 1. [Plex Scrobbler](#plex-scrobbler) - 2. [Trakt.tv Sync](#trakttv-sync) - 3. [Tmdb Sync](#tmdb-sync) + 1. [Plex Scrobbler](#plex-scrobbler) + 2. [Trakt.tv Sync](#trakttv-sync) + 3. [Tmdb Sync](#tmdb-sync) 5. [Development](#development) + ## About + This is a web application to track and rate your watched movies (like a digital movie diary). -It was created because I wanted a self hosted solution instead of using external providers like trakt.tv or letterboxd and I wanted the focus to be on MY watch history (-> no social media features). +It was created because I wanted a self hosted solution instead of using external providers like trakt.tv or letterboxd and I wanted the focus to be on MY watch history (-> no +social media features). **Features:** + - add or update movie watch dates and ratings (only possible when logged in) - statistics about your watched movies (e.g. most watched actors, most watched directors, most watched genres etc) - PWA: can be installed as an app ([How to install PWAs in chrome](https://support.google.com/chrome/answer/9658361?hl=en&co=GENIE.Platform%3DAndroid&oco=1)) @@ -31,6 +35,7 @@ It was created because I wanted a self hosted solution instead of using external **Disclaimer:** This project is still in an experimental (but imo usable) state. I am planning to add more and improve existing features before creating a 1.0 realease. + ## Install via docker You must provide a tmdb api key (see https://www.themoviedb.org/settings/api) @@ -81,6 +86,7 @@ volumes: ``` + ## Important: First steps - Run database migrations: `docker exec movary php bin/console.php movary:database:migration --migrate` @@ -108,12 +114,15 @@ LOG_FILE="tmp/app.log" LOG_LEVEL=warning ``` -More configuration can be done via the base image webdevops/php-nginx, checkout their [docs](https://dockerfile.readthedocs.io/en/latest/content/DockerImages/dockerfiles/php-nginx.html) for more. +More configuration can be done via the base image webdevops/php-nginx, checkout +their [docs](https://dockerfile.readthedocs.io/en/latest/content/DockerImages/dockerfiles/php-nginx.html) for more. + ## Features + ### Plex Scrobbler Automatically track movies watched in plex with movary. @@ -123,11 +132,13 @@ You can generate your plex webhook url on the settings page (`/settings`). Add the generated url as a [webhook to plex](https://support.plex.tv/articles/115002267687-webhooks/). + ### trakt.tv sync -You can sync your watch history and ratings from trakt.tv. +You can sync your watch history and ratings from trakt.tv. -The user used in the sync process must have a trakt client id set (can be set via web UI on the settings page or via cli `movary:user:change-trakt-client-id`). +The user used in the sync process must have a trakt username and client id set (can be set via web UI on the settings page or via cli `movary:user:change-trakt-client-id` +and `movary:user:change-trakt-username`). Example (syncing history and ratings for user with id 1): @@ -147,6 +158,7 @@ Example (syncing history and ratings for user with id 1): Use if you want to sync everything from trakt regardless if there was a change since the last sync. + ### tmdb sync Update movie (meta) data with themoviedb.org information. @@ -164,6 +176,7 @@ Example: Maximum number of movies to sync + ## Development ### Setup diff --git a/bin/console.php b/bin/console.php index cf608d84..ac424f91 100644 --- a/bin/console.php +++ b/bin/console.php @@ -9,6 +9,7 @@ $application->add($container->get(Movary\Command\CreateUser::class)); $application->add($container->get(Movary\Command\ChangeUserPassword::class)); $application->add($container->get(Movary\Command\DatabaseMigration::class)); -$application->add($container->get(Movary\Command\ChangeUserTraktId::class)); +$application->add($container->get(Movary\Command\ChangeUserTraktClientId::class)); +$application->add($container->get(Movary\Command\ChangeUserTraktUserName::class)); $application->run(); diff --git a/db/migrations/20220713163724_AddTraktClientIdToUserTable.php b/db/migrations/20220713163724_AddTraktClientIdToUserTable.php index 470ffe64..a482694f 100644 --- a/db/migrations/20220713163724_AddTraktClientIdToUserTable.php +++ b/db/migrations/20220713163724_AddTraktClientIdToUserTable.php @@ -8,6 +8,7 @@ public function down() : void { $this->execute( <<execute( <<cacheWatchedService->fetchAllUniqueTraktIds($userId); } - public function fetchUserMovieHistoryByMovieId(string $clientId, TraktId $traktId) : User\Movie\History\DtoList + public function fetchUserMovieHistoryByMovieId(string $clientId, string $username, TraktId $traktId) : User\Movie\History\DtoList { - $responseData = $this->client->get($clientId, sprintf('/users/%s/history/movies/%d', $this->username, $traktId->asInt())); + $responseData = $this->client->get($clientId, sprintf('/users/%s/history/movies/%d', $username, $traktId->asInt())); return User\Movie\History\DtoList::createFromArray($responseData); } - public function fetchUserMoviesRatings(string $clientId,) : User\Movie\Rating\DtoList + public function fetchUserMoviesRatings(string $clientId, string $username) : User\Movie\Rating\DtoList { - $responseData = $this->client->get($clientId, sprintf('/users/%s/ratings/movies', $this->username)); + $responseData = $this->client->get($clientId, sprintf('/users/%s/ratings/movies', $username)); return User\Movie\Rating\DtoList::createFromArray($responseData); } - public function fetchUserMoviesWatched(string $clientId) : User\Movie\Watched\DtoList + public function fetchUserMoviesWatched(string $clientId, string $username) : User\Movie\Watched\DtoList { - $responseData = $this->client->get($clientId, sprintf('/users/%s/watched/movies', $this->username)); + $responseData = $this->client->get($clientId, sprintf('/users/%s/watched/movies', $username)); return User\Movie\Watched\DtoList::createFromArray($responseData); } diff --git a/src/Application/Service/Trakt/Exception/TraktUserNameNotSet.php b/src/Application/Service/Trakt/Exception/TraktUserNameNotSet.php new file mode 100644 index 00000000..7ca604fd --- /dev/null +++ b/src/Application/Service/Trakt/Exception/TraktUserNameNotSet.php @@ -0,0 +1,7 @@ +traktApi->fetchUserMovieHistoryByMovieId($traktClientId, $traktId) as $movieHistoryEntry) { + foreach ($this->traktApi->fetchUserMovieHistoryByMovieId($traktClientId, $username, $traktId) as $movieHistoryEntry) { $watchDate = Date::createFromDateTime($movieHistoryEntry->getWatchedAt()); $playsPerDates->incrementPlaysForDate($watchDate); diff --git a/src/Application/Service/Trakt/SyncRatings.php b/src/Application/Service/Trakt/SyncRatings.php index b688fc40..588c6075 100644 --- a/src/Application/Service/Trakt/SyncRatings.php +++ b/src/Application/Service/Trakt/SyncRatings.php @@ -5,6 +5,7 @@ use Movary\Api; use Movary\Application; use Movary\Application\Service\Trakt\Exception\TraktClientIdNotSet; +use Movary\Application\Service\Trakt\Exception\TraktUserNameNotSet; use Movary\ValueObject\PersonalRating; class SyncRatings @@ -25,7 +26,12 @@ public function execute(int $userId, bool $overwriteExistingData = false) : void throw new TraktClientIdNotSet(); } - $this->traktApiCacheUserMovieRatingService->set($userId, $this->traktApi->fetchUserMoviesRatings($traktClientId)); + $traktUserName = $this->userApi->findTraktUserName($userId); + if ($traktUserName === null) { + throw new TraktUserNameNotSet(); + } + + $this->traktApiCacheUserMovieRatingService->set($userId, $this->traktApi->fetchUserMoviesRatings($traktClientId, $traktUserName)); foreach ($this->movieApi->fetchAll() as $movie) { $traktId = $movie->getTraktId(); diff --git a/src/Application/Service/Trakt/SyncWatchedMovies.php b/src/Application/Service/Trakt/SyncWatchedMovies.php index 07f18151..79c98ffc 100644 --- a/src/Application/Service/Trakt/SyncWatchedMovies.php +++ b/src/Application/Service/Trakt/SyncWatchedMovies.php @@ -6,6 +6,7 @@ use Movary\Api\Trakt\ValueObject\Movie\TraktId; use Movary\Application; use Movary\Application\Service\Trakt\Exception\TraktClientIdNotSet; +use Movary\Application\Service\Trakt\Exception\TraktUserNameNotSet; use Movary\ValueObject\Date; use Psr\Log\LoggerInterface; @@ -30,7 +31,12 @@ public function execute(int $userId, bool $overwriteExistingData = false, bool $ throw new TraktClientIdNotSet(); } - $watchedMovies = $this->traktApi->fetchUserMoviesWatched($traktClientId); + $traktUserName = $this->userApi->findTraktUserName($userId); + if ($traktUserName === null) { + throw new TraktUserNameNotSet(); + } + + $watchedMovies = $this->traktApi->fetchUserMoviesWatched($traktClientId, $traktUserName); foreach ($watchedMovies as $watchedMovie) { $traktId = $watchedMovie->getMovie()->getTraktId(); @@ -41,7 +47,7 @@ public function execute(int $userId, bool $overwriteExistingData = false, bool $ continue; } - $this->syncMovieHistory($traktClientId, $userId, $traktId, $movie, $overwriteExistingData); + $this->syncMovieHistory($traktClientId, $traktUserName, $userId, $traktId, $movie, $overwriteExistingData); $this->traktApiCacheUserMovieWatchedService->setOne($userId, $traktId, $watchedMovie->getLastUpdated()); } @@ -95,9 +101,15 @@ private function isWatchedCacheUpToDate(int $userId, Api\Trakt\ValueObject\User\ return $cacheLastUpdated !== null && $watchedMovie->getLastUpdated()->isEqual($cacheLastUpdated) === true; } - private function syncMovieHistory(string $traktClientId, int $userId, TraktId $traktId, Application\Movie\Entity $movie, bool $overwriteExistingData) : void - { - $traktHistoryEntries = $this->playsPerDateFetcher->fetchTraktPlaysPerDate($traktClientId, $traktId); + private function syncMovieHistory( + string $traktClientId, + string $traktUserName, + int $userId, + TraktId $traktId, + Application\Movie\Entity $movie, + bool $overwriteExistingData + ) : void { + $traktHistoryEntries = $this->playsPerDateFetcher->fetchTraktPlaysPerDate($traktClientId, $traktUserName, $traktId); foreach ($this->movieApi->fetchHistoryByMovieId($movie->getId(), $userId) as $localHistoryEntry) { $localHistoryEntryDate = Date::createFromString($localHistoryEntry['watched_at']); diff --git a/src/Application/User/Api.php b/src/Application/User/Api.php index 2984c86f..ac68d7db 100644 --- a/src/Application/User/Api.php +++ b/src/Application/User/Api.php @@ -30,6 +30,11 @@ public function findTraktClientId(int $userId) : ?string return $this->repository->findTraktClientId($userId); } + public function findTraktUserName(int $userId) : ?string + { + return $this->repository->findTraktUserName($userId); + } + public function findUserIdByPlexWebhookId(string $webhookId) : ?int { return $this->repository->findUserIdByPlexWebhookId($webhookId); @@ -59,4 +64,9 @@ public function updateTraktClientId(int $userId, ?string $traktClientId) : void { $this->repository->updateTraktClientId($userId, $traktClientId); } + + public function updateTraktUserName(int $userId, ?string $traktUserName) : void + { + $this->repository->updateTraktUserName($userId, $traktUserName); + } } diff --git a/src/Application/User/Repository.php b/src/Application/User/Repository.php index 28cc0988..77f87bdd 100644 --- a/src/Application/User/Repository.php +++ b/src/Application/User/Repository.php @@ -78,6 +78,17 @@ public function findTraktClientId(int $userId) : ?string return $plexWebhookId; } + public function findTraktUserName(int $userId) : ?string + { + $plexWebhookId = $this->dbConnection->fetchOne('SELECT `trakt_user_name` FROM `user` WHERE `id` = ?', [$userId]); + + if ($plexWebhookId === false) { + return null; + } + + return $plexWebhookId; + } + public function findUserByEmail(string $email) : ?Entity { $data = $this->dbConnection->fetchAssociative('SELECT * FROM `user` WHERE `email` = ?', [$email]); @@ -160,4 +171,17 @@ public function updateTraktClientId(int $userId, ?string $traktClientId) : void ] ); } + + public function updateTraktUserName(int $userId, ?string $traktUserName) : void + { + $this->dbConnection->update( + 'user', + [ + 'trakt_user_name' => $traktUserName, + ], + [ + 'id' => $userId, + ] + ); + } } diff --git a/src/Command/ChangeUserTraktId.php b/src/Command/ChangeUserTraktClientId.php similarity index 97% rename from src/Command/ChangeUserTraktId.php rename to src/Command/ChangeUserTraktClientId.php index 739ea7fa..069eed1a 100644 --- a/src/Command/ChangeUserTraktId.php +++ b/src/Command/ChangeUserTraktClientId.php @@ -8,7 +8,7 @@ use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; -class ChangeUserTraktId extends Command +class ChangeUserTraktClientId extends Command { protected static $defaultName = self::COMMAND_BASE_NAME . ':user:change-trakt-client-id'; diff --git a/src/Command/ChangeUserTraktUserName.php b/src/Command/ChangeUserTraktUserName.php new file mode 100644 index 00000000..d209ba72 --- /dev/null +++ b/src/Command/ChangeUserTraktUserName.php @@ -0,0 +1,53 @@ +setDescription('Change user trakt client id.') + ->addArgument('userId', InputArgument::REQUIRED, 'ID of user') + ->addArgument('traktUserName', InputArgument::REQUIRED, 'New trakt username for user'); + } + + // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter + protected function execute(InputInterface $input, OutputInterface $output) : int + { + $userId = (int)$input->getArgument('userId'); + $traktUserName = $input->getArgument('traktUserName'); + + if (empty($traktUserName) === true) { + $traktUserName = null; + } + + try { + $this->userApi->updateTraktUserName($userId, $traktUserName); + } catch (\Throwable $t) { + $this->logger->error('Could not change trakt username.', ['exception' => $t]); + + $this->generateOutput($output, 'Could not update trakt username.'); + + return Command::FAILURE; + } + + $this->generateOutput($output, 'Updated trakt username.'); + return Command::SUCCESS; + } +} diff --git a/src/Command/SyncTrakt.php b/src/Command/SyncTrakt.php index fb10b13c..ea69655e 100644 --- a/src/Command/SyncTrakt.php +++ b/src/Command/SyncTrakt.php @@ -3,6 +3,7 @@ namespace Movary\Command; use Movary\Application\Service\Trakt\Exception\TraktClientIdNotSet; +use Movary\Application\Service\Trakt\Exception\TraktUserNameNotSet; use Movary\Application\Service\Trakt\SyncRatings; use Movary\Application\Service\Trakt\SyncWatchedMovies; use Psr\Log\LoggerInterface; @@ -71,6 +72,10 @@ protected function execute(InputInterface $input, OutputInterface $output) : int } catch (TraktClientIdNotSet $t) { $this->generateOutput($output, 'ERROR: User as no trakt client id set.'); + return Command::FAILURE; + } catch (TraktUserNameNotSet $t) { + $this->generateOutput($output, 'ERROR: User as no trakt user name set.'); + return Command::FAILURE; } catch (\Throwable $t) { $this->generateOutput($output, 'ERROR: Could not complete trakt sync.'); diff --git a/src/Factory.php b/src/Factory.php index 3b3fd653..2c1508b2 100644 --- a/src/Factory.php +++ b/src/Factory.php @@ -108,11 +108,10 @@ public static function createTmdbApiClient(ContainerInterface $container, Config ); } - public static function createTraktApi(ContainerInterface $container, Config $config) : Trakt\Api + public static function createTraktApi(ContainerInterface $container) : Trakt\Api { return new Trakt\Api( $container->get(Trakt\Client::class), - $config->getAsString('TRAKT_USERNAME'), $container->get(Watched\Service::class), ); } diff --git a/src/HttpController/SettingsController.php b/src/HttpController/SettingsController.php index 6dccd492..d71beb62 100644 --- a/src/HttpController/SettingsController.php +++ b/src/HttpController/SettingsController.php @@ -35,6 +35,7 @@ public function render() : Response $this->twig->render('page/settings.html.twig', [ 'plexWebhookUrl' => $this->userApi->findPlexWebhookId($userId) ?? '-', 'traktClientId' => $this->userApi->findTraktClientId($userId), + 'traktUserName' => $this->userApi->findTraktUserName($userId), 'applicationVersion' => $this->applicationVersion ?? '-', 'lastSyncTrakt' => $this->syncLogRepository->findLastTraktSync() ?? '-', 'lastSyncTmdb' => $this->syncLogRepository->findLastTmdbSync() ?? '-', @@ -50,13 +51,18 @@ public function updateTrakt(Request $request) : Response } $traktClientId = $request->getPostParameters()['traktClientId']; + $traktUserName = $request->getPostParameters()['traktUserName']; $userId = $this->authenticationService->getCurrentUserId(); if (empty($traktClientId) === true) { $traktClientId = null; } + if (empty($traktUserName) === true) { + $traktUserName = null; + } $this->userApi->updateTraktClientId($userId, $traktClientId); + $this->userApi->updateTraktUserName($userId, $traktUserName); return Response::create( StatusCode::createSeeOther(), diff --git a/templates/page/settings.html.twig b/templates/page/settings.html.twig index c0300902..12ea15f4 100644 --- a/templates/page/settings.html.twig +++ b/templates/page/settings.html.twig @@ -23,11 +23,16 @@

To generate a client id visit this url here.

+

Username:

+
+ +
+

Client ID:

- +