Skip to content

Commit

Permalink
Merge pull request #500 from leepeuker/add-stevenlu-list-for-radarr
Browse files Browse the repository at this point in the history
Add a Radarr feed URL for watchlist sync
  • Loading branch information
JVT038 authored Oct 1, 2023
2 parents ae2ef25 + 6d8e09d commit a4035e7
Show file tree
Hide file tree
Showing 19 changed files with 546 additions and 9 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php declare(strict_types=1);

use Phinx\Migration\AbstractMigration;

final class AddRadarrFeedUuidColumnToUserTable extends AbstractMigration
{
public function down() : void
{
$this->execute(
<<<SQL
ALTER TABLE user DROP COLUMN radarr_feed_uuid
SQL,
);
}

public function up() : void
{
$this->execute(
<<<SQL
ALTER TABLE user ADD COLUMN radarr_feed_uuid CHAR(36) DEFAULT NULL AFTER plex_scrobble_ratings
SQL,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
<?php declare(strict_types=1);

use Phinx\Migration\AbstractMigration;

final class AddRadarrFeedUuidColumnToUserTable extends AbstractMigration
{
public function down() : void
{
$this->execute(
<<<SQL
CREATE TABLE `tmp_user` (
`id` INTEGER,
`email` TEXT NOT NULL,
`name` TEXT NOT NULL,
`password` TEXT NOT NULL,
`totp_uri` TEXT DEFAULT NULL,
`is_admin` TINYINT(1) DEFAULT 0,
`dashboard_visible_rows` TEXT DEFAULT NULL,
`dashboard_extended_rows` TEXT DEFAULT NULL,
`dashboard_order_rows` TEXT DEFAULT NULL,
`jellyfin_access_token` TEXT DEFAULT NULL,
`jellyfin_user_id` TEXT DEFAULT NULL,
`jellyfin_server_url` TEXT DEFAULT NULL,
`jellyfin_sync_enabled` TINYINT(1) DEFAULT 0,
`privacy_level` INTEGER DEFAULT 1,
`date_format_id` INTEGER DEFAULT 0,
`trakt_user_name` TEXT,
`plex_webhook_uuid` TEXT,
`jellyfin_webhook_uuid` TEXT,
`emby_webhook_uuid` TEXT,
`trakt_client_id` TEXT,
`plex_client_id` TEXT DEFAULT NULL,
`plex_client_temporary_code` TEXT DEFAULT NULL,
`plex_access_token` TEXT DEFAULT NULL,
`plex_account_id` TEXT DEFAULT NULL,
`plex_server_url` TEXT DEFAULT NULL,
`jellyfin_scrobble_views` INTEGER DEFAULT 1,
`emby_scrobble_views` INTEGER DEFAULT 1,
`plex_scrobble_views` INTEGER DEFAULT 1,
`plex_scrobble_ratings` INTEGER DEFAULT 0,
`watchlist_automatic_removal_enabled` INTEGER DEFAULT 1,
`country` TEXT DEFAULT NULL,
`core_account_changes_disabled` INTEGER DEFAULT 0,
`created_at` TEXT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE (`email`),
UNIQUE (`name`)
)
SQL,
);
$this->execute(
'INSERT INTO `tmp_user` (
`id`,
`email`,
`name`,
`password`,
`totp_uri`,
`is_admin`,
`dashboard_visible_rows`,
`dashboard_extended_rows`,
`dashboard_order_rows`,
`jellyfin_access_token`,
`jellyfin_user_id`,
`jellyfin_server_url`,
`privacy_level`,
`date_format_id`,
`trakt_user_name`,
`plex_webhook_uuid`,
`jellyfin_webhook_uuid`,
`emby_webhook_uuid`,
`trakt_client_id`,
`plex_client_id`,
`plex_client_temporary_code`,
`plex_access_token`,
`plex_account_id`,
`plex_server_url`,
`jellyfin_scrobble_views`,
`emby_scrobble_views`,
`plex_scrobble_views`,
`plex_scrobble_ratings`,
`watchlist_automatic_removal_enabled`,
`country`,
`core_account_changes_disabled`,
`created_at`
) SELECT
`id`,
`email`,
`name`,
`password`,
`totp_uri`,
`is_admin`,
`dashboard_visible_rows`,
`dashboard_extended_rows`,
`dashboard_order_rows`,
`jellyfin_access_token`,
`jellyfin_user_id`,
`jellyfin_server_url`,
`privacy_level`,
`date_format_id`,
`trakt_user_name`,
`plex_webhook_uuid`,
`jellyfin_webhook_uuid`,
`emby_webhook_uuid`,
`trakt_client_id`,
`plex_client_id`,
`plex_client_temporary_code`,
`plex_access_token`,
`plex_account_id`,
`plex_server_url`,
`jellyfin_scrobble_views`,
`emby_scrobble_views`,
`plex_scrobble_views`,
`plex_scrobble_ratings`,
`watchlist_automatic_removal_enabled`,
`country`,
`core_account_changes_disabled`,
`created_at` FROM user',
);
$this->execute('DROP TABLE `user`');
$this->execute('ALTER TABLE `tmp_user` RENAME TO `user`');
}

public function up() : void
{
$this->execute(
<<<SQL
CREATE TABLE `tmp_user` (
`id` INTEGER,
`email` TEXT NOT NULL,
`name` TEXT NOT NULL,
`password` TEXT NOT NULL,
`totp_uri` TEXT DEFAULT NULL,
`is_admin` TINYINT(1) DEFAULT 0,
`dashboard_visible_rows` TEXT DEFAULT NULL,
`dashboard_extended_rows` TEXT DEFAULT NULL,
`dashboard_order_rows` TEXT DEFAULT NULL,
`jellyfin_access_token` TEXT DEFAULT NULL,
`jellyfin_user_id` TEXT DEFAULT NULL,
`jellyfin_server_url` TEXT DEFAULT NULL,
`jellyfin_sync_enabled` TINYINT(1) DEFAULT 0,
`privacy_level` INTEGER DEFAULT 1,
`date_format_id` INTEGER DEFAULT 0,
`trakt_user_name` TEXT,
`plex_webhook_uuid` TEXT,
`jellyfin_webhook_uuid` TEXT,
`emby_webhook_uuid` TEXT,
`trakt_client_id` TEXT,
`plex_client_id` TEXT DEFAULT NULL,
`plex_client_temporary_code` TEXT DEFAULT NULL,
`plex_access_token` TEXT DEFAULT NULL,
`plex_account_id` TEXT DEFAULT NULL,
`plex_server_url` TEXT DEFAULT NULL,
`jellyfin_scrobble_views` INTEGER DEFAULT 1,
`emby_scrobble_views` INTEGER DEFAULT 1,
`plex_scrobble_views` INTEGER DEFAULT 1,
`plex_scrobble_ratings` INTEGER DEFAULT 0,
`radarr_feed_uuid` TEXT DEFAULT NULL,
`watchlist_automatic_removal_enabled` INTEGER DEFAULT 1,
`country` TEXT DEFAULT NULL,
`core_account_changes_disabled` INTEGER DEFAULT 0,
`created_at` TEXT NOT NULL,
PRIMARY KEY (`id`),
UNIQUE (`email`),
UNIQUE (`name`)
)
SQL,
);
$this->execute(
'INSERT INTO `tmp_user` (
`id`,
`email`,
`name`,
`password`,
`totp_uri`,
`is_admin`,
`dashboard_visible_rows`,
`dashboard_extended_rows`,
`dashboard_order_rows`,
`jellyfin_access_token`,
`jellyfin_user_id`,
`jellyfin_server_url`,
`jellyfin_sync_enabled`,
`privacy_level`,
`date_format_id`,
`trakt_user_name`,
`plex_webhook_uuid`,
`jellyfin_webhook_uuid`,
`emby_webhook_uuid`,
`trakt_client_id`,
`plex_client_id`,
`plex_client_temporary_code`,
`plex_access_token`,
`plex_account_id`,
`plex_server_url`,
`jellyfin_scrobble_views`,
`emby_scrobble_views`,
`plex_scrobble_views`,
`plex_scrobble_ratings`,
`watchlist_automatic_removal_enabled`,
`country`,
`core_account_changes_disabled`,
`created_at`
) SELECT * FROM user',
);
$this->execute('DROP TABLE `user`');
$this->execute('ALTER TABLE `tmp_user` RENAME TO `user`');
}
}
15 changes: 15 additions & 0 deletions docs/features/radarr.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
## Radarr list

### Description

You can set up Radarr to automatically import movies added to your Movary watchlist via the Radarr [lists import feature](https://wiki.servarr.com/radarr/supported#stevenluimport).

### Instruction

- Generate the feed url in Movary on the Radarr integration settings page (`/settings/integrations/radarr`)
- Go to your Radarr Settings and select "Lists"
- Click on the plus to add a new list
- In the section Advanced List select "StevenLu Custom"
- Copy the Movary feed url to the "URL" input in Radarr and save

Feel free to change any other settings of the StevenLu list, it doesn't affect Movary or the watchlist.
3 changes: 2 additions & 1 deletion mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,8 @@ nav:
- Letterboxd: features/letterboxd.md
- Trakt TV: features/trakt.md
- Netflix: features/netflix.md
- Development:
- Radarr: features/radarr.md
- Development:
- Setup: development/setup.md
- Directory structure: development/file-structure.md
- How does the backend work?: development/structure.md
Expand Down
44 changes: 44 additions & 0 deletions public/js/settings-integration-radarr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
async function regenerateRadarrFeedId() {
removeAlert('alertFeedUrlDiv')

regenerateRadarrFeedRequest().then(webhookUrl => {
setRadarrFeedUrl(webhookUrl)
addAlert('alertFeedUrlDiv', 'Generated new feed url', 'success')
document.getElementById('deleteRadarrFeedButton').classList.remove('disabled')
}).catch((error) => {
console.log(error)
addAlert('alertFeedUrlDiv', 'Could not generate feed url', 'danger')
})
}

async function regenerateRadarrFeedRequest() {
const response = await fetch('/settings/radarr/feed', {'method': 'put'})

if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}
const data = await response.json()

return data.url
}

async function deleteRadarrFeedId() {
await fetch('/settings/radarr/feed', {'method': 'delete'}).then(response => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`)
}

setRadarrFeedUrl()
addAlert('alertFeedUrlDiv', 'Deleted feed url', 'success');
});
}

function setRadarrFeedUrl(webhookUrl) {
if (webhookUrl) {
document.getElementById('radarrFeedUrl').innerHTML = webhookUrl
document.getElementById('deleteRadarrFeedButton').classList.remove('disabled')
} else {
document.getElementById('radarrFeedUrl').innerHTML = '-'
document.getElementById('deleteRadarrFeedButton').classList.add('disabled')
}
}
8 changes: 8 additions & 0 deletions settings/routes.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

use Movary\HttpController\Api;
use Movary\HttpController\Web;
use Movary\HttpController\Web\RadarrController;
use Movary\Service\Router\Dto\RouteList;
use Movary\Service\Router\RouterService;

Expand Down Expand Up @@ -146,6 +147,11 @@ function addWebRoutes(RouterService $routerService, FastRoute\RouteCollector $ro
$routes->add('PUT', '/settings/users/{userId:\d+}', [Web\UserController::class, 'updateUser'], [Web\Middleware\UserIsAuthenticated::class]);
$routes->add('DELETE', '/settings/users/{userId:\d+}', [Web\UserController::class, 'deleteUser'], [Web\Middleware\UserIsAuthenticated::class]);

$routes->add('GET', '/settings/integrations/radarr', [Web\SettingsController::class, 'renderRadarrPage'], [Web\Middleware\UserIsAuthenticated::class]);
$routes->add('PUT', '/settings/radarr/feed', [RadarrController::class, 'regenerateRadarrFeedUrl'], [Web\Middleware\UserIsAuthenticated::class]);
$routes->add('DELETE', '/settings/radarr/feed', [RadarrController::class, 'deleteRadarrFeedUrl'], [Web\Middleware\UserIsAuthenticated::class]);


##########
# Movies #
##########
Expand Down Expand Up @@ -210,5 +216,7 @@ function addApiRoutes(RouterService $routerService, FastRoute\RouteCollector $ro

$routes->add('GET', '/movies/search', [Api\MovieSearchController::class, 'search'], [Api\Middleware\IsAuthenticated::class]);

$routes->add('GET', '/feed/radarr/{id:.+}', [Api\RadarrController::class, 'renderRadarrFeed']);

$routerService->addRoutesToRouteCollector($routeCollector, $routes);
}
2 changes: 1 addition & 1 deletion src/Domain/Movie/Watchlist/MovieWatchlistApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public function addMovieToWatchlist(int $userId, int $movieId, ?DateTime $addedA
$this->repository->addMovieToWatchlist($userId, $movieId, $addedAt);
}

public function fetchAllWatchlistItems(int $userId) : ?array
public function fetchAllWatchlistItems(int $userId) : array
{
return $this->repository->fetchAllWatchlistItems($userId);
}
Expand Down
2 changes: 1 addition & 1 deletion src/Domain/Movie/Watchlist/MovieWatchlistRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public function addMovieToWatchlist(int $userId, int $movieId, ?DateTime $addedA
);
}

public function fetchAllWatchlistItems(int $userId) : ?array
public function fetchAllWatchlistItems(int $userId) : array
{
return $this->dbConnection->fetchAllAssociative(
'SELECT title, tmdb_id, imdb_id, added_at FROM watchlist JOIN movie m ON movie_id = m.id WHERE user_id = ?', [$userId],
Expand Down
19 changes: 19 additions & 0 deletions src/Domain/User/UserApi.php
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ public function deletePlexWebhookId(int $userId) : void
$this->repository->setPlexWebhookId($userId, null);
}

public function deleteRadarrFeedId(int $userId) : void
{
$this->repository->setRadarrFeedId($userId, null);
}

public function deleteTotpUri(int $userId) : void
{
$this->repository->updateTotpUri($userId, null);
Expand Down Expand Up @@ -232,6 +237,11 @@ public function findUserIdByPlexWebhookId(string $webhookId) : ?int
return $this->repository->findUserIdByPlexWebhookId($webhookId);
}

public function findUserIdByRadarrFeedId(string $feedId) : ?int
{
return $this->repository->findUserIdByRadarrFeedId($feedId);
}

public function generateApiToken(int $userId) : void
{
$token = bin2hex(random_bytes(16));
Expand Down Expand Up @@ -282,6 +292,15 @@ public function regeneratePlexWebhookId(int $userId) : string
return $plexWebhookId;
}

public function regenerateRadarrFeedId(int $userId) : string
{
$radarrFeedId = (string)Uuid::uuid4();

$this->repository->setRadarrFeedId($userId, $radarrFeedId);

return $radarrFeedId;
}

public function updateCoreAccountChangesDisabled(int $userId, bool $updateCoreAccountChangesDisabled) : void
{
$this->repository->updateCoreAccountChangesDisabled($userId, $updateCoreAccountChangesDisabled);
Expand Down
Loading

0 comments on commit a4035e7

Please sign in to comment.