Skip to content

Commit

Permalink
Merge pull request #88 from PlexRipper/dev
Browse files Browse the repository at this point in the history
Release v0.8.7
  • Loading branch information
JasonLandbridge committed Oct 4, 2021
2 parents 59c93ac + 12d43e2 commit 7ac74e9
Show file tree
Hide file tree
Showing 94 changed files with 4,964 additions and 1,501 deletions.
52 changes: 41 additions & 11 deletions CHANGELOG.md
@@ -1,10 +1,50 @@
## [0.8.7]

### Added

- Added two-factor authentication compatiblity, meaning PlexRipper now works with two-factor authentication protected Plex accounts.
- Added the awesome French translation from @starnakin, thank you so much!
- Added a progress window showing the individual servers being connected to when setting up an PlexAccount.
- Added a warning that deleting an plexAccount might take a long time due to the amount of data which has to be deleted.
- Added a thank you for the awesome contribution from Starnakin to the README.
- Added migration check for adding ClientId to already created Plex accounts, this avoids users having to re-setup their accounts in PlexRipper.

### Changed

- When confirming an action in the confirmation dialog, a loading spinner will now show.
- When a translation is missing, it will now show the English variant.
- When no downloads have been selected in the download page, the "Clear Completed" is now disabled.
- Updated to the new PlexAPI SignIn process
- Improved the PlexApi HttpClient to return errors given by Plex.

### Fixed

- Fixed the "New device connected" spam that Plex server owners would get due to a randomly generated ClientId's being used per request by PlexRipper. This is now unique and consistent for every PlexAccount.
- Fixed the delete button missing from the Plex account update window.
- Fixed the confirmation window prevented from being closed.
- Fixed the menu titles now being translateable.
- Fixed the DateTime settings not being translatable.
- Fixed the browser client not updating its store after resetting the database.
- Fixed the error window having a very wide window due to unwrapped text.
- Fixed the TvShows libraries getting stuck in a infinite loading loop when viewed, TV show libraries can now be viewed again.

## [0.8.6]

### Added

- Added an improved Notification sidebar
- Added all text as translations keys to the language file to ensure everything can get translated.
- Added Language switcher under Settings => UI, only supported English and the to be translated French.

### Changed

- Notifications can now be cleared with a click of a button
- Fixed the lang folder link in the README
- Added the French language file and language support (Still needs translations).
- Added the language option to the config file
- Rewritten how settings are saved, they should now work much better
- Added feedback section to the ReadMe
- Ensured the config/settings file is now better protected against corruption and invalid values

### Fixed

Expand All @@ -18,14 +58,4 @@
- Fixed an error when trying to add a Movie or TvShow destination folder under Settings => Paths, should now work correctly
- Fixed the "no downloads" message not always showing correctly when there are no downloads in progress.
- Fixed the setup page not showing the correct background and throwing an error when that happens
- Fixed the skip button during the setup wizard not appearing

### Changed

