Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@

## 💬 Discord Server

**Before anything else, I invite you to join my Discord server for faster help, discussions, and important information such as an organizd list of known bugs that are currently being tracker or planned features scheduled for future releases!**
**Before anything else, I invite you to join my Discord server for faster assistance, discussions, and important information such as an organized list of known bugs that are currently being tracked or planned features scheduled for future releases!**

##### This helps avoid duplicate requests and keeps everyone informed about what's coming next!

Expand All @@ -34,14 +34,14 @@ I also have a dedicated channel on the [r/JellyfinCommunity](https://discord.gg/
- **🔍 Media Search**: Search for movies and TV shows with `/search` command - you can then request it later within the message embed
- **🔥 Trending Content**: Browse weekly trending movies and TV shows with `/trending` command
- **📤 One-Click Requests**: Directly request media to Jellyseerr with `/request` command
- **📺 Smart TV Handling**: Choose specific seasons when searching for TV series using `/search`, or request all the seasons at once with `/request`
- **📺 Smart TV Handling**: Choose specific seasons when searching for TV series using `/search`, or request all seasons at once with `/request`
- **🎚️ Server and Quality**: Choose which Radarr or Sonarr instance to request to, and which quality profile
- **🚫 Duplicate Detection**: Automatically checks if content already exists in Jellyseerr before allowing requests
- **🚫 Duplicate Detection**: Automatically check if content already exists in Jellyseerr before allowing requests
- **🏷️ Tag Selection**: Select Radarr/Sonarr tags when requesting media for better organization and categorization
- **📬 Jellyfin Notifications**: Automatic Discord notifications when new media is added to your library
- **📚 Library Filtering and Mapping**: Choose which Jellyfin libraries send Discord notifications and on what channel
- **📚 Library Filtering and Mapping**: Choose which Jellyfin libraries send notifications and to which Discord channel
- **👤 User Mapping**: Map Discord users to Jellyseerr accounts so requests appear from the correct user
- **🔐 Role-Based Permissions**: Control who can use bot commands through Discord roles (allowlist/blocklist)
- **🔐 Role-Based Permissions**: Control which users can use bot commands via Discord roles (allowlist/blocklist)
- **🔔 Private Notifications**: Optional PM when your requested content becomes available on Jellyfin
- **👻 Ephemeral Mode**: Make bot responses visible only to the command user
- **🌍 Multi-Language Support**: Fully translated interface with automatic language detection
Expand Down
26 changes: 26 additions & 0 deletions anchorr.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?xml version="1.0"?>
<Container version="2">
<Name>Anchorr</Name>
<Repository>nairdah/anchorr:latest</Repository>
<Registry>https://hub.docker.com/r/nairdah/anchorr/</Registry>
<Network>bridge</Network>
<MyIP/>
<Shell>sh</Shell>
<Privileged>false</Privileged>
<Support>https://forums.unraid.net/topic/196118-support-anchorr-discord-bot-for-requesting-moviestv-and-receiving-notifications-when-items-are-added-to-your-media-server/</Support>
<Project>https://github.com/nairdahh/Anchorr</Project>
<Overview>Discord bot for requesting movies/TV and receiving notifications when items are added to your Jellyfin server.</Overview>
<Category>Tools:</Category>
<WebUI>http://[IP]:8282</WebUI>
<TemplateURL/>
<Icon>https://raw.githubusercontent.com/nairdahh/Anchorr/refs/heads/main/assets/logo.png</Icon>
<ExtraParams/>
<PostArgs/>
<CPUset/>
<DonateText/>
<DonateLink/>
<Requires/>
<Config Name="Host Path 1" Target="/config" Default="" Mode="rw" Description="" Type="Path" Display="always" Required="false" Mask="false">/mnt/user/appdata/anchorr</Config>
<Config Name="Host Port 1" Target="8282" Default="8282" Mode="tcp" Description="Web UI port" Type="Port" Display="always" Required="false" Mask="false">8282</Config>
<TailscaleStateDir/>
</Container>
103 changes: 61 additions & 42 deletions jellyfinWebhook.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,15 @@ import logger from "./utils/logger.js";
import { fetchOMDbData } from "./api/omdb.js";
import { findBestBackdrop } from "./api/tmdb.js";

function isValidUrl(string) {
try {
new URL(string);
return true;
} catch (_) {
return false;
}
}

const debouncedSenders = new Map();
const sentNotifications = new Map();
const episodeMessages = new Map(); // Track Discord messages for editing: SeriesId -> { messageId, channelId }
Expand Down Expand Up @@ -411,7 +420,7 @@ async function processAndSendNotification(
"web/index.html",
`!/details?id=${ItemId}&serverId=${ServerId}`
);
if (jellyfinUrl && (jellyfinUrl.startsWith("http://") || jellyfinUrl.startsWith("https://"))) {
if (isValidUrl(jellyfinUrl)) {
embed.setURL(jellyfinUrl);
}

Expand Down Expand Up @@ -515,37 +524,46 @@ async function processAndSendNotification(

if (imdbId) {
if (showButtonLetterboxd) {
buttonComponents.push(
new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setLabel("Letterboxd")
.setURL(`https://letterboxd.com/imdb/${imdbId}`)
);
const letterboxdUrl = `https://letterboxd.com/imdb/${imdbId}`;
if (isValidUrl(letterboxdUrl)) {
buttonComponents.push(
new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setLabel("Letterboxd")
.setURL(letterboxdUrl)
);
}
}

if (showButtonImdb) {
buttonComponents.push(
new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setLabel("IMDb")
.setURL(`https://www.imdb.com/title/${imdbId}/`)
);
const imdbUrl = `https://www.imdb.com/title/${imdbId}/`;
if (isValidUrl(imdbUrl)) {
buttonComponents.push(
new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setLabel("IMDb")
.setURL(imdbUrl)
);
}
}
}

if (showButtonWatch) {
buttonComponents.push(
new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setLabel("▶ Watch Now!")
.setURL(
buildJellyfinUrl(
ServerUrl,
"web/index.html",
`!/details?id=${ItemId}&serverId=${ServerId}`
)
)
const watchUrl = buildJellyfinUrl(
ServerUrl,
"web/index.html",
`!/details?id=${ItemId}&serverId=${ServerId}`
);
if (isValidUrl(watchUrl)) {
buttonComponents.push(
new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setLabel("▶ Watch Now!")
.setURL(watchUrl)
);
} else {
logger.warn(`Invalid watch URL generated: ${watchUrl}. Skipping watch button.`);
}
}

const buttons = buttonComponents.length > 0 ? new ActionRowBuilder().addComponents(buttonComponents) : null;
Expand Down Expand Up @@ -675,14 +693,17 @@ async function processAndSendNotification(
const user = await client.users.fetch(userId);
const dmEmbed = new EmbedBuilder()
.setAuthor({ name: "✅ Your request is now available!" })
.setTitle(embedTitle)
.setURL(
buildJellyfinUrl(
ServerUrl,
"web/index.html",
`!/details?id=${ItemId}&serverId=${ServerId}`
)
)
.setTitle(embedTitle);

const dmJellyfinUrl = buildJellyfinUrl(
ServerUrl,
"web/index.html",
`!/details?id=${ItemId}&serverId=${ServerId}`
);
if (isValidUrl(dmJellyfinUrl)) {
dmEmbed.setURL(dmJellyfinUrl);
}
dmEmbed
.setColor(process.env.EMBED_COLOR_SUCCESS || "#a6e3a1")
.setDescription(
`${
Expand All @@ -700,20 +721,18 @@ async function processAndSendNotification(
dmEmbed.setImage(backdropUrl);
}

const dmButtons = new ActionRowBuilder().addComponents(
const dmButtons = isValidUrl(dmJellyfinUrl) ? new ActionRowBuilder().addComponents(
new ButtonBuilder()
.setStyle(ButtonStyle.Link)
.setLabel("▶ Watch Now!")
.setURL(
buildJellyfinUrl(
ServerUrl,
"web/index.html",
`!/details?id=${ItemId}&serverId=${ServerId}`
)
)
);
.setURL(dmJellyfinUrl)
) : null;

await user.send({ embeds: [dmEmbed], components: [dmButtons] });
const messageOptions = { embeds: [dmEmbed] };
if (dmButtons) {
messageOptions.components = [dmButtons];
}
await user.send(messageOptions);
logger.info(`Sent DM notification to user ${userId} for ${embedTitle}`);
} catch (err) {
logger.error(
Expand Down
12 changes: 6 additions & 6 deletions locales/de.json
Original file line number Diff line number Diff line change
Expand Up @@ -265,12 +265,12 @@
"title": "Über Anchorr",
"description": "Anchorr ist eine selbst gehostete, leichtgewichtige Brücke, die deinen Jellyfin-Medienserver mit Discord verbindet. Sie benachrichtigt dich in einem dedizierten Kanal, wenn neue Medien zu deiner Bibliothek hinzugefügt werden, und mit Jellyseerr-Integration kannst du Filme und Serien direkt von Discord aus suchen und anfordern.",
"features_title": "Funktionen",
"feature_search": "/search <title> Suche nach einem Film oder einer Serie mit automatischen Vorschlägen. Ein Embed mit detaillierten Informationen wird angezeigt, über das du die Medien anfordern kannst. Bei TV-Serien hast du die Option, die gewünschten Staffeln für die Anfrage auszuwählen.",
"feature_request": "/request <title> Sende sofort eine Anfrage für deinen gewählten Film oder deine Serie direkt an Jellyseerr. Bei TV-Serien werden automatisch alle Staffeln angefragt.",
"feature_notifications": "Erhalte schön formatierte Benachrichtigungen auf deinem ausgewählten Discord-Kanal, wann immer neue Medien zu deiner Jellyfin-Bibliothek hinzugefügt werden.",
"feature_databases": "Mit den TMDB- und OMDb-Datenbanken erhalten Discord-Nachrichten ein schönes und gut strukturiertes Aussehen mit wichtigen Informationen über die angeforderten oder hinzugefügten Medieninhalte.",
"feature_permissions": "Kontrolliere Berechtigungen mit rollenbasiertem Zugang (Erlaubnis-/Sperrliste), leite Benachrichtigungen an verschiedene Kanäle nach Jellyfin-Bibliothek weiter und verknüpfe Benutzer mit Jellyseerr-Konten für genaue Anfragenverfolgung.",
"feature_direct_messages": "Erhalte direkte Nachrichten auf Discord, wenn angefragter Inhalt in deiner Bibliothek verfügbar wird. Verpasse nie, wenn deine Anfragen bereit sind!",
"feature_search": "<code>/search &lt;title&gt;</code> Suche nach einem Film oder einer Serie mit automatischen Vorschlägen. Ein Embed mit detaillierten Informationen wird angezeigt, über das du die Medien anfordern kannst. Bei <strong style=\"color: var(--blue);\">TV-Serien</strong> hast du die Option, die gewünschten Staffeln für die Anfrage auszuwählen.",
"feature_request": "<code>/request &lt;title&gt;</code> Sende sofort eine Anfrage für deinen gewählten Film oder deine Serie direkt an <strong style=\"color: var(--blue);\">Jellyseerr</strong>. Bei <strong style=\"color: var(--blue);\">TV-Serien</strong> werden automatisch alle Staffeln angefragt.",
"feature_notifications": "Erhalte schön formatierte Benachrichtigungen auf deinem ausgewählten <strong style=\"color: var(--blue);\">Discord</strong>-Kanal, wann immer neue Medien zu deiner <strong style=\"color: var(--blue);\">Jellyfin</strong>-Bibliothek hinzugefügt werden.",
"feature_databases": "Mit den <strong style=\"color: var(--blue);\">TMDB</strong>- und <strong style=\"color: var(--blue);\">OMDb</strong>-Datenbanken erhalten <strong style=\"color: var(--blue);\">Discord</strong>-Nachrichten ein schönes und gut strukturiertes Aussehen mit wichtigen Informationen über die angeforderten oder hinzugefügten Medieninhalte.",
"feature_permissions": "Kontrolliere Berechtigungen mit <strong style=\"color: var(--blue);\">rollenbasierendem Zugang</strong> (Erlaubnis-/Sperrliste), leite Benachrichtigungen an verschiedene Kanäle nach <strong style=\"color: var(--blue);\">Jellyfin-Bibliothek</strong> weiter und verknüpfe Benutzer mit <strong style=\"color: var(--blue);\">Jellyseerr</strong>-Konten für genaue Anfragenverfolgung.",
"feature_direct_messages": "Erhalte <strong style=\"color: var(--blue);\">direkte Nachrichten</strong> auf <strong style=\"color: var(--blue);\">Discord</strong>, wenn angefragter Inhalt in deiner Bibliothek verfügbar wird. Verpasse nie, wenn deine Anfragen bereit sind!",
"support_title": "Projekt unterstützen",
"support_description": "Wenn du Anchorr nützlich findest, erwäge, seine Entwicklung zu unterstützen. Jeder Kaffee hilft, nächtliche Coding-Sessions anzutreiben!",
"contribute_title": "Mitwirken",
Expand Down
12 changes: 6 additions & 6 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -215,12 +215,12 @@
"title": "About Anchorr",
"description": "Anchorr is a self-hosted, lightweight bridge that connects your Jellyfin media server with Discord. It notifies you on a dedicated channel when new media is added to your library, and with Jellyseerr integration, you can search and request movies and shows directly from Discord.",
"features_title": "Features",
"feature_search": "/search <title> Search for a movie or show with auto-suggestions. An embed with detailed information is shown, from which you can request the media. For TV shows, you have the option to select the desired seasons for the request.",
"feature_request": "/request <title> Instantly send a request for your chosen movie or show directly to Jellyseerr. For TV shows, this automatically requests all seasons.",
"feature_notifications": "Receive beautifully formatted notifications on your selected Discord channel whenever new media is added to your Jellyfin library.",
"feature_databases": "Using the TMDB and OMDb databases, Discord messages get a beautiful and well-structured look, with essential information about the requested or added media content.",
"feature_permissions": "Control permissions with role-based access (allowlist/blocklist), route notifications to different channels by Jellyfin library, and link users to Jellyseerr accounts for accurate request tracking.",
"feature_direct_messages": "Get direct messages on Discord when requested content becomes available in your library. Never miss when your requests are ready!",
"feature_search": "<code>/search &lt;title&gt;</code> Search for a movie or show with auto-suggestions. An embed with detailed information is shown, from which you can request the media. For <strong style=\"color: var(--blue);\">TV shows</strong>, you have the option to select the desired seasons for the request.",
"feature_request": "<code>/request &lt;title&gt;</code> Instantly send a request for your chosen movie or show directly to <strong style=\"color: var(--blue);\">Jellyseerr</strong>. For <strong style=\"color: var(--blue);\">TV shows</strong>, this automatically requests all seasons.",
"feature_notifications": "Receive beautifully formatted notifications on your selected <strong style=\"color: var(--blue);\">Discord</strong> channel whenever new media is added to your <strong style=\"color: var(--blue);\">Jellyfin</strong> library.",
"feature_databases": "Using the <strong style=\"color: var(--blue);\">TMDB</strong> and <strong style=\"color: var(--blue);\">OMDb</strong> databases, <strong style=\"color: var(--blue);\">Discord</strong> messages get a beautiful and well-structured look, with essential information about the requested or added media content.",
"feature_permissions": "Control permissions with <strong style=\"color: var(--blue);\">role-based access</strong> (allowlist/blocklist), route notifications to different channels by <strong style=\"color: var(--blue);\">Jellyfin library</strong>, and link users to <strong style=\"color: var(--blue);\">Jellyseerr</strong> accounts for accurate request tracking.",
"feature_direct_messages": "Get <strong style=\"color: var(--blue);\">direct messages</strong> on <strong style=\"color: var(--blue);\">Discord</strong> when requested content becomes available in your library. Never miss when your requests are ready!",
"support_title": "Support the Project",
"support_description": "If you find Anchorr useful, consider supporting its development. Every coffee helps fuel late-night coding sessions!",
"contribute_title": "Contribute",
Expand Down
Loading