Skip to content

Instantly share code, notes, and snippets.

@Fronix
Last active July 24, 2021 15:57
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save Fronix/92dd3319845e61c7af063db5f6e8961b to your computer and use it in GitHub Desktop.
Save Fronix/92dd3319845e61c7af063db5f6e8961b to your computer and use it in GitHub Desktop.
Script to delete movies that are not downloaded or watched for X months/last played date from Radarr (Outdated)
{
/** OUTDATED **/
//based on https://gist.github.com/ojame/93560d99149124ea2f89b9d78cbcadd1
// RadarrAPI: Go to Radarr and click 'settings' => 'general'.
// TautulliAPI: Go to Tautulli and click 'Settings' => 'Web Interface'
// Open the JavaScript Console in Google Chrome (View => Developer => Javascript Console)
// Past the following in. Hit enter and away you go.
// Alternatively you can use Sources->Snippets in Chrome
//Update 2020-04-08:
// Added possibility to delete movies based on last_played date
// If lastPlayedOlderThan < lastPlayedDate the movie will be deleted (movies with no last_played date don't get deleted)
//Blacklist ids that you still want to keep (these wont get deleted)
const blacklistedRadarrIds = [];
//Configure your API information for Radarr and Tautulli
const radarrUrl = '';
const radarrApiKey = '';
const tautulliUrl = '';
const tautulliApiKey = '';
//The sectionId for your library (Find it in Tautulli>Libraries click on your library and check the url for ?section_id=X)
const tautulliSectionId = '';
//Set to TRUE if you want Radarr to delete these files (they will be unavaiable in Plex after *BE CAREFUL*)
const deleteFiles = false;
// -- These settings decide when movies get deleted -- //
// Only one of these settings can be set at a time
// How many months old a movie has to be to be deleted (example: 5 = all movies older than 5 months get deleted)
// Format: numeric
const monthsSinceAdded = 5; // set to null to disable
// If you want to delete movies based on when it was last played put a date in this setting
// Example: 2019-05-05 = All movies that were last played before this date will be deleted (movies that were never watched don't get deleted)
// Format: YYYY-MM-DD
const lastPlayedOlderThan = null; // set to null to disable
// -- END of these settings decide when movies get deleted -- //
//end of configs
let ids = [];
let _movies = [];
let index = 0;
function getMonthsBetween(date1,date2) {
var roundUpFractionalMonths = false;
var startDate=date1;
var endDate=date2;
var inverse=false;
if(date1>date2)
{
startDate=date2;
endDate=date1;
inverse=true;
}
var yearsDifference=endDate.getFullYear()-startDate.getFullYear();
var monthsDifference=endDate.getMonth()-startDate.getMonth();
var daysDifference=endDate.getDate()-startDate.getDate();
var monthCorrection=0;
if(roundUpFractionalMonths && daysDifference>0)
{
monthCorrection=1;
}
else if(roundUpFractionalMonths!==true && daysDifference<0)
{
monthCorrection=-1;
}
return (inverse?-1:1)*(yearsDifference*12+monthsDifference+monthCorrection);
};
function percentage() {
return `[${(((index + 1) / _movies.length) * 100).toFixed(2)}%]`
}
const deleteMovie = (id) =>
fetch(`${radarrUrl}${id}?deleteFiles=${deleteFiles}&addExclusion=false`, {
method: 'DELETE',
headers: {
'X-Api-Key': radarrApiKey,
},
}).then(() => {
index++;
if(blacklistedRadarrIds.includes(ids[index])){
console.log(`${_movies.find(m => m.id === ids[index]).title} is blacklisted. Skipping`);
deleteMovie(ids[index + 1]);
} else {
if (ids[index]) {
console.log(`${percentage()} Deleting ${_movies.find(m => m.id === ids[index]).title}`);
deleteMovie(ids[index]);
} else {
console.log('Finished deleting movies')
}
}
});
const FAKEdeleteMovie = (id) => {
index++;
if(blacklistedRadarrIds.includes(ids[index])){
console.log(`${_movies.find(m => m.id === ids[index]).title} is blacklisted. Skipping`);
deleteMovie(ids[index + 1]);
} else {
if (ids[index]) {
console.log(`${percentage()} Deleting ${_movies.find(m => m.id === ids[index]).title}`);
deleteMovie(ids[index]);
} else {
console.log('Finished deleting movies')
}
}
}
const getLastPlayedAt = (lpd, mlpd) => {
const lastPlayed = new Date(lpd);
const minLastPlayed = new Date(mlpd);
return lastPlayed < minLastPlayed;
}
const getUnwatchedLibraryMedaInfo = () =>
fetch(`${tautulliUrl}get_library_media_info?apikey=${tautulliApiKey}&section_id=${tautulliSectionId}&length=500`, {
method: 'GET',
}).then(res => res.json()).then(movies => movies.data);
const getRadarrMovies = () =>
fetch(`${radarrUrl}`, {
method: 'GET',
headers: {
'X-Api-Key': radarrApiKey,
}
}).then(res => res.json()).then(movies => movies);
const getAllMovieInfo = () => Promise.all([getRadarrMovies(), getUnwatchedLibraryMedaInfo()]);
const getNiceDate = (date) => new Date(date.getTime() - (date.getTimezoneOffset() * 60000 )).toISOString().split("T")[0];
const longest = (key, array) => Math.max(...array.map(it => it[key].length));
const addNumSpaces = (num) => ' '.repeat(num) + '|';
if(!radarrUrl || !tautulliUrl || !radarrApiKey || !tautulliApiKey) {
alert('You need to setup your urls and api keys')
} else {
getAllMovieInfo().then(([radarrMovies, tatulliMovies]) => {
console.log('Movie info fetched!');
if(!monthsSinceAdded && lastPlayedOlderThan) {
const moviesToDelete = radarrMovies.filter(m => tatulliMovies.find(({ title, last_played }) => m.title === title && last_played !== null && getLastPlayedAt(last_played * 1000, lastPlayedOlderThan)))
.map(m => tatulliMovies.find(({ title }) => m.title === title) && ({...m, last_played: tatulliMovies.find(({ title }) => m.title === title).last_played}));
_movies = moviesToDelete;
ids = _movies.map(x => x.id);
console.log('Sure you want to delete these movies?:');
_movies.map(x => console.log(x.title, x.title.length < longest('title', _movies) ? addNumSpaces(longest('title', _movies) - x.title.length) : addNumSpaces(0), 'Last played:', getNiceDate(new Date(x.last_played * 1000)), '|', `(id: ${x.id})`))
const confirm = window.confirm('Sure you want to delete these movies? (Check console)');
if(confirm) {
console.log('OK deleting..');
if(blacklistedRadarrIds.includes(ids[index])){
console.log(`${_movies.find(m => m.id === ids[index]).title} is blacklisted. Skipping`);
deleteMovie(ids[index + 1]);
} else {
if (ids.length) {
console.log(`${percentage()} Deleting ${_movies.find(m => m.id === ids[index]).title}`);
deleteMovie(ids[index]);
} else {
console.log('There doesn`t seem to be any movies to delete');
}
}
} else {
console.log('Aborted')
}
} else if (monthsSinceAdded && !lastPlayedOlderThan) {
const unwatchedMovies = tatulliMovies.filter(x => !x.play_count)
const neverDownloaded = radarrMovies.filter(m => !m.downloaded && m.status === "released" && getMonthsBetween(new Date(m.added), new Date()) >= monthsSinceAdded);
const neverWatched = radarrMovies.filter(m => unwatchedMovies.find(({ title, added_at }) => m.title === title && getMonthsBetween(new Date(added_at * 1000), new Date()) >= monthsSinceAdded));
_movies = neverDownloaded.concat(neverWatched);
ids = _movies.map(x => x.id);
console.log('Sure you want to delete these movies?:');
_movies.map(x => console.log(x.title, x.title.length < longest('title', _movies) ? addNumSpaces(longest('title', _movies) - x.title.length) : addNumSpaces(0), 'added', getMonthsBetween(new Date(x.added), new Date()), 'months ago', `(id: ${x.id})`))
const confirm = window.confirm('Sure you want to delete these movies? (Check console)');
if(confirm) {
console.log('OK deleting..');
if(blacklistedRadarrIds.includes(ids[index])){
console.log(`${_movies.find(m => m.id === ids[index]).title} is blacklisted. Skipping`);
deleteMovie(ids[index + 1]);
} else {
if (ids.length) {
console.log(`${percentage()} Deleting ${_movies.find(m => m.id === ids[index]).title}`);
deleteMovie(ids[index]);
} else {
console.log('There doesn`t seem to be any movies to delete');
}
}
} else {
console.log('Aborted')
}
} else {
console.log('You have not set either monthsSinceAdded or lastPlayedOlderThan, choose one option')
return;
}
});
}
}
@eretromicin
Copy link

