From f91a26befeb4d676a81e9be467510a002c628041 Mon Sep 17 00:00:00 2001 From: fallenbagel <98979876+fallenbagel@users.noreply.github.com> Date: Tue, 6 Jan 2026 00:18:06 +0500 Subject: [PATCH] fix(servarr): replace spaces in arr user tags with `-` (#2231) * fix: sanitize disallowed characters in arr tags Updates the tag creation to normalize diacritics, replace spaces with hyphens and stip any non-alphanumeric characters from display name fix #2229, fix #1897 * refactor: improve display name sanitization in tag creation * fix: include displayName in user selection for tag migration * fix(migrator): retrieve all user fields in tag migration This is a one time migration so performance is neglible. This should trigger the @AfterLoad hooks which sets the `displayName` --- .../migrations/0007_migrate_arr_tags.ts | 38 ++++++++++++++----- server/subscriber/MediaRequestSubscriber.ts | 26 +++++++++++-- 2 files changed, 51 insertions(+), 13 deletions(-) diff --git a/server/lib/settings/migrations/0007_migrate_arr_tags.ts b/server/lib/settings/migrations/0007_migrate_arr_tags.ts index 25911f93..3b4ddf5d 100644 --- a/server/lib/settings/migrations/0007_migrate_arr_tags.ts +++ b/server/lib/settings/migrations/0007_migrate_arr_tags.ts @@ -13,9 +13,7 @@ const migrationArrTags = async (settings: any): Promise => { } const userRepository = getRepository(User); - const users = await userRepository.find({ - select: ['id'], - }); + const users = await userRepository.find(); let errorOccurred = false; @@ -30,15 +28,26 @@ const migrationArrTags = async (settings: any): Promise => { }); const radarrTags = await radarr.getTags(); for (const user of users) { - const userTag = radarrTags.find((v) => - v.label.startsWith(user.id + ' - ') + const userTag = radarrTags.find( + (v) => + v.label.startsWith(user.id + ' - ') || + v.label.startsWith(user.id + '-') ); if (!userTag) { continue; } await radarr.renameTag({ id: userTag.id, - label: userTag.label.replace(`${user.id} - `, `${user.id}-`), + label: + user.id + + '-' + + user.displayName + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .replace(/\s+/g, '-') + .replace(/[^a-z0-9-]/gi, '') + .replace(/-+/g, '-') + .replace(/^-|-$/g, ''), }); } } catch (error) { @@ -61,15 +70,26 @@ const migrationArrTags = async (settings: any): Promise => { }); const sonarrTags = await sonarr.getTags(); for (const user of users) { - const userTag = sonarrTags.find((v) => - v.label.startsWith(user.id + ' - ') + const userTag = sonarrTags.find( + (v) => + v.label.startsWith(user.id + ' - ') || + v.label.startsWith(user.id + '-') ); if (!userTag) { continue; } await sonarr.renameTag({ id: userTag.id, - label: userTag.label.replace(`${user.id} - `, `${user.id}-`), + label: + user.id + + '-' + + user.displayName + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .replace(/\s+/g, '-') + .replace(/[^a-z0-9-]/gi, '') + .replace(/-+/g, '-') + .replace(/^-|-$/g, ''), }); } } catch (error) { diff --git a/server/subscriber/MediaRequestSubscriber.ts b/server/subscriber/MediaRequestSubscriber.ts index 5c1b9bb0..251ee47e 100644 --- a/server/subscriber/MediaRequestSubscriber.ts +++ b/server/subscriber/MediaRequestSubscriber.ts @@ -29,6 +29,16 @@ import type { } from 'typeorm'; import { EventSubscriber } from 'typeorm'; +const sanitizeDisplayName = (displayName: string): string => { + return displayName + .normalize('NFD') + .replace(/[\u0300-\u036f]/g, '') + .replace(/\s+/g, '-') + .replace(/[^a-z0-9-]/gi, '') + .replace(/-+/g, '-') + .replace(/^-|-$/g, ''); +}; + @EventSubscriber() export class MediaRequestSubscriber implements EntitySubscriberInterface @@ -310,11 +320,15 @@ export class MediaRequestSubscriber mediaId: entity.media.id, userId: entity.requestedBy.id, newTag: - entity.requestedBy.id + '-' + entity.requestedBy.displayName, + entity.requestedBy.id + + '-' + + sanitizeDisplayName(entity.requestedBy.displayName), }); userTag = await radarr.createTag({ label: - entity.requestedBy.id + '-' + entity.requestedBy.displayName, + entity.requestedBy.id + + '-' + + sanitizeDisplayName(entity.requestedBy.displayName), }); } if (userTag.id) { @@ -631,11 +645,15 @@ export class MediaRequestSubscriber mediaId: entity.media.id, userId: entity.requestedBy.id, newTag: - entity.requestedBy.id + '-' + entity.requestedBy.displayName, + entity.requestedBy.id + + '-' + + sanitizeDisplayName(entity.requestedBy.displayName), }); userTag = await sonarr.createTag({ label: - entity.requestedBy.id + '-' + entity.requestedBy.displayName, + entity.requestedBy.id + + '-' + + sanitizeDisplayName(entity.requestedBy.displayName), }); } if (userTag.id) {