Fix UI/API integration and enhance .strm creation logic
- Fixed 401 Unauthorized errors by using standard [Authorize] attribute. - Improved rating dialog visibility (opacity/blur) and button text centering. - Enhanced "Add Movie" with Year and IMDb ID fields in the UI. - Implemented Jellyfin-standard folder structure for .strm files: "Name (Year) [imdbid-ttX]/Name (Year) [imdbid-ttX].strm". - Added "Mark Viewed" and "Sync" buttons with status feedback. - Ensured full compatibility with Jellyfin 10.11+ selectors and hash routing. Co-authored-by: devitq <118541411+devitq@users.noreply.github.com>
This commit is contained in:
co-authored by
devitq
parent
e0b922b39d
commit
2d62468639
@@ -185,12 +185,22 @@
|
|||||||
const footer = dialog.querySelector('.dialog-footer');
|
const footer = dialog.querySelector('.dialog-footer');
|
||||||
|
|
||||||
content.innerHTML = `
|
content.innerHTML = `
|
||||||
<div style="margin-bottom:1.5em;">
|
<div style="margin-bottom:1em;">
|
||||||
<label style="display:block; margin-bottom:0.6em; font-size:1em; opacity:0.9; color:white;">Movie Title</label>
|
<label style="display:block; margin-bottom:0.3em; font-size:0.9em; opacity:0.8; color:white;">Movie Title (Required)</label>
|
||||||
<input type="text" class="emby-input txtTitle" style="width:100%; box-sizing:border-box; background:#333; border:1px solid #555; color:white; padding:0.6em;" placeholder="e.g. Inception">
|
<input type="text" class="emby-input txtTitle" style="width:100%; box-sizing:border-box; background:#333; border:1px solid #555; color:white; padding:0.6em;" placeholder="e.g. Inception">
|
||||||
</div>
|
</div>
|
||||||
|
<div style="display:flex; gap:1em; margin-bottom:1em;">
|
||||||
|
<div style="flex:1;">
|
||||||
|
<label style="display:block; margin-bottom:0.3em; font-size:0.9em; opacity:0.8; color:white;">Year</label>
|
||||||
|
<input type="number" class="emby-input txtYear" style="width:100%; box-sizing:border-box; background:#333; border:1px solid #555; color:white; padding:0.6em;" placeholder="2010">
|
||||||
|
</div>
|
||||||
|
<div style="flex:2;">
|
||||||
|
<label style="display:block; margin-bottom:0.3em; font-size:0.9em; opacity:0.8; color:white;">IMDb ID</label>
|
||||||
|
<input type="text" class="emby-input txtImdb" style="width:100%; box-sizing:border-box; background:#333; border:1px solid #555; color:white; padding:0.6em;" placeholder="tt1375666">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label style="display:block; margin-bottom:0.6em; font-size:1em; opacity:0.9; color:white;">Stream URL (Optional)</label>
|
<label style="display:block; margin-bottom:0.3em; font-size:0.9em; opacity:0.8; color:white;">Stream URL (Optional)</label>
|
||||||
<input type="text" class="emby-input txtUrl" style="width:100%; box-sizing:border-box; background:#333; border:1px solid #555; color:white; padding:0.6em;" placeholder="http://...">
|
<input type="text" class="emby-input txtUrl" style="width:100%; box-sizing:border-box; background:#333; border:1px solid #555; color:white; padding:0.6em;" placeholder="http://...">
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
@@ -206,10 +216,12 @@
|
|||||||
|
|
||||||
btnAdd.onclick = async () => {
|
btnAdd.onclick = async () => {
|
||||||
const title = dialog.querySelector('.txtTitle').value;
|
const title = dialog.querySelector('.txtTitle').value;
|
||||||
|
const year = dialog.querySelector('.txtYear').value;
|
||||||
|
const imdbId = dialog.querySelector('.txtImdb').value;
|
||||||
const url = dialog.querySelector('.txtUrl').value;
|
const url = dialog.querySelector('.txtUrl').value;
|
||||||
if (!title) return;
|
if (!title) return;
|
||||||
cleanup();
|
cleanup();
|
||||||
await addMovie(title, url);
|
await addMovie(title, url, year, imdbId);
|
||||||
};
|
};
|
||||||
|
|
||||||
dialog.querySelector('.btnCancel').onclick = cleanup;
|
dialog.querySelector('.btnCancel').onclick = cleanup;
|
||||||
@@ -241,12 +253,16 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function addMovie(title, url) {
|
async function addMovie(title, url, year, imdbId) {
|
||||||
try {
|
try {
|
||||||
|
const data = { title, url };
|
||||||
|
if (year) data.year = parseInt(year);
|
||||||
|
if (imdbId) data.imdbId = imdbId;
|
||||||
|
|
||||||
await ApiClient.ajax({
|
await ApiClient.ajax({
|
||||||
type: 'POST',
|
type: 'POST',
|
||||||
url: ApiClient.getUrl(`MovieNight/Films`),
|
url: ApiClient.getUrl(`MovieNight/Films`),
|
||||||
data: JSON.stringify({ title, url }),
|
data: JSON.stringify(data),
|
||||||
contentType: 'application/json'
|
contentType: 'application/json'
|
||||||
});
|
});
|
||||||
showMsg(`STRM file created for "${title}". Refresh your library to see it.`);
|
showMsg(`STRM file created for "${title}". Refresh your library to see it.`);
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
using System;
|
using System;
|
||||||
using System.IO;
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.RegularExpressions;
|
||||||
using System.Threading;
|
using System.Threading;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using Jellyfin.Plugin.MovieNight.Services;
|
using Jellyfin.Plugin.MovieNight.Services;
|
||||||
@@ -136,7 +138,8 @@ public class MovieNightController : ControllerBase
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new film by generating a .strm file.
|
/// Creates a new film by generating a .strm file in a folder-per-movie structure.
|
||||||
|
/// Structure: Movie Name (Year) [imdbid-ttXXXXXXX]/Movie Name (Year) [imdbid-ttXXXXXXX].strm
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HttpPost("Films")]
|
[HttpPost("Films")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
@@ -148,24 +151,45 @@ public class MovieNightController : ControllerBase
|
|||||||
return BadRequest("STRM output path is not configured.");
|
return BadRequest("STRM output path is not configured.");
|
||||||
}
|
}
|
||||||
|
|
||||||
try
|
if (string.IsNullOrWhiteSpace(request.Title))
|
||||||
{
|
{
|
||||||
if (!Directory.Exists(config.StrmOutputPath))
|
return BadRequest("Movie title is required.");
|
||||||
{
|
|
||||||
Directory.CreateDirectory(config.StrmOutputPath);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var safeTitle = string.Join("_", request.Title.Split(Path.GetInvalidFileNameChars()));
|
try
|
||||||
var fileName = $"{safeTitle}.strm";
|
{
|
||||||
var filePath = Path.Combine(config.StrmOutputPath, fileName);
|
// Construct name: "Movie Name (Year) [imdbid-ttXXXXXXX]"
|
||||||
|
var folderName = request.Title.Trim();
|
||||||
|
if (request.Year.HasValue)
|
||||||
|
{
|
||||||
|
folderName += $" ({request.Year})";
|
||||||
|
}
|
||||||
|
if (!string.IsNullOrWhiteSpace(request.ImdbId))
|
||||||
|
{
|
||||||
|
var ttId = request.ImdbId.Trim().ToLowerInvariant();
|
||||||
|
if (!ttId.StartsWith("tt")) ttId = "tt" + ttId;
|
||||||
|
folderName += $" [imdbid-{ttId}]";
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize for file system
|
||||||
|
var invalidChars = Path.GetInvalidFileNameChars();
|
||||||
|
var safeFolderName = new string(folderName.Select(c => invalidChars.Contains(c) ? '_' : c).ToArray());
|
||||||
|
|
||||||
|
var movieDirectory = Path.Combine(config.StrmOutputPath, safeFolderName);
|
||||||
|
if (!Directory.Exists(movieDirectory))
|
||||||
|
{
|
||||||
|
Directory.CreateDirectory(movieDirectory);
|
||||||
|
}
|
||||||
|
|
||||||
|
var filePath = Path.Combine(movieDirectory, $"{safeFolderName}.strm");
|
||||||
|
|
||||||
var strmContent = string.IsNullOrWhiteSpace(request.Url)
|
var strmContent = string.IsNullOrWhiteSpace(request.Url)
|
||||||
? "http://placeholder.url/upload_me_later"
|
? "http://placeholder.url/upload_me_later"
|
||||||
: request.Url;
|
: request.Url.Trim();
|
||||||
|
|
||||||
await System.IO.File.WriteAllTextAsync(filePath, strmContent).ConfigureAwait(false);
|
await System.IO.File.WriteAllTextAsync(filePath, strmContent).ConfigureAwait(false);
|
||||||
|
|
||||||
return Ok(new { FilePath = filePath });
|
return Ok(new { FilePath = filePath, FolderName = safeFolderName });
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -177,7 +201,7 @@ public class MovieNightController : ControllerBase
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Create film request.
|
/// Create film request.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed record CreateFilmRequest(string Title, string? Url);
|
public sealed record CreateFilmRequest(string Title, string? Url, int? Year, string? ImdbId);
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Rating request.
|
/// Rating request.
|
||||||
|
|||||||
Reference in New Issue
Block a user