Skip to content

Commit

Permalink
Merge pull request #49 from leepeuker/add-trakt-client-id-to-user-set…
Browse files Browse the repository at this point in the history
…tings-page

Add trakt username to settings page
  • Loading branch information
leepeuker authored Jul 13, 2022
2 parents 7481ec5 + c4b7893 commit 5ba770c
Show file tree
Hide file tree
Showing 16 changed files with 172 additions and 30 deletions.
27 changes: 20 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

<a name="#about"></a>

## 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))
Expand All @@ -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.

<a name="#install-via-docker"></a>

## Install via docker

You must provide a tmdb api key (see https://www.themoviedb.org/settings/api)
Expand Down Expand Up @@ -81,6 +86,7 @@ volumes:
```
<a name="#important-first-steps"></a>
## Important: First steps
- Run database migrations: `docker exec movary php bin/console.php movary:database:migration --migrate`
Expand Down Expand Up @@ -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.
<a name="#features"></a>
## Features
<a name="#plex-scrobbler"></a>
### Plex Scrobbler
Automatically track movies watched in plex with movary.
Expand All @@ -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/).
<a name="#trakttv-sync"></a>
### 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):
Expand All @@ -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.
<a name="#tmdb-sync"></a>
### tmdb sync
Update movie (meta) data with themoviedb.org information.
Expand All @@ -164,6 +176,7 @@ Example:
Maximum number of movies to sync
<a name="#development"></a>
## Development
### Setup
Expand Down
3 changes: 2 additions & 1 deletion bin/console.php
Original file line number Diff line number Diff line change
Expand Up @@ -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();
4 changes: 3 additions & 1 deletion db/migrations/20220713163724_AddTraktClientIdToUserTable.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public function down() : void
{
$this->execute(
<<<SQL
ALTER TABLE user DROP COLUMN trakt_user_name;
ALTER TABLE user DROP COLUMN trakt_client_id;
SQL
);
Expand All @@ -17,7 +18,8 @@ public function up() : void
{
$this->execute(
<<<SQL
ALTER TABLE user ADD COLUMN trakt_client_id VARCHAR(255) DEFAULT NULL AFTER plex_webhook_uuid;
ALTER TABLE user ADD COLUMN trakt_user_name VARCHAR(255) DEFAULT NULL AFTER plex_webhook_uuid;
ALTER TABLE user ADD COLUMN trakt_client_id VARCHAR(255) DEFAULT NULL AFTER trakt_user_name;
SQL
);
}
Expand Down
13 changes: 6 additions & 7 deletions src/Api/Trakt/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@ class Api
{
public function __construct(
private readonly Client $client,
private readonly string $username,
private readonly Watched\Service $cacheWatchedService
) {
}
Expand All @@ -20,23 +19,23 @@ public function fetchUniqueCachedTraktIds(int $userId) : array
return $this->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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php declare(strict_types=1);

namespace Movary\Application\Service\Trakt\Exception;

class TraktUserNameNotSet extends \Exception
{
}
4 changes: 2 additions & 2 deletions src/Application/Service/Trakt/PlaysPerDateFetcher.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ public function __construct(private readonly Api $traktApi)
{
}

public function fetchTraktPlaysPerDate(string $traktClientId, TraktId $traktId) : PlaysPerDateDtoList
public function fetchTraktPlaysPerDate(string $traktClientId, string $username, TraktId $traktId) : PlaysPerDateDtoList
{
$playsPerDates = PlaysPerDateDtoList::create();

foreach ($this->traktApi->fetchUserMovieHistoryByMovieId($traktClientId, $traktId) as $movieHistoryEntry) {
foreach ($this->traktApi->fetchUserMovieHistoryByMovieId($traktClientId, $username, $traktId) as $movieHistoryEntry) {
$watchDate = Date::createFromDateTime($movieHistoryEntry->getWatchedAt());

$playsPerDates->incrementPlaysForDate($watchDate);
Expand Down
8 changes: 7 additions & 1 deletion src/Application/Service/Trakt/SyncRatings.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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();
Expand Down
22 changes: 17 additions & 5 deletions src/Application/Service/Trakt/SyncWatchedMovies.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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();
Expand All @@ -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());
}
Expand Down Expand Up @@ -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']);
Expand Down
10 changes: 10 additions & 0 deletions src/Application/User/Api.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}
24 changes: 24 additions & 0 deletions src/Application/User/Repository.php
Original file line number Diff line number Diff line change
Expand Up @@ -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]);
Expand Down Expand Up @@ -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,
]
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
53 changes: 53 additions & 0 deletions src/Command/ChangeUserTraktUserName.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
<?php declare(strict_types=1);

namespace Movary\Command;

use Movary\Application\User;
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;

class ChangeUserTraktUserName extends Command
{
protected static $defaultName = self::COMMAND_BASE_NAME . ':user:change-trakt-username';

public function __construct(
private readonly User\Api $userApi,
private readonly LoggerInterface $logger,
) {
parent::__construct();
}

protected function configure() : void
{
$this
->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;
}
}
5 changes: 5 additions & 0 deletions src/Command/SyncTrakt.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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.');
Expand Down
Loading

0 comments on commit 5ba770c

Please sign in to comment.