parent
c900b45190
commit
8eb6149d35
@ -0,0 +1,465 @@
|
||||
/* eslint-disable block-scoped-var,linebreak-style */
|
||||
module.exports.dependencies = ['axios'];
|
||||
|
||||
module.exports.details = function details() {
|
||||
return {
|
||||
id: 'Tdarr_Plugin_TD03_TOAD_Discord_Notify_Plus',
|
||||
Stage: 'Post-processing',
|
||||
Name: 'Send Discord Notification - Plus.',
|
||||
Type: 'Video',
|
||||
Operation: '',
|
||||
Description: 'Send notification to a Discord webhook along with artwork taken from either Sonarr or Radarr. \n \n ',
|
||||
Version: '0.8',
|
||||
Link: '',
|
||||
Tags: '3rd party,post-processing,configurable',
|
||||
|
||||
Inputs: [
|
||||
{
|
||||
name: 'discord_url',
|
||||
tooltip: `
|
||||
Enter the full URL for the discord webhook. Must include https://
|
||||
|
||||
\\n Example:\\n
|
||||
https://discord.com/api/webhooks/.....`,
|
||||
},
|
||||
{
|
||||
name: 'sonarr_url',
|
||||
tooltip: `
|
||||
Enter the full URL for the sonarr instance, including port. Must include http(s)://
|
||||
|
||||
\\n Example:\\n
|
||||
http://192.168.0.50:8989`,
|
||||
},
|
||||
{
|
||||
name: 'sonarr_api',
|
||||
tooltip: `
|
||||
Enter the api key for sonarr
|
||||
|
||||
\\n Example:\\n
|
||||
4545sdffds45sdf4sds`,
|
||||
},
|
||||
{
|
||||
name: 'radarr_url',
|
||||
tooltip: `
|
||||
Enter the full URL for the radarr instance, including port. Must include http(s)://
|
||||
|
||||
\\n Example:\\n
|
||||
http://192.168.0.50:8989`,
|
||||
},
|
||||
{
|
||||
name: 'radarr_api',
|
||||
tooltip: `
|
||||
Enter the api key for radarr
|
||||
|
||||
\\n Example:\\n
|
||||
4545sdffds45sdf4sds`,
|
||||
},
|
||||
{
|
||||
name: 'debug_enabled',
|
||||
tooltip: `
|
||||
Enter true or false to toggle enhanced console logging - default is false
|
||||
|
||||
\\n Example:\\n
|
||||
true`,
|
||||
},
|
||||
],
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.plugin = async function plugin(file, librarySettings, inputs, otherArguments) {
|
||||
// plugin basic prep
|
||||
|
||||
const response = {
|
||||
infoLog: '',
|
||||
};
|
||||
// eslint-disable-next-line global-require,import/no-unresolved
|
||||
const axios = require('axios');
|
||||
|
||||
// Variable setup
|
||||
|
||||
let debugmode = false; // default debug mode to false
|
||||
const { discord_url } = inputs; // maps the discord url from inputs
|
||||
const { sonarr_url } = inputs; // maps the sonarr url from inputs
|
||||
const { sonarr_api } = inputs; // maps the sonarr api key from inputs
|
||||
const { radarr_url } = inputs; // maps the radarr url from inputs
|
||||
const { radarr_api } = inputs; // maps the radarr api key from inputs
|
||||
let filesize_name = ''; // prepares for the formatted filesize variable
|
||||
let poster_url = ''; // for the poster thumpnail url from either Sonarr or Radarr
|
||||
let transcoderesult = ''; // holds the transcoder result from the Tdarr file object
|
||||
let oldfilesize = ''; // holds the old file size
|
||||
const fields = []; // blank array in prep for the Discord fields
|
||||
let sonarrresponse = '';
|
||||
let radarrresponse = '';
|
||||
let discordresponse = '';
|
||||
let tmp = '';
|
||||
let subfooter = 'Subtitles:';
|
||||
let matchingresult = '';
|
||||
let fileimdbId = [];
|
||||
|
||||
// get the file name from the full path
|
||||
let filename = file.file.split('/');
|
||||
filename = filename[filename.length - 1];
|
||||
|
||||
// get the file path by removing the file name
|
||||
const filepath = file.file
|
||||
.split('/')
|
||||
.slice(0, -1)
|
||||
.join('/');
|
||||
// eslint-disable-next-line max-len
|
||||
const seasonfilepath = file.file
|
||||
.split('/')
|
||||
.slice(0, -2)
|
||||
.join('/'); // removes an extra folder if season folders are not enabled
|
||||
|
||||
// regex used to find the IMDB ID within a file name - tt followed by 7 or 8 digits
|
||||
if (filename.match(/tt\d{7,8}/)) {
|
||||
fileimdbId = filename.match(/tt\d{7,8}/);
|
||||
}
|
||||
|
||||
// useful functions
|
||||
|
||||
// Controls enhanced logging should debug mode be enabled
|
||||
function consoleLog(message) {
|
||||
switch (debugmode) {
|
||||
case true:
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`[D-PLUS] ${message}`);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Formats certain words to capitalise the first letter
|
||||
function capitalizeFirstLetter(string) {
|
||||
return string.charAt(0).toUpperCase() + string.slice(1);
|
||||
}
|
||||
|
||||
// Formats the file size from bytes to a more readible format
|
||||
function convertBytes(bytes) {
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||
|
||||
if (bytes === 0) {
|
||||
return 'n/a';
|
||||
}
|
||||
// eslint-disable-next-line radix
|
||||
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
|
||||
if (i === 0) {
|
||||
return `${bytes} ${sizes[i]}`;
|
||||
}
|
||||
return `${(bytes / 1024 ** i).toFixed(1)} ${sizes[i]}`;
|
||||
}
|
||||
|
||||
// Begin Plugin
|
||||
|
||||
// Check if debug mode is enabled and if so, log some key information
|
||||
if (inputs.debug_enabled === 'true') {
|
||||
debugmode = true;
|
||||
consoleLog(`Discord plus plugin processing ${file.file} with debug logging enabled...`);
|
||||
consoleLog(`tdarr url is ${inputs.tdarr_url}`); // str.replace(str.substring(4,11), "*******")
|
||||
consoleLog(
|
||||
`discord url is ${inputs.discord_url.replace(inputs.discord_url.substring(40, 100), '****************')}`,
|
||||
);
|
||||
consoleLog(`sonarr url is ${inputs.sonarr_url.replace(inputs.sonarr_url.substring(7, 14), '*******')}`);
|
||||
consoleLog(`sonarr api is ${inputs.sonarr_api.replace(inputs.sonarr_api.substring(6, 20), '*******')}`);
|
||||
consoleLog(`radarr url is ${inputs.radarr_url.replace(inputs.radarr_url.substring(6, 20), '*******')}`);
|
||||
consoleLog(`radarr api is ${inputs.radarr_api.replace(inputs.radarr_api.substring(3, 9), '*******')}`);
|
||||
consoleLog(`Filepath is ${filepath}`);
|
||||
consoleLog(`Season filepath is ${seasonfilepath}`);
|
||||
} else {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Discord plus plugin processing ${file.file} with debug logging disabled...`);
|
||||
}
|
||||
|
||||
// Check if key inputs have been configured. If they haven't then exit plugin.
|
||||
if (discord_url.toLowerCase().indexOf('https://') === -1 || typeof discord_url === 'undefined') {
|
||||
response.infoLog += '☒Plugin options have not been configured, please configure options. \n ';
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('Discord URL is empty or not in the correct format');
|
||||
response.processFile = false;
|
||||
return response;
|
||||
}
|
||||
consoleLog('Discord URL configured correctly');
|
||||
|
||||
// Take the file size from the new file and format it
|
||||
if (file.file_size) {
|
||||
filesize_name = convertBytes(file.file_size * 1048576);
|
||||
consoleLog(`File size is ${filesize_name}`);
|
||||
} else {
|
||||
filesize_name = '0';
|
||||
}
|
||||
|
||||
// Take the file size from the old file and format it
|
||||
if (typeof otherArguments.originalLibraryFile.file_size !== 'undefined') {
|
||||
oldfilesize = convertBytes(otherArguments.originalLibraryFile.file_size * 1048576);
|
||||
consoleLog(`Old file size is ${oldfilesize}`);
|
||||
} else {
|
||||
oldfilesize = '0';
|
||||
}
|
||||
|
||||
// Check the transcode result and format the queued response more clearly
|
||||
if (file.TranscodeDecisionMaker === 'Queued') {
|
||||
transcoderesult = 'Re-Queued';
|
||||
} else transcoderesult = file.TranscodeDecisionMaker;
|
||||
|
||||
// Checking Tdarr for the original file size
|
||||
consoleLog(`Original file size is ${oldfilesize}`);
|
||||
|
||||
// Let's get some data from Sonarr to see if the poster URL can be grabbed
|
||||
if (inputs.sonarr_url) {
|
||||
const sonarrRequest = async () => {
|
||||
try {
|
||||
sonarrresponse = await axios.get(`${sonarr_url}/api/series?apikey=${sonarr_api}`);
|
||||
return sonarrresponse.data;
|
||||
} catch (err) {
|
||||
consoleLog(`There was an error: ${err}`);
|
||||
return err;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const sonarrResult = await sonarrRequest();
|
||||
consoleLog(`Sonarr returned ${sonarrResult.length} records`);
|
||||
if (sonarrResult.length > 0) {
|
||||
for (let i = 0; i < sonarrResult.length; i += 1) {
|
||||
if (sonarrResult[i].imdbId || sonarrResult[i].path) {
|
||||
// compare either the imdb, filepath or season file path against the results until a match is found
|
||||
// eslint-disable-next-line max-len
|
||||
if (
|
||||
sonarrResult[i].path === filepath // matches on imdb ID
|
||||
|| sonarrResult[i].path === seasonfilepath // matches on folder path
|
||||
|| sonarrResult[i].imdbId === fileimdbId // matches on folder path minus series folder
|
||||
) {
|
||||
// eslint-disable-next-line max-len
|
||||
if (sonarrResult[i].imdbId === fileimdbId) {
|
||||
matchingresult = `IMDB ID ${fileimdbId}`;
|
||||
} else if (sonarrResult[i].path === filepath) {
|
||||
matchingresult = `File Path ${filepath}`;
|
||||
} else {
|
||||
matchingresult = `Season Path ${seasonfilepath}`;
|
||||
}
|
||||
// eslint-disable-next-line max-len
|
||||
response.infoLog += `☑ The matched show from Sonarr is ${sonarrResult[i].title} using the ${matchingresult} \n `;
|
||||
consoleLog(`The matched show from Sonarr is ${sonarrResult[i].title} using the ${matchingresult}`);
|
||||
if (sonarrResult[i].images) {
|
||||
const sonarr_poster = sonarrResult[i].images.find((item) => item.coverType === 'poster');
|
||||
poster_url = sonarr_poster.remoteUrl;
|
||||
consoleLog(`Poster URL taken from Sonarr: ${poster_url}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
consoleLog('There was an error with Sonarr');
|
||||
consoleLog(err);
|
||||
}
|
||||
}
|
||||
|
||||
// Let's get some data from Radarr to see if the poster URL can be grabbed
|
||||
if (inputs.radarr_url) {
|
||||
if (poster_url === '') {
|
||||
const radarrRequest = async () => {
|
||||
try {
|
||||
radarrresponse = await axios.get(`${radarr_url}/api/v3/movie?apikey=${radarr_api}`);
|
||||
return radarrresponse.data;
|
||||
} catch (err) {
|
||||
consoleLog(`There was an error: ${err}`);
|
||||
return err;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
const radarr_result = await radarrRequest();
|
||||
consoleLog(`Radarr returned ${radarr_result.length} records`);
|
||||
if (radarr_result.length > 0) {
|
||||
for (let i = 0; i < radarr_result.length; i += 1) {
|
||||
if (radarr_result[i].imdbId || radarr_result[i].path) {
|
||||
// eslint-disable-next-line max-len
|
||||
if (
|
||||
radarr_result[i].imdbId === fileimdbId // matches on imdb ID
|
||||
|| radarr_result[i].path === filepath // matches on folder path
|
||||
|| radarr_result[i].path === seasonfilepath // matches on folder path minus series folder
|
||||
) {
|
||||
if (radarr_result[i].imdbId === fileimdbId) {
|
||||
matchingresult = `IMDB ID ${fileimdbId}`;
|
||||
} else if (radarr_result[i].path === filepath) {
|
||||
matchingresult = `File Path ${filepath}`;
|
||||
} else {
|
||||
matchingresult = `Season Path ${seasonfilepath}`;
|
||||
}
|
||||
// eslint-disable-next-line max-len
|
||||
response.infoLog += `☑ The matched movie from Radarr is ${radarr_result[i].title} using the ${matchingresult} \n `;
|
||||
consoleLog(`The matched movie from Radarr is ${radarr_result[i].title} using the ${matchingresult}`);
|
||||
if (radarr_result[i].images) {
|
||||
const radarr_poster = radarr_result[i].images.find((item) => item.coverType === 'poster');
|
||||
poster_url = radarr_poster.remoteUrl;
|
||||
consoleLog(`Poster URL taken from Radarr: ${poster_url}`);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
consoleLog('There was an error with Radarr');
|
||||
consoleLog(err);
|
||||
}
|
||||
} else {
|
||||
consoleLog('Poster url already found, skipping Radarr');
|
||||
}
|
||||
} else {
|
||||
consoleLog('Radarr not enabled');
|
||||
}
|
||||
|
||||
// build the discord embded object
|
||||
fields.push({ name: 'Result', value: `${transcoderesult}`, inline: true });
|
||||
fields.push({ name: 'File Size', value: `${filesize_name}`, inline: true });
|
||||
if (transcoderesult !== 'Not required') {
|
||||
// we don't need to show the original file size if nothing was done
|
||||
fields.push({ name: 'Previous Size', value: `${oldfilesize}`, inline: true });
|
||||
} else {
|
||||
fields.push({ name: 'Previous Size', value: 'n/a', inline: true });
|
||||
}
|
||||
fields.push({ name: 'File', value: `${file.file}`, inline: false });
|
||||
|
||||
// if the file contains a video track, add in the resolution
|
||||
switch (file.fileMedium) {
|
||||
case 'video':
|
||||
fields.push({ name: 'Type', value: `${capitalizeFirstLetter(file.fileMedium)}`, inline: true });
|
||||
fields.push({ name: 'Resolution', value: `${file.video_resolution}`, inline: true });
|
||||
// todo something with bitrate
|
||||
/* if (typeof file.bit_rate !== 'undefined'){
|
||||
fields.push({ name: 'Bitrate', value: `${convertBytes(file.bit_rate)}`, inline: true });
|
||||
} */
|
||||
break;
|
||||
default:
|
||||
fields.push({ name: 'Type', value: `${file.fileMedium}`, inline: false });
|
||||
break;
|
||||
}
|
||||
// if ffProbeRead data is available, loop through and add each stream to the object
|
||||
if (file.ffProbeRead === 'success') {
|
||||
for (let i = 0; i < file.ffProbeData.streams.length; i += 1) {
|
||||
// eslint-disable-next-line max-len
|
||||
// Discord has a max embed quantity of 25 - 6 are taken already so this leaves 20 for the streams. Any more will be cropped.
|
||||
|
||||
if (i < 25) {
|
||||
// eslint-disable-next-line max-len
|
||||
switch (file.ffProbeData.streams[i].codec_type) {
|
||||
case 'subtitle':
|
||||
// eslint-disable-next-line max-len
|
||||
subfooter += ` Stream ${i} - ${capitalizeFirstLetter(file.ffProbeData.streams[i].codec_type)} Codec ( ${
|
||||
file.ffProbeData.streams[i].codec_name
|
||||
} ) Lang ( ${file.ffProbeData.streams[i].tags.language} ) Title ( ${
|
||||
file.ffProbeData.streams[i].tags.title
|
||||
} ) |`;
|
||||
break;
|
||||
case 'video':
|
||||
if (typeof file.ffProbeData.streams[i].tags.language !== 'undefined') {
|
||||
// eslint-disable-next-line max-len
|
||||
tmp = {
|
||||
name: `Stream ${i} - ${capitalizeFirstLetter(file.ffProbeData.streams[i].codec_type)}`,
|
||||
// eslint-disable-next-line max-len
|
||||
value: `Codec ( ${file.ffProbeData.streams[i].codec_name} ) Lang ( ${file.ffProbeData.streams[i].tags.language} )`,
|
||||
inline: true,
|
||||
};
|
||||
fields.push(tmp);
|
||||
} else {
|
||||
// eslint-disable-next-line max-len
|
||||
tmp = {
|
||||
name: `Stream ${i} - ${capitalizeFirstLetter(file.ffProbeData.streams[i].codec_type)}`,
|
||||
value: `Codec ( ${file.ffProbeData.streams[i].codec_name} )`,
|
||||
inline: true,
|
||||
};
|
||||
fields.push(tmp);
|
||||
}
|
||||
break;
|
||||
case 'audio':
|
||||
if (typeof file.ffProbeData.streams[i].tags !== 'undefined') {
|
||||
// eslint-disable-next-line max-len
|
||||
tmp = {
|
||||
name: `Stream ${i} - ${capitalizeFirstLetter(file.ffProbeData.streams[i].codec_type)}`,
|
||||
// eslint-disable-next-line max-len
|
||||
value: `Codec ( ${file.ffProbeData.streams[i].codec_name} ) Chans ( ${file.ffProbeData.streams[i].channels} ) Lang ( ${file.ffProbeData.streams[i].tags.language} )`,
|
||||
inline: true,
|
||||
};
|
||||
fields.push(tmp);
|
||||
} else {
|
||||
// eslint-disable-next-line max-len
|
||||
tmp = {
|
||||
name: `Stream ${i} - ${capitalizeFirstLetter(file.ffProbeData.streams[i].codec_type)}`,
|
||||
// eslint-disable-next-line max-len
|
||||
value: `Codec ( ${file.ffProbeData.streams[i].codec_name} ) Chans ( ${file.ffProbeData.streams[i].channels} )`,
|
||||
inline: true,
|
||||
};
|
||||
fields.push(tmp);
|
||||
}
|
||||
break;
|
||||
default:
|
||||
// eslint-disable-next-line max-len
|
||||
// tmp = { name: `Stream ${i} - ${capitalizeFirstLetter(file.ffProbeData.streams[i].codec_type)}`, value: `${file.ffProbeData.streams[i].codec_type} (${file.ffProbeData.streams[i].codec_name})`, inline: true };
|
||||
break;
|
||||
}
|
||||
} else consoleLog('Max embed limit reached');
|
||||
}
|
||||
consoleLog('End of Streams loop');
|
||||
consoleLog(`Final fields quantity added to Discord is ${fields.length}`);
|
||||
} else consoleLog('No ffProbeRead data');
|
||||
|
||||
// starting new discord notification code
|
||||
// Let's get some data from Sonarr to see if the poster URL can be grabbed
|
||||
|
||||
const data = JSON.stringify({
|
||||
embeds: [
|
||||
{
|
||||
title: `${filename}`,
|
||||
description: '',
|
||||
thumbnail: {
|
||||
url: `${poster_url}`,
|
||||
},
|
||||
fields,
|
||||
footer: {
|
||||
text: `${subfooter}`,
|
||||
},
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
const discordPost = async () => {
|
||||
try {
|
||||
discordresponse = await axios.post(`${discord_url}?wait=true`, data, {
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
});
|
||||
return discordresponse.status;
|
||||
} catch (err) {
|
||||
consoleLog(`There was an error: ${err}`);
|
||||
return err;
|
||||
}
|
||||
};
|
||||
|
||||
try {
|
||||
discordresponse = await discordPost();
|
||||
consoleLog(`Discord returned response ${discordresponse}`);
|
||||
response.infoLog += '☑ Discord notified \n ';
|
||||
} catch (err) {
|
||||
consoleLog('There was an error with Discord');
|
||||
response.infoLog += '☒ Discord failed \n ';
|
||||
consoleLog(err);
|
||||
}
|
||||
|
||||
/* // Discord Notification
|
||||
consoleLog('Starting discord notify');
|
||||
await new Promise((resolve) => setTimeout(resolve, 5000));
|
||||
const discordresponse = await postDiscordNotification();
|
||||
|
||||
if (discordresponse.status === 200) {
|
||||
response.infoLog += '☑ Discord notified \n ';
|
||||
} else {
|
||||
response.infoLog += '☒ Discord failed \n ';
|
||||
// eslint-disable-next-line no-console
|
||||
console.log(`Discord failed with error ${discordresponse.status}`);
|
||||
} */
|
||||
|
||||
return response;
|
||||
};
|
||||
Loading…
Reference in new issue