- Notifications can now be cleared with a click of a button
- Fixed the lang folder link in the README
- Added the French language file and language support (Still needs translations).
- Added the language option to the config file
- Rewritten how settings are saved, they should now work much better
- Added feedback section to the ReadMe
- Ensured the config/settings file is now better protected against corruption and invalid values
- Fixed the skip button during the setup wizard not appearing
7 changes: 7 additions & 0 deletions PlexRipper.sln
Expand Up @@ -66,6 +66,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Environment", "src\Environm
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Logging.UnitTests", "tests\UnitTests\Logging.UnitTests\Logging.UnitTests.csproj", "{54F528D3-4782-4A8F-A3B2-532F12DB9179}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlexApi.IntegrationTests", "tests\IntegrationTests\PlexApi.IntegrationTests\PlexApi.IntegrationTests.csproj", "{521C9269-ED53-48DB-BE46-1F283F81CA1D}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -156,6 +158,10 @@ Global
{54F528D3-4782-4A8F-A3B2-532F12DB9179}.Debug|Any CPU.Build.0 = Debug|Any CPU
{54F528D3-4782-4A8F-A3B2-532F12DB9179}.Release|Any CPU.ActiveCfg = Release|Any CPU
{54F528D3-4782-4A8F-A3B2-532F12DB9179}.Release|Any CPU.Build.0 = Release|Any CPU
{521C9269-ED53-48DB-BE46-1F283F81CA1D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{521C9269-ED53-48DB-BE46-1F283F81CA1D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{521C9269-ED53-48DB-BE46-1F283F81CA1D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{521C9269-ED53-48DB-BE46-1F283F81CA1D}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -184,6 +190,7 @@ Global
{985E17A9-D6EE-458A-931B-108A7D11A3AD} = {EEB5E527-6D2D-4280-9A24-7472229C7B91}
{F601827D-2C9B-4012-B595-C7A3B5ADC56B} = {EEB5E527-6D2D-4280-9A24-7472229C7B91}
{54F528D3-4782-4A8F-A3B2-532F12DB9179} = {BC7C4F2B-5B33-4A5F-9718-3A672B10A312}
{521C9269-ED53-48DB-BE46-1F283F81CA1D} = {DABD59AB-7F20-4D04-8601-9EC7BE2F4373}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {B4C02A51-7A0F-40AF-B559-20772614ED60}
Expand Down
10 changes: 7 additions & 3 deletions README.md
Expand Up @@ -30,12 +30,13 @@ Any feedback, good or bad, is very much appreciated! You can create an [Github i
## Current Features

- Runs in a Docker container and can be deployed on any type of platform that can host a Docker container!
- A beautiful GUI which has a Light and Dark-mode theme!

- Add multiple Plex accounts to easily download from the accessible Plex servers!

- A beautiful GUI which has a Light and Dark-mode theme!

- Multi-threaded downloading and PlexRipper moves your finished downloads straight to your own Plex media collection!

- Multi-language support, currently with English and French

- Will still work even if Plex.tv is down by connecting directly to the media servers!

Expand All @@ -44,8 +45,9 @@ Any feedback, good or bad, is very much appreciated! You can create an [Github i
## Upcoming features

- Download music and photo's, currently only movies and tv shows!

- Search through and filter media available for downloading!
- Sonarr and Radarr integration
- Download Plex Collections with custom banner and thumbnail

- **PlexRipper Authentication, right now there is ZERO security so make sure to NOT expose the Web UI to the public internet! e.g. forwarding a domain to the container etc**

Expand Down Expand Up @@ -123,6 +125,8 @@ Make PlexRipper easier to use for everyone by translating it to your native lang

An absolute massive thank you to the following people!

- [Starnakin](https://github.com/starnakin) for his awesome work on translating PlexRipper to French!

- [Ninthwalker](https://github.com/ninthwalker) for his great work on [Saverr](https://github.com/ninthwalker/saverr), which showed me the method of downloading media from Plex!

- [Michael Altmann](https://github.com/altmann) for his awesome work on [Fluent Results](https://github.com/altmann/FluentResults) and the time he spent helping with making Fluent Results work in PlexRipper!
Expand Down
42 changes: 42 additions & 0 deletions src/Application/Common/DTO/PlexApi/AuthPin.cs
@@ -0,0 +1,42 @@
using System;
using System.Collections.Generic;
using Newtonsoft.Json;

namespace PlexRipper.Application.Common
{
public class AuthPin
{
[JsonProperty(Required = Required.Always)]
public List<PlexError> Errors { get; set; }

[JsonProperty(Required = Required.Always)]
public int Id { get; set; }

[JsonProperty(Required = Required.Always)]
public string Code { get; set; }

[JsonProperty(Required = Required.Always)]
public bool Trusted { get; set; }

[JsonProperty(Required = Required.Always)]
public string ClientIdentifier { get; set; }

[JsonProperty(Required = Required.Always)]
public AuthPinLocation Location { get; set; }

[JsonProperty(Required = Required.Always)]
public int ExpiresIn { get; set; }

[JsonProperty(Required = Required.Always)]
public DateTime CreatedAt { get; set; }

[JsonProperty(Required = Required.Always)]
public DateTime ExpiresAt { get; set; }

[JsonProperty(Required = Required.Always)]
public string AuthToken { get; set; }

[JsonProperty(Required = Required.Always)]
public string NewRegistration { get; set; }
}
}
34 changes: 34 additions & 0 deletions src/Application/Common/DTO/PlexApi/AuthPinLocation.cs
@@ -0,0 +1,34 @@
using Newtonsoft.Json;

namespace PlexRipper.Application.Common
{
public class AuthPinLocation
{
[JsonProperty(Required = Required.Always)]
public string Code { get; set; }

[JsonProperty(Required = Required.Always)]
public bool EuropeanUnionMember { get; set; }

[JsonProperty(Required = Required.Always)]
public string ContinentCode { get; set; }

[JsonProperty(Required = Required.Always)]
public string Country { get; set; }

[JsonProperty(Required = Required.Always)]
public string City { get; set; }

[JsonProperty(Required = Required.Always)]
public string TimeZone { get; set; }

[JsonProperty(Required = Required.Always)]
public string PostalCode { get; set; }

[JsonProperty(Required = Required.Always)]
public string Subdivisions { get; set; }

[JsonProperty(Required = Required.Always)]
public string Coordinates { get; set; }
}
}
21 changes: 21 additions & 0 deletions src/Application/Common/DTO/PlexApi/PlexErrorsResponse.cs
@@ -0,0 +1,21 @@
using System.Collections.Generic;
using FluentResults;
using Newtonsoft.Json;

namespace PlexRipper.Application.Common
{
public class PlexErrorsResponse
{
[JsonProperty(Required = Required.Always)]
public List<PlexError> Errors { get; set; }
}

public class PlexError : Error
{
[JsonProperty(Required = Required.Always)]
public int Code { get; set; }

[JsonProperty(Required = Required.Always)]
public int Status { get; set; }
}
}
8 changes: 5 additions & 3 deletions src/Application/Common/Interfaces/PlexApi/IPlexApiService.cs
Expand Up @@ -13,10 +13,8 @@ public interface IPlexApiService
/// <summary>
/// Returns the <see cref="PlexAccount" /> after PlexApi validation.
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns></returns>
Task<Result<PlexAccount>> PlexSignInAsync(string username, string password);
Task<Result<PlexAccount>> PlexSignInAsync(PlexAccount plexAccount);

Task<string> RefreshPlexAuthTokenAsync(PlexAccount account);

Expand Down Expand Up @@ -56,5 +54,9 @@ public interface IPlexApiService

Task<Result<List<PlexTvShowSeason>>> GetAllSeasonsAsync(string serverAuthToken, string plexFullHost,
string plexLibraryKey);

Task<Result<AuthPin>> Get2FAPin(string clientId);

Task<Result<AuthPin>> Check2FAPin(int pinId, string clientId);
}
}
@@ -0,0 +1,6 @@
using PlexRipper.Domain;

namespace PlexRipper.Application.Common
{
public interface IMigrationService : ISetupAsync { }
}
29 changes: 18 additions & 11 deletions src/Application/Common/Interfaces/Services/IPlexAccountService.cs
Expand Up @@ -7,13 +7,7 @@ namespace PlexRipper.Application.Common
{
public interface IPlexAccountService
{
/// <summary>
/// Check if this account is valid by querying the Plex API.
/// </summary>
/// <param name="username"></param>
/// <param name="password"></param>
/// <returns>Are the account credentials valid.</returns>
Task<Result> ValidatePlexAccountAsync(string username, string password);
Task<Result<PlexAccount>> ValidatePlexAccountAsync(PlexAccount plexAccount);

/// <summary>
/// Checks if an <see cref="PlexAccount"/> with the same username already exists.
Expand All @@ -23,7 +17,8 @@ public interface IPlexAccountService
Task<Result<bool>> CheckIfUsernameIsAvailableAsync(string username);

/// <summary>
/// Returns the <see cref="PlexAccount"/> with the corresponding <see cref="PlexAccount"/> and the accessible <see cref="PlexServer"/>s.
/// Returns the <see cref="PlexAccount"/> with the corresponding <see cref="PlexAccount"/>
/// and the accessible <see cref="PlexServer">PlexServers</see>.
/// </summary>
/// <param name="accountId">The Id to retrieve the <see cref="PlexAccount"/> by.</param>
/// <returns>The account found.</returns>
Expand All @@ -45,15 +40,27 @@ public interface IPlexAccountService
/// <returns></returns>
Task<Result> DeletePlexAccountAsync(int plexAccountId);

Task<Result<PlexAccount>> UpdatePlexAccountAsync(PlexAccount plexAccount);

Task<Result<PlexAccount>> UpdatePlexAccountAsync(dynamic plexAccountDto);
Task<Result<PlexAccount>> UpdatePlexAccountAsync(PlexAccount plexAccount, bool inspectServers = false);

/// <summary>
/// Refreshes the <see cref="PlexServer"/> and <see cref="PlexLibrary"/> access of the <see cref="PlexAccount"/>.
/// </summary>
/// <param name="plexAccountId">Can be 0 to refresh all enabled PlexAccounts.</param>
/// <returns>If successful.</returns>
Task<Result> RefreshPlexAccount(int plexAccountId = 0);

/// <summary>
/// This retrieves all the <see cref="PlexAccount"/> related data from the PlexApi.
/// It's assumed that the <see cref="PlexAccount"/> has already been created in the Database.
/// </summary>
/// <param name="plexAccountId">The is of <see cref="PlexAccount"/> to setup.</param>
/// <returns>The list of <see cref="PlexServer">PlexServers</see> which are accessible by this account.</returns>
Task<Result<List<PlexServer>>> SetupAccountAsync(int plexAccountId);

Task<Result<AuthPin>> Get2FAPin(string clientId);

Task<Result<AuthPin>> Check2FAPin(int pinId, string clientId);

string GeneratePlexAccountClientId();
}
}
14 changes: 13 additions & 1 deletion src/Application/Common/Interfaces/Services/IPlexServerService.cs
Expand Up @@ -7,6 +7,12 @@ namespace PlexRipper.Application.Common
{
public interface IPlexServerService
{
/// <summary>
/// Retrieves the latest <see cref="PlexServer"/> data, and the corresponding <see cref="PlexLibrary"/>,
/// from the PlexAPI and stores it in the Database.
/// </summary>
/// <param name="plexAccount">The <see cref="PlexAccount"/> used to retrieve the accessible <see cref="PlexServer">PlexServers</see>.</param>
/// <returns>Is successful.</returns>
Task<Result<List<PlexServer>>> RetrieveAccessiblePlexServersAsync(PlexAccount plexAccount);

Task<Result<PlexServer>> GetServerAsync(int plexServerId);
Expand All @@ -23,7 +29,13 @@ public interface IPlexServerService
/// <returns>The list of <see cref="PlexServer">PlexServers</see>.</returns>
Task<Result<List<PlexServer>>> GetAllPlexServersAsync(bool includeLibraries, int plexAccountId = 0);

Task<Result> InspectPlexServers(int plexAccountId, List<int> plexServerIds);
/// <summary>
/// Will inspect all <see cref="PlexServer">PlexServers</see> added to this <see cref="PlexAccount"/>
/// and checks its connectivity status and which libraries can be accessed.
/// </summary>
/// <param name="plexAccountId"></param>
/// <returns></returns>
Task<Result> InspectPlexServers(int plexAccountId);

Task<Result> SyncPlexServers(bool forceSync = false);

Expand Down
Expand Up @@ -22,7 +22,7 @@ public interface ISignalRService

Task SendNotification(Notification notification);

void SendServerInspectStatusProgress(InspectServerProgress progress);
Task SendServerInspectStatusProgress(InspectServerProgress progress);

void SendServerSyncProgressUpdate(SyncServerProgress syncServerProgress);
}
Expand Down

0 comments on commit 7ac74e9

Please sign in to comment.