Skip to content

Commit

Permalink
Merge branch 'rc/from-1.9.0.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
Viperinius committed Nov 5, 2024
2 parents 601a7a7 + 302d404 commit 103cbfa
Show file tree
Hide file tree
Showing 9 changed files with 71 additions and 15 deletions.
6 changes: 3 additions & 3 deletions Directory.Build.props
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<Project>
<PropertyGroup>
<Version>1.9.1.0</Version>
<AssemblyVersion>1.9.1.0</AssemblyVersion>
<FileVersion>1.9.1.0</FileVersion>
<Version>1.10.0.0</Version>
<AssemblyVersion>1.10.0.0</AssemblyVersion>
<FileVersion>1.10.0.0</FileVersion>
</PropertyGroup>
</Project>
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ Go to the section `Playlist Configuration` and click on `Add new playlist`. This
- `Spotify ID`: Paste the identifier of the Spotify playlist that you want to import in here.
- `Target Name`: Jellyfin playlist name. Keep this empty if the original name from Spotify should be used.
- `Target User`: If you want to set another user as the playlist owner, select them here.
- `Set To Private`: If you want to limit the playlist visibility to the targeted owner, check this box.
- `Always From Scratch`: If you want to delete and recreate the Jellyfin playlist on each import run, check this box.

> [!WARNING]
Expand All @@ -81,6 +82,7 @@ Following "Spotify ID" formats work:
If you just want to import all playlists of a Spotify user, go to `Users Configuration` and click `Add new user`.
- `Spotify ID`: Paste the identifier of the Spotify user that you want to import in here.
- `Target User`: If you want to set another user as the playlist owner, select them here.
- `Set To Private`: If you want to limit the playlist visibility to the targeted owner, check this box.
- `Only Original Playlists`: If you want to follow all playlists of a user, including collaborative ones, leave this unchecked. If you only want to follow the playlists that the user created, check this box.