Hi there, thank you for uploading this script! i was wondering if you could help me out a bit, i am having some issues and am not well versed in javascript.

This is the error i am receiving, i have the URL as http://127.0.0.1:8181 because it is on the local machine, I also tried doing it on a separate machine with the local ip and same issue. Any suggestions?

Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'Window': Failed to parse URL from http://127.0.0.1:8181 get_library_media_info?apikey=xxxxxxxxxxxxxxx&section_id=1&length=500
at :1:876
at getUnwatchedLibraryMedaInfo (:111:4)
at getAllMovieInfo (:123:65)
at :132:3

@Fronix
Copy link
Author

Fronix commented May 7, 2020

Hi there, thank you for uploading this script! i was wondering if you could help me out a bit, i am having some issues and am not well versed in javascript.

This is the error i am receiving, i have the URL as http://127.0.0.1:8181 because it is on the local machine, I also tried doing it on a separate machine with the local ip and same issue. Any suggestions?

Uncaught (in promise) TypeError: Failed to execute 'fetch' on 'Window': Failed to parse URL from http://127.0.0.1:8181 get_library_media_info?apikey=xxxxxxxxxxxxxxx&section_id=1&length=500
at :1:876
at getUnwatchedLibraryMedaInfo (:111:4)
at getAllMovieInfo (:123:65)
at :132:3

