Merge remote-tracking branch 'upstream/develop' into newsletter-refactor

This commit is contained in:
Florian Dupret 2022-01-16 18:32:29 +01:00
commit 1d816fe093
52 changed files with 1004 additions and 371 deletions

View file

@ -1,3 +1,47 @@
# [4.10.0](https://github.com/Ombi-app/Ombi/compare/v4.9.2...v4.10.0) (2022-01-14)
### Features
* **notifications:** :sparkles: Send new request email notifications to power users ([#4462](https://github.com/Ombi-app/Ombi/issues/4462)) ([10cc0c0](https://github.com/Ombi-app/Ombi/commit/10cc0c0951f13221179516f8ff5c44dbecc9a0fd))
## [4.9.2](https://github.com/Ombi-app/Ombi/compare/v4.9.1...v4.9.2) (2022-01-14)
### Bug Fixes
* :bug: Add UI for Emby recently added cronjob settings ([#4469](https://github.com/Ombi-app/Ombi/issues/4469)) ([7d47bbe](https://github.com/Ombi-app/Ombi/commit/7d47bbe92204855bf75d70b8fa548f9c3f3612bc))
* **sonarr:** :bug: Fixed an issue where we could attempt to add a series to sonarr before sonarr has got all the metadata [#4459](https://github.com/Ombi-app/Ombi/issues/4459) ([5c691dc](https://github.com/Ombi-app/Ombi/commit/5c691dc98437a4cd24560ff625414fe05dd22f89))
* **wizard:** :bug: Fixed the issue where the Application Url wasn't validated in the wizard ([33b8d11](https://github.com/Ombi-app/Ombi/commit/33b8d1111a1c6663d8c0bbd912be4660da7d013f)), closes [#4417](https://github.com/Ombi-app/Ombi/issues/4417)
## [4.9.1](https://github.com/Ombi-app/Ombi/compare/v4.9.0...v4.9.1) (2022-01-14)
### Bug Fixes
* **discover:** 🌐 Localize episodes names in TV details ([#4467](https://github.com/Ombi-app/Ombi/issues/4467)) [skip ci] ([35806ea](https://github.com/Ombi-app/Ombi/commit/35806ea2d2c866d628cf08577026a02ab04e49d9))
* **email-notifications:** :bug: Fixed the issue where legacy requests were showing broken poster images [#4452](https://github.com/Ombi-app/Ombi/issues/4452) ([0ece2fd](https://github.com/Ombi-app/Ombi/commit/0ece2fd6e0eb01e0b7d4d2a01e1a276c7a9c5a51))
* **emby/jellyfin:** :bug: A more reliable Emby and Jellyfin sync [skip ci] ([ad677fa](https://github.com/Ombi-app/Ombi/commit/ad677fa02eb75633014e9c9791c21ed2d6a23229))
# [4.9.0](https://github.com/Ombi-app/Ombi/compare/v4.8.1...v4.9.0) (2022-01-05)
### Features
* **customization:** :sparkles: Added possibility for custom favicons ([40af659](https://github.com/Ombi-app/Ombi/commit/40af6593b668d4712327c18f92f5b7b5a0a65e26))
## [4.8.1](https://github.com/Ombi-app/Ombi/compare/v4.8.0...v4.8.1) (2022-01-04)
# [4.8.0](https://github.com/Ombi-app/Ombi/compare/v4.7.11...v4.8.0) (2021-12-22) # [4.8.0](https://github.com/Ombi-app/Ombi/compare/v4.7.11...v4.8.0) (2021-12-22)
@ -137,57 +181,3 @@
## [4.6.3](https://github.com/Ombi-app/Ombi/compare/v4.6.2...v4.6.3) (2021-11-11)
### Bug Fixes
* **discover:** :bug: Display TV + movies on actor page in user language ([#4395](https://github.com/Ombi-app/Ombi/issues/4395)) ([fe635c7](https://github.com/Ombi-app/Ombi/commit/fe635c7106bc487ff879bdc8a73bab16cb389b97))
* **permissions:** :bug: Improved the security around the role "Manage Own Requests" ([#4397](https://github.com/Ombi-app/Ombi/issues/4397)) ([334a32b](https://github.com/Ombi-app/Ombi/commit/334a32bca42f90198d9b720d2bdb710a583be47f)), closes [#4391](https://github.com/Ombi-app/Ombi/issues/4391)
* **search:** Fixed some cases where search wouldn't work correctly ([#4398](https://github.com/Ombi-app/Ombi/issues/4398)) ([4410790](https://github.com/Ombi-app/Ombi/commit/4410790bc096826bc11554098f846e3acb59589a))
## [4.6.2](https://github.com/Ombi-app/Ombi/compare/v4.6.1...v4.6.2) (2021-11-10)
### Bug Fixes
* **discover:** TV shows now display on the Actor Pages ([#4388](https://github.com/Ombi-app/Ombi/issues/4388)) ([6b716e7](https://github.com/Ombi-app/Ombi/commit/6b716e722076e3d1e6bf2097c5263645d5ea9edf))
## [4.6.1](https://github.com/Ombi-app/Ombi/compare/v4.6.0...v4.6.1) (2021-11-10)
### Bug Fixes
* :bug: Fixed the MySQL issue after .net 6 upgrade [#4393](https://github.com/Ombi-app/Ombi/issues/4393) ([fea7ff0](https://github.com/Ombi-app/Ombi/commit/fea7ff05139e9ff50c8097fa5389b4ef9ad21a15))
* **translations:** 🌐 New translations from Crowdin [skip ci] ([c6acb45](https://github.com/Ombi-app/Ombi/commit/c6acb45f8d3f371c0b4024c4272849d0d0cc867f))
* **translations:** 🌐 New translations from Crowdin [skip ci] ([18c220a](https://github.com/Ombi-app/Ombi/commit/18c220a0cd0d19e45a07d0c319da2b9512778a8a))
# [4.6.0](https://github.com/Ombi-app/Ombi/compare/v4.4.0...v4.6.0) (2021-11-09)
### Features
* :sparkles: Upgrade Ombi to .NET 6 ([#4390](https://github.com/Ombi-app/Ombi/issues/4390)) ([719eb7d](https://github.com/Ombi-app/Ombi/commit/719eb7dbe37b3a72d264e2f670067518eef70694)), closes [#4392](https://github.com/Ombi-app/Ombi/issues/4392)
# [4.4.0](https://github.com/Ombi-app/Ombi/compare/v4.3.2...v4.4.0) (2021-11-06)
### Bug Fixes
* **request-list:** :bug: Fixed an issue where the request options were not appearing for Music requests ([c0406a2](https://github.com/Ombi-app/Ombi/commit/c0406a2ddebafb03d98ed25cdf7d89dc9a600c7d))
### Features
* **mass-email:** :sparkles: Added the ability to configure the Mass Email, we can now send BCC and we are less likely to be rate limited when not using bcc [#4377](https://github.com/Ombi-app/Ombi/issues/4377) ([ca655ae](https://github.com/Ombi-app/Ombi/commit/ca655ae57042dec44106a2f2ef5ba2e6f1019ee4))

View file

@ -135,13 +135,6 @@ Here are some of the features Ombi has:
<sub><b>Matt Jeanes</b></sub> <sub><b>Matt Jeanes</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/bernarden">
<img src="https://avatars.githubusercontent.com/u/12289537?v=4" width="50;" alt="bernarden"/>
<br />
<sub><b>Victor Usoltsev</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/sephrat"> <a href="https://github.com/sephrat">
<img src="https://avatars.githubusercontent.com/u/34862846?v=4" width="50;" alt="sephrat"/> <img src="https://avatars.githubusercontent.com/u/34862846?v=4" width="50;" alt="sephrat"/>
@ -149,6 +142,13 @@ Here are some of the features Ombi has:
<sub><b>Sephrat</b></sub> <sub><b>Sephrat</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/bernarden">
<img src="https://avatars.githubusercontent.com/u/12289537?v=4" width="50;" alt="bernarden"/>
<br />
<sub><b>Victor Usoltsev</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/dhruvb14"> <a href="https://github.com/dhruvb14">
<img src="https://avatars.githubusercontent.com/u/4459649?v=4" width="50;" alt="dhruvb14"/> <img src="https://avatars.githubusercontent.com/u/4459649?v=4" width="50;" alt="dhruvb14"/>
@ -730,6 +730,13 @@ Here are some of the features Ombi has:
<sub><b>M4tta</b></sub> <sub><b>M4tta</b></sub>
</a> </a>
</td> </td>
<td align="center">
<a href="https://github.com/maartenheebink">
<img src="https://avatars.githubusercontent.com/u/28894544?v=4" width="50;" alt="maartenheebink"/>
<br />
<sub><b>Maartenheebink</b></sub>
</a>
</td>
<td align="center"> <td align="center">
<a href="https://github.com/masterhuck"> <a href="https://github.com/masterhuck">
<img src="https://avatars.githubusercontent.com/u/4671442?v=4" width="50;" alt="masterhuck"/> <img src="https://avatars.githubusercontent.com/u/4671442?v=4" width="50;" alt="masterhuck"/>
@ -750,15 +757,15 @@ Here are some of the features Ombi has:
<br /> <br />
<sub><b>Tdorsey</b></sub> <sub><b>Tdorsey</b></sub>
</a> </a>
</td> </td></tr>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/thegame3202"> <a href="https://github.com/thegame3202">
<img src="https://avatars.githubusercontent.com/u/22148848?v=4" width="50;" alt="thegame3202"/> <img src="https://avatars.githubusercontent.com/u/22148848?v=4" width="50;" alt="thegame3202"/>
<br /> <br />
<sub><b>Mike</b></sub> <sub><b>Mike</b></sub>
</a> </a>
</td></tr> </td>
<tr>
<td align="center"> <td align="center">
<a href="https://github.com/zobe123"> <a href="https://github.com/zobe123">
<img src="https://avatars.githubusercontent.com/u/13840542?v=4" width="50;" alt="zobe123"/> <img src="https://avatars.githubusercontent.com/u/13840542?v=4" width="50;" alt="zobe123"/>

View file

@ -89,7 +89,7 @@ namespace Ombi.Core.Engine.V2
foreach (var tvSeason in show.seasons.Where(x => x.season_number != 0)) // skip the first season foreach (var tvSeason in show.seasons.Where(x => x.season_number != 0)) // skip the first season
{ {
var seasonEpisodes = (await _movieApi.GetSeasonEpisodes(show.id, tvSeason.season_number, token)); var seasonEpisodes = (await _movieApi.GetSeasonEpisodes(show.id, tvSeason.season_number, token, langCode));
MapSeasons(mapped.SeasonRequests, tvSeason, seasonEpisodes); MapSeasons(mapped.SeasonRequests, tvSeason, seasonEpisodes);
} }

View file

@ -309,6 +309,22 @@ namespace Ombi.Core.Senders
private async Task SendToSonarr(ChildRequests model, SonarrSeries result, SonarrSettings s) private async Task SendToSonarr(ChildRequests model, SonarrSeries result, SonarrSettings s)
{ {
// Check to ensure we have the all the seasons, ensure the Sonarr metadata has grabbed all the data
Season existingSeason = null;
foreach (var season in model.SeasonRequests)
{
var attempt = 0;
existingSeason = result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber);
while (existingSeason == null && attempt < 5)
{
attempt++;
Logger.LogInformation("There was no season numer {0} in Sonarr for title {1}. Will try again as the metadata did not get created", season.SeasonNumber, model.ParentRequest.Title);
result = await SonarrApi.GetSeriesById(result.id, s.ApiKey, s.FullUri);
existingSeason = result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber);
await Task.Delay(500);
}
}
var episodesToUpdate = new List<Episode>(); var episodesToUpdate = new List<Episode>();
// Ok, now let's sort out the episodes. // Ok, now let's sort out the episodes.
@ -327,24 +343,20 @@ namespace Ombi.Core.Senders
await Task.Delay(500); await Task.Delay(500);
} }
var seriesChanges = false;
foreach (var req in model.SeasonRequests) foreach (var season in model.SeasonRequests)
{ {
foreach (var ep in req.Episodes) foreach (var ep in season.Episodes)
{ {
var sonarrEp = sonarrEpList.FirstOrDefault(x => var sonarrEp = sonarrEpList.FirstOrDefault(x =>
x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == req.SeasonNumber); x.episodeNumber == ep.EpisodeNumber && x.seasonNumber == season.SeasonNumber);
if (sonarrEp != null && !sonarrEp.monitored) if (sonarrEp != null && !sonarrEp.monitored)
{ {
sonarrEp.monitored = true; sonarrEp.monitored = true;
episodesToUpdate.Add(sonarrEp); episodesToUpdate.Add(sonarrEp);
} }
} }
}
var seriesChanges = false;
foreach (var season in model.SeasonRequests)
{
var sonarrEpisodeList = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber).ToList(); var sonarrEpisodeList = sonarrEpList.Where(x => x.seasonNumber == season.SeasonNumber).ToList();
var sonarrEpCount = sonarrEpisodeList.Count; var sonarrEpCount = sonarrEpisodeList.Count;
var ourRequestCount = season.Episodes.Count; var ourRequestCount = season.Episodes.Count;
@ -358,14 +370,6 @@ namespace Ombi.Core.Senders
//var distinctEpisodes = ourEpisodes.Distinct().ToList(); //var distinctEpisodes = ourEpisodes.Distinct().ToList();
//var missingEpisodes = Enumerable.Range(distinctEpisodes.Min(), distinctEpisodes.Count).Except(distinctEpisodes); //var missingEpisodes = Enumerable.Range(distinctEpisodes.Min(), distinctEpisodes.Count).Except(distinctEpisodes);
var existingSeason =
result.seasons.FirstOrDefault(x => x.seasonNumber == season.SeasonNumber);
if (existingSeason == null)
{
Logger.LogError("There was no season numer {0} in Sonarr for title {1}", season.SeasonNumber, model.ParentRequest.Title);
continue;
}
if (sonarrEpCount == ourRequestCount /*|| !missingEpisodes.Any()*/) if (sonarrEpCount == ourRequestCount /*|| !missingEpisodes.Any()*/)
{ {

View file

@ -2,6 +2,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ombi.Api.Discord; using Ombi.Api.Discord;
using Ombi.Api.Discord.Models; using Ombi.Api.Discord.Models;
@ -21,8 +22,8 @@ namespace Ombi.Notifications.Agents
public DiscordNotification(IDiscordApi api, ISettingsService<DiscordNotificationSettings> sn, public DiscordNotification(IDiscordApi api, ISettingsService<DiscordNotificationSettings> sn,
ILogger<DiscordNotification> log, INotificationTemplatesRepository r, ILogger<DiscordNotification> log, INotificationTemplatesRepository r,
IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music, IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um)
: base(sn, r, m, t, s, log, sub, music, userPref) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

View file

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MailKit.Net.Smtp; using MailKit.Net.Smtp;
@ -22,7 +23,7 @@ namespace Ombi.Notifications.Agents
{ {
public EmailNotification(ISettingsService<EmailNotificationSettings> settings, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, IEmailProvider prov, ISettingsService<CustomizationSettings> c, public EmailNotification(ISettingsService<EmailNotificationSettings> settings, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, IEmailProvider prov, ISettingsService<CustomizationSettings> c,
ILogger<EmailNotification> log, UserManager<OmbiUser> um, IRepository<RequestSubscription> sub, IMusicRequestRepository music, ILogger<EmailNotification> log, UserManager<OmbiUser> um, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(settings, r, m, t, c, log, sub, music, userPref) IRepository<UserNotificationPreferences> userPref) : base(settings, r, m, t, c, log, sub, music, userPref, um)
{ {
EmailProvider = prov; EmailProvider = prov;
Logger = log; Logger = log;
@ -114,8 +115,16 @@ namespace Ombi.Notifications.Agents
var plaintext = await LoadPlainTextMessage(NotificationType.NewRequest, model, settings); var plaintext = await LoadPlainTextMessage(NotificationType.NewRequest, model, settings);
message.Other.Add("PlainTextBody", plaintext); message.Other.Add("PlainTextBody", plaintext);
foreach (var recipient in (await GetPrivilegedUsers()).DistinctBy(x => x.Email))
{
if (recipient.Email.IsNullOrEmpty())
{
continue;
}
message.To = recipient.Email;
await Send(message, settings); await Send(message, settings);
} }
}
protected override async Task NewIssue(NotificationOptions model, EmailNotificationSettings settings) protected override async Task NewIssue(NotificationOptions model, EmailNotificationSettings settings)
{ {

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ombi.Api.Gotify; using Ombi.Api.Gotify;
using Ombi.Core.Settings; using Ombi.Core.Settings;
@ -17,7 +18,7 @@ namespace Ombi.Notifications.Agents
{ {
public GotifyNotification(IGotifyApi api, ISettingsService<GotifySettings> sn, ILogger<GotifyNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, public GotifyNotification(IGotifyApi api, ISettingsService<GotifySettings> sn, ILogger<GotifyNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music, ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref) IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

View file

@ -23,7 +23,7 @@ namespace Ombi.Notifications.Agents
public LegacyMobileNotification(IOneSignalApi api, ISettingsService<MobileNotificationSettings> sn, ILogger<LegacyMobileNotification> log, INotificationTemplatesRepository r, public LegacyMobileNotification(IOneSignalApi api, ISettingsService<MobileNotificationSettings> sn, ILogger<LegacyMobileNotification> log, INotificationTemplatesRepository r,
IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<NotificationUserId> notification, IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<NotificationUserId> notification,
UserManager<OmbiUser> um, IRepository<RequestSubscription> sub, IMusicRequestRepository music, IRepository<Issues> issueRepository, UserManager<OmbiUser> um, IRepository<RequestSubscription> sub, IMusicRequestRepository music, IRepository<Issues> issueRepository,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref) IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{ {
_api = api; _api = api;
_logger = log; _logger = log;
@ -59,7 +59,7 @@ namespace Ombi.Notifications.Agents
}; };
// Get admin devices // Get admin devices
var playerIds = await GetAdmins(NotificationType.NewRequest); var playerIds = await GetPrivilegedUsersPlayerIds();
await Send(playerIds, notification, settings, model, true); await Send(playerIds, notification, settings, model, true);
} }
@ -77,7 +77,7 @@ namespace Ombi.Notifications.Agents
}; };
// Get admin devices // Get admin devices
var playerIds = await GetAdmins(NotificationType.Issue); var playerIds = await GetAdmins();
await Send(playerIds, notification, settings, model); await Send(playerIds, notification, settings, model);
} }
@ -106,7 +106,7 @@ namespace Ombi.Notifications.Agents
else else
{ {
// Send to admin // Send to admin
var playerIds = await GetAdmins(NotificationType.IssueComment); var playerIds = await GetAdmins();
await Send(playerIds, notification, settings, model); await Send(playerIds, notification, settings, model);
} }
} }
@ -147,7 +147,7 @@ namespace Ombi.Notifications.Agents
}; };
// Get admin devices // Get admin devices
var playerIds = await GetAdmins(NotificationType.Test); var playerIds = await GetAdmins();
await Send(playerIds, notification, settings, model); await Send(playerIds, notification, settings, model);
} }
@ -241,15 +241,25 @@ namespace Ombi.Notifications.Agents
await Send(playerIds, notification, settings, model); await Send(playerIds, notification, settings, model);
} }
private async Task<List<string>> GetAdmins(NotificationType type) private async Task<List<string>> GetAdmins()
{ {
var adminUsers = (await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin)).Select(x => x.Id).ToList(); return await GetNotificationRecipients(await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin));
}
private async Task<List<string>> GetPrivilegedUsersPlayerIds()
{
return await GetNotificationRecipients(await GetPrivilegedUsers());
}
private async Task<List<string>> GetNotificationRecipients(IEnumerable<OmbiUser> users)
{
var adminUsers = users.Select(x => x.Id).ToList();
var notificationUsers = _notifications.GetAll().Include(x => x.User).Where(x => adminUsers.Contains(x.UserId)); var notificationUsers = _notifications.GetAll().Include(x => x.User).Where(x => adminUsers.Contains(x.UserId));
var playerIds = await notificationUsers.Select(x => x.PlayerId).ToListAsync(); var playerIds = await notificationUsers.Select(x => x.PlayerId).ToListAsync();
if (!playerIds.Any()) if (!playerIds.Any())
{ {
_logger.LogInformation( _logger.LogInformation(
$"there are no admins to send a notification for {type}, for agent {NotificationAgent.Mobile}"); $"there are no users to send a notification for agent {NotificationAgent.Mobile}");
return null; return null;
} }
return playerIds; return playerIds;

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ombi.Api.Mattermost; using Ombi.Api.Mattermost;
using Ombi.Api.Mattermost.Models; using Ombi.Api.Mattermost.Models;
@ -19,7 +20,7 @@ namespace Ombi.Notifications.Agents
{ {
public MattermostNotification(IMattermostApi api, ISettingsService<MattermostNotificationSettings> sn, ILogger<MattermostNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, public MattermostNotification(IMattermostApi api, ISettingsService<MattermostNotificationSettings> sn, ILogger<MattermostNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music, ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref) IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

View file

@ -23,7 +23,7 @@ namespace Ombi.Notifications.Agents
public MobileNotification(ICloudMobileNotification api, ISettingsService<MobileNotificationSettings> sn, ILogger<MobileNotification> log, INotificationTemplatesRepository r, public MobileNotification(ICloudMobileNotification api, ISettingsService<MobileNotificationSettings> sn, ILogger<MobileNotification> log, INotificationTemplatesRepository r,
IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<MobileDevices> notification, IMovieRequestRepository m, ITvRequestRepository t, ISettingsService<CustomizationSettings> s, IRepository<MobileDevices> notification,
UserManager<OmbiUser> um, IRepository<RequestSubscription> sub, IMusicRequestRepository music, IRepository<Issues> issueRepository, UserManager<OmbiUser> um, IRepository<RequestSubscription> sub, IMusicRequestRepository music, IRepository<Issues> issueRepository,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref) IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{ {
_api = api; _api = api;
_logger = log; _logger = log;
@ -62,7 +62,7 @@ namespace Ombi.Notifications.Agents
}; };
// Get admin devices // Get admin devices
var playerIds = await GetAdmins(NotificationType.NewRequest); var playerIds = await GetPrivilegedUsersPlayerIds();
await Send(playerIds, notification, settings, model, true); await Send(playerIds, notification, settings, model, true);
} }
@ -82,7 +82,7 @@ namespace Ombi.Notifications.Agents
}; };
// Get admin devices // Get admin devices
var playerIds = await GetAdmins(NotificationType.Issue); var playerIds = await GetAdmins();
await Send(playerIds, notification, settings, model); await Send(playerIds, notification, settings, model);
} }
@ -112,7 +112,7 @@ namespace Ombi.Notifications.Agents
else else
{ {
// Send to admin // Send to admin
var playerIds = await GetAdmins(NotificationType.IssueComment); var playerIds = await GetAdmins();
await Send(playerIds, notification, settings, model); await Send(playerIds, notification, settings, model);
} }
} }
@ -157,7 +157,7 @@ namespace Ombi.Notifications.Agents
}; };
// Get admin devices // Get admin devices
var playerIds = await GetAdmins(NotificationType.Test); var playerIds = await GetAdmins();
await Send(playerIds, notification, settings, model); await Send(playerIds, notification, settings, model);
} }
@ -279,15 +279,25 @@ namespace Ombi.Notifications.Agents
await Send(playerIds, notification, settings, model); await Send(playerIds, notification, settings, model);
} }
private async Task<List<string>> GetAdmins(NotificationType type) private async Task<List<string>> GetAdmins()
{ {
var adminUsers = (await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin)).Select(x => x.Id).ToList(); return await GetNotificationRecipients(await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin));
}
private async Task<List<string>> GetPrivilegedUsersPlayerIds()
{
return await GetNotificationRecipients(await GetPrivilegedUsers());
}
private async Task<List<string>> GetNotificationRecipients(IEnumerable<OmbiUser> users)
{
var adminUsers = users.Select(x => x.Id).ToList();
var notificationUsers = _notifications.GetAll().Include(x => x.User).Where(x => adminUsers.Contains(x.UserId)); var notificationUsers = _notifications.GetAll().Include(x => x.User).Where(x => adminUsers.Contains(x.UserId));
var playerIds = await notificationUsers.Select(x => x.Token).ToListAsync(); var playerIds = await notificationUsers.Select(x => x.Token).ToListAsync();
if (!playerIds.Any()) if (!playerIds.Any())
{ {
_logger.LogInformation( _logger.LogInformation(
$"there are no admins to send a notification for {type}, for agent {NotificationAgent.Mobile}"); $"there are no users to send a notification for agent {NotificationAgent.Mobile}");
return null; return null;
} }
return playerIds; return playerIds;
@ -377,7 +387,7 @@ namespace Ombi.Notifications.Agents
}; };
// Get admin devices // Get admin devices
var playerIds = await GetAdmins(NotificationType.PartiallyAvailable); var playerIds = await GetAdmins();
await Send(playerIds, notification, settings, model, true); await Send(playerIds, notification, settings, model, true);
} }
} }

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ombi.Api.Pushbullet; using Ombi.Api.Pushbullet;
using Ombi.Core.Settings; using Ombi.Core.Settings;
@ -17,7 +18,7 @@ namespace Ombi.Notifications.Agents
{ {
public PushbulletNotification(IPushbulletApi api, ISettingsService<PushbulletSettings> sn, ILogger<PushbulletNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, public PushbulletNotification(IPushbulletApi api, ISettingsService<PushbulletSettings> sn, ILogger<PushbulletNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music, ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref) IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ombi.Api.Pushbullet; using Ombi.Api.Pushbullet;
using Ombi.Api.Pushover; using Ombi.Api.Pushover;
@ -18,7 +19,7 @@ namespace Ombi.Notifications.Agents
{ {
public PushoverNotification(IPushoverApi api, ISettingsService<PushoverSettings> sn, ILogger<PushoverNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, public PushoverNotification(IPushoverApi api, ISettingsService<PushoverSettings> sn, ILogger<PushoverNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music, ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref) IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

View file

@ -1,5 +1,6 @@
using System; using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ombi.Api.Slack; using Ombi.Api.Slack;
using Ombi.Api.Slack.Models; using Ombi.Api.Slack.Models;
@ -18,7 +19,7 @@ namespace Ombi.Notifications.Agents
{ {
public SlackNotification(ISlackApi api, ISettingsService<SlackNotificationSettings> sn, ILogger<SlackNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, public SlackNotification(ISlackApi api, ISettingsService<SlackNotificationSettings> sn, ILogger<SlackNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music, ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref) IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

View file

@ -10,6 +10,7 @@ using Ombi.Store.Entities;
using Ombi.Store.Repository; using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests; using Ombi.Store.Repository.Requests;
using Ombi.Api.Telegram; using Ombi.Api.Telegram;
using Microsoft.AspNetCore.Identity;
namespace Ombi.Notifications.Agents namespace Ombi.Notifications.Agents
{ {
@ -19,7 +20,7 @@ namespace Ombi.Notifications.Agents
INotificationTemplatesRepository r, IMovieRequestRepository m, INotificationTemplatesRepository r, IMovieRequestRepository m,
ITvRequestRepository t, ISettingsService<CustomizationSettings> s ITvRequestRepository t, ISettingsService<CustomizationSettings> s
, IRepository<RequestSubscription> sub, IMusicRequestRepository music, , IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t,s,log, sub, music, userPref) IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t,s,log, sub, music, userPref, um)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

View file

@ -1,6 +1,7 @@
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ombi.Api.Webhook; using Ombi.Api.Webhook;
using Ombi.Core.Settings; using Ombi.Core.Settings;
@ -18,7 +19,7 @@ namespace Ombi.Notifications.Agents
{ {
public WebhookNotification(IWebhookApi api, ISettingsService<WebhookSettings> sn, ILogger<WebhookNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t, public WebhookNotification(IWebhookApi api, ISettingsService<WebhookSettings> sn, ILogger<WebhookNotification> log, INotificationTemplatesRepository r, IMovieRequestRepository m, ITvRequestRepository t,
ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music, ISettingsService<CustomizationSettings> s, IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t, s, log, sub, music, userPref) IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t, s, log, sub, music, userPref, um)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

View file

@ -10,6 +10,7 @@ using Ombi.Store.Entities;
using Ombi.Store.Repository; using Ombi.Store.Repository;
using Ombi.Store.Repository.Requests; using Ombi.Store.Repository.Requests;
using Ombi.Api.Twilio; using Ombi.Api.Twilio;
using Microsoft.AspNetCore.Identity;
namespace Ombi.Notifications.Agents namespace Ombi.Notifications.Agents
{ {
@ -19,7 +20,7 @@ namespace Ombi.Notifications.Agents
INotificationTemplatesRepository r, IMovieRequestRepository m, INotificationTemplatesRepository r, IMovieRequestRepository m,
ITvRequestRepository t, ISettingsService<CustomizationSettings> s ITvRequestRepository t, ISettingsService<CustomizationSettings> s
, IRepository<RequestSubscription> sub, IMusicRequestRepository music, , IRepository<RequestSubscription> sub, IMusicRequestRepository music,
IRepository<UserNotificationPreferences> userPref) : base(sn, r, m, t,s,log, sub, music, userPref) IRepository<UserNotificationPreferences> userPref, UserManager<OmbiUser> um) : base(sn, r, m, t,s,log, sub, music, userPref, um)
{ {
Api = api; Api = api;
Logger = log; Logger = log;

View file

@ -1,6 +1,8 @@
using System; using System;
using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Ombi.Core.Settings; using Ombi.Core.Settings;
@ -19,7 +21,7 @@ namespace Ombi.Notifications
{ {
protected BaseNotification(ISettingsService<T> settings, INotificationTemplatesRepository templateRepo, IMovieRequestRepository movie, ITvRequestRepository tv, protected BaseNotification(ISettingsService<T> settings, INotificationTemplatesRepository templateRepo, IMovieRequestRepository movie, ITvRequestRepository tv,
ISettingsService<CustomizationSettings> customization, ILogger<BaseNotification<T>> log, IRepository<RequestSubscription> sub, IMusicRequestRepository album, ISettingsService<CustomizationSettings> customization, ILogger<BaseNotification<T>> log, IRepository<RequestSubscription> sub, IMusicRequestRepository album,
IRepository<UserNotificationPreferences> notificationUserPreferences) IRepository<UserNotificationPreferences> notificationUserPreferences, UserManager<OmbiUser> um)
{ {
Settings = settings; Settings = settings;
TemplateRepository = templateRepo; TemplateRepository = templateRepo;
@ -30,6 +32,7 @@ namespace Ombi.Notifications
_log = log; _log = log;
AlbumRepository = album; AlbumRepository = album;
UserNotificationPreferences = notificationUserPreferences; UserNotificationPreferences = notificationUserPreferences;
_userManager = um;
Settings.ClearCache(); Settings.ClearCache();
} }
@ -43,6 +46,7 @@ namespace Ombi.Notifications
protected IRepository<UserNotificationPreferences> UserNotificationPreferences { get; set; } protected IRepository<UserNotificationPreferences> UserNotificationPreferences { get; set; }
private ISettingsService<CustomizationSettings> CustomizationSettings { get; } private ISettingsService<CustomizationSettings> CustomizationSettings { get; }
private readonly ILogger<BaseNotification<T>> _log; private readonly ILogger<BaseNotification<T>> _log;
private readonly UserManager<OmbiUser> _userManager;
protected ChildRequests TvRequest { get; set; } protected ChildRequests TvRequest { get; set; }
@ -210,6 +214,13 @@ namespace Ombi.Notifications
.FirstOrDefault(x => x.Agent == agent && x.UserId == userId); .FirstOrDefault(x => x.Agent == agent && x.UserId == userId);
} }
protected async Task<IEnumerable<OmbiUser>> GetPrivilegedUsers()
{
IEnumerable<OmbiUser> recipients = await _userManager.GetUsersInRoleAsync(OmbiRoles.Admin);
recipients = recipients.Concat(await _userManager.GetUsersInRoleAsync(OmbiRoles.PowerUser));
return recipients;
}
private NotificationMessageContent Parse(NotificationOptions model, NotificationTemplates template, NotificationAgent agent) private NotificationMessageContent Parse(NotificationOptions model, NotificationTemplates template, NotificationAgent agent)
{ {
var resolver = new NotificationMessageResolver(); var resolver = new NotificationMessageResolver();

View file

@ -43,7 +43,17 @@ namespace Ombi.Notifications
var img = req?.PosterPath ?? string.Empty; var img = req?.PosterPath ?? string.Empty;
if (img.HasValue()) if (img.HasValue())
{ {
PosterImage = $"https://image.tmdb.org/t/p/w300/{req?.PosterPath?.TrimStart('/') ?? string.Empty}"; if (img.StartsWith("http"))
{
// This means it's a legacy request from when we used TvMaze as a provider.
// The poster url is the fully qualified address, so just use it
PosterImage = img;
}
else
{
PosterImage =
$"https://image.tmdb.org/t/p/w300/{img?.TrimStart('/') ?? string.Empty}";
}
} }
CalculateRequestStatus(req); CalculateRequestStatus(req);
} }
@ -60,9 +70,18 @@ namespace Ombi.Notifications
AdditionalInformation = opts.AdditionalInformation; AdditionalInformation = opts.AdditionalInformation;
var img = req?.ParentRequest?.PosterPath ?? string.Empty; var img = req?.ParentRequest?.PosterPath ?? string.Empty;
if (img.HasValue()) if (img.HasValue())
{
if (img.StartsWith("http"))
{
// This means it's a legacy request from when we used TvMaze as a provider.
// The poster url is the fully qualified address, so just use it
PosterImage = img;
}
else
{ {
PosterImage = PosterImage =
$"https://image.tmdb.org/t/p/w300/{req?.ParentRequest?.PosterPath?.TrimStart('/') ?? string.Empty}"; $"https://image.tmdb.org/t/p/w300/{img?.TrimStart('/') ?? string.Empty}";
}
} }
// Generate episode list. // Generate episode list.

View file

@ -143,7 +143,7 @@ namespace Ombi.Schedule.Jobs.Emby
tv = await Api.GetAllShows(server.ApiKey, parentId, 0, AmountToTake, server.AdministratorId, server.FullUri); tv = await Api.GetAllShows(server.ApiKey, parentId, 0, AmountToTake, server.AdministratorId, server.FullUri);
} }
var totalTv = tv.TotalRecordCount; var totalTv = tv.TotalRecordCount;
var processed = 1; var processed = 0;
while (processed < totalTv) while (processed < totalTv)
{ {
foreach (var tvShow in tv.Items) foreach (var tvShow in tv.Items)
@ -208,7 +208,7 @@ namespace Ombi.Schedule.Jobs.Emby
movies = await Api.GetAllMovies(server.ApiKey, parentId, 0, AmountToTake, server.AdministratorId, server.FullUri); movies = await Api.GetAllMovies(server.ApiKey, parentId, 0, AmountToTake, server.AdministratorId, server.FullUri);
} }
var totalCount = movies.TotalRecordCount; var totalCount = movies.TotalRecordCount;
var processed = 1; var processed = 0;
var mediaToAdd = new HashSet<EmbyContent>(); var mediaToAdd = new HashSet<EmbyContent>();
while (processed < totalCount) while (processed < totalCount)
{ {

View file

@ -122,7 +122,7 @@ namespace Ombi.Schedule.Jobs.Emby
allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, 0, AmountToTake, server.AdministratorId, server.FullUri); allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, 0, AmountToTake, server.AdministratorId, server.FullUri);
} }
var total = allEpisodes.TotalRecordCount; var total = allEpisodes.TotalRecordCount;
var processed = 1; var processed = 0;
var epToAdd = new HashSet<EmbyEpisode>(); var epToAdd = new HashSet<EmbyEpisode>();
while (processed < total) while (processed < total)
{ {

View file

@ -118,7 +118,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
var mediaToAdd = new HashSet<JellyfinContent>(); var mediaToAdd = new HashSet<JellyfinContent>();
var tv = await Api.GetAllShows(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri); var tv = await Api.GetAllShows(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri);
var totalTv = tv.TotalRecordCount; var totalTv = tv.TotalRecordCount;
var processed = 1; var processed = 0;
while (processed < totalTv) while (processed < totalTv)
{ {
foreach (var tvShow in tv.Items) foreach (var tvShow in tv.Items)
@ -178,7 +178,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
{ {
var movies = await Api.GetAllMovies(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri); var movies = await Api.GetAllMovies(server.ApiKey, parentId, 0, 200, server.AdministratorId, server.FullUri);
var totalCount = movies.TotalRecordCount; var totalCount = movies.TotalRecordCount;
var processed = 1; var processed = 0;
var mediaToAdd = new HashSet<JellyfinContent>(); var mediaToAdd = new HashSet<JellyfinContent>();
while (processed < totalCount) while (processed < totalCount)
{ {

View file

@ -98,7 +98,7 @@ namespace Ombi.Schedule.Jobs.Jellyfin
{ {
var allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, 0, 200, server.AdministratorId, server.FullUri); var allEpisodes = await Api.GetAllEpisodes(server.ApiKey, parentIdFilter, 0, 200, server.AdministratorId, server.FullUri);
var total = allEpisodes.TotalRecordCount; var total = allEpisodes.TotalRecordCount;
var processed = 1; var processed = 0;
var epToAdd = new HashSet<JellyfinEpisode>(); var epToAdd = new HashSet<JellyfinEpisode>();
while (processed < total) while (processed < total)
{ {

View file

@ -12,6 +12,7 @@
public bool RecentlyAddedPage { get; set; } public bool RecentlyAddedPage { get; set; }
public bool UseCustomPage { get; set; } public bool UseCustomPage { get; set; }
public bool HideAvailableFromDiscover { get; set; } public bool HideAvailableFromDiscover { get; set; }
public string Favicon { get; set; }
public string AddToUrl(string part) public string AddToUrl(string part)
{ {

View file

@ -1,4 +1,4 @@
import { OverlayContainer } from '@angular/cdk/overlay'; import { OverlayContainer } from '@angular/cdk/overlay';
import { Component, OnInit, HostBinding, Inject } from "@angular/core"; import { Component, OnInit, HostBinding, Inject } from "@angular/core";
import { NavigationStart, Router } from "@angular/router"; import { NavigationStart, Router } from "@angular/router";
@ -34,6 +34,7 @@ export class AppComponent implements OnInit {
public userName: string; public userName: string;
public userEmail: string; public userEmail: string;
public accessToken: string; public accessToken: string;
public favicon: string;
private hubConnected: boolean; private hubConnected: boolean;
@ -91,6 +92,12 @@ export class AppComponent implements OnInit {
this.applicationName = this.customizationSettings.applicationName; this.applicationName = this.customizationSettings.applicationName;
this.document.getElementsByTagName('title')[0].innerText = this.applicationName; this.document.getElementsByTagName('title')[0].innerText = this.applicationName;
} }
if (this.customizationSettings && this.customizationSettings.favicon) {
this.favicon = this.customizationSettings.favicon;
this.document.getElementById('favicon').setAttribute('href', this.favicon);
}
if (this.customizationSettings && this.customizationSettings.customCss) { if (this.customizationSettings && this.customizationSettings.customCss) {
var dom = this.document.getElementsByTagName('head')[0]; var dom = this.document.getElementsByTagName('head')[0];
var css = document.createElement("style"); var css = document.createElement("style");

View file

@ -1,4 +1,4 @@
import { ISettings } from "./ICommon"; import { ISettings } from "./ICommon";
import { RequestLimitType } from "."; import { RequestLimitType } from ".";
export interface IExternalSettings extends ISettings { export interface IExternalSettings extends ISettings {
@ -193,6 +193,7 @@ export interface ICustomizationSettings extends ISettings {
recentlyAddedPage: boolean; recentlyAddedPage: boolean;
useCustomPage: boolean; useCustomPage: boolean;
hideAvailableFromDiscover: boolean; hideAvailableFromDiscover: boolean;
favicon: string;
} }
export interface IJobSettings { export interface IJobSettings {

View file

@ -34,6 +34,15 @@
</div> </div>
</div> </div>
</div> </div>
<mat-hint>
The favicon url should be an externally accesible URL. Leave blank for default.
</mat-hint>
<div class="md-form-field">
<mat-form-field appearance="outline">
<mat-label>Custom Favicon</mat-label>
<input matInput type="url" [(ngModel)]="settings.favicon">
</mat-form-field>
</div>
<div class="md-form-field"> <div class="md-form-field">
<mat-slide-toggle [(ngModel)]="settings.hideAvailableFromDiscover" matTooltip="Any media content that is available will now be hidden on the discover page, the user still can search for it"> <mat-slide-toggle [(ngModel)]="settings.hideAvailableFromDiscover" matTooltip="Any media content that is available will now be hidden on the discover page, the user still can search for it">
Hide Available Content On The Discover Page Hide Available Content On The Discover Page

View file

@ -86,6 +86,14 @@
<button mat-raised-button type="button" class="btn btn-sm btn-primary-outline cronbtn" (click)="testCron(form.get('embyContentSync')?.value)">Test</button> <button mat-raised-button type="button" class="btn btn-sm btn-primary-outline cronbtn" (click)="testCron(form.get('embyContentSync')?.value)">Test</button>
</div> </div>
<div class="form-group cronBox">
<mat-form-field appearance="outline" floatLabel=always>
<mat-label for="embyRecentlyAddedSync" class="control-mat-label">Emby Recently Added Sync</mat-label>
<input type="text" matInput [ngClass]="{'form-error': form.get('embyRecentlyAddedSync').hasError('required')}" id="embyRecentlyAddedSync" name="embyRecentlyAddedSync" formControlName="embyRecentlyAddedSync">
<small *ngIf="form.get('embyRecentlyAddedSync').hasError('required')" class="error-text">The Emby Recently Added Sync is required</small></mat-form-field>
<button mat-raised-button type="button" class="btn btn-sm btn-primary-outline cronbtn" (click)="testCron(form.get('embyRecentlyAddedSync')?.value)">Test</button>
</div>
<div class="form-group cronBox"> <div class="form-group cronBox">
<mat-form-field appearance="outline" floatLabel=always> <mat-form-field appearance="outline" floatLabel=always>
<mat-label for="jellyfinContentSync" class="control-label">Jellyfin Sync</mat-label> <mat-label for="jellyfinContentSync" class="control-label">Jellyfin Sync</mat-label>

View file

@ -36,7 +36,7 @@ export class JobsComponent implements OnInit {
retryRequests: [x.retryRequests, Validators.required], retryRequests: [x.retryRequests, Validators.required],
mediaDatabaseRefresh: [x.mediaDatabaseRefresh, Validators.required], mediaDatabaseRefresh: [x.mediaDatabaseRefresh, Validators.required],
autoDeleteRequests: [x.autoDeleteRequests, Validators.required], autoDeleteRequests: [x.autoDeleteRequests, Validators.required],
EmbyRecentlyAddedSync: [x.embyRecentlyAddedSync, Validators.required], embyRecentlyAddedSync: [x.embyRecentlyAddedSync, Validators.required],
}); });
}); });
} }

View file

@ -61,7 +61,7 @@
<div class="md-form-field"> <div class="md-form-field">
<mat-form-field appearance="outline"> <mat-form-field appearance="outline">
<mat-label>Admin Email</mat-label> <mat-label>Admin Email</mat-label>
<input matInput formControlName="adminEmail" matTooltip="The administrator email will be used to send emails for admin only notifications (e.g. New Requests that require approvals)"> <input matInput formControlName="adminEmail" matTooltip="The administrator email will be used to send emails for admin only notifications (e.g. raised issues)">
<mat-error *ngIf="emailForm.get('adminEmail').hasError('required')"> <mat-error *ngIf="emailForm.get('adminEmail').hasError('required')">
Admin Email is <strong>required</strong> Admin Email is <strong>required</strong>
</mat-error> </mat-error>

View file

@ -62,7 +62,6 @@ export class RadarrComponent implements OnInit {
{ name: "Announced", value: "Announced" }, { name: "Announced", value: "Announced" },
{ name: "In Cinemas", value: "InCinemas" }, { name: "In Cinemas", value: "InCinemas" },
{ name: "Physical / Web", value: "Released" }, { name: "Physical / Web", value: "Released" },
{ name: "PreDb", value: "PreDb" },
]; ];
} }

View file

@ -1,11 +1,12 @@
import { AfterViewInit, Component, OnInit, ViewChild } from "@angular/core"; import { AfterViewInit, Component, OnInit, ViewChild } from "@angular/core";
import { Router } from "@angular/router"; import { IdentityService, NotificationService, SettingsService } from "../../services";
import { CustomizationFacade } from "../../state/customization/customization.facade";
import { ICreateWizardUser } from "../../interfaces"; import { ICreateWizardUser } from "../../interfaces";
import { IdentityService, NotificationService } from "../../services";
import { IOmbiConfigModel } from "../models/OmbiConfigModel"; import { IOmbiConfigModel } from "../models/OmbiConfigModel";
import { WizardService } from "../services/wizard.service";
import { MatHorizontalStepper } from'@angular/material/stepper'; import { MatHorizontalStepper } from'@angular/material/stepper';
import { StepperSelectionEvent } from "@angular/cdk/stepper"; import { Router } from "@angular/router";
import { WizardService } from "../services/wizard.service";
@Component({ @Component({
templateUrl: "./welcome.component.html", templateUrl: "./welcome.component.html",
@ -18,7 +19,8 @@ export class WelcomeComponent implements OnInit {
public config: IOmbiConfigModel; public config: IOmbiConfigModel;
constructor(private router: Router, private identityService: IdentityService, constructor(private router: Router, private identityService: IdentityService,
private notificationService: NotificationService, private WizardService: WizardService) { } private notificationService: NotificationService, private WizardService: WizardService,
private settingsService: SettingsService, private customizationFacade: CustomizationFacade) { }
public ngOnInit(): void { public ngOnInit(): void {
this.localUser = { this.localUser = {
@ -34,10 +36,27 @@ export class WelcomeComponent implements OnInit {
} }
public createUser() { public createUser() {
this.WizardService.addOmbiConfig(this.config).subscribe(config => { if (this.config.applicationUrl) {
this.settingsService.verifyUrl(this.config.applicationUrl).subscribe(x => {
if (!x) {
this.notificationService.error(`The URL "${this.config.applicationUrl}" is not valid. Please format it correctly e.g. http://www.google.com/`);
this.stepper.selectedIndex = 3;
return;
}
this.saveConfig();
});
} else {
this.saveConfig();
}
}
private saveConfig() {
this.WizardService.addOmbiConfig(this.config).subscribe({
next: (config) => {
if(config != null) { if(config != null) {
this.identityService.createWizardUser(this.localUser).subscribe(x => { this.identityService.createWizardUser(this.localUser).subscribe(x => {
if (x.result) { if (x.result) {
this.customizationFacade.loadCustomziationSettings().subscribe();
// save the config // save the config
this.router.navigate(["login"]); this.router.navigate(["login"]);
} else { } else {
@ -48,6 +67,9 @@ export class WelcomeComponent implements OnInit {
} }
}); });
} }
}, configErr => this.notificationService.error(configErr)); },
error: (configErr) => this.notificationService.error(configErr)
});
} }
} }

View file

@ -6,7 +6,7 @@
<base href="/" /> <base href="/" />
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet"> <link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500" rel="stylesheet">
<link rel="icon" type="image/png" href="images/favicon/favicon.ico"/> <link id="favicon" rel="icon" type="image/png" href="images/favicon/favicon.ico"/>
<link rel="apple-touch-icon" type="image/png" href="images/favicon/apple-touch-icon.png"/> <link rel="apple-touch-icon" type="image/png" href="images/favicon/apple-touch-icon.png"/>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />

View file

@ -334,6 +334,7 @@ namespace Ombi.Controllers.V1
[ApiExplorerSettings(IgnoreApi = true)] [ApiExplorerSettings(IgnoreApi = true)]
[HttpPost("customization/urlverify")] [HttpPost("customization/urlverify")]
[AllowAnonymous]
public bool VerifyUrl([FromBody]UrlVerifyModel url) public bool VerifyUrl([FromBody]UrlVerifyModel url)
{ {
return Uri.TryCreate(url.Url, UriKind.Absolute, out var __); return Uri.TryCreate(url.Url, UriKind.Absolute, out var __);

View file

@ -23,6 +23,12 @@
appName = "Ombi"; appName = "Ombi";
} }
var favicon = customization.Favicon;
if (string.IsNullOrEmpty(favicon))
{
favicon = "images/favicon/favicon.ico";
}
} }
<!DOCTYPE html> <!DOCTYPE html>

View file

@ -226,7 +226,12 @@
"MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!", "MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!",
"TvRequestQuotaExceeded": "You have exceeded your Episode request quota!", "TvRequestQuotaExceeded": "You have exceeded your Episode request quota!",
"AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!" "AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!"
} },
"Notify": "Notify",
"RemoveNotification": "Remove Notifications",
"SuccessfulNotify": "You will now be notified for title {{title}}",
"SuccessfulUnNotify": "You will no longer be notified for title {{title}}",
"CouldntNotify": "Couldn't notify title {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Проблеми", "Title": "Проблеми",

View file

@ -0,0 +1,435 @@
{
"Login": {
"SignInButton": "Přihlásit se",
"UsernamePlaceholder": "Uživatelské jméno",
"PasswordPlaceholder": "Heslo",
"RememberMe": "Zapamatovat si mě",
"SignInWith": "Přihlásit se pomocí {{appName}}",
"SignInWithPlex": "Přihlásit se přes Plex",
"ForgottenPassword": "Zapomněli jste heslo?",
"Errors": {
"IncorrectCredentials": "Nesprávné uživatelské jméno nebo heslo"
}
},
"Common": {
"ContinueButton": "Pokračovat",
"Available": "Dostupný",
"Approved": "Schváleno",
"Pending": "Čeká na vyřízení",
"PartiallyAvailable": "Částečně dostupné",
"Monitored": "Monitorováno",
"NotAvailable": "Není k dispozici",
"ProcessingRequest": "Zpracovávání žádosti",
"PendingApproval": "Čeká na schválení",
"RequestDenied": "Žádost zamítnuta",
"NotRequested": "Nevyžádáno",
"Requested": "Vyžádáno",
"Search": "Hledat",
"Request": "Žádost",
"Denied": "Zamítnuto",
"Approve": "Schválit",
"PartlyAvailable": "Částečně k dispozici",
"ViewDetails": "View Details",
"Errors": {
"Validation": "Please check your entered values"
},
"Cancel": "Cancel",
"Submit": "Submit",
"Update": "Update",
"tvShow": "TV Show",
"movie": "Movie",
"album": "Album"
},
"PasswordReset": {
"EmailAddressPlaceholder": "Email Address",
"ResetPasswordButton": "Reset Password"
},
"LandingPage": {
"OnlineHeading": "Currently Online",
"OnlineParagraph": "The media server is currently online",
"PartiallyOnlineHeading": "Partially Online",
"PartiallyOnlineParagraph": "The media server is partially online.",
"MultipleServersUnavailable": "There are {{serversUnavailable}} servers offline out of {{totalServers}}.",
"SingleServerUnavailable": "There is {{serversUnavailable}} server offline out of {{totalServers}}.",
"OfflineHeading": "Currently Offline",
"OfflineParagraph": "The media server is currently offline.",
"CheckPageForUpdates": "Check this page for continuous site updates."
},
"ErrorPages": {
"NotFound": "Page not found"
},
"NavigationBar": {
"Discover": "Discover",
"Search": "Search",
"Requests": "Requests",
"UserManagement": "Users",
"Issues": "Issues",
"Vote": "Vote",
"Donate": "Donate!",
"DonateLibraryMaintainer": "Donate to Library Maintainer",
"DonateTooltip": "This is how I convince my wife to let me spend my spare time developing Ombi 😁",
"UpdateAvailableTooltip": "Update Available!",
"Settings": "Settings",
"Welcome": "Welcome {{username}}",
"UpdateDetails": "Update Details",
"Logout": "Logout",
"OpenMobileApp": "Open Mobile App",
"RecentlyAdded": "Recently Added",
"ChangeTheme": "Change Theme",
"Calendar": "Calendar",
"UserPreferences": "Preferences",
"FeatureSuggestion": "Features",
"FeatureSuggestionTooltip": "Have a great new idea? Suggest it here!",
"Filter": {
"Movies": "Movies",
"TvShows": "TV Shows",
"Music": "Music",
"People": "People"
},
"MorningWelcome": "Good morning!",
"AfternoonWelcome": "Good afternoon!",
"EveningWelcome": "Good evening!"
},
"Search": {
"Title": "Search",
"Paragraph": "Want to watch something that is not currently available? No problem, just search for it below and request it!",
"MoviesTab": "Movies",
"TvTab": "TV Shows",
"MusicTab": "Music",
"AdvancedSearch": "You can fill in any of the below to discover new media. All of the results are sorted by popularity",
"AdvancedSearchHeader": "Advanced Search",
"Suggestions": "Suggestions",
"NoResults": "Sorry, we didn't find any results!",
"DigitalDate": "Digital Release: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}",
"ViewOnPlex": "Play On Plex",
"ViewOnEmby": "Play On Emby",
"ViewOnJellyfin": "Play On Jellyfin",
"RequestAdded": "Request for {{title}} has been added successfully",
"Similar": "Similar",
"Refine": "Refine",
"SearchBarPlaceholder": "Type Here to Search",
"Movies": {
"PopularMovies": "Popular Movies",
"UpcomingMovies": "Upcoming Movies",
"TopRatedMovies": "Top Rated Movies",
"NowPlayingMovies": "Now Playing Movies",
"HomePage": "Home Page",
"Trailer": "Trailer"
},
"TvShows": {
"Popular": "Popular",
"Trending": "Trending",
"MostWatched": "Most Watched",
"MostAnticipated": "Most Anticipated",
"Results": "Results",
"AirDate": "Air Date:",
"AllSeasons": "All Seasons",
"FirstSeason": "First Season",
"LatestSeason": "Latest Season",
"Select": "Select ...",
"SubmitRequest": "Submit Request",
"Season": "Season {{seasonNumber}}",
"SelectAllInSeason": "Select All in Season {{seasonNumber}}"
},
"AdvancedSearchInstructions": "Please choose what type of media you are searching for:",
"YearOfRelease": "Year of Release",
"SearchGenre": "Search Genre",
"SearchKeyword": "Search Keyword",
"SearchProvider": "Search Provider",
"KeywordSearchingDisclaimer": "Please note that Keyword Searching is very hit and miss due to the inconsistent data in TheMovieDb"
},
"Requests": {
"Title": "Requests",
"Paragraph": "Below you can see yours and all other requests, as well as their download and approval status.",
"MoviesTab": "Movies",
"ArtistName": "Artist",
"AlbumName": "Album Name",
"TvTab": "TV Shows",
"MusicTab": "Music",
"RequestedBy": "Requested By",
"Status": "Status",
"RequestStatus": "Request status",
"Denied": " Denied:",
"TheatricalRelease": "Theatrical Release: {{date}}",
"ReleaseDate": "Released: {{date}}",
"TheatricalReleaseSort": "Theatrical Release",
"DigitalRelease": "Digital Release: {{date}}",
"RequestDate": "Request Date",
"QualityOverride": "Quality Override:",
"RootFolderOverride": "Root Folder Override:",
"ChangeRootFolder": "Root Folder",
"ChangeQualityProfile": "Quality Profile",
"MarkUnavailable": "Mark Unavailable",
"MarkAvailable": "Mark Available",
"Remove": "Remove",
"Deny": "Deny",
"DenyReason": "Deny Reason",
"DeniedReason": "Denied Reason",
"Season": "Season",
"GridTitle": "Title",
"AirDate": "Air Date",
"GridStatus": "Status",
"ReportIssue": "Report Issue",
"Filter": "Filter",
"Sort": "Sort",
"SeasonNumberHeading": "Season: {seasonNumber}",
"SortTitleAsc": "Title ▲",
"SortTitleDesc": "Title ▼",
"SortRequestDateAsc": "Request Date ▲",
"SortRequestDateDesc": "Request Date ▼",
"SortStatusAsc": "Status ▲",
"SortStatusDesc": "Status ▼",
"Remaining": {
"Quota": "{{remaining}}/{{total}} requests remaining",
"NextDays": "Another request will be added in {{time}} days",
"NextHours": "Another request will be added in {{time}} hours",
"NextMinutes": "Another request will be added in {{time}} minutes",
"NextMinute": "Another request will be added in {{time}} minute"
},
"AllRequests": "All Requests",
"PendingRequests": "Pending Requests",
"ProcessingRequests": "Processing Requests",
"AvailableRequests": "Available Requests",
"DeniedRequests": "Denied Requests",
"RequestsToDisplay": "Requests to display",
"RequestsTitle": "Title",
"Details": "Details",
"Options": "Options",
"RequestPanel": {
"Delete": "Delete Request",
"Approve": "Approve Request",
"ChangeAvailability": "Mark Available",
"Deleted": "Successfully deleted selected items",
"Approved": "Successfully approved selected items"
},
"SuccessfullyApproved": "Successfully Approved",
"SuccessfullyDeleted": "Request successfully deleted",
"NowAvailable": "Request is now available",
"NowUnavailable": "Request is now unavailable",
"SuccessfullyReprocessed": "Successfully Re-processed the request",
"DeniedRequest": "Denied Request",
"RequestCollection": "Request Collection",
"CollectionSuccesfullyAdded": "The collection {{name}} has been successfully added!",
"NeedToSelectEpisodes": "You need to select some episodes!",
"RequestAddedSuccessfully": "Request for {{title}} has been added successfully",
"ErrorCodes": {
"AlreadyRequested": "This has already been requested",
"EpisodesAlreadyRequested": "We already have episodes requested from this series",
"NoPermissionsOnBehalf": "You do not have the correct permissions to request on behalf of users!",
"NoPermissions": "You do not have the correct permissions!",
"RequestDoesNotExist": "Request does not exist",
"ChildRequestDoesNotExist": "Child Request does not exist",
"NoPermissionsRequestMovie": "You do not have permissions to Request a Movie",
"NoPermissionsRequestTV": "You do not have permissions to Request a TV Show",
"NoPermissionsRequestAlbum": "You do not have permissions to Request an Album",
"MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!",
"TvRequestQuotaExceeded": "You have exceeded your Episode request quota!",
"AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!"
},
"Notify": "Notify",
"RemoveNotification": "Remove Notifications",
"SuccessfulNotify": "You will now be notified for title {{title}}",
"SuccessfulUnNotify": "You will no longer be notified for title {{title}}",
"CouldntNotify": "Couldn't notify title {{title}}"
},
"Issues": {
"Title": "Issues",
"IssuesForTitle": "Issues for {{title}}",
"PendingTitle": "Pending Issues",
"InProgressTitle": "In Progress Issues",
"ResolvedTitle": "Resolved Issues",
"ColumnTitle": "Title",
"Count": "Count",
"Category": "Category",
"Status": "Status",
"Details": "Details",
"Description": "Description",
"NoComments": "No Comments!",
"MarkInProgress": "Mark In Progress",
"MarkResolved": "Mark Resolved",
"SendMessageButton": "Send",
"Subject": "Subject",
"Comments": "Comments",
"WriteMessagePlaceholder": "Write your message here...",
"ReportedBy": "Reported By",
"IssueDialog": {
"Title": "Report an issue",
"DescriptionPlaceholder": "Please describe the issue",
"TitlePlaceholder": "Short title of your issue",
"SelectCategory": "Select Category",
"IssueCreated": "Issue has been created"
},
"Outstanding": "There are outstanding issues",
"ResolvedDate": "Resolved date",
"CreatedDate": "Raised on",
"MarkedAsResolved": "This issue has now been marked as resolved!",
"MarkedAsInProgress": "This issue has now been marked as in progress!",
"Delete": "Delete issue",
"DeletedIssue": "Issue has been deleted",
"Chat": "Chat",
"EnterYourMessage": "Enter Your Message",
"Requested": "Requested",
"UserOnDate": "{{user}} on {{date}}"
},
"Filter": {
"ClearFilter": "Clear Filter",
"FilterHeaderAvailability": "Availability",
"FilterHeaderRequestStatus": "Status",
"Approved": "Approved",
"PendingApproval": "Pending Approval",
"WatchProviders": "Watch Providers",
"Keywords": "Keywords"
},
"UserManagment": {
"TvRemaining": "TV: {{remaining}}/{{total}} remaining",
"MovieRemaining": "Movies: {{remaining}}/{{total}} remaining",
"MusicRemaining": "Music: {{remaining}}/{{total}} remaining",
"TvDue": "TV: {{date}}",
"MovieDue": "Movie: {{date}}",
"MusicDue": "Music: {{date}}"
},
"Votes": {
"CompletedVotesTab": "Voted",
"VotesTab": "Votes Needed"
},
"MediaDetails": {
"Denied": "Denied",
"Trailers": "Trailers",
"RecommendationsTitle": "Recommendations",
"SimilarTitle": "Similar",
"VideosTitle": "Videos",
"AlbumsTitle": "Albums",
"RequestAllAlbums": "Request All Albums",
"ClearSelection": "Clear Selection",
"RequestSelectedAlbums": "Request Selected Albums",
"ViewCollection": "View Collection",
"NotEnoughInfo": "Unfortunately there is not enough information about this show yet!",
"AdvancedOptions": "Advanced Options",
"AutoApproveOptions": "You can configure the request here, once requested it will be sent to your DVR application and will be auto approved! Please note, this is optional, just press Request to skip!",
"AutoApproveOptionsTv": "You can configure the request here, once requested it will be sent to your DVR application and will be auto approved! If the request is already in Sonarr, we will not change the root folder or quality profile if you set it! Please note, this is optional, just press Request to skip!",
"AutoApproveOptionsTvShort": "You can configure the request here, once requested it will be sent to your DVR application! If the request is already in Sonarr, we will not change the root folder or quality profile if you set it! Please note, this is optional, just press Request to skip!",
"QualityProfilesSelect": "Select A Quality Profile",
"RootFolderSelect": "Select A Root Folder",
"LanguageProfileSelect": "Select A Language Profile",
"Status": "Status:",
"StatusValues": {
"Rumored": "Rumored",
"Planned": "Planned",
"In Production": "In Production",
"Post Production": "Post Production",
"Released": "Released",
"Running": "Running",
"Returning Series": "Returning Series",
"Ended": "Ended",
"Canceled": "Canceled"
},
"Seasons": "Seasons:",
"Episodes": "Episodes:",
"Availability": "Availability:",
"RequestStatus": "Request Status",
"Quality": "Quality:",
"RootFolderOverride": "Root Folder Override:",
"QualityOverride": "Quality Override:",
"Network": "Network:",
"GenresLabel": "Genres:",
"Genres": "Genres",
"FirstAired": "First Aired:",
"TheatricalRelease": "Release:",
"DigitalRelease": "Digital Release:",
"Votes": "Votes:",
"Runtime": "Runtime:",
"Minutes": "{{runtime}} Minutes",
"Revenue": "Revenue:",
"Budget": "Budget:",
"Keywords": "Keywords/Tags:",
"Casts": {
"CastTitle": "Cast"
},
"EpisodeSelector": {
"AllSeasonsTooltip": "This will request every season for this show",
"FirstSeasonTooltip": "This will only request the First Season for this show",
"LatestSeasonTooltip": "This will only request the Latest Season for this show",
"NoEpisodes": "There unfortunately is no episode data for this show yet!",
"SeasonNumber": "Season {{number}}"
},
"SonarrConfiguration": "Sonarr Configuration",
"RadarrConfiguration": "Radarr Configuration",
"RequestOnBehalf": "Request on behalf of",
"PleaseSelectUser": "Please select a user",
"StreamingOn": "Streaming On:",
"RequestedBy": "Requested By:",
"RequestedByOn": "Requested By {{user}} on {{date}}",
"RequestDate": "Request Date:",
"DeniedReason": "Denied Reason:",
"ReProcessRequest": "Re-Process Request",
"Music": {
"Type": "Type:",
"Country": "Country:",
"StartDate": "Start Date:",
"EndDate": "EndDate:"
}
},
"Discovery": {
"PopularTab": "Popular",
"TrendingTab": "Trending",
"UpcomingTab": "Upcoming",
"SeasonalTab": "Seasonal",
"RecentlyRequestedTab": "Recently Requested",
"Movies": "Movies",
"Combined": "Combined",
"Tv": "TV",
"CardDetails": {
"Availability": "Availability",
"Studio": "Studio",
"Network": "Network",
"UnknownNetwork": "Unknown",
"RequestStatus": "Request Status",
"Director": "Director",
"InCinemas": "In Cinemas",
"FirstAired": "First Aired",
"Writer": "Writer",
"ExecProducer": "Exec Producer"
},
"NoSearch": "Sorry, nothing matches your search!"
},
"UserPreferences": {
"Welcome": "Welcome {{username}}!",
"OmbiLanguage": "Language",
"DarkMode": "Dark Mode",
"Updated": "Successfully Updated",
"StreamingCountry": "Streaming Country",
"StreamingCountryDescription": "This is the country code that we will display streaming information for. If you are in the US please select US and you will get US related streaming information.",
"LanguageDescription": "This is the language you would like the Ombi interface to be displayed in.",
"MobileQRCode": "Mobile QR Code",
"LegacyApp": "Launch Legacy App",
"NoQrCode": "Please contact your administrator to enable QR codes",
"UserType": "User Type:",
"ChangeDetails": "Change Details",
"NeedCurrentPassword": "You need your current password to make any changes here",
"CurrentPassword": "Current Password",
"EmailAddress": "Email Address",
"NewPassword": "New Password",
"NewPasswordConfirm": "New Password Confirm",
"Security": "Security",
"Profile": "Profile",
"UpdatedYourInformation": "Updated your information",
"Unsubscribed": "Unsubscribed!"
},
"UserTypeLabel": {
"1": "Local User",
"2": "Plex User",
"3": "Emby User",
"4": "Emby Connect User",
"5": "Jellyfin User"
},
"Paginator": {
"itemsPerPageLabel": "Items per page:",
"nextPageLabel": "Next page",
"previousPageLabel": "Previous page",
"firstPageLabel": "First page",
"lastPageLabel": "Last page",
"rangePageLabel1": "0 of {{length}}",
"rangePageLabel2": "{{startIndex}} {{endIndex}} of {{length}}"
}
}

View file

@ -226,7 +226,12 @@
"MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!", "MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!",
"TvRequestQuotaExceeded": "You have exceeded your Episode request quota!", "TvRequestQuotaExceeded": "You have exceeded your Episode request quota!",
"AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!" "AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!"
} },
"Notify": "Notify",
"RemoveNotification": "Remove Notifications",
"SuccessfulNotify": "You will now be notified for title {{title}}",
"SuccessfulUnNotify": "You will no longer be notified for title {{title}}",
"CouldntNotify": "Couldn't notify title {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Problemer", "Title": "Problemer",

View file

@ -226,7 +226,12 @@
"MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!", "MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!",
"TvRequestQuotaExceeded": "You have exceeded your Episode request quota!", "TvRequestQuotaExceeded": "You have exceeded your Episode request quota!",
"AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!" "AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!"
} },
"Notify": "Notify",
"RemoveNotification": "Remove Notifications",
"SuccessfulNotify": "You will now be notified for title {{title}}",
"SuccessfulUnNotify": "You will no longer be notified for title {{title}}",
"CouldntNotify": "Couldn't notify title {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Probleme", "Title": "Probleme",

View file

@ -226,7 +226,12 @@
"MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!", "MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!",
"TvRequestQuotaExceeded": "You have exceeded your Episode request quota!", "TvRequestQuotaExceeded": "You have exceeded your Episode request quota!",
"AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!" "AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!"
} },
"Notify": "Notify",
"RemoveNotification": "Remove Notifications",
"SuccessfulNotify": "You will now be notified for title {{title}}",
"SuccessfulUnNotify": "You will no longer be notified for title {{title}}",
"CouldntNotify": "Couldn't notify title {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Problemas", "Title": "Problemas",

View file

@ -226,7 +226,12 @@
"MovieRequestQuotaExceeded": "Vous avez dépassé votre quota de demandes de films !", "MovieRequestQuotaExceeded": "Vous avez dépassé votre quota de demandes de films !",
"TvRequestQuotaExceeded": "Vous avez dépassé votre quota de demandes d'épisodes !", "TvRequestQuotaExceeded": "Vous avez dépassé votre quota de demandes d'épisodes !",
"AlbumRequestQuotaExceeded": "Vous avez dépassé votre quota de demandes d'albums !" "AlbumRequestQuotaExceeded": "Vous avez dépassé votre quota de demandes d'albums !"
} },
"Notify": "Notifier",
"RemoveNotification": "Supprimer les notifications",
"SuccessfulNotify": "Vous allez maintenant être notifié pour {{title}}",
"SuccessfulUnNotify": "Vous ne serez plus notifié pour {{title}}",
"CouldntNotify": "Impossible de notifier {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Problèmes", "Title": "Problèmes",
@ -310,13 +315,13 @@
"Status": "Statut :", "Status": "Statut :",
"StatusValues": { "StatusValues": {
"Rumored": "Rumeur", "Rumored": "Rumeur",
"Planned": "Planifié", "Planned": "Prévu",
"In Production": "En Production", "In Production": "En Production",
"Post Production": "Post-production", "Post Production": "Post-production",
"Released": "Sorti", "Released": "Sorti",
"Running": "En cours", "Running": "En cours",
"Returning Series": "Série en cours", "Returning Series": "Série en cours",
"Ended": "Finie", "Ended": "Terminée",
"Canceled": "Annulée" "Canceled": "Annulée"
}, },
"Seasons": "Saisons :", "Seasons": "Saisons :",
@ -324,13 +329,13 @@
"Availability": "Disponibilité :", "Availability": "Disponibilité :",
"RequestStatus": "Statut de la Demande", "RequestStatus": "Statut de la Demande",
"Quality": "Qualité :", "Quality": "Qualité :",
"RootFolderOverride": "Remplacement du répertoire racine :", "RootFolderOverride": "Répertoire racine remplacé :",
"QualityOverride": "Remplacement de la qualité :", "QualityOverride": "Qualité remplacée :",
"Network": "Diffuseur :", "Network": "Diffuseur :",
"GenresLabel": "Genres :", "GenresLabel": "Genres :",
"Genres": "Genres", "Genres": "Genres",
"FirstAired": "Première diffusion :", "FirstAired": "Première diffusion :",
"TheatricalRelease": "Sortie :", "TheatricalRelease": "Sortie en salle :",
"DigitalRelease": "Sortie numérique :", "DigitalRelease": "Sortie numérique :",
"Votes": "Votes :", "Votes": "Votes :",
"Runtime": "Durée :", "Runtime": "Durée :",

View file

@ -62,7 +62,7 @@
"Discover": "Felfedezés", "Discover": "Felfedezés",
"Search": "Keresés", "Search": "Keresés",
"Requests": "Kérések", "Requests": "Kérések",
"UserManagement": "Felhasználók kezelése", "UserManagement": "Felhasználók",
"Issues": "Problémák", "Issues": "Problémák",
"Vote": "Szavazás", "Vote": "Szavazás",
"Donate": "Adakozás!", "Donate": "Adakozás!",
@ -78,7 +78,7 @@
"ChangeTheme": "Téma váltása", "ChangeTheme": "Téma váltása",
"Calendar": "Naptár", "Calendar": "Naptár",
"UserPreferences": "Beállítások", "UserPreferences": "Beállítások",
"FeatureSuggestion": "Feature Suggestion", "FeatureSuggestion": "Funkciójavaslás",
"FeatureSuggestionTooltip": "Van egy jó ötleted? Oszd meg itt!", "FeatureSuggestionTooltip": "Van egy jó ötleted? Oszd meg itt!",
"Filter": { "Filter": {
"Movies": "Filmek", "Movies": "Filmek",
@ -219,14 +219,19 @@
"NoPermissionsOnBehalf": "Nincs jogosultságod más felhasználók nevében kérni!", "NoPermissionsOnBehalf": "Nincs jogosultságod más felhasználók nevében kérni!",
"NoPermissions": "Nincs megfelelő jogosultságod!", "NoPermissions": "Nincs megfelelő jogosultságod!",
"RequestDoesNotExist": "A kérés nem létezik", "RequestDoesNotExist": "A kérés nem létezik",
"ChildRequestDoesNotExist": "Child Request does not exist", "ChildRequestDoesNotExist": "Gyerek kérés nem létezik",
"NoPermissionsRequestMovie": "Nincs jogosultságod filmet kérni", "NoPermissionsRequestMovie": "Nincs jogosultságod filmet kérni",
"NoPermissionsRequestTV": "Nincs jogosultságod sorozatot kérni", "NoPermissionsRequestTV": "Nincs jogosultságod sorozatot kérni",
"NoPermissionsRequestAlbum": "Nincs jogosultságod albumot kérni", "NoPermissionsRequestAlbum": "Nincs jogosultságod albumot kérni",
"MovieRequestQuotaExceeded": "Túllépted a megszabott film kérési kereted!", "MovieRequestQuotaExceeded": "Túllépted a megszabott film kérési kereted!",
"TvRequestQuotaExceeded": "Túllépted a megszabott epizód kérési kereted!", "TvRequestQuotaExceeded": "Túllépted a megszabott epizód kérési kereted!",
"AlbumRequestQuotaExceeded": "Túllépted a megszabott album kérési kereted!" "AlbumRequestQuotaExceeded": "Túllépted a megszabott album kérési kereted!"
} },
"Notify": "Értesítés",
"RemoveNotification": "Értesítések törlése",
"SuccessfulNotify": "Mostantól értesítve leszel erről: {{title}}",
"SuccessfulUnNotify": "Mostantól nem leszel értesítve erről: {{title}}",
"CouldntNotify": "Nem lehetett értesíteni erről: {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Problémák", "Title": "Problémák",

View file

@ -226,7 +226,12 @@
"MovieRequestQuotaExceeded": "Hai superato la tua quota di richieste di Film!", "MovieRequestQuotaExceeded": "Hai superato la tua quota di richieste di Film!",
"TvRequestQuotaExceeded": "Hai superato la tua quota di richieste di Episodi!", "TvRequestQuotaExceeded": "Hai superato la tua quota di richieste di Episodi!",
"AlbumRequestQuotaExceeded": "Hai superato la tua quota di richieste di Album!" "AlbumRequestQuotaExceeded": "Hai superato la tua quota di richieste di Album!"
} },
"Notify": "Notifica",
"RemoveNotification": "Rimuovi Notifiche",
"SuccessfulNotify": "Sarai notificato per il titolo {{title}}",
"SuccessfulUnNotify": "Non sarai più notificato per il titolo {{title}}",
"CouldntNotify": "Impossibile notificare il titolo {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Problemi", "Title": "Problemi",
@ -273,7 +278,7 @@
"FilterHeaderRequestStatus": "Stato", "FilterHeaderRequestStatus": "Stato",
"Approved": "Approvato", "Approved": "Approvato",
"PendingApproval": "In attesa di approvazione", "PendingApproval": "In attesa di approvazione",
"WatchProviders": "Watch Providers", "WatchProviders": "Guarda i Fornitori",
"Keywords": "Parole Chiave" "Keywords": "Parole Chiave"
}, },
"UserManagment": { "UserManagment": {

View file

@ -226,7 +226,12 @@
"MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!", "MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!",
"TvRequestQuotaExceeded": "You have exceeded your Episode request quota!", "TvRequestQuotaExceeded": "You have exceeded your Episode request quota!",
"AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!" "AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!"
} },
"Notify": "Notify",
"RemoveNotification": "Remove Notifications",
"SuccessfulNotify": "You will now be notified for title {{title}}",
"SuccessfulUnNotify": "You will no longer be notified for title {{title}}",
"CouldntNotify": "Couldn't notify title {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Problemen", "Title": "Problemen",

View file

@ -226,7 +226,12 @@
"MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!", "MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!",
"TvRequestQuotaExceeded": "You have exceeded your Episode request quota!", "TvRequestQuotaExceeded": "You have exceeded your Episode request quota!",
"AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!" "AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!"
} },
"Notify": "Notify",
"RemoveNotification": "Remove Notifications",
"SuccessfulNotify": "You will now be notified for title {{title}}",
"SuccessfulUnNotify": "You will no longer be notified for title {{title}}",
"CouldntNotify": "Couldn't notify title {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Mangler", "Title": "Mangler",

View file

@ -226,7 +226,12 @@
"MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!", "MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!",
"TvRequestQuotaExceeded": "You have exceeded your Episode request quota!", "TvRequestQuotaExceeded": "You have exceeded your Episode request quota!",
"AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!" "AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!"
} },
"Notify": "Notify",
"RemoveNotification": "Remove Notifications",
"SuccessfulNotify": "You will now be notified for title {{title}}",
"SuccessfulUnNotify": "You will no longer be notified for title {{title}}",
"CouldntNotify": "Couldn't notify title {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Problemy", "Title": "Problemy",

View file

@ -1,203 +1,203 @@
{ {
"Login": { "Login": {
"SignInButton": "Sign in", "SignInButton": "Iniciar sessão",
"UsernamePlaceholder": "Username", "UsernamePlaceholder": "Nome de usuário",
"PasswordPlaceholder": "Password", "PasswordPlaceholder": "Senha",
"RememberMe": "Remember Me", "RememberMe": "Lembrar-me",
"SignInWith": "Sign in with {{appName}}", "SignInWith": "Entre com {{appName}}",
"SignInWithPlex": "Sign in with Plex", "SignInWithPlex": "Iniciar sessão com Plex",
"ForgottenPassword": "Forgot your password?", "ForgottenPassword": "Esqueceu sua senha?",
"Errors": { "Errors": {
"IncorrectCredentials": "Incorrect username or password" "IncorrectCredentials": "Nome de usuário ou senha incorretos"
} }
}, },
"Common": { "Common": {
"ContinueButton": "Continue", "ContinueButton": "Continuar",
"Available": "Available", "Available": "Disponível",
"Approved": "Approved", "Approved": "Aprovado",
"Pending": "Pending", "Pending": "Pendente",
"PartiallyAvailable": "Partially Available", "PartiallyAvailable": "Parcialmente Disponível",
"Monitored": "Monitored", "Monitored": "Monitorado",
"NotAvailable": "Not Available", "NotAvailable": "Não Disponível",
"ProcessingRequest": "Processing Request", "ProcessingRequest": "Processando Solicitação",
"PendingApproval": "Pending Approval", "PendingApproval": "Aprovação Pendente",
"RequestDenied": "Request Denied", "RequestDenied": "Solicitação Negada",
"NotRequested": "Not Requested", "NotRequested": "Não Solicitado",
"Requested": "Requested", "Requested": "Solicitado",
"Search": "Search", "Search": "Buscar",
"Request": "Request", "Request": "Solicitar",
"Denied": "Denied", "Denied": "Negado",
"Approve": "Approve", "Approve": "Aprovar",
"PartlyAvailable": "Partly Available", "PartlyAvailable": "Parcialmente Disponível",
"ViewDetails": "View Details", "ViewDetails": "Ver detalhes",
"Errors": { "Errors": {
"Validation": "Please check your entered values" "Validation": "Por favor, verifique os dados inseridos"
}, },
"Cancel": "Cancel", "Cancel": "Cancelar",
"Submit": "Submit", "Submit": "Submeter",
"Update": "Update", "Update": "Atualizar",
"tvShow": "TV Show", "tvShow": "Series",
"movie": "Movie", "movie": "Filmes",
"album": "Album" "album": "Álbum"
}, },
"PasswordReset": { "PasswordReset": {
"EmailAddressPlaceholder": "Email Address", "EmailAddressPlaceholder": "Endereço de e-mail",
"ResetPasswordButton": "Reset Password" "ResetPasswordButton": "Redefinir Senha"
}, },
"LandingPage": { "LandingPage": {
"OnlineHeading": "Currently Online", "OnlineHeading": "Online no momento",
"OnlineParagraph": "The media server is currently online", "OnlineParagraph": "O servidor de mídia está atualmente online",
"PartiallyOnlineHeading": "Partially Online", "PartiallyOnlineHeading": "Parcialmente Online",
"PartiallyOnlineParagraph": "The media server is partially online.", "PartiallyOnlineParagraph": "O servidor de mídia está parcialmente online.",
"MultipleServersUnavailable": "There are {{serversUnavailable}} servers offline out of {{totalServers}}.", "MultipleServersUnavailable": "Existem {{serversUnavailable}} servidores offline de um total de {{totalServers}}.",
"SingleServerUnavailable": "There is {{serversUnavailable}} server offline out of {{totalServers}}.", "SingleServerUnavailable": "Existe {{serversUnavailable}} servidor offline de um total de {{totalServers}}.",
"OfflineHeading": "Currently Offline", "OfflineHeading": "Offline Agora",
"OfflineParagraph": "The media server is currently offline.", "OfflineParagraph": "O servidor de mídia está atualmente offline.",
"CheckPageForUpdates": "Check this page for continuous site updates." "CheckPageForUpdates": "Verifique esta página para acompanhar as atualizações do site."
}, },
"ErrorPages": { "ErrorPages": {
"NotFound": "Page not found" "NotFound": "Pagina não encontrada"
}, },
"NavigationBar": { "NavigationBar": {
"Discover": "Discover", "Discover": "Explorar",
"Search": "Search", "Search": "Pesquisar",
"Requests": "Requests", "Requests": "Solicitações",
"UserManagement": "Users", "UserManagement": "User Management",
"Issues": "Issues", "Issues": "Problemas",
"Vote": "Vote", "Vote": "Votar",
"Donate": "Donate!", "Donate": "Doações!",
"DonateLibraryMaintainer": "Donate to Library Maintainer", "DonateLibraryMaintainer": "Doar para o Dono da Biblioteca",
"DonateTooltip": "This is how I convince my wife to let me spend my spare time developing Ombi 😁", "DonateTooltip": "É assim que convenço minha esposa a me deixar passar o meu tempo livre desenvolvendo o Ombi 😁",
"UpdateAvailableTooltip": "Update Available!", "UpdateAvailableTooltip": "Atualização Disponível!",
"Settings": "Settings", "Settings": "Configurações",
"Welcome": "Welcome {{username}}", "Welcome": "Bem-vindo(a), {{username}}",
"UpdateDetails": "Update Details", "UpdateDetails": "Detalhes da Atualização",
"Logout": "Logout", "Logout": "Desconectar",
"OpenMobileApp": "Open Mobile App", "OpenMobileApp": "Abrir Aplicativo do Celular",
"RecentlyAdded": "Recently Added", "RecentlyAdded": "Recentemente Adicionado",
"ChangeTheme": "Change Theme", "ChangeTheme": "Trocar Tema",
"Calendar": "Calendar", "Calendar": "Calendário",
"UserPreferences": "Preferences", "UserPreferences": "Preferências",
"FeatureSuggestion": "Features", "FeatureSuggestion": "Feature Suggestion",
"FeatureSuggestionTooltip": "Have a great new idea? Suggest it here!", "FeatureSuggestionTooltip": "Teve uma ótima idéia? Sugira aqui!",
"Filter": { "Filter": {
"Movies": "Movies", "Movies": "Filmes",
"TvShows": "TV Shows", "TvShows": "Séries",
"Music": "Music", "Music": "Músicas",
"People": "People" "People": "Pessoas"
}, },
"MorningWelcome": "Good morning!", "MorningWelcome": "Bom dia!",
"AfternoonWelcome": "Good afternoon!", "AfternoonWelcome": "Boa tarde!",
"EveningWelcome": "Good evening!" "EveningWelcome": "Boa noite!"
}, },
"Search": { "Search": {
"Title": "Search", "Title": "Pesquisar",
"Paragraph": "Want to watch something that is not currently available? No problem, just search for it below and request it!", "Paragraph": "Quer assistir a algo que não está disponível? Sem problemas, basta pesquisar abaixo e solicitar!",
"MoviesTab": "Movies", "MoviesTab": "Filmes",
"TvTab": "TV Shows", "TvTab": "Séries",
"MusicTab": "Music", "MusicTab": "Músicas",
"AdvancedSearch": "You can fill in any of the below to discover new media. All of the results are sorted by popularity", "AdvancedSearch": "Você pode preencher qualquer um dos abaixo para descobrir novas mídias. Todos os resultados são classificados por popularidade",
"AdvancedSearchHeader": "Advanced Search", "AdvancedSearchHeader": "Pesquisa avançada",
"Suggestions": "Suggestions", "Suggestions": "Sugestões",
"NoResults": "Sorry, we didn't find any results!", "NoResults": "Desculpe, não encontramos nenhum resultado!",
"DigitalDate": "Digital Release: {{date}}", "DigitalDate": "Lançamento Digital: {{date}}",
"TheatricalRelease": "Theatrical Release: {{date}}", "TheatricalRelease": "Lançamento nos Cinemas: {{date}}",
"ViewOnPlex": "Play On Plex", "ViewOnPlex": "View On Plex",
"ViewOnEmby": "Play On Emby", "ViewOnEmby": "View On Emby",
"ViewOnJellyfin": "Play On Jellyfin", "ViewOnJellyfin": "Assistir no Jellyfin",
"RequestAdded": "Request for {{title}} has been added successfully", "RequestAdded": "O pedido de {{title}} foi adicionado com sucesso",
"Similar": "Similar", "Similar": "Semelhantes",
"Refine": "Refine", "Refine": "Corrigir",
"SearchBarPlaceholder": "Type Here to Search", "SearchBarPlaceholder": "Digite Aqui para Pesquisar",
"Movies": { "Movies": {
"PopularMovies": "Popular Movies", "PopularMovies": "Filmes Populares",
"UpcomingMovies": "Upcoming Movies", "UpcomingMovies": "Filmes Em Breve",
"TopRatedMovies": "Top Rated Movies", "TopRatedMovies": "Filmes Mais Votados",
"NowPlayingMovies": "Now Playing Movies", "NowPlayingMovies": "Filmes em Cartaz",
"HomePage": "Home Page", "HomePage": "Página Inicial",
"Trailer": "Trailer" "Trailer": "Trailer"
}, },
"TvShows": { "TvShows": {
"Popular": "Popular", "Popular": "Popular",
"Trending": "Trending", "Trending": "Tendências",
"MostWatched": "Most Watched", "MostWatched": "Mais Assistidos",
"MostAnticipated": "Most Anticipated", "MostAnticipated": "Mais Aguardados",
"Results": "Results", "Results": "Resultados",
"AirDate": "Air Date:", "AirDate": "Data de Exibição:",
"AllSeasons": "All Seasons", "AllSeasons": "Todas as Temporadas",
"FirstSeason": "First Season", "FirstSeason": "Primeira temporada",
"LatestSeason": "Latest Season", "LatestSeason": "Última Temporada",
"Select": "Select ...", "Select": "Selecionar...",
"SubmitRequest": "Submit Request", "SubmitRequest": "Enviar solicitação",
"Season": "Season {{seasonNumber}}", "Season": "Temporada: {{seasonNumber}}",
"SelectAllInSeason": "Select All in Season {{seasonNumber}}" "SelectAllInSeason": "Selecione Tudo na Temporada {{seasonNumber}}"
}, },
"AdvancedSearchInstructions": "Please choose what type of media you are searching for:", "AdvancedSearchInstructions": "Por favor, escolha o tipo de mídia que você está procurando:",
"YearOfRelease": "Year of Release", "YearOfRelease": "Ano de lançamento",
"SearchGenre": "Search Genre", "SearchGenre": "Pesquisa por gênero",
"SearchKeyword": "Search Keyword", "SearchKeyword": "Pesquisar palavras-chave",
"SearchProvider": "Search Provider", "SearchProvider": "Provedores de pesquisa",
"KeywordSearchingDisclaimer": "Please note that Keyword Searching is very hit and miss due to the inconsistent data in TheMovieDb" "KeywordSearchingDisclaimer": "Por favor, note que a busca por palavra-chave é muito acertada e perdida devido aos dados inconsistentes no TheMovieDb"
}, },
"Requests": { "Requests": {
"Title": "Requests", "Title": "Solicitações",
"Paragraph": "Below you can see yours and all other requests, as well as their download and approval status.", "Paragraph": "Abaixo, você pode ver o seu e todos os outros pedidos, bem como o seu download e status de aprovação.",
"MoviesTab": "Movies", "MoviesTab": "Filmes",
"ArtistName": "Artist", "ArtistName": "Artistas",
"AlbumName": "Album Name", "AlbumName": "Nome do álbum",
"TvTab": "TV Shows", "TvTab": "Séries",
"MusicTab": "Music", "MusicTab": "Músicas",
"RequestedBy": "Requested By", "RequestedBy": "Solicitado por",
"Status": "Status", "Status": "Status",
"RequestStatus": "Request status", "RequestStatus": "Status da solicitação",
"Denied": " Denied:", "Denied": " Negados:",
"TheatricalRelease": "Theatrical Release: {{date}}", "TheatricalRelease": "Lançamento nos Cinemas: {{date}}",
"ReleaseDate": "Released: {{date}}", "ReleaseDate": "Lançado: {{date}}",
"TheatricalReleaseSort": "Theatrical Release", "TheatricalReleaseSort": "Lançamento nos Cinemas",
"DigitalRelease": "Digital Release: {{date}}", "DigitalRelease": "Lançamento Digital: {{date}}",
"RequestDate": "Request Date", "RequestDate": "Data da Solicitação",
"QualityOverride": "Quality Override:", "QualityOverride": "Substituição de Qualidade:",
"RootFolderOverride": "Root Folder Override:", "RootFolderOverride": "Substituição da Pasta Raiz:",
"ChangeRootFolder": "Root Folder", "ChangeRootFolder": "Pasta Raiz",
"ChangeQualityProfile": "Quality Profile", "ChangeQualityProfile": "Perfil de Qualidade",
"MarkUnavailable": "Mark Unavailable", "MarkUnavailable": "Marcar como Indisponível",
"MarkAvailable": "Mark Available", "MarkAvailable": "Marcar como Disponível",
"Remove": "Remove", "Remove": "Remover",
"Deny": "Deny", "Deny": "Negar",
"DenyReason": "Deny Reason", "DenyReason": "Razão da rejeição",
"DeniedReason": "Denied Reason", "DeniedReason": "Razão da rejeição",
"Season": "Season", "Season": "Temporada",
"GridTitle": "Title", "GridTitle": "Título",
"AirDate": "AirDate", "AirDate": "AirDate",
"GridStatus": "Status", "GridStatus": "Status",
"ReportIssue": "Report Issue", "ReportIssue": "Relatar Problema",
"Filter": "Filter", "Filter": "Filtro",
"Sort": "Sort", "Sort": "Ordenar por",
"SeasonNumberHeading": "Season: {seasonNumber}", "SeasonNumberHeading": "Temporada: {seasonNumber}",
"SortTitleAsc": "Title ▲", "SortTitleAsc": "Título ▲",
"SortTitleDesc": "Title ▼", "SortTitleDesc": "Título ▼",
"SortRequestDateAsc": "Request Date ▲", "SortRequestDateAsc": "Data da Solicitação ▲",
"SortRequestDateDesc": "Request Date ▼", "SortRequestDateDesc": "Data da Solicitação ▼",
"SortStatusAsc": "Status ▲", "SortStatusAsc": "Status ▲",
"SortStatusDesc": "Status ▼", "SortStatusDesc": "Status ▼",
"Remaining": { "Remaining": {
"Quota": "{{remaining}}/{{total}} requests remaining", "Quota": "{{remaining}}/{{total}} solicitações restantes",
"NextDays": "Another request will be added in {{time}} days", "NextDays": "Outra solicitação será adicionada em {{time}} dias",
"NextHours": "Another request will be added in {{time}} hours", "NextHours": "Outra solicitação será adicionada em {{time}} horas",
"NextMinutes": "Another request will be added in {{time}} minutes", "NextMinutes": "Outra solicitação será adicionada em {{time}} minutos",
"NextMinute": "Another request will be added in {{time}} minute" "NextMinute": "Outra solicitação será adicionada em {{time}} minuto"
}, },
"AllRequests": "All Requests", "AllRequests": "Todas solicitações",
"PendingRequests": "Pending Requests", "PendingRequests": "Solicitações pendentes",
"ProcessingRequests": "Processing Requests", "ProcessingRequests": "Solicitações em andamento",
"AvailableRequests": "Available Requests", "AvailableRequests": "Solicitação Disponíveis",
"DeniedRequests": "Denied Requests", "DeniedRequests": "Solicitações Negadas",
"RequestsToDisplay": "Requests to display", "RequestsToDisplay": "Itens por página",
"RequestsTitle": "Title", "RequestsTitle": "Título",
"Details": "Details", "Details": "Detalhes",
"Options": "Options", "Options": "Opções",
"RequestPanel": { "RequestPanel": {
"Delete": "Delete Request", "Delete": "Apagar Solicitação",
"Approve": "Approve Request", "Approve": "Approve Request",
"ChangeAvailability": "Mark Available", "ChangeAvailability": "Mark Available",
"Deleted": "Successfully deleted selected items", "Deleted": "Successfully deleted selected items",
@ -226,28 +226,33 @@
"MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!", "MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!",
"TvRequestQuotaExceeded": "You have exceeded your Episode request quota!", "TvRequestQuotaExceeded": "You have exceeded your Episode request quota!",
"AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!" "AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!"
} },
"Notify": "Notify",
"RemoveNotification": "Remove Notifications",
"SuccessfulNotify": "You will now be notified for title {{title}}",
"SuccessfulUnNotify": "You will no longer be notified for title {{title}}",
"CouldntNotify": "Couldn't notify title {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Issues", "Title": "Problemas",
"IssuesForTitle": "Issues for {{title}}", "IssuesForTitle": "Issues for {{title}}",
"PendingTitle": "Pending Issues", "PendingTitle": "Problemas pendentes",
"InProgressTitle": "In Progress Issues", "InProgressTitle": "Problemas em resolução",
"ResolvedTitle": "Resolved Issues", "ResolvedTitle": "Problemas Resolvidos",
"ColumnTitle": "Title", "ColumnTitle": "Título",
"Count": "Count", "Count": "Count",
"Category": "Category", "Category": "Categoria",
"Status": "Status", "Status": "Status",
"Details": "Details", "Details": "Detalhes",
"Description": "Description", "Description": "Descrição",
"NoComments": "No Comments!", "NoComments": "Sem Comentários!",
"MarkInProgress": "Mark In Progress", "MarkInProgress": "Marcar como em andamento",
"MarkResolved": "Mark Resolved", "MarkResolved": "Marcar como resolvido",
"SendMessageButton": "Send", "SendMessageButton": "Enviar",
"Subject": "Subject", "Subject": "Assunto",
"Comments": "Comments", "Comments": "Comentários",
"WriteMessagePlaceholder": "Write your message here...", "WriteMessagePlaceholder": "Escreva sua mensagem aqui...",
"ReportedBy": "Reported By", "ReportedBy": "Reportado por",
"IssueDialog": { "IssueDialog": {
"Title": "Report an issue", "Title": "Report an issue",
"DescriptionPlaceholder": "Please describe the issue", "DescriptionPlaceholder": "Please describe the issue",
@ -268,35 +273,35 @@
"UserOnDate": "{{user}} on {{date}}" "UserOnDate": "{{user}} on {{date}}"
}, },
"Filter": { "Filter": {
"ClearFilter": "Clear Filter", "ClearFilter": "Limpar Filtro",
"FilterHeaderAvailability": "Availability", "FilterHeaderAvailability": "Disponibilidade",
"FilterHeaderRequestStatus": "Status", "FilterHeaderRequestStatus": "Status",
"Approved": "Approved", "Approved": "Aprovado",
"PendingApproval": "Pending Approval", "PendingApproval": "Aprovação Pendente",
"WatchProviders": "Watch Providers", "WatchProviders": "Watch Providers",
"Keywords": "Keywords" "Keywords": "Keywords"
}, },
"UserManagment": { "UserManagment": {
"TvRemaining": "TV: {{remaining}}/{{total}} remaining", "TvRemaining": "Séries: {{remaining}}/{{total}} restantes",
"MovieRemaining": "Movies: {{remaining}}/{{total}} remaining", "MovieRemaining": "Filmes: {{remaining}}/{{total}} restantes",
"MusicRemaining": "Music: {{remaining}}/{{total}} remaining", "MusicRemaining": "Músicas: {{remaining}}/{{total}} restantes",
"TvDue": "TV: {{date}}", "TvDue": "Série: {{date}}",
"MovieDue": "Movie: {{date}}", "MovieDue": "Filme: {{date}}",
"MusicDue": "Music: {{date}}" "MusicDue": "Música: {{date}}"
}, },
"Votes": { "Votes": {
"CompletedVotesTab": "Voted", "CompletedVotesTab": "Votado",
"VotesTab": "Votes Needed" "VotesTab": "Votos necessários"
}, },
"MediaDetails": { "MediaDetails": {
"Denied": "Denied", "Denied": "Denied",
"Trailers": "Trailers", "Trailers": "Trailers",
"RecommendationsTitle": "Recommendations", "RecommendationsTitle": "Recommendations",
"SimilarTitle": "Similar", "SimilarTitle": "Similar",
"VideosTitle": "Videos", "VideosTitle": "Vídeos",
"AlbumsTitle": "Albums", "AlbumsTitle": "Álbuns",
"RequestAllAlbums": "Request All Albums", "RequestAllAlbums": "Solicitar Todos Álbuns",
"ClearSelection": "Clear Selection", "ClearSelection": "Limpar Seleção",
"RequestSelectedAlbums": "Request Selected Albums", "RequestSelectedAlbums": "Request Selected Albums",
"ViewCollection": "View Collection", "ViewCollection": "View Collection",
"NotEnoughInfo": "Unfortunately there is not enough information about this show yet!", "NotEnoughInfo": "Unfortunately there is not enough information about this show yet!",

View file

@ -226,7 +226,12 @@
"MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!", "MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!",
"TvRequestQuotaExceeded": "You have exceeded your Episode request quota!", "TvRequestQuotaExceeded": "You have exceeded your Episode request quota!",
"AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!" "AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!"
} },
"Notify": "Notify",
"RemoveNotification": "Remove Notifications",
"SuccessfulNotify": "You will now be notified for title {{title}}",
"SuccessfulUnNotify": "You will no longer be notified for title {{title}}",
"CouldntNotify": "Couldn't notify title {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Проблемы", "Title": "Проблемы",

View file

@ -226,7 +226,12 @@
"MovieRequestQuotaExceeded": "Prekročili ste limit požiadaviek na filmy!", "MovieRequestQuotaExceeded": "Prekročili ste limit požiadaviek na filmy!",
"TvRequestQuotaExceeded": "Prekročili ste limit požiadaviek na epizódy!", "TvRequestQuotaExceeded": "Prekročili ste limit požiadaviek na epizódy!",
"AlbumRequestQuotaExceeded": "Prekročili ste limit požiadaviek na albumy!" "AlbumRequestQuotaExceeded": "Prekročili ste limit požiadaviek na albumy!"
} },
"Notify": "Notify",
"RemoveNotification": "Remove Notifications",
"SuccessfulNotify": "You will now be notified for title {{title}}",
"SuccessfulUnNotify": "You will no longer be notified for title {{title}}",
"CouldntNotify": "Couldn't notify title {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Problémy", "Title": "Problémy",

View file

@ -226,7 +226,12 @@
"MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!", "MovieRequestQuotaExceeded": "You have exceeded your Movie request quota!",
"TvRequestQuotaExceeded": "You have exceeded your Episode request quota!", "TvRequestQuotaExceeded": "You have exceeded your Episode request quota!",
"AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!" "AlbumRequestQuotaExceeded": "You have exceeded your Album request quota!"
} },
"Notify": "Avisering",
"RemoveNotification": "Ta bort avisering",
"SuccessfulNotify": "Du kommer nu att aviseras för titel {{title}}",
"SuccessfulUnNotify": "Du kommer inte längre att aviseras för titel {{title}}",
"CouldntNotify": "Kunde inte avisera för {{title}}"
}, },
"Issues": { "Issues": {
"Title": "Problem", "Title": "Problem",

View file

@ -226,7 +226,12 @@
"MovieRequestQuotaExceeded": "您的电影请求已超出配额!", "MovieRequestQuotaExceeded": "您的电影请求已超出配额!",
"TvRequestQuotaExceeded": "您的剧集请求已超出配额!", "TvRequestQuotaExceeded": "您的剧集请求已超出配额!",
"AlbumRequestQuotaExceeded": "您的专辑请求已超出配额!" "AlbumRequestQuotaExceeded": "您的专辑请求已超出配额!"
} },
"Notify": "通知",
"RemoveNotification": "删除通知",
"SuccessfulNotify": "您将收到标题为 {{title}} 的通知",
"SuccessfulUnNotify": "您将不再收到标题为 {{title}} 的通知",
"CouldntNotify": "无法通知标题 {{title}}"
}, },
"Issues": { "Issues": {
"Title": "问题", "Title": "问题",

View file

@ -110,11 +110,11 @@ describe("Search Tests", () => {
Page.navbar.searchFilter.tvToggle.click(); Page.navbar.searchFilter.tvToggle.click();
cy.wait('@searchResponse'); cy.wait('@searchResponse');
const card = Page.getCard('2710', false); const card = Page.getCard('131927', false);
card.topLevelCard.realHover(); card.topLevelCard.realHover();
card.title.should('have.text', "Dexter: New Blood"); card.title.should('have.text', "Dexter: New Blood");
card.overview.contains('Irish pub'); card.overview.contains('Iron Lake');
card.requestType.contains('TV Show'); card.requestType.contains('TV Show');
card.requestButton.should('exist'); card.requestButton.should('exist');
}); });

View file

@ -1,3 +1,3 @@
{ {
"version": "4.8.0" "version": "4.10.0"
} }