The following "Spotify ID" formats work:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ public TargetPlaylistConfiguration()
/// </summary>
public string UserName { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the playlist should be private to the target user.
/// </summary>
public bool IsPrivate { get; set; }

/// <summary>
/// Gets or sets a value indicating whether to delete and recreate the playlist from scratch each run.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public TargetUserConfiguration()
/// </summary>
public string UserName { get; set; }

/// <summary>
/// Gets or sets a value indicating whether the playlist should be private to the target user.
/// </summary>
public bool IsPrivate { get; set; }

/// <summary>
/// Gets or sets a value indicating whether only original playlists should be collected.
/// </summary>
Expand Down
2 changes: 2 additions & 0 deletions Viperinius.Plugin.SpotifyImport/Configuration/configPage.html
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ <h3 class="sectionTitle">Playlist Configuration</h3>
<th class="detailTableHeaderCell" data-priority="persist">Spotify ID</th>
<th class="detailTableHeaderCell" data-priority="persist">Target Name</th>
<th class="detailTableHeaderCell" data-priority="persist">Target User</th>
<th class="detailTableHeaderCell" data-priority="persist">Set To Private</th>
<th class="detailTableHeaderCell" data-priority="persist">Always From Scratch</th>
<th></th>
</thead>
Expand Down Expand Up @@ -122,6 +123,7 @@ <h3 class="sectionTitle">Users Configuration</h3>
<thead>
<th class="detailTableHeaderCell" data-priority="persist">Spotify ID</th>
<th class="detailTableHeaderCell" data-priority="persist">Target User</th>
<th class="detailTableHeaderCell" data-priority="persist">Set To Private</th>
<th class="detailTableHeaderCell" data-priority="persist">Only Original Playlists</th>
<th></th>
</thead>
Expand Down
24 changes: 19 additions & 5 deletions Viperinius.Plugin.SpotifyImport/Configuration/playlistConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ function getRecreateFromScratchHtml(alwaysFromScratch) {
</td>`;
}

function getIsPrivateHtml(isPrivate) {
return `<td class="detailTableBodyCell cellPlaylistIsPrivate">
<label class="emby-checkbox-label">
<input type="checkbox" is="emby-checkbox" ${isPrivate ? 'checked' : ''}/>
<span></span>
</label>
</td>`;
}

function getOnlyOwnHtml(onlyOwn) {
return `<td class="detailTableBodyCell cellPlaylistOnlyOwn">
<label class="emby-checkbox-label">
Expand All @@ -46,11 +55,12 @@ function getOnlyOwnHtml(onlyOwn) {
</td>`;
}

function createPlaylistRowHtml(playlistId, name, user, alwaysFromScratch) {
function createPlaylistRowHtml(playlistId, name, user, isPrivate, alwaysFromScratch) {
const row = `<tr class="detailTableBodyRow detailTableBodyRow-shaded">
${getPlaylistIdElementHtml(playlistId)}
${getNameElementHtml(name)}
${getUserSelectHtml(user)}
${getIsPrivateHtml(isPrivate)}
${getRecreateFromScratchHtml(alwaysFromScratch)}
<td>
<button class="paper-icon-button-light" type="button" onclick="this.closest('tr').remove()">
Expand All @@ -62,10 +72,11 @@ function createPlaylistRowHtml(playlistId, name, user, alwaysFromScratch) {
return row;
}

function createUserRowHtml(spotifyUser, jellyfinUser, onlyOwn) {
function createUserRowHtml(spotifyUser, jellyfinUser, isPrivate, onlyOwn) {
const row = `<tr class="detailTableBodyRow detailTableBodyRow-shaded">
${getPlaylistIdElementHtml(spotifyUser)}
${getUserSelectHtml(jellyfinUser)}
${getIsPrivateHtml(isPrivate)}
${getOnlyOwnHtml(onlyOwn)}
<td>
<button class="paper-icon-button-light" type="button" onclick="this.closest('tr').remove()">
Expand All @@ -87,7 +98,7 @@ function loadPlaylistTable(page, config) {
config.Playlists.forEach(pl => {
const user = users.find(u => u.name === pl.UserName);
const userId = user?.id || '';
rowsHtml += createPlaylistRowHtml(pl.Id, pl.Name, userId, pl.RecreateFromScratch);
rowsHtml += createPlaylistRowHtml(pl.Id, pl.Name, userId, pl.IsPrivate, pl.RecreateFromScratch);
});

tableBody.innerHTML = rowsHtml;
Expand All @@ -113,9 +124,8 @@ function loadUsersTable(page, config) {
const spotifyUser = user.Id;
const jellyfinUser = users.find(u => u.name === user.UserName);
const jellyfinUserId = jellyfinUser?.id || '';
const onlyOwn = user.OnlyOwnPlaylists;

rowsHtml += createUserRowHtml(spotifyUser, jellyfinUserId, onlyOwn);
rowsHtml += createUserRowHtml(spotifyUser, jellyfinUserId, user.IsPrivate, user.OnlyOwnPlaylists);
});

tableBody.innerHTML = rowsHtml;
Expand All @@ -138,12 +148,14 @@ function getPlaylistTableData(page) {
const renameTo = r.querySelector('td.cellPlaylistName').innerText.trim();
const userSelect = r.querySelector('td.cellPlaylistUser > select');
const jellyfinUser = userSelect.options[userSelect.selectedIndex].text.trim();
const isPrivate = r.querySelector('td.cellPlaylistIsPrivate > * input').checked;
const recreateFromScratch = r.querySelector('td.cellPlaylistFromScratch > * input').checked;

return {
Id: spotifyId,
Name: renameTo,
UserName: jellyfinUser,
IsPrivate: isPrivate,
RecreateFromScratch : recreateFromScratch,
};
});
Expand All @@ -164,11 +176,13 @@ function getUsersTableData(page) {
const spotifyUser = r.querySelector('td.cellPlaylistId').innerText.trim();
const userSelect = r.querySelector('td.cellPlaylistUser > select');
const jellyfinUser = userSelect.options[userSelect.selectedIndex].text.trim();
const isPrivate = r.querySelector('td.cellPlaylistIsPrivate > * input').checked;
const onlyOwn = r.querySelector('td.cellPlaylistOnlyOwn > * input').checked;

return {
Id: spotifyUser,
UserName: jellyfinUser,
IsPrivate: isPrivate,
OnlyOwnPlaylists: onlyOwn
};

Expand Down
16 changes: 12 additions & 4 deletions Viperinius.Plugin.SpotifyImport/PlaylistSync.cs
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ public async Task Execute(CancellationToken cancellationToken = default)
Id = targetUser.Id,
Name = string.Empty,
UserName = targetUser.UserName,
IsPrivate = targetUser.IsPrivate,
RecreateFromScratch = false,
};
}
Expand All @@ -89,7 +90,7 @@ public async Task Execute(CancellationToken cancellationToken = default)
jfPlaylistName = providerPlaylist.Name;
}

var playlist = await GetOrCreatePlaylistByName(jfPlaylistName, user, targetConfig.RecreateFromScratch).ConfigureAwait(false);
var playlist = await GetOrCreatePlaylistByName(jfPlaylistName, user, targetConfig.IsPrivate, targetConfig.RecreateFromScratch).ConfigureAwait(false);

if (playlist == null)
{
Expand All @@ -114,6 +115,12 @@ public async Task Execute(CancellationToken cancellationToken = default)
updateReason |= ItemUpdateType.MetadataEdit;
}

if ((!targetConfig.IsPrivate) != playlist.OpenAccess)
{
playlist.OpenAccess = !targetConfig.IsPrivate;
updateReason |= ItemUpdateType.MetadataEdit;
}

if (updateReason != ItemUpdateType.None)
{
await _libraryManager.UpdateItemAsync(playlist, playlist.GetParent(), updateReason, cancellationToken).ConfigureAwait(false);
Expand Down Expand Up @@ -522,7 +529,7 @@ private static bool ItemMatchesTrackInfo(Audio audioItem, ProviderTrackInfo trac
return true;
}

private async Task<Playlist?> GetOrCreatePlaylistByName(string name, User user, bool deleteExistingPlaylist)
private async Task<Playlist?> GetOrCreatePlaylistByName(string name, User user, bool shouldBePrivate, bool deleteExistingPlaylist)
{
var playlists = _playlistManager.GetPlaylists(user.Id);
var playlist = playlists.Where(p => p.Name == name).FirstOrDefault();
Expand All @@ -545,13 +552,14 @@ private static bool ItemMatchesTrackInfo(Audio audioItem, ProviderTrackInfo trac
{
Name = name,
MediaType = MediaType.Audio,
UserId = user.Id
UserId = user.Id,
Public = !shouldBePrivate,
}).ConfigureAwait(false);

if (!string.IsNullOrWhiteSpace(result.Id))
{
playlists = _playlistManager.GetPlaylists(user.Id);
playlist = playlists.Where(p => p.Id.ToString().Replace("-", string.Empty, StringComparison.InvariantCulture) == result.Id).FirstOrDefault();
playlist = playlists.Where(p => p.Id.ToString().Replace("-", string.Empty, StringComparison.InvariantCulture) == result.Id).FirstOrDefault((Playlist?)null);
}
}

Expand Down
14 changes: 12 additions & 2 deletions Viperinius.Plugin.SpotifyImport/Tasks/SpotifyImportTask.cs
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,17 @@ public async Task ExecuteAsync(IProgress<double> progress, CancellationToken can
if (userPlaylists != null)
{
playlistIds.AddRange(userPlaylists);
userPlaylists.ForEach(id => userPlaylistMapping.Add(id, user.Id));
userPlaylists.ForEach(id =>
{
if (userPlaylistMapping.ContainsKey(id))
{
_logger.LogWarning("Found and ignored duplicate playlist id {Id} of user {User}", id, user.Id);
}
else
{
userPlaylistMapping.Add(id, user.Id);
}
});
}
}

Expand Down Expand Up @@ -115,7 +125,7 @@ public IEnumerable<TaskTriggerInfo> GetDefaultTriggers()
{
Type = TaskTriggerInfo.TriggerDaily,
TimeOfDayTicks = TimeSpan.FromHours(3).Ticks,
MaxRuntimeTicks = TimeSpan.FromHours(1).Ticks,
MaxRuntimeTicks = TimeSpan.FromHours(3).Ticks,
}
};
}
Expand Down
12 changes: 11 additions & 1 deletion build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name: "Spotify Import"
guid: "F03D0ADB-289F-4986-BD6F-2468025249B3"
imageUrl: "https://github.com/Viperinius/jellyfin-plugin-spotify-import/raw/master/viperinius-plugin-spotifyimport.png"
version: "1.9.1.0"
version: "1.10.0.0"
targetAbi: "10.9.9.0"
framework: "net8.0"
overview: "This plugin imports playlists from Spotify."
Expand All @@ -18,6 +18,16 @@ artifacts:
changelog: |2-
# Changelog
## [1.10.0.0] - 2024-09-14
### Added
- New option to set playlists as private or public to all users
### Fixed
- Exception if Spotify returns duplicate playlists for a user
## [1.9.1.0] - 2024-09-01
(Internal testing release)
Expand Down

0 comments on commit 103cbfa

Please sign in to comment.