Hey no problem :) First off I want to warn you that you use this script at your own risk. If you're not that good at JavasScript my suggestion is that you don't use the script, you can easily delete all your movies in one fell swoop if you make a mistake.

To answer your question:
Make sure you don't have a \n (new line) character in your API key configs or that you haven't gotten some nasty new lines when copying the code. If that isn't the problem, i'd suggest trying another browser/updating your browser.

@Fronix
Copy link
Author

Fronix commented May 7, 2020

@eretromicin on second thought it looks like you've missed a / in your url :) Try replacing you apiUrls from http://127.0.0.1:8181 to http://127.0.0.1:8181/

@thefarelkid
Copy link

Is there a way to make this script run if you don't have Tautulli?

@thefarelkid
Copy link

Scratch that. Radarr has a movie editor button that I wasn't previously aware of. That took care of the bulk delete!

@douginoz
Copy link

Doesn't seem to work (now).

Tautulli Version V2.5.3
Ubuntu Version 20.04 server
Radarr Version 0.2.0.1504
Mono Version 6.10.0.104

I pasted in exactly the code and inserted the url and api keys:

const radarrUrl = 'http://192.168.1.20:7878/';
const radarrApiKey = '<key>';
const tautulliUrl = 'http://192.168.1.21:8181/';
const tautulliApiKey = '<key>';

as well as added the sectionID:
const tautulliSectionId = '2';

When I run it in the Google Chrome console I get:


Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0       192.168.1.20:7878/
(vm679 140, which is 'getAllMovieInfo().then(([radarrMovies, tatulliMovies]) => {')
Access to fetch at 'http://192.168.1.21:8181/get_library_media_info?apikey=<key>&section_id=2&length=500' from origin 'http://192.168.1.20:7878' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

@Fronix
Copy link
Author

Fronix commented Jul 30, 2020

Doesn't seem to work (now).

Tautulli Version V2.5.3
Ubuntu Version 20.04 server
Radarr Version 0.2.0.1504
Mono Version 6.10.0.104

I pasted in exactly the code and inserted the url and api keys:

const radarrUrl = 'http://192.168.1.20:7878/';
const radarrApiKey = '<key>';
const tautulliUrl = 'http://192.168.1.21:8181/';
const tautulliApiKey = '<key>';

as well as added the sectionID:
const tautulliSectionId = '2';

When I run it in the Google Chrome console I get:


Uncaught (in promise) SyntaxError: Unexpected token < in JSON at position 0       192.168.1.20:7878/
(vm679 140, which is 'getAllMovieInfo().then(([radarrMovies, tatulliMovies]) => {')
Access to fetch at 'http://192.168.1.21:8181/get_library_media_info?apikey=<key>&section_id=2&length=500' from origin 'http://192.168.1.20:7878' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Try using the URL that you use to access tautulli and radarr, you are being blocked by your webbrowser's CORS policy which is the reason the script is failing. If neither tatulli or radarr is public you can try enabling CORS (see here https://enable-cors.org/index.html for info on how to). But only do this if radarr and tautulli is NOT accessible from outside your network.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment