';
-;top.onlocationchange = event => script.init();
+/* Start Injected */
+${ code }
+/* End Injected */
let ScriptReadyState;
@@ -307,7 +526,7 @@ return (script.RegExp = RegExp(
.replace(/\\*\\./g,'([^\\\\.]+\\\\.)?')
.replace(/\\/\\*/g,'/[^$]*'),'i')
).test
-("${ url.href }")?
+(location.href)?
/* URL matches pattern */
script.ready?
/* Script has the "ready" property */
@@ -325,11 +544,13 @@ return (script.RegExp = RegExp(
/* Script doesn't have the "ready" property */
script.init():
/* URL doesn't match pattern */
-(console.warn("The domain '${ org }' ('${ url.href }') does not match the domain pattern '" + script.url + "' (" + script.RegExp + ")"), 5000);
+(console.warn("The domain '${ org }' (" + location.href + ") does not match the domain pattern '" + script.url + "' (" + script.RegExp + ")"), 5000);
})(document.queryBy));
console.log('[${ name }]', ${ name });
+top.onlocationchange = (event) => chrome.runtime.sendMessage({ type: '$INIT$', options: { script: '${ script }' } }, callback => callback);
+
;${ name };`
) }, results => handle(results, LAST_ID = id, LAST_INSTANCE = instance, LAST_JS = script, LAST_TYPE = type))
})
@@ -342,6 +563,14 @@ console.log('[${ name }]', ${ name });
chrome.tabs.executeScript(id, { code: LAST }, results => handle(results, LAST_ID, LAST_INSTANCE, LAST_JS, LAST_TYPE));
break;
+ case '$INIT$':
+ chrome.tabs.getCurrent(tab => {
+ instance = RandomName();
+
+ setTimeout(() => tabchange([ tab ]), 5000);
+ });
+ break;
+
case 'FOUND':
FOUND[request.instance] = request.found;
break;
diff --git a/src/popup/index.html b/src/popup/index.html
index dedd62b..4554a5a 100644
--- a/src/popup/index.html
+++ b/src/popup/index.html
@@ -164,8 +164,8 @@
box-shadow: 0 10px 128px inset #6A8592;
}
- #gostream:hover {
- box-shadow: 0 10px 128px inset #028CC9;
+ #vumoo:hover {
+ box-shadow: 0 10px 128px inset #DD1B2F;
}
#shana-project:hover {
@@ -192,6 +192,14 @@
box-shadow: 0 10px 128px inset #222222;
}
+ #gostream:hover {
+ box-shadow: 0 10px 128px inset #028CC9;
+ }
+
+ #tubi:hover {
+ box-shadow: 0 10px 128px inset #26262D;
+ }
+
#local-plex:hover {
box-shadow: 0 10px 128px inset #F9BD03;
}
@@ -216,6 +224,10 @@
box-shadow: 0 10px 128px inset #E48F34;
}
+ #local-medusa:hover {
+ box-shadow: 0 10px 128px inset #26B043;
+ }
+
[save-file]:after, [cost-cash-low]:after, [cost-cash-med]:after, [cost-cash-hig]:after {
content: "____";
color: transparent;
@@ -241,7 +253,7 @@
float: right;
}
- [is-shy] label:after {
+ [is-shy] label:after, [is-dead] label:after {
content: " \1f910";
float: right;
}
@@ -250,6 +262,11 @@
background: url("../img/48.png") no-repeat center;
}
+ [not-safe] label:after {
+ content: " \1F527";
+ float: right;
+ }
+
/* $1 - $10 */
[cost-cash-low]:after {
background: url("../img/$48.png") no-repeat center;
@@ -301,7 +318,7 @@
-
+ |
@@ -316,13 +333,13 @@
-
+ |
|
-
+ |
@@ -337,27 +354,33 @@
|
+
+
+
+
+
+ |
|
-
+ |
|
+
+
+
|
-
-
-
@@ -370,36 +393,36 @@
|
+
+
+
|
-
-
-
|
-
+ |
|
+
+
+
|
-
-
-
@@ -412,16 +435,16 @@
|
+
+
+
|
-
-
-
-
+
+
+
|
-
-
-
-
-
-
-
-
- |
@@ -482,6 +499,21 @@
|
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+ |
+
diff --git a/src/popup/index.js b/src/popup/index.js
index ac75a63..faf74de 100644
--- a/src/popup/index.js
+++ b/src/popup/index.js
@@ -55,7 +55,9 @@ document.body.onload = function() {
"disabled": "Not yet implemented",
"is-shy": "Can only be accessed via: {*}",
"is-slow": "Resource intensive (loads slowly)",
+ "is-dead": "Isn't meant to show the Web to Plex button",
"local": "Opens a link to ^{*}",
+ "not-safe": "Updated irregularly, may drop support",
"pop-ups": "Contains annoying/intrusive ads and/or pop-ups",
"save-file": "Uses {*} before using your manager(s)",
// $0.99 one time; $0.99 - $9.99/mon
@@ -81,8 +83,10 @@ document.body.onload = function() {
let elements = document.querySelectorAll(selectors.join(','));
- for(let element, index = 0, length = elements.length; index < length; index++)
+ for(let element, index = 0, length = elements.length; index < length; index++) {
+ let number = 1;
for(let attribute in messages)
if(attribute in (element = elements[index]).attributes)
- element.title = `${ parse(messages[attribute], attribute, element) }. ${ element.title }`;
+ element.title += `\n${(number++)}) ${ parse(messages[attribute], attribute, element) }.`;
+ }
}
diff --git a/src/sites/__test__.js b/src/sites/__test__.js
new file mode 100644
index 0000000..a4c3de9
--- /dev/null
+++ b/src/sites/__test__.js
@@ -0,0 +1,2 @@
+/* global sendUpdate(type:string, details:object) */
+(init = () => sendUpdate('SCRIPT', { script: '__test__' }))();
diff --git a/src/sites/common.css b/src/sites/common.css
index 7ea7c9a..ec296d3 100644
--- a/src/sites/common.css
+++ b/src/sites/common.css
@@ -116,8 +116,13 @@
background: #2A2AFF !important;
}
-/* Web to Plex warning notifications */
-.web-to-plex-notification.warning {
+/* Web to Plex success notifications */
+.web-to-plex-notification.success {
+ background: #03BDF9 !important;
+}
+
+/* Web to Plex error/warning notifications */
+.web-to-plex-notification.warning, .web-to-plex-notification.error {
background: #FF2A2A !important;
}
@@ -218,9 +223,14 @@
max-width: 60% !important;
}
-.web-to-plex-prompt-option h2 {
+.web-to-plex-prompt-option.mutable > h2 {
+ background: #0000 !important;
+ color: inherit !important;
font-family: inherit !important;
font-size: initial !important;
+ text-align: inherit !important;
+
+ margin: inherit !important;
}
.web-to-plex-prompt-option.mutable > .remove {
diff --git a/src/sites/flenix/index.js b/src/sites/flenix/index.js
deleted file mode 100644
index c5444d6..0000000
--- a/src/sites/flenix/index.js
+++ /dev/null
@@ -1,50 +0,0 @@
-/* global parseOptions, modifyPlexButton, findPlexMedia */
-function isMoviePage() {
- // An example movie page: /movies/3030-the-1517-to-paris.html
- return /\/(movies?|views?)\//.test(window.location.pathname);
-}
-function isMoviePageReady() {
- return !!document.querySelector('#videoplayer video').getAttribute('onplay') != '';
-}
-function init() {
- if (isMoviePage())
- if (isMoviePageReady())
- initPlexThingy();
- else
- // This almost never happens, but sometimes the page is too slow so we need to wait a bit.
- setTimeout(init, 1000);
-}
-parseOptions().then(() => {
- window.addEventListener('popstate', init);
- window.addEventListener('pushstate-changed', init);
- init();
-});
-async function initPlexThingy() {
- let button = renderPlexButton();
- if (!button)
- return /* Fatal Error: Fail Silently */;
- let $title = document.querySelector('#dle-content .about > h1'),
- $date = document.querySelector('.features > .reset:nth-child(2) a');
- if (!$title || !$date)
- return modifyPlexButton(
- button,
- 'error',
- 'Could not extract title or year from Flenix'
- ),
- null;
- let meta = {
- method: 'POST',
- headers: {'Content-Type': 'application/x-www-form-urlencoded'},
- mode: 'no-cors'
- };
- let title = $title.innerText.trim(),
- year = $date.innerText,
- type = 'movie';
- let Db = await getIDs({ title, year, type }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
- title = Db.title;
- year = Db.year;
- findPlexMedia({ title, year, button, IMDbID, TMDbID, TVDbID, type, remote: '/engine/ajax/get.php', locale: 'flenix' });
-}
\ No newline at end of file
diff --git a/src/sites/google/play.js b/src/sites/google/play.js
index 448d8a7..6d67eba 100644
--- a/src/sites/google/play.js
+++ b/src/sites/google/play.js
@@ -1,2 +1,2 @@
/* global sendUpdate(type:string, details:object) */
-(init = () => sendUpdate('SCRIPT', { script: 'play.google' }))();
+(init = () => sendUpdate('SCRIPT', { script: 'google.play' }))();
diff --git a/src/sites/flenix/index.css b/src/sites/tubi/index.css
similarity index 100%
rename from src/sites/flenix/index.css
rename to src/sites/tubi/index.css
diff --git a/src/sites/tubi/index.js b/src/sites/tubi/index.js
new file mode 100644
index 0000000..f4a7ec8
--- /dev/null
+++ b/src/sites/tubi/index.js
@@ -0,0 +1,2 @@
+/* global sendUpdate(type:string, details:object) */
+(init = () => sendUpdate('SCRIPT', { script: 'tubi' }))();
diff --git a/src/sites/vumoo/index.css b/src/sites/vumoo/index.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/sites/vumoo/index.js b/src/sites/vumoo/index.js
new file mode 100644
index 0000000..d932306
--- /dev/null
+++ b/src/sites/vumoo/index.js
@@ -0,0 +1,2 @@
+/* global sendUpdate(type:string, details:object) */
+(init = () => sendUpdate('SCRIPT', { script: 'vumoo' }))();
diff --git a/src/utils.js b/src/utils.js
index 8995c14..f815fa3 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -1,1735 +1,1979 @@
/* eslint-disable no-unused-vars */
-/* global config */
-
-let DISABLE_DEBUGGER = false;
-
-let date = (new Date),
- terminal =
- DISABLE_DEBUGGER?
- { error: m => m, info: m => m, log: m => m, warn: m => m, group: m => m, groupEnd: m => m }:
- console;
-
-let YEAR = date.getFullYear(),
- MONTH = date.getMonth() + 1,
- DATE = date.getDate(),
- NOTIFIED = false;
-
-let getURL = url => chrome.extension.getURL(url),
- $ = (selector, container) => queryBy(selector, container),
- init;
-
-let IMG_URL = {
- 'icon_16': getURL('img/16.png'),
- 'icon_48': getURL('img/48.png'),
- 'icon_white_16': getURL('img/_16.png'),
- 'icon_white_48': getURL('img/_48.png'),
- 'icon_outline_16': getURL('img/o16.png'),
- 'icon_outline_48': getURL('img/o48.png'),
- 'hide_icon_16': getURL('img/hide.16.png'),
- 'hide_icon_48': getURL('img/hide.48.png'),
- 'show_icon_16': getURL('img/show.16.png'),
- 'show_icon_48': getURL('img/show.48.png'),
- 'plexit_icon_16': getURL('img/plexit.16.png'),
- 'plexit_icon_48': getURL('img/plexit.48.png'),
- 'reload_icon_16': getURL('img/reload.16.png'),
- 'reload_icon_48': getURL('img/reload.48.png'),
- 'close_icon_16': getURL('img/close.16.png'),
- 'close_icon_48': getURL('img/close.48.png'),
- 'settings_icon_16': getURL('img/settings.16.png'),
- 'settings_icon_48': getURL('img/settings.48.png'),
- 'noise_background': getURL('img/noise.png'),
- 'nil': getURL('img/null.png'),
-};
+/* global config, init, sendUpdate, "Helpers" */
+
+let config, init, sendUpdate;
+
+(async date => {
+
+ // default date items
+ let YEAR = date.getFullYear(),
+ MONTH = date.getMonth() + 1,
+ DATE = date.getDate(),
+ NOTIFIED = false;
+
+ // simple helpers
+ let extURL = url => chrome.extension.getURL(url),
+ $ = (selector, container) => queryBy(selector, container);
+
+ let IMG_URL = {
+ 'nil': extURL('img/null.png'),
+ 'icon_16': extURL('img/16.png'),
+ 'icon_48': extURL('img/48.png'),
+ 'hide_icon_16': extURL('img/hide.16.png'),
+ 'hide_icon_48': extURL('img/hide.48.png'),
+ 'show_icon_16': extURL('img/show.16.png'),
+ 'show_icon_48': extURL('img/show.48.png'),
+ 'close_icon_16': extURL('img/close.16.png'),
+ 'close_icon_48': extURL('img/close.48.png'),
+ 'icon_white_16': extURL('img/_16.png'),
+ 'icon_white_48': extURL('img/_48.png'),
+ 'plexit_icon_16': extURL('img/plexit.16.png'),
+ 'plexit_icon_48': extURL('img/plexit.48.png'),
+ 'reload_icon_16': extURL('img/reload.16.png'),
+ 'reload_icon_48': extURL('img/reload.48.png'),
+ 'icon_outline_16': extURL('img/o16.png'),
+ 'icon_outline_48': extURL('img/o48.png'),
+ 'noise_background': extURL('img/noise.png'),
+ 'settings_icon_16': extURL('img/settings.16.png'),
+ 'settings_icon_48': extURL('img/settings.48.png'),
+ };
-// the storage
-const storage = chrome.storage.sync || chrome.storage.local;
+ // the storage - priority to sync
+ const storage = chrome.storage.sync || chrome.storage.local;
-async function load(name = '') {
- if(!name) return;
+ async function load(name = '') {
+ if(!name)
+ return /* invalid name */;
- name = 'Cache-Data/' + btoa(name.toLowerCase().replace(/\s+/g, ''));
+ name = 'Cache-Data/' + btoa(name.toLowerCase().replace(/\s+/g, ''));
- return new Promise((resolve, reject) => {
- function LOAD(DISK) {
- let data = JSON.parse(DISK[name] || null);
+ return new Promise((resolve, reject) => {
+ function LOAD(DISK) {
+ let data = JSON.parse(DISK[name] || null);
- return resolve(data);
- }
+ return resolve(data);
+ }
- storage.get(null, DISK => {
- if (chrome.runtime.lastError)
- chrome.storage.local.get(null, LOAD);
- else
- LOAD(DISK);
+ storage.get(null, DISK => {
+ if(chrome.runtime.lastError)
+ chrome.storage.local.get(null, LOAD);
+ else
+ LOAD(DISK);
+ });
});
- });
-}
-
-async function save(name = '', data) {
- if(!name) return;
-
- name = 'Cache-Data/' + btoa(name.toLowerCase().replace(/\s+/g, ''));
- data = JSON.stringify(data);
-
- await storage.set({[name]: data}, () => data);
-
- return name;
-}
-
-async function kill(name) {
- return storage.remove(['Cache-Data/' + btoa(name.toLowerCase().replace(/\s+/g, ''))]);
-}
-
-// create and/or queue a notification
-// state = "error" - red
-// state = "update" - blue
-// state = "info" - grey
-// anything else for state will show as orange
-class Notification {
- constructor(state, text, timeout = 7000, callback = () => {}, requiresClick = true) {
- let queue = (Notification.queue = Notification.queue || { list: [] }),
- last = queue.list[queue.list.length - 1];
-
- if((config.NotifyNewOnly && /\balready (exists|(been )?added)\b/.test(text)) || (config.NotifyOnlyOnce && NOTIFIED && state === 'info'))
- return /* Don't match /.../i as to not match item titles */;
-
- NOTIFIED = true;
-
- if (last && last.done === false)
- return (last => setTimeout(() => new Notification(state, text, timeout, callback, requiresClick), +(new Date) - last.start))(last);
+ }
- let element = furnish(`div.web-to-plex-notification.${state}`, {
- onmouseup: event => {
- let notification = Notification.queue[event.target.id],
- element = notification.element;
+ async function save(name = '', data) {
+ if(!name)
+ return /* invalid name */;
- notification.done = true;
- Notification.queue.list.splice(notification.index, 1);
- clearTimeout(notification.job);
- element.remove();
+ name = 'Cache-Data/' + btoa(name.toLowerCase().replace(/\s+/g, ''));
+ data = JSON.stringify(data);
- let removed = delete Notification.queue[notification.id];
+ await storage.set({[name]: data}, () => data);
- return (event.requiresClick)? null: notification.callback(removed);
- }
- }, text);
-
- queue[element.id = +(new Date)] = {
- start: +element.id,
- stop: +element.id + timeout,
- span: +timeout,
- done: false,
- index: queue.list.length,
- job: setTimeout(() => element.onmouseup({ target: element, requiresClick }), timeout),
- id: +element.id,
- callback, element
- };
- queue.list.push(queue[element.id]);
+ return name;
+ }
- document.body.appendChild(element);
+ async function remove(name) {
+ if(!name)
+ return /* invalid name */;
- return queue[element.id];
+ return await storage.remove(['Cache-Data/' + btoa(name.toLowerCase().replace(/\s+/g, ''))]);
}
-}
-class Prompt {
- constructor(prompt_type, options, callback = () => {}, container = document.body) {
- let prompt, remove,
- array = (options instanceof Array? options: [].slice.call(options)),
- data = [...array],
- profiles = {
- movie: JSON.parse(
- config.usingRadarr?
- config.radarrQualities:
- config.usingWatcher?
- config.watcherQualities:
- '[]'
- ),
- show: JSON.parse(
- config.usingSonarr?
- config.sonarrQualities:
- '[]'
- )
- },
- locations = {
- movie: JSON.parse(
- config.usingRadarr?
- config.radarrStoragePaths:
- config.usingWatcher?
- config.watcherStoragePaths:
- '[]'
- ),
- show: JSON.parse(
- config.usingSonarr?
- config.sonarrStoragePaths:
- '[]'
- )
- },
- defaults = {
- movie: (
- config.usingRadarr?
- { quality: config.__radarrQuality, location: config.__radarrStoragePath }:
- {}
- ),
- show: (
- config.usingSonarr?
- { quality: config.__sonarrQuality, location: config.__sonarrStoragePath }:
- {}
- )
+ /* Notifications */
+ // create and/or queue a notification
+ // state = "warning" - red
+ // state = "error"
+ // state = "update" - blue
+ // state = "info" - grey
+ // anything else for state will show as orange
+ class Notification {
+ constructor(state, text, timeout = 7000, callback = () => {}, requiresClick = true) {
+ let queue = (Notification.queue = Notification.queue || { list: [] }),
+ last = queue.list[queue.list.length - 1];
+
+ if(((state == 'error' || state == 'warning') && config.NotifyNewOnly && /\balready\s+(exists?|(been\s+)?added)\b/.test(text)) || (config.NotifyOnlyOnce && NOTIFIED && state === 'info'))
+ return /* Don't match /.../i as to not match item titles */;
+ NOTIFIED = true;
+
+ if(last && !last.done)
+ return (last => setTimeout(() => new Notification(state, text, timeout, callback, requiresClick), +(new Date) - last.start))(last);
+
+ let element = furnish(`div.web-to-plex-notification.${state}`, {
+ onmouseup: event => {
+ let notification = Notification.queue[event.target.id],
+ element = notification.element;
+
+ notification.done = true;
+ Notification.queue.list.splice(notification.index, 1);
+ clearTimeout(notification.job);
+ element.remove();
+
+ let removed = delete Notification.queue[notification.id];
+
+ return (event.requiresClick)? null: notification.callback(removed);
+ }
+ }, text);
+
+ queue[element.id = +(new Date)] = {
+ start: +element.id,
+ stop: +element.id + timeout,
+ span: +timeout,
+ done: false,
+ index: queue.list.length,
+ job: setTimeout(() => element.onmouseup({ target: element, requiresClick }), timeout),
+ id: +element.id,
+ callback, element
};
+ queue.list.push(queue[element.id]);
- switch(prompt_type) {
- /* Allows the user to add and remove items from a list */
- case 'prompt':
- case 'input':
- remove = element => {
- let prompter = $('.web-to-plex-prompt').first,
- header = $('.web-to-plex-prompt-header').first,
- counter = $('.web-to-plex-prompt-options').first;
-
- if(element === true)
- return prompter.remove();
- else
- element.remove();
+ document.body.appendChild(element);
- data.splice(+element.value, 1, null);
- header.innerText = 'Approve ' + counter.children.length + (counter.children.length == 1?' item': ' items');
+ return queue[element.id];
+ }
+ }
+
+ class Prompt {
+ constructor(prompt_type, options, callback = () => {}, container = document.body) {
+ let prompt, remove,
+ array = (options instanceof Array? options: [].slice.call(options)),
+ data = [...array],
+ profiles = {
+ movie: JSON.parse(
+ config.usingRadarr?
+ config.radarrQualities:
+ config.usingWatcher?
+ config.watcherQualities:
+ '[]'
+ ),
+ show: JSON.parse(
+ config.usingSonarr?
+ config.sonarrQualities:
+ config.usingMedusa?
+ config.medusaQualities:
+ '[]'
+ )
+ },
+ locations = {
+ movie: JSON.parse(
+ config.usingRadarr?
+ config.radarrStoragePaths:
+ config.usingWatcher?
+ config.watcherStoragePaths:
+ '[]'
+ ),
+ show: JSON.parse(
+ config.usingSonarr?
+ config.sonarrStoragePaths:
+ config.usingMedusa?
+ config.medusaStoragePaths:
+ '[]'
+ )
+ },
+ defaults = {
+ movie: (
+ config.usingRadarr?
+ { quality: config.__radarrQuality, location: config.__radarrStoragePath }:
+ {}
+ ),
+ show: (
+ config.usingSonarr?
+ { quality: config.__sonarrQuality, location: config.__sonarrStoragePath }:
+ config.usingMedusa?
+ { quality: config.__medusaQuality, location: config.__medusaStoragePath }:
+ {}
+ )
};
- prompt = furnish('div.web-to-plex-prompt', {},
- furnish('div.web-to-plex-prompt-body', {},
- // The prompt's title
- furnish('h1.web-to-plex-prompt-header', {}, 'Approve ' + array.length + (array.length == 1? ' item': ' items')),
-
- // The prompt's items
- furnish('div.web-to-plex-prompt-options', {},
- ...(ITEMS => {
- let elements = [];
-
- for(let index = 0, length = ITEMS.length, ITEM, P_QUA, P_LOC; index < length; index++) {
- ITEM = ITEMS[index];
-
- elements.push(
- furnish('li.web-to-plex-prompt-option.mutable', { value: index, innerHTML: `${ index + 1 } \u00b7 ${ ITEM.title }${ ITEM.year? ` (${ ITEM.year })`: '' } \u2014 ${ ITEM.type }` },
- furnish('button.remove', { title: `Remove "${ ITEM.title }"`, onmouseup: event => { remove(event.target.parentElement); event.target.remove() } }),
- (
- config.PromptQuality?
- P_QUA = furnish('select.quality', { index, onchange: event => data[event.target.getAttribute('index')].quality = event.target.value }, ...profiles[/(movie|film|cinema)/i.test(ITEM.type)?'movie':'show'].map(Q => furnish('option', { value: Q.id }, Q.name))):
- ''
- ),(
- config.PromptLocation?
- P_LOC = furnish('select.location', { index, onchange: event => data[event.target.getAttribute('index')].location = event.target.value }, ...locations[/(movie|film|cinema)/i.test(ITEM.type)?'movie':'show'].map(Q => furnish('option', { value: Q.id }, Q.path))):
- ''
+ switch(prompt_type) {
+ /* Allows the user to add and remove items from a list */
+ case 'prompt':
+ case 'input':
+ remove = element => {
+ let prompter = $('.web-to-plex-prompt').first,
+ header = $('.web-to-plex-prompt-header').first,
+ counter = $('.web-to-plex-prompt-options').first;
+
+ if(element === true)
+ return prompter.remove();
+ else
+ element.remove();
+
+ data.splice(+element.value, 1, null);
+ header.innerText = 'Approve ' + counter.children.length + (counter.children.length == 1?' item': ' items');
+ };
+
+ prompt = furnish('div.web-to-plex-prompt', {},
+ furnish('div.web-to-plex-prompt-body', {},
+ // The prompt's title
+ furnish('h1.web-to-plex-prompt-header', {}, 'Approve ' + array.length + (array.length == 1? ' item': ' items')),
+
+ // The prompt's items
+ furnish('div.web-to-plex-prompt-options', {},
+ ...(ITEMS => {
+ let elements = [];
+
+ for(let index = 0, length = ITEMS.length, ITEM, P_QUA, P_LOC; index < length; index++) {
+ ITEM = ITEMS[index];
+
+ elements.push(
+ furnish('li.web-to-plex-prompt-option.mutable', { value: index, innerHTML: `${ index + 1 } \u00b7 ${ ITEM.title }${ ITEM.year? ` (${ ITEM.year })`: '' } \u2014 ${ ITEM.type }` },
+ furnish('button.remove', { title: `Remove "${ ITEM.title }"`, onmouseup: event => { remove(event.target.parentElement); event.target.remove() } }),
+ (
+ config.PromptQuality?
+ P_QUA = furnish('select.quality', { index, onchange: event => data[event.target.getAttribute('index')].quality = event.target.value }, ...profiles[/(movie|film|cinema)/i.test(ITEM.type)?'movie':'show'].map(Q => furnish('option', { value: Q.id }, Q.name))):
+ ''
+ ),(
+ config.PromptLocation?
+ P_LOC = furnish('select.location', { index, onchange: event => data[event.target.getAttribute('index')].location = event.target.value }, ...locations[/(movie|film|cinema)/i.test(ITEM.type)?'movie':'show'].map(Q => furnish('option', { value: Q.id }, Q.path))):
+ ''
+ )
)
- )
- );
-
- if(P_QUA) P_QUA.value = defaults[ITEM.type].quality;
- if(P_LOC) P_LOC.value = defaults[ITEM.type].location;
+ );
- P_QUA = P_LOC = null;
- }
+ if(P_QUA) P_QUA.value = defaults[ITEM.type].quality;
+ if(P_LOC) P_LOC.value = defaults[ITEM.type].location;
- return elements
- })(array)
- ),
-
- // The engagers
- furnish('div.web-to-plex-prompt-footer', {},
- furnish('input.web-to-plex-prompt-input[type=text]', { placeholder: 'Add an item (enter to add): Title (Year) Type / ID Type', title: 'Solo: A Star Wars Story (2018) movie / tt3778644 m', onkeydown: async event => {
- if (event.keyCode === 13) {
- let title, year, type, self = event.target, R = RegExp,
- movie = /^(m(?:ovies?)?|f(?:ilms?)?|c(?:inemas?)?)/i,
- Db, IMDbID, TMDbID, TVDbID, value = self.value;
-
- self.setAttribute('disabled', self.disabled = true);
- self.value = `Searching for "${ value }"...`;
- data = data.filter(value => value !== null && value !== undefined);
-
- if(/^\s*((?:tt)?\d+)(?:\s+(\w+)|\s*)?$/i.test(value)) {
- let APIID = R.$1,
- type = R.$2 || (data.length? data[0].type: 'movie'),
- APIType = movie.test(type)? /^tt/i.test(APIID)? 'imdb': 'tmdb': 'tvdb';
-
- type = movie.test(type)? 'movie': 'show';
-
- Db = await getIDs({ type, APIID, APIType });
- IMDbID = Db.imdb;
- TMDbID = Db.tmdb;
- TVDbID = Db.tvdb;
-
- title = Db.title;
- year = Db.year;
- } else if(/^([^]+)(\s*\(\d{2,4}\)\s*|\s+\d{2,4}\s+)([\w\s\-]+)$/.test(value)) {
- title = R.$1;
- year = R.$2 || YEAR + '';
- type = R.$3 || (data.length? data[0].type: 'movie');
-
- year = +year.replace(/\D/g, '').replace(/^\d{2}$/, '20$&');
- type = movie.test(type)? 'movie': 'show';
-
- Db = await getIDs({ type, title, year });
- IMDbID = Db.imdb;
- TMDbID = Db.tmdb;
- TVDbID = Db.tvdb;
+ P_QUA = P_LOC = null;
}
- event.preventDefault();
- if(type && title && !(/^(?:tt)?$/i.test(IMDbID || '') && /^0?$/.test(+TMDbID | 0) && /^0?$/.test(+TVDbID | 0))) {
- remove(true);
- new Prompt(prompt_type, [{ ...Db, type, IMDbID, TMDbID, TVDbID }, ...data], callback, container);
- } else {
- self.disabled = self.removeAttribute('disabled');
- self.value = value;
- new Notification('error', `Couldn't find "${ value }"`);
+ return elements
+ })(array)
+ ),
+
+ // The engagers
+ furnish('div.web-to-plex-prompt-footer', {},
+ furnish('input.web-to-plex-prompt-input[type=text]', { placeholder: 'Add an item (enter to add): Title (Year) Type / ID Type', title: 'Solo: A Star Wars Story (2018) movie / tt3778644 m', onkeydown: async event => {
+ if(event.keyCode === 13) {
+ let title, year, type, self = event.target, R = RegExp,
+ movie = /^(m(?:ovies?)?|f(?:ilms?)?|c(?:inemas?)?)/i,
+ Db, IMDbID, TMDbID, TVDbID, value = self.value;
+
+ self.setAttribute('disabled', self.disabled = true);
+ self.value = `Searching for "${ value }"...`;
+ data = data.filter(value => value !== null && value !== undefined);
+
+ if(/^\s*((?:tt)?\d+)(?:\s+(\w+)|\s*)?$/i.test(value)) {
+ let APIID = R.$1,
+ type = R.$2 || (data.length? data[0].type: 'movie'),
+ APIType = movie.test(type)? /^tt/i.test(APIID)? 'imdb': 'tmdb': 'tvdb';
+
+ type = movie.test(type)? 'movie': 'show';
+
+ Db = await getIDs({ type, APIID, APIType });
+ IMDbID = Db.imdb;
+ TMDbID = Db.tmdb;
+ TVDbID = Db.tvdb;
+
+ title = Db.title;
+ year = Db.year;
+ } else if(/^([^]+)(\s*\(\d{2,4}\)\s*|\s+\d{2,4}\s+)([\w\s\-]+)$/.test(value)) {
+ title = R.$1;
+ year = R.$2 || YEAR + '';
+ type = R.$3 || (data.length? data[0].type: 'movie');
+
+ year = +year.replace(/\D/g, '').replace(/^\d{2}$/, '20$&');
+ type = movie.test(type)? 'movie': 'show';
+
+ Db = await getIDs({ type, title, year });
+ IMDbID = Db.imdb;
+ TMDbID = Db.tmdb;
+ TVDbID = Db.tvdb;
+ }
+
+ event.preventDefault();
+ if(type && title && !(/^(?:tt)?$/i.test(IMDbID || '') && /^0?$/.test(+TMDbID | 0) && /^0?$/.test(+TVDbID | 0))) {
+ remove(true);
+ new Prompt(prompt_type, [{ ...Db, type, IMDbID, TMDbID, TVDbID }, ...data], callback, container);
+ } else {
+ self.disabled = self.removeAttribute('disabled');
+ self.value = value;
+ new Notification('error', `Couldn't find "${ value }"`);
+ }
}
- }
- } }),
- furnish('button.web-to-plex-prompt-decline', { onmouseup: event => { remove(true); callback([]) }, title: 'Close' }, '\u2718'),
- furnish('button.web-to-plex-prompt-accept', { onmouseup: event => { remove(true); new Prompt(prompt_type, options, callback, container) }, title: 'Reset' }, '\u21BA'),
- furnish('button.web-to-plex-prompt-accept', { onmouseup: event => { remove(true); callback(data.filter(value => value !== null && value !== undefined)) }, title: 'Continue' }, '\u2714')
+ } }),
+ furnish('button.web-to-plex-prompt-decline', { onmouseup: event => { remove(true); callback([]) }, title: 'Close' }, '\u2718'),
+ furnish('button.web-to-plex-prompt-accept', { onmouseup: event => { remove(true); new Prompt(prompt_type, options, callback, container) }, title: 'Reset' }, '\u21BA'),
+ furnish('button.web-to-plex-prompt-accept', { onmouseup: event => { remove(true); callback(data.filter(value => value !== null && value !== undefined)) }, title: 'Continue' }, '\u2714')
+ )
)
- )
- );
- break;
+ );
+ break;
+
+ /* Allows the user to remove predetermined items */
+ case 'select':
+ remove = element => {
+ let prompter = $('.web-to-plex-prompt').first,
+ header = $('.web-to-plex-prompt-header').first,
+ counter = $('.web-to-plex-prompt-options').first;
+
+ if(element === true)
+ return prompter.remove();
+ else
+ element.remove();
+
+ data.splice(+element.value, 1, null);
+ header.innerText = 'Approve ' + counter.children.length + (counter.children.length == 1?' item': ' items');
+ };
+
+ prompt = furnish('div.web-to-plex-prompt', {},
+ furnish('div.web-to-plex-prompt-body', {},
+ // The prompt's title
+ furnish('h1.web-to-plex-prompt-header', {}, 'Approve ' + array.length + (array.length == 1? ' item': ' items')),
+
+ // The prompt's items
+ furnish('div.web-to-plex-prompt-options', {},
+ ...(ITEMS => {
+ let elements = [];
+
+ for(let index = 0, length = ITEMS.length, ITEM, P_QUA, P_LOC; index < length; index++) {
+ ITEM = ITEMS[index];
+
+ elements.push(
+ furnish('li.web-to-plex-prompt-option.mutable', { value: index, innerHTML: `${ index + 1 } \u00b7 ${ ITEM.title }${ ITEM.year? ` (${ ITEM.year })`: '' } \u2014 ${ ITEM.type }` },
+ furnish('button.remove', { title: `Remove "${ ITEM.title }"`, onmouseup: event => { remove(event.target.parentElement); event.target.remove() } }),
+ (
+ config.PromptQuality?
+ P_QUA = furnish('select.quality', { index, onchange: event => data[event.target.getAttribute('index')].quality = event.target.value }, ...profiles[/(movie|film|cinema)/i.test(ITEM.type)?'movie':'show'].map(Q => furnish('option', { value: Q.id }, Q.name))):
+ ''
+ ),(
+ config.PromptLocation?
+ P_LOC = furnish('select.location', { index, onchange: event => data[event.target.getAttribute('index')].location = event.target.value }, ...locations[/(movie|film|cinema)/i.test(ITEM.type)?'movie':'show'].map(Q => furnish('option', { value: Q.id }, Q.path))):
+ ''
+ )
+ )
+ );
- /* Allows the user to remove predetermined items */
- case 'select':
- remove = element => {
- let prompter = $('.web-to-plex-prompt').first,
- header = $('.web-to-plex-prompt-header').first,
- counter = $('.web-to-plex-prompt-options').first;
+ if(P_QUA) P_QUA.value = defaults[ITEM.type].quality;
+ if(P_LOC) P_LOC.value = defaults[ITEM.type].location;
- if(element === true)
- return prompter.remove();
- else
- element.remove();
-
- data.splice(+element.value, 1, null);
- header.innerText = 'Approve ' + counter.children.length + (counter.children.length == 1?' item': ' items');
- };
+ P_QUA = P_LOC = null;
+ }
- prompt = furnish('div.web-to-plex-prompt', {},
- furnish('div.web-to-plex-prompt-body', {},
- // The prompt's title
- furnish('h1.web-to-plex-prompt-header', {}, 'Approve ' + array.length + (array.length == 1? ' item': ' items')),
-
- // The prompt's items
- furnish('div.web-to-plex-prompt-options', {},
- ...(ITEMS => {
- let elements = [];
-
- for(let index = 0, length = ITEMS.length, ITEM, P_QUA, P_LOC; index < length; index++) {
- ITEM = ITEMS[index];
-
- elements.push(
- furnish('li.web-to-plex-prompt-option.mutable', { value: index, innerHTML: `${ index + 1 } \u00b7 ${ ITEM.title }${ ITEM.year? ` (${ ITEM.year })`: '' } \u2014 ${ ITEM.type }` },
- furnish('button.remove', { title: `Remove "${ ITEM.title }"`, onmouseup: event => { remove(event.target.parentElement); event.target.remove() } }),
- (
- config.PromptQuality?
- P_QUA = furnish('select.quality', { index, onchange: event => data[event.target.getAttribute('index')].quality = event.target.value }, ...profiles[/(movie|film|cinema)/i.test(ITEM.type)?'movie':'show'].map(Q => furnish('option', { value: Q.id }, Q.name))):
- ''
- ),(
- config.PromptLocation?
- P_LOC = furnish('select.location', { index, onchange: event => data[event.target.getAttribute('index')].location = event.target.value }, ...locations[/(movie|film|cinema)/i.test(ITEM.type)?'movie':'show'].map(Q => furnish('option', { value: Q.id }, Q.path))):
- ''
- )
- )
- );
+ return elements
+ })(array)
+ ),
- if(P_QUA) P_QUA.value = defaults[ITEM.type].quality;
- if(P_LOC) P_LOC.value = defaults[ITEM.type].location;
+ // The engagers
+ furnish('div.web-to-plex-prompt-footer', {},
+ furnish('button.web-to-plex-prompt-decline', { onmouseup: event => { remove(true); callback([]) }, title: 'Close' }, '\u2718'),
+ furnish('button.web-to-plex-prompt-accept', { onmouseup: event => { remove(true); new Prompt(prompt_type, options, callback, container) }, title: 'Reset' }, '\u21BA'),
+ furnish('button.web-to-plex-prompt-accept', { onmouseup: event => { remove(true); callback(data.filter(value => value !== null && value !== undefined)) }, title: 'Continue' }, '\u2714')
+ )
+ )
+ );
+ break;
+
+ /* Allows the user to modify a single item (before being pushed) */
+ case 'modify':
+ let { title, year, type, IMDbID, TMDbID, TVDbID } = options,
+ P_QUA, P_LOC;
+
+ let i = IMDbID,
+ t = TMDbID,
+ v = TVDbID,
+ s = 'style="text-decoration: none !important; color: #cc7b19 !important; font-style: italic !important;" target="_blank"';
+
+ i = /^tt-?$/.test(i)? '': i;
+ t = /^0?$/.test(t)? '': t;
+ v = /^0?$/.test(v)? '': v;
+
+ remove = element => {
+ let prompter = $('.web-to-plex-prompt').first,
+ header = $('.web-to-plex-prompt-header').first,
+ counter = $('.web-to-plex-prompt-options').first;
+
+ if(element === true)
+ return prompter.remove();
+ else
+ element.remove();
+ };
+
+ type = /(movie|film|cinema)/i.test(type)?'movie':'show';
+
+ prompt = furnish('div.web-to-plex-prompt', {},
+ furnish('div.web-to-plex-prompt-body', {},
+ // The prompt's title
+ furnish('h1.web-to-plex-prompt-header', { innerHTML: `${ title }${ year? ` (${ year })`: '' } \u2014 ${ type }` }),
+
+ // The prompt's items
+ furnish('div.web-to-plex-prompt-options', {},
+ furnish('div.web-to-plex-prompt-option', { innerHTML: `${ i? `${i}`: '/' } \u2014 ${ t? `${t}`: '/' } \u2014 ${ v? `${v}`: '/' }` }),
+ (
+ config.PromptQuality?
+ P_QUA = furnish('select.quality', { onchange: event => options.quality = event.target.value }, ...profiles[type].map(Q => furnish('option', { value: Q.id }, Q.name))):
+ ''
+ ),
+ furnish('br'),
+ (
+ config.PromptLocation?
+ P_LOC = furnish('select.location', { onchange: event => options.location = event.target.value }, ...locations[type].map(Q => furnish('option', { value: Q.id }, Q.path))):
+ ''
+ )
+ ),
+
+ // The engagers
+ furnish('div.web-to-plex-prompt-footer', {},
+ furnish('button.web-to-plex-prompt-decline', { onmouseup: event => { remove(true); callback([]) }, title: 'Close' }, '\u2718'),
+ furnish('button.web-to-plex-prompt-accept', { onmouseup: event => { remove(true); new Prompt(prompt_type, options, callback, container) }, title: 'Reset' }, '\u21BA'),
+ furnish('button.web-to-plex-prompt-accept', { onmouseup: event => { remove(true); callback(options) }, title: 'Continue' }, '\u2714')
+ )
+ )
+ );
- P_QUA = P_LOC = null;
- }
+ if(P_QUA) P_QUA.value = defaults[type].quality;
+ if(P_LOC) P_LOC.value = defaults[type].location;
- return elements
- })(array)
- ),
+ P_QUA = P_LOC = null;
+ break;
- // The engagers
- furnish('div.web-to-plex-prompt-footer', {},
- furnish('button.web-to-plex-prompt-decline', { onmouseup: event => { remove(true); callback([]) }, title: 'Close' }, '\u2718'),
- furnish('button.web-to-plex-prompt-accept', { onmouseup: event => { remove(true); new Prompt(prompt_type, options, callback, container) }, title: 'Reset' }, '\u21BA'),
- furnish('button.web-to-plex-prompt-accept', { onmouseup: event => { remove(true); callback(data.filter(value => value !== null && value !== undefined)) }, title: 'Continue' }, '\u2714')
- )
- )
- );
- break;
+ default:
+ return terminal.warn(`Unknown prompt type "${ prompt_type }"`);
+ break;
+ }
- default:
- return terminal.warn(`Unknown prompt type "${ prompt_type }"`);
- break;
+ return container.append(prompt), prompt;
}
+ }
- return container.append(prompt), prompt;
+ // self explanatory
+ function openOptionsPage() {
+ chrome.runtime.sendMessage({
+ type: 'OPEN_OPTIONS'
+ });
}
-}
-// Send an update query to background.js
-function sendUpdate(type, options = {}) {
- terminal.log(`Requesting update: ${ type }`, options);
+ // Send an update query to background.js
+ sendUpdate = (type, options = {}, postToo) => {
+ if(config)
+ terminal.log(`Requesting update: ${ type }`, options);
- chrome.runtime.sendMessage({
- type,
- options
- });
-}
+ chrome.runtime.sendMessage({
+ type,
+ options
+ });
-// get the saved options
-function $getOptions() {
- return new Promise((resolve, reject) => {
- function handleOptions(options) {
- if ((!options.plexToken || !options.servers) && !options.DO_NOT_USE)
- return reject(new Error('Required options are missing')),
- null;
-
- let server, o;
-
- if (!options.DO_NOT_USE) {
- // For now we support only one Plex server, but the options already
- // allow multiple for easy migration in the future.
- server = options.servers[0];
- o = {
- server: {
- ...server,
- // Compatibility for users who have not updated their settings yet.
- connections: server.connections || [{ uri: server.url }]
- },
- ...options
- };
+ if(postToo)
+ top.postMessage(options);
+ };
- options.plexURL = o.plexURL?
- `${ o.plexURL }web#!/server/${ o.server.id }/`:
- `https://app.plex.tv/web/app#!/server/${ o.server.id }/`;
- } else {
- o = options;
- }
+ // get the saved options
+ function __getOptions__() {
+ return new Promise((resolve, reject) => {
+ function handleOptions(options) {
+ if((!options.plexToken || !options.servers) && !options.DO_NOT_USE)
+ return reject(new Error('Required options are missing')),
+ null;
+
+ let server, o;
+
+ if(!options.DO_NOT_USE) {
+ // For now we support only one Plex server, but the options already
+ // allow multiple for easy migration in the future.
+ server = options.servers[0];
+ o = {
+ server: {
+ ...server,
+ // Compatibility for users who have not updated their settings yet.
+ connections: server.connections || [{ uri: server.url }]
+ },
+ ...options
+ };
+
+ options.plexURL = o.plexURL?
+ `${ o.plexURL }web#!/server/${ o.server.id }/`:
+ `https://app.plex.tv/web/app#!/server/${ o.server.id }/`;
+ } else {
+ o = options;
+ }
- if (o.couchpotatoBasicAuthUsername)
- o.couchpotatoBasicAuth = {
- username: o.couchpotatoBasicAuthUsername,
- password: o.couchpotatoBasicAuthPassword
- };
+ if(o.couchpotatoBasicAuthUsername)
+ o.couchpotatoBasicAuth = {
+ username: o.couchpotatoBasicAuthUsername,
+ password: o.couchpotatoBasicAuthPassword
+ };
+
+ // TODO: stupid copy/pasta
+ if(o.watcherBasicAuthUsername)
+ o.watcherBasicAuth = {
+ username: o.watcherBasicAuthUsername,
+ password: o.watcherBasicAuthPassword
+ };
+
+ if(o.radarrBasicAuthUsername)
+ o.radarrBasicAuth = {
+ username: o.radarrBasicAuthUsername,
+ password: o.radarrBasicAuthPassword
+ };
+
+ if(o.sonarrBasicAuthUsername)
+ o.sonarrBasicAuth = {
+ username: o.sonarrBasicAuthUsername,
+ password: o.sonarrBasicAuthPassword
+ };
+
+ if(o.medusaBasicAuthUsername)
+ o.medusaBasicAuth = {
+ username: o.medusaBasicAuthUsername,
+ password: o.medusaBasicAuthPassword
+ };
+
+ if(o.usingOmbi && o.ombiURLRoot && o.ombiToken) {
+ o.ombiURL = o.ombiURLRoot;
+ } else {
+ delete o.ombiURL; // prevent variable ghosting
+ }
- // TODO: stupid copy/pasta
- if (o.watcherBasicAuthUsername)
- o.watcherBasicAuth = {
- username: o.watcherBasicAuthUsername,
- password: o.watcherBasicAuthPassword
- };
+ if(o.usingCouchPotato && o.couchpotatoURLRoot && o.couchpotatoToken) {
+ o.couchpotatoURL = `${ items.couchpotatoURLRoot }/api/${encodeURIComponent(o.couchpotatoToken)}`;
+ } else {
+ delete o.couchpotatoURL; // prevent variable ghosting
+ }
- if (o.radarrBasicAuthUsername)
- o.radarrBasicAuth = {
- username: o.radarrBasicAuthUsername,
- password: o.radarrBasicAuthPassword
- };
+ if(o.usingWatcher && o.watcherURLRoot && o.watcherToken) {
+ o.watcherURL = o.watcherURLRoot;
+ } else {
+ delete o.watcherURL; // prevent variable ghosting
+ }
- if (o.sonarrBasicAuthUsername)
- o.sonarrBasicAuth = {
- username: o.sonarrBasicAuthUsername,
- password: o.sonarrBasicAuthPassword
- };
+ if(o.usingRadarr && o.radarrURLRoot && o.radarrToken) {
+ o.radarrURL = o.radarrURLRoot;
+ } else {
+ delete o.radarrURL; // prevent variable ghosting
+ }
- if (o.ombiURLRoot && o.ombiToken) {
- o.ombiURL = o.ombiURLRoot;
- } else {
- delete o.ombiURL; // prevent variable ghosting
- }
+ if(o.usingSonarr && o.sonarrURLRoot && o.sonarrToken) {
+ o.sonarrURL = o.sonarrURLRoot;
+ } else {
+ delete o.sonarrURL; // prevent variable ghosting
+ }
- if (o.couchpotatoURLRoot && o.couchpotatoToken) {
- o.couchpotatoURL = `${ items.couchpotatoURLRoot }/api/${encodeURIComponent(o.couchpotatoToken)}`;
- } else {
- delete o.couchpotatoURL; // prevent variable ghosting
- }
+ if(o.usingMedusa && o.medusaURLRoot && o.medusaToken) {
+ o.medusaURL = o.medusaURLRoot;
+ } else {
+ delete o.medusaURL; // prevent variable ghosting
+ }
- if (o.watcherURLRoot && o.watcherToken) {
- o.watcherURL = o.watcherURLRoot;
- } else {
- delete o.watcherURL; // prevent variable ghosting
+ resolve(o);
}
- if (o.radarrURLRoot && o.radarrToken) {
- o.radarrURL = o.radarrURLRoot;
- } else {
- delete o.radarrURL; // prevent variable ghosting
- }
+ storage.get(null, options => {
+ if(chrome.runtime.lastError)
+ chrome.storage.local.get(null, handleOptions);
+ else
+ handleOptions(options);
+ });
+ });
+ }
- if (o.sonarrURLRoot && o.sonarrToken) {
- o.sonarrURL = o.sonarrURLRoot;
- } else {
- delete o.sonarrURL; // prevent variable ghosting
- }
+ // self explanatory, returns an object; sets the config variable
+ function parseOptions() {
+ return __getOptions__()
+ .then(
+ options => (config = options),
+ error => {
+ new Notification(
+ 'warning',
+ 'Fill in missing Web to Plex options',
+ 15000,
+ openOptionsPage
+ );
+ throw error;
+ }
+ );
+ }
- resolve(o);
- }
+ await parseOptions();
- storage.get(null, options => {
- if (chrome.runtime.lastError)
- chrome.storage.local.get(null, handleOptions);
- else
- handleOptions(options);
- });
- });
-}
+ let AUTO_GRAB = {
+ ENABLED: config.UseAutoGrab,
+ LIMIT: config.AutoGrabLimit,
+ },
+ DISABLE_DEBUGGER = !config.ExtensionBranchType, // = { false: Developer Mode, true: Standard Mode }
+ terminal =
+ DISABLE_DEBUGGER?
+ { error: m => m, info: m => m, log: m => m, warn: m => m, group: m => m, groupEnd: m => m }:
+ console;
-// self explanatory
-function openOptionsPage() {
- chrome.runtime.sendMessage({
- type: 'OPEN_OPTIONS'
- });
-}
+ terminal.log('DISABLE_DEBUGGER:', DISABLE_DEBUGGER, config);
-// self explanatory, returns an object
-function parseOptions() {
- return $getOptions()
- .then(
- options => (config = options),
- error => {
- new Notification(
- 'warning',
- 'Fill in missing Web to Plex options',
- 15000,
- openOptionsPage
- );
- throw error;
- }
- );
-}
+ // parse the formatted headers and URL
+ function HandleProxyHeaders(Headers = "", URL = "") {
+ let headers = {};
-let config = parseOptions(),
- AUTO_GRAB = {
- ENABLED: config.UseAutoGrab,
- LIMIT: config.AutoGrabLimit,
- };
+ Headers.replace(/^[ \t]*([^\=\s]+)[ \t]*=[ \t]*((["'`])(?:[^\\\3]*|\\.)\3|[^\f\n\r\v]*)/gm, ($0, $1, $2, $3, $$, $_) => {
+ let string = !!$3;
-function HandleProxyHeaders(Headers = "", URL = "") {
- let headers = {};
+ if(string) {
+ headers[$1] = $2.replace(RegExp(`^${ $3 }|${ $3 }$`, 'g'), '');
+ } else {
+ $2 = $2.replace(/@([\w\.]+)/g, (_0, _1, _$, __) => {
+ let path = _1.split('.'), property = top;
- Headers.replace(/^[ \t]*([^\=\s]+)[ \t]*=[ \t]*((["'`])(?:[^\\\3]*|\\.)\3|[^\f\n\r\v]*)/gm, ($0, $1, $2, $3, $$, $_) => {
- let string = !!$3;
+ for(let index = 0, length = path.length; index < length; index++)
+ property = property[path[index]];
- if(string) {
- headers[$1] = $2.replace(RegExp(`^${ $3 }|${ $3 }$`, 'g'), '');
- } else {
- $2 = $2.replace(/@([\w\.]+)/g, (_0, _1, _$, __) => {
- let path = _1.split('.'), property = top;
+ headers[$1] = property;
+ })
+ .replace(/@\{b(ase-?)?64-url\}/gi, btoa(URL))
+ .replace(/@\{enc(ode)?-url\}/gi, encodeURIComponent(URL))
+ .replace(/@\{(raw-)?url\}/gi, URL);
+ }
+ });
- for(let index = 0, length = path.length; index < length; index++)
- property = property[path[index]];
+ return headers;
+ }
- headers[$1] = property;
- })
- .replace(/@\{b(ase-?)?64-url\}/gi, btoa(URL))
- .replace(/@\{enc(ode)?-url\}/gi, encodeURIComponent(URL))
- .replace(/@\{(raw-)?url\}/gi, URL);
+ // fetch/search for the item's media ID(s)
+ // rerun enum - [0bWXYZ] - [Tried Different URL | Tried Matching Title | Tried Loose Searching | Tried Rerunning Altogether]
+ async function getIDs({ title, year, type, IMDbID, TMDbID, TVDbID, APIType, APIID, meta, rerun }) {
+ let json = {}, // returned object
+ data = {}, // mutated object
+ promise, // query promise
+ api = {
+ tmdb: config.TMDbAPI || 'bcb95f026f9a01ffa707fcff71900e94',
+ omdb: config.OMDbAPI || 'PlzBanMe',
+ ombi: config.ombiToken,
+ },
+ apit = APIType || type, // api type (depends on "rqut")
+ apid = APIID || null, // api id
+ iid = IMDbID || null, // IMDbID
+ mid = TMDbID || null, // TMDbID
+ tid = TVDbID || null, // TVDbID
+ rqut = apit, // request type: tmdb, imdb, or tvdb
+ manable = config.ManagerSearch && !(rerun & 0b1000), // is the user's "Manager Searches" option enabled?
+ UTF_16 = /[^0\u0020-\u007e, 1\u00a1\u00bf-\u00ff, 2\u0100-\u017f, 3\u0180-\u024f, 4\u0300-\u036f, 5\u0370-\u03ff, 6\u0400-\u04ff, 7\u0500-\u052f, 8\u20a0-\u20bf]+/g;
+
+ type = type || null;
+ meta = { ...meta, mode: 'cors' };
+ rqut =
+ /(tv|show|series)/i.test(rqut)?
+ 'tvdb':
+ /(movie|film|cinema)s?/i.test(rqut)?
+ 'tmdb':
+ rqut || '*';
+ manable = manable && (config.usingOmbi || (config.usingRadarr && rqut == 'tmdb') || ((config.usingSonarr || config.usingMedusa) && rqut == 'tvdb'));
+ title = (title? title.replace(/\s*[\:,]\s*seasons?\s+\d+.*$/i, '').toCaps(): "")
+ .replace(/[\u2010-\u2015]/g, '-') // fancy hyphen
+ .replace(/[\u201a\u275f]/g, ',') // fancy comma
+ .replace(/[\u2018\u2019\u201b\u275b\u275c]/g, "'") // fancy apostrophe
+ .replace(/[\u201c-\u201f\u275d\u275e]/g, '"') // fancy quotation marks
+ .replace(UTF_16, ''); // only accept "usable" characters
+ /* 0[ -~], 1[¡¿-ÿ], 2[Ā-ſ], 3[ƀ-ɏ], 4[ò-oͯ], 5[Ͱ-Ͽ], 6[Ѐ-ӿ], 7[Ԁ-ԯ], 8[₠-₿] */
+ /** Symbol Classes
+ 0) Basic Latin, and standard characters
+ 1) Latin (Supplement)
+ 2) Latin Extended I
+ 3) Latin Extended II
+ 4) Diatrical Marks
+ 5) Greek & Coptic
+ 6) Basic Cyrillic
+ 7) Cyrillic (Supplement)
+ 8) Currency Symbols
+ */
+ year = year? (year + '').replace(/\D+/g, ''): year;
+
+ let plus = (string, character = '+') => string.replace(/\s+/g, character);
+
+ let local, savename;
+
+ if(year) {
+ savename = `${title} (${year}).${rqut}`.toLowerCase(),
+ local = await load(savename);
+ } else {
+ year = await load(`${title}.${rqut}`.toLowerCase()) || year;
+ `${title} (${year}).${rqut}`.toLowerCase();
+ local = await load(savename);
}
- });
- return headers;
-}
+ if(local) {
+ terminal.log('[LOCAL] Search results', local);
+ return local;
+ }
-// fetch/search for the item's media ID(s)
-async function getIDs({ title, year, type, IMDbID, TMDbID, TVDbID, APIType, APIID, meta, rerun }) {
- let json = {}, // returned object
- data = {}, // mutated object
- promise, // query promise
- api = {
- tmdb: config.TMDbAPI || 'bcb95f026f9a01ffa707fcff71900e94',
- omdb: config.OMDbAPI || 'PlzBanMe',
- ombi: config.ombiToken,
- },
- apit = APIType || type, // api type (depends on "rqut")
- apid = APIID || null, // api id
- iid = IMDbID || null, // IMDbID
- mid = TMDbID || null, // TMDbID
- tid = TVDbID || null, // TVDbID
- rqut = apit, // request type: tmdb, imdb, or tvdb
- manable = config.ManagerSearch && !rerun; // is the user's "Manager Searches" option enabled?
-
- type = type || null;
- meta = { ...meta, mode: 'cors' };
- rqut =
- /(tv|show|series)/i.test(rqut)?
- 'tvdb':
- /(movie|film|cinema)s?/i.test(rqut)?
- 'tmdb':
- rqut || '*';
- manable = manable && (config.usingOmbi || (config.usingRadarr && rqut == 'tmdb') || (config.usingSonarr && rqut == 'tvdb'));
- title = (title? title.replace(/\s*[\:,]\s*seasons?\s+\d+.*$/i, '').toCaps(): "")
- .replace(/\u201a/g, ',') // fancy comma
- .replace(/[\u2019\u201b]/g, "'") // fancy apostrophe
- .replace(/[\u201c\u201d]/g, '"') // fancy quotation marks
- .replace(/[^\u0000-\u00ff]+/g, ''); // only accept UTF-8 characters
- year = year? (year + '').replace(/\D+/g, ''): year;
-
- let plus = (string, character = '+') => string.replace(/\s+/g, character);
-
- let local, savename;
-
- if(year) {
- savename = `${title} (${year}).${rqut}`.toLowerCase(),
- local = await load(savename);
- } else {
- year = await load(`${title}.${rqut}`.toLowerCase()) || year;
- `${title} (${year}).${rqut}`.toLowerCase();
- local = await load(savename);
- }
+ /* the rest of this function is a beautiful mess that will need to be dealt with later... but it works */
+ let url =
+ (manable && title && config.usingOmbi)?
+ `${ config.ombiURLRoot }api/v1/Search/${ (rqut == 'imdb' || rqut == 'tmdb' || apit == 'movie')? 'movie': 'tv' }/${ plus(title, '%20') }/?apikey=${ api.ombi }`:
+ (manable && (config.usingRadarr || config.usingSonarr || config.usingMedusa))?
+ (config.usingRadarr && (rqut == 'imdb' || rqut == 'tmdb'))?
+ (mid)?
+ `${ config.radarrURLRoot }api/movie/lookup/tmdb?tmdbId=${ mid }&apikey=${ config.radarrToken }`:
+ (iid)?
+ `${ config.radarrURLRoot }api/movie/lookup/imdb?imdbId=${ iid }&apikey=${ config.radarrToken }`:
+ `${ config.radarrURLRoot }api/movie/lookup?term=${ plus(title, '%20') }&apikey=${ config.radarrToken }`:
+ (config.usingSonarr)?
+ (tid)?
+ `${ config.sonarrURLRoot }api/series/lookup?term=tvdb:${ tid }&apikey=${ config.sonarrToken }`:
+ `${ config.sonarrURLRoot }api/series/lookup?term=${ plus(title, '%20') }&apikey=${ config.sonarrToken }`:
+ (config.usingMedusa)?
+ (tid)?
+ `${ config.medusarURLRoot }api/v2/series/tvdb${ tid }?detailed=true&${ tid }&api_key=${ config.medusaToken }`:
+ `${ config.medusaURLRoot }api/v2/internal/searchIndexersForShowName?query=${ plus(title) }&indexerId=0&api_key=${ config.medusaToken }`:
+ null:
+ (rqut == 'imdb' || (rqut == '*' && !iid && title) || (rqut == 'tvdb' && !iid && title && !(rerun & 0b1000)) && (rerun |= 0b1000))?
+ (iid)?
+ `https://www.omdbapi.com/?i=${ iid }&apikey=${ api.omdb }`:
+ (year)?
+ `https://www.omdbapi.com/?t=${ plus(title) }&y=${ year }&apikey=${ api.omdb }`:
+ `https://www.omdbapi.com/?t=${ plus(title) }&apikey=${ api.omdb }`:
+ (rqut == 'tmdb' || (rqut == '*' && !mid && title && year) || apit == 'movie')?
+ (apit && apid)?
+ `https://api.themoviedb.org/3/${ apit }/${ apid }?api_key=${ api.tmdb }`:
+ (iid)?
+ `https://api.themoviedb.org/3/find/${ iid || mid || tid }?api_key=${ api.tmdb }&external_source=${ iid? 'imdb': mid? 'tmdb': 'tvdb' }_id`:
+ `https://api.themoviedb.org/3/search/${ apit }?api_key=${ api.tmdb }&query=${ encodeURI(title) }${ year? '&year=' + year: '' }`:
+ (rqut == 'tvdb' || (rqut == '*' && !tid && title) || (apid == tid))?
+ (tid)?
+ `https://api.tvmaze.com/shows/?thetvdb=${ tid }`:
+ (iid)?
+ `https://api.tvmaze.com/shows/?imdb=${ iid }`:
+ `https://api.tvmaze.com/search/shows?q=${ encodeURI(title) }`:
+ (title)?
+ (apit && year)?
+ `https://www.theimdbapi.org/api/find/${ apit }?title=${ encodeURI(title) }&year=${ year }`:
+ `https://www.theimdbapi.org/api/find/movie?title=${ encodeURI(title) }${ year? '&year=' + year: '' }`:
+ null;
+
+ if(url === null) return null;
+
+ let proxy = config.proxy,
+ cors = proxy.url, // if cors is requried and not uspported, proxy through this URL
+ headers = HandleProxyHeaders(proxy.headers, url);
+
+ if(proxy.enabled && /(^http:\/\/)(?!localhost|127\.0\.0\.1(?:\/8)?|::1(?:\/128)?|:\d+)\b/i.test(url)) {
+ url = cors
+ .replace(/\{b(ase-?)?64-url\}/gi, btoa(url))
+ .replace(/\{enc(ode)?-url\}/gi, encodeURIComponent(url))
+ .replace(/\{(raw-)?url\}/gi, url);
+
+ terminal.log({ proxy, url, headers });
+ }
- if(local) {
- terminal.log('[LOCAL] Search results', local);
- return local;
- }
+ terminal.log(`Searching for "${ title } (${ year })" in ${ type || apit }/${ rqut }${ proxy.enabled? '[PROXY]': '' } => ${ url }`);
- /* the rest of this function is a beautiful mess that will need to be dealt with later... but it works */
- let url =
- (manable && title && config.usingOmbi)?
- `${ config.ombiURLRoot }api/v1/Search/${ (rqut == 'imdb' || rqut == 'tmdb' || apit == 'movie')? 'movie': 'tv' }/${ plus(title, '%20') }/?apikey=${ api.ombi }`:
- (manable && (config.usingRadarr || config.usingSonarr))?
- (config.usingRadarr && (rqut == 'imdb' || rqut == 'tmdb'))?
- (mid)?
- `${ config.radarrURLRoot }api/movie/lookup/tmdb?tmdbId=${ mid }&apikey=${ config.radarrToken }`:
- (iid)?
- `${ config.radarrURLRoot }api/movie/lookup/imdb?imdbId=${ iid }&apikey=${ config.radarrToken }`:
- `${ config.radarrURLRoot }api/movie/lookup?term=${ plus(title, '%20') }&apikey=${ config.radarrToken }`:
- (tid)?
- `${ config.sonarrURLRoot }api/series/lookup?term=tvdb:${ tid }&apikey=${ config.sonarrToken }`:
- `${ config.sonarrURLRoot }api/series/lookup?term=${ plus(title, '%20') }&apikey=${ config.sonarrToken }`:
- (rqut == 'imdb' || (rqut == '*' && !iid && title) || (rqut == 'tvdb' && !iid && title && rerun))?
- (iid)?
- `https://www.omdbapi.com/?i=${ iid }&apikey=${ api.omdb }`:
- (year)?
- `https://www.omdbapi.com/?t=${ plus(title) }&y=${ year }&apikey=${ api.omdb }`:
- `https://www.omdbapi.com/?t=${ plus(title) }&apikey=${ api.omdb }`:
- (rqut == 'tmdb' || (rqut == '*' && !mid && title && year) || apit == 'movie')?
- (apit && apid)?
- `https://api.themoviedb.org/3/${ apit }/${ apid }?api_key=${ api.tmdb }`:
- (iid)?
- `https://api.themoviedb.org/3/find/${ iid || mid || tid }?api_key=${ api.tmdb }&external_source=${ iid? 'imdb': mid? 'tmdb': 'tvdb' }_id`:
- `https://api.themoviedb.org/3/search/${ apit }?api_key=${ api.tmdb }&query=${ encodeURI(title) }${ year? '&year=' + year: '' }`:
- (rqut == 'tvdb' || (rqut == '*' && !tid && title) || (apid == tid))?
- (tid)?
- `https://api.tvmaze.com/shows/?thetvdb=${ tid }`:
- (iid)?
- `https://api.tvmaze.com/shows/?imdb=${ iid }`:
- `https://api.tvmaze.com/search/shows?q=${ encodeURI(title) }`:
- (title)?
- (apit && year)?
- `https://www.theimdbapi.org/api/find/${ apit }?title=${ encodeURI(title) }&year=${ year }`:
- `https://www.theimdbapi.org/api/find/movie?title=${ encodeURI(title) }${ year? '&year=' + year: '' }`:
- null;
-
- if(url === null) return null;
-
- let proxy = config.proxy,
- cors = proxy.url, // if cors is requried and not uspported, proxy through this URL
- headers = HandleProxyHeaders(proxy.headers, url);
-
- if(proxy.enabled && /(^http:\/\/)(?!localhost|127\.0\.0\.1(?:\/8)?|::1(?:\/128)?|:\d+)\b/i.test(url)) {
- url = cors
- .replace(/\{b(ase-?)?64-url\}/gi, btoa(url))
- .replace(/\{enc(ode)?-url\}/gi, encodeURIComponent(url))
- .replace(/\{(raw-)?url\}/gi, url);
-
- terminal.log({ proxy, url, headers });
- }
+ await(proxy.enabled? fetch(url, { mode: "cors", headers }): fetch(url))
+ .then(response => response.text())
+ .then(data => {
+ try {
+ if(data)
+ json = JSON.parse(data);
+ } catch(error) {
+ terminal.error(`Failed to parse JSON: "${ data }"`);
+ }
+ })
+ .catch(error => {
+ throw error;
+ });
- terminal.log(`Searching for "${ title } (${ year })" in ${ type || apit }/${ rqut }${ proxy.enabled? '[PROXY]': '' } => ${ url }`);
+ terminal.log('Search results', { title, year, url, json });
- await(proxy? fetch(url, { mode: "cors", headers }): fetch(url))
- .then(response => response.text())
- .then(data => {
- try {
- if(data)
- json = JSON.parse(data);
- } catch(error) {
- terminal.error(`Failed to parse JSON: "${ data }"`);
- }
- })
- .catch(error => {
- throw error;
- });
+ if('results' in json)
+ json = json.results;
- terminal.log('Search results', { title, year, url, json });
+ if(json instanceof Array) {
+ let b = { release_date: '', year: '' },
+ t = (s = "") => s.toLowerCase(),
+ c = (s = "") => t(s).replace(/\&/g, 'and').replace(UTF_16, ''),
+ k = (s = "") => {
- if('results' in json)
- json = json.results;
+ let r = [
+ [/(?!^\s*)\b(show|series|a([st]|nd?|cross|fter|lthough)?|b(e(cause|fore|tween)|ut|y)|during|from|in(to)?|[io][fn]|[fn]?or|the|[st]o|through|under|with(out)?|yet)\b\s*/gi, ''],
+ // try replacing common words, e.g. Conjunctions, "Show," "Series," etc.
+ [/\^\s*|\s*$/g, ''],
+ [/\s+/g, '|']
+ ];
- if(json instanceof Array) {
- let b = { release_date: '', year: '' },
- t = (s = "") => s.toLowerCase(),
- c = (s = "") => t(s).replace(/\&/g, 'and').replace(/\W+/g, ''),
- k = (s = "") => {
+ for(let i = 0; i < r.length; i++) {
+ if(/^([\(\|\)]+)?$/.test(s)) return "";
- let r = [
- [/(?!^\s*)\b(show|series|a([st]|nd?|cross|fter|lthough)?|b(e(cause|fore|tween)|ut|y)|during|from|in(to)?|[io][fn]|[fn]?or|the|[st]o|through|under|with(out)?|yet)\b\s*/gi, ''],
- // try replacing common words, e.g. Conjunctions, "Show," "Series," etc.
- [/\s+/g, '|']
- ];
+ s = s.replace(r[i][0], r[i][1]);
+ }
- for(let i = 0; i < r.length; i++) {
- if(/^([\(\|\)]+)?$/.test(s)) return "";
+ return c(s);
+ },
+ R = (s = "", S = "", n = !0) => {
+ let l = s.split(' ').length, L = S.split(' ').length,
+ score = 100 * (((S.match(RegExp(`\\b(${k(s)})\\b`, 'i')) || [null]).length) / (L || 1)),
+ passing = config.UseLooseScore | 0;
- s = s.replace(r[i][0], r[i][1]);
- }
+ terminal.log(`=> "${ s }"/"${ S }" = ${ score }`);
+ score *= (l > L? (L||1)/l: L > l? (l||1)/L: 1);
+ terminal.log(`~> ... = ${ score }`);
- return c(s);
- },
- R = (s = "", S = "", n = !0) => {
- let score = 100 * ((S.match(RegExp(`\\b(${k(s)})\\b`, 'i')) || [null]).length / (S.split(' ').length || 1)),
- passing = config.UseLooseScore | 0;
+ return (S != '' && score >= passing) || (n? R(S, s, !n): n);
+ },
+ en = /^(u[ks]-?|utf8-?)?en(glish)?$/i;
+
+ // Find an exact match: Title (Year) | #IMDbID
+ let index, found, $data, lastscore;
+ for(index = 0, found = false, $data, lastscore = 0; (title && year) && index < json.length && !found; rerun |= 0b0100, index++) {
+ $data = json[index];
+
+ let altt = $data.alternativeTitles,
+ $alt = (altt && altt.length? altt.filter(v => t(v) == t(title))[0]: null);
+
+ // Managers
+ if(manable)
+ // Medusa
+ if(config.usingMedusa && $data instanceof Array)
+ found = ((t($data[4]) == t(title) || $alt) && +year === +$data[5].slice(0, 4))?
+ $alt || $data:
+ found;
+ // Radarr & Sonarr
+ else if(config.usingRadarr || config.usingSonarr)
+ found = ((t($data.title) == t(title) || $alt) && +year === +$data.year)?
+ $alt || $data:
+ found;
+ //api.tvmaze.com/
+ else if(('externals' in ($data = $data.show || $data) || 'show' in $data) && $data.premiered)
+ found = (iid == $data.externals.imdb || t($data.name) == t(title) && year == $data.premiered.slice(0, 4))?
+ $data:
+ found;
+ //api.themoviedb.org/ \local
+ else if(('movie_results' in $data || 'tv_results' in $data || 'results' in $data) && $data.release_date)
+ found = (DATA => {
+ if(DATA.results)
+ if(rqut == 'tmdb')
+ DATA.movie_results = DATA.results;
+ else
+ DATA.tv_results = DATA.results;
+
+ let i, f, o, l;
+
+ for(i = 0, f = !1, o = DATA.movie_results, l = o.length | 0; i < l; i++)
+ f = (t(o.title) === t(title) && o.release_date.slice(0, 4) == year);
+
+ for(i = (+f * l), o = (f? o: DATA.tv_results), l = (f? l: o.length | 0); i < l; i++)
+ f = (t(o.name) === t(title) && o.first_air_date.slice(0, 4) == year);
+
+ return f? o: f = !!iid;
+ })($data);
+ //api.themoviedb.org/ \remote
+ else if(('original_name' in $data || 'original_title' in $data) && $data.release_date)
+ found = (tid == $data.id || (t($data.original_name || $data.original_title) == t(title) || t($data.name) == t(title)) && year == ($data || b).release_date.slice(0, 4))?
+ $data:
+ found;
+ //theimdbapi.org/
+ else if($data.release_date)
+ found = (t($data.title) === t(title) && year == ($data.url || $data || b).release_date.slice(0, 4))?
+ $data:
+ found;
- return (S != '' && score >= passing) || (n? R(S, s, !n): n);
- },
- en = /^(u[ks]-?|utf8-?)?en(glish)?$/i;
-
- // Find an exact match: Title (Year) | #IMDbID
- let index, found, $data, lastscore;
- for(index = 0, found = false, $data, lastscore = 0; (title && year) && index < json.length && !found; index++) {
- $data = json[index];
-
- let altt = $data.alternativeTitles,
- $alt = (altt && altt.length? altt.filter(v => t(v) == t(title))[0]: null);
-
- // Radarr & Sonarr
- if(manable)
- found = ((t($data.title) == t(title) || $alt) && +year === +$data.year)?
- $alt || $data:
- found;
- //api.tvmaze.com/
- else if(('externals' in ($data = $data.show || $data) || 'show' in $data) && $data.premiered)
- found = (iid == $data.externals.imdb || t($data.name) == t(title) && year == $data.premiered.slice(0, 4))?
- $data:
- found;
- //api.themoviedb.org/ \local
- else if(('movie_results' in $data || 'tv_results' in $data || 'results' in $data) && $data.release_date)
- found = (DATA => {
- if(DATA.results)
- if(rqut == 'tmdb')
- DATA.movie_results = DATA.results;
- else
- DATA.tv_results = DATA.results;
-
- let i, f, o, l;
-
- for(i = 0, f = !1, o = DATA.movie_results, l = o.length | 0; i < l; i++)
- f = (t(o.title) === t(title) && o.release_date.slice(0, 4) == year);
-
- for(i = (+f * l), o = (f? o: DATA.tv_results), l = (f? l: o.length | 0); i < l; i++)
- f = (t(o.name) === t(title) && o.first_air_date.slice(0, 4) == year);
-
- return f? o: f = !!iid;
- })($data);
- //api.themoviedb.org/ \remote
- else if(('original_name' in $data || 'original_title' in $data) && $data.release_date)
- found = (tid == $data.id || (t($data.original_name || $data.original_title) == t(title) || t($data.name) == t(title)) && year == ($data || b).release_date.slice(0, 4))?
- $data:
- found;
- //theimdbapi.org/
- else if($data.release_date)
- found = (t($data.title) === t(title) && year == ($data.url || $data || b).release_date.slice(0, 4))?
- $data:
- found;
-
-// terminal.log(`Strict Matching: ${ !!found }`, !!found? found: null);
- }
+ terminal.log(`Strict Matching: ${ !!found }`, !!found? found: null);
+ }
- // Find a close match: Title
- for(index = 0; title && index < json.length && (!found || lastscore > 0); index++) {
- $data = json[index];
-
- let altt = $data.alternativeTitles,
- $alt = (altt && altt.length? altt.filter(v => c(v) == c(title)): null);
-
- // Radarr & Sonarr
- if(manable)
- found = (c($data.title) == c(title) || $alt)?
- $alt || $data:
- found;
- //api.tvmaze.com/
- else if('externals' in ($data = $data.show || $data) || 'show' in $data)
- found =
- // ignore language barriers
- (c($data.name) == c(title))?
+ // Find a close match: Title
+ for(index = 0; title && index < json.length && (!found || lastscore > 0); rerun |= 0b0100, index++) {
+ $data = json[index];
+
+ let altt = $data.alternativeTitles,
+ $alt = (altt && altt.length? altt.filter(v => c(v) == c(title)): null);
+
+ // Managers
+ if(manable)
+ // Medusa
+ if(config.usingMedusa && $data instanceof Array)
+ found = (c($data[4]) == c(title) || $alt)?
+ $alt || $data:
+ found;
+ // Radarr & Sonarr
+ if(config.usingRadarr || config.usingSonarr)
+ found = (c($data.title) == c(title) || $alt)?
+ $alt || $data:
+ found;
+ //api.tvmaze.com/
+ else if('externals' in ($data = $data.show || $data) || 'show' in $data)
+ found =
+ // ignore language barriers
+ (c($data.name) == c(title))?
+ $data:
+ // trust the api matching
+ ($data.score > lastscore)?
+ (lastscore = $data.score || $data.vote_count, $data):
+ found;
+ //api.themoviedb.org/ \local
+ else if('movie_results' in $data || 'tv_results' in $data || 'results' in $data)
+ found = (DATA => {
+ let i, f, o, l;
+
+ if(DATA.results)
+ if(rqut == 'tmdb')
+ DATA.movie_results = DATA.results;
+ else
+ DATA.tv_results = DATA.results;
+
+ for(i = 0, f = !1, o = DATA.movie_results, l = o.length | 0; i < l; i++)
+ f = (c(o.title) == c(title));
+
+ for(i = (+f * l), o = (f? o: DATA.tv_results), l = (f? l: o.length | 0); i < l; i++)
+ f = (c(o.name) == c(title));
+
+ return f? o: f;
+ })($data);
+ //api.themoviedb.org/ \remote
+ else if('original_name' in $data || 'original_title' in $data || 'name' in $data)
+ found = (c($data.original_name || $data.original_title || $data.name) == c(title))?
+ $data:
+ found;
+ //theimdbapi.org/
+ else if(en.test($data.language))
+ found = (c($data.title) == c(title))?
$data:
- // trust the api matching
- ($data.score > lastscore)?
- (lastscore = $data.score || $data.vote_count, $data):
found;
- //api.themoviedb.org/ \local
- else if('movie_results' in $data || 'tv_results' in $data || 'results' in $data)
- found = (DATA => {
- let i, f, o, l;
-
- if(DATA.results)
- if(rqut == 'tmdb')
- DATA.movie_results = DATA.results;
- else
- DATA.tv_results = DATA.results;
-
- for(i = 0, f = !1, o = DATA.movie_results, l = o.length | 0; i < l; i++)
- f = (c(o.title) == c(title));
-
- for(i = (+f * l), o = (f? o: DATA.tv_results), l = (f? l: o.length | 0); i < l; i++)
- f = (c(o.name) == c(title));
-
- return f? o: f;
- })($data);
- //api.themoviedb.org/ \remote
- else if('original_name' in $data || 'original_title' in $data || 'name' in $data)
- found = (c($data.original_name || $data.original_title || $data.name) == c(title))?
- $data:
- found;
- //theimdbapi.org/
- else if(en.test($data.language))
- found = (c($data.title) == c(title))?
- $data:
- found;
-
-// terminal.log(`Title Matching: ${ !!found }`, !!found? found: null);
- }
- // Find an OK match (Loose Searching): Title ~ Title
- // The examples below are correct
- // GOOD, found: VRV's "Bakemonogatari" vs. TVDb's "Monogatari Series"
- // /\b(monogatari)\b/i.test('bakemonogatari') === true
- // this is what this option is for
- // OK, found: "The Title of This is Bad" vs. "The Title of This is OK" (this is semi-errornous)
- // /\b(title|this|bad)\b/i.test('title this ok') === true
- // this may be a possible match, but it may also be an error: 'title' and 'this'
- // BAD, not found: "Gun Show Showdown" vs. "Gundarr"
- // /\b(gun|showdown)\b/i.test('gundarr') === false
- // this should not match; the '\b' (border between \w and \W) keeps them from matching
- for(index = 0; config.UseLoose && title && index < json.length && (!found || lastscore > 0); index++) {
- $data = json[index];
-
- let altt = $data.alternativeTitles,
- $alt = (altt && altt.length? altt.filter(v => R(v, title)): null);
-
- // Radarr & Sonarr
- if(manable)
- found = (R($data.name, title) || $alt)?
- $alt || $data:
- found;
- //api.tvmaze.com/
- else if('externals' in ($data = $data.show || $data) || 'show' in $data)
- found =
- // ignore language barriers
- (R($data.name, title) || terminal.log('Matching:', [$data.name, title], R($data.name, title)))?
+ terminal.log(`Title Matching: ${ !!found }`, !!found? found: null);
+ }
+
+ // Find an OK match (Loose Searching): Title ~ Title
+ // The examples below are correct
+ // GOOD, found: VRV's "Bakemonogatari" vs. TVDb's "Monogatari Series"
+ // /\b(monogatari)\b/i.test('bakemonogatari') === true
+ // this is what this option was designed for
+ // OK, found: "The Title of This is Bad" vs. "The Title of This is OK" (this is semi-errornous)
+ // /\b(title|this|bad)\b/i.test('title this ok') === true
+ // this may be a possible match, but it may also be an error: 'title' and 'this'
+ // the user's defined threshold is used in this case (above 65% would match these two items)
+ // BAD, not found: "Gun Show Showdown" vs. "Gundarr"
+ // /\b(gun|showdown)\b/i.test('gundarr') === false
+ // this should not match; the '\b' (border between \w and \W) keeps them from matching
+ for(index = 0; config.UseLoose && title && index < json.length && (!found || lastscore > 0); rerun |= 0b0010, index++) {
+ $data = json[index];
+
+ let altt = $data.alternativeTitles,
+ $alt = (altt && altt.length? altt.filter(v => R(v, title)): null);
+
+ // Managers
+ if(manable)
+ // Medusa
+ if(config.usingMedusa && $data instanceof Array)
+ found = (R($data[4], title) || $alt)?
+ $alt || $data:
+ found;
+ // Radarr & Sonarr
+ if(config.usingRadarr || config.usingSonarr)
+ found = (R($data.name || $data.title, title) || $alt)?
+ $alt || $data:
+ found;
+ //api.tvmaze.com/
+ else if('externals' in ($data = $data.show || $data) || 'show' in $data)
+ found =
+ // ignore language barriers
+ (R($data.name, title) || terminal.log('Matching:', [$data.name, title], R($data.name, title)))?
+ $data:
+ // trust the api matching
+ ($data.score > lastscore)?
+ (lastscore = $data.score, $data):
+ found;
+ //api.themoviedb.org/ \local
+ else if('movie_results' in $data || 'tv_results' in $data)
+ found = (DATA => {
+ let i, f, o, l;
+
+ for(i = 0, f = !1, o = DATA.movie_results, l = o.length | 0; i < l; i++)
+ f = R(o.title, title);
+
+ for(i = (+f * l), o = (f? o: DATA.tv_results), l = (f? l: o.length | 0); i < l; i++)
+ f = R(o.name, title);
+
+ return f? o: f;
+ })($data);
+ //api.themoviedb.org/ \remote
+ else if('original_name' in $data || 'original_title' in $data)
+ found = (R($data.original_name, title) || R($data.original_title, title) || R($data.name, title))?
+ $data:
+ found;
+ //theimdbapi.org/
+ else if(en.test($data.language))
+ found = (R($data.title, title))?
$data:
- // trust the api matching
- ($data.score > lastscore)?
- (lastscore = $data.score, $data):
found;
- //api.themoviedb.org/ \local
- else if('movie_results' in $data || 'tv_results' in $data)
- found = (DATA => {
- let i, f, o, l;
-
- for(i = 0, f = !1, o = DATA.movie_results, l = o.length | 0; i < l; i++)
- f = R(o.title, title);
-
- for(i = (+f * l), o = (f? o: DATA.tv_results), l = (f? l: o.length | 0); i < l; i++)
- f = R(o.name, title);
-
- return f? o: f;
- })($data);
- //api.themoviedb.org/ \remote
- else if('original_name' in $data || 'original_title' in $data)
- found = (R($data.original_name, title) || R($data.original_title, title) || R($data.name, title))?
- $data:
- found;
- //theimdbapi.org/
- else if(en.test($data.language))
- found = (R($data.title, title))?
- $data:
- found;
-
-// terminal.log(`Loose Matching: ${ !!found }`, !!found? found: null);
- }
- json = found;
- }
+ terminal.log(`Loose Matching: ${ !!found }`, !!found? found: null);
+ }
- if((json === undefined || json === null || json === false) && !rerun)
- return json = getIDs({ title, year: YEAR, type, IMDbID, TMDbID, TVDbID, APIType, APIID, meta, rerun: true });
- else if((json === undefined || json === null))
- json = { IMDbID, TMDbID, TVDbID };
+ json = found;
+ }
- let ei = 'tt',
- mr = 'movie_results',
- tr = 'tv_results';
+ if((json === undefined || json === null || json === false) && (rerun & 0b0001))
+ return rerun |= 0b0001, json = getIDs({ title, year: YEAR, type, IMDbID, TMDbID, TVDbID, APIType, APIID, meta, rerun });
+ else if((json === undefined || json === null))
+ json = { IMDbID, TMDbID, TVDbID };
+
+ let ei = 'tt',
+ mr = 'movie_results',
+ tr = 'tv_results';
+
+ json = json && mr in json? json[mr].length > json[tr].length? json[mr]: json[tr]: json;
+
+ if(json instanceof Array && (!config.usingMedusa? true: (config.usingSonarr || config.usingOmbi)))
+ json = json[0];
+
+ if(!json)
+ json = { IMDbID, TMDbID, TVDbID };
+
+ // Ombi, Medusa, Radarr and Sonarr
+ if(manable)
+ data = (
+ (config.usingMedusa && !(config.usingSonarr || config.usingOmbi))?
+ {
+ imdb: iid || ei,
+ tmdb: mid | 0,
+ tvdb: tid || json[3] || (json[8]? json[8][1]: 0),
+ title: json.title || title,
+ year: +(json.year || year)
+ }:
+ {
+ imdb: iid || json.imdbId || ei,
+ tmdb: mid || json.tmdbId || json.theMovieDbId | 0,
+ tvdb: tid || json.tvdbId || json.theTvDbId | 0,
+ title: json.title || title,
+ year: +(json.year || year)
+ }
+ );
+ //api.tvmaze.com/
+ else if('externals' in (json = json.show || json))
+ data = {
+ imdb: iid || json.externals.imdb || ei,
+ tmdb: mid || json.externals.themoviedb | 0,
+ tvdb: tid || json.externals.thetvdb | 0,
+ title: json.name || title,
+ year: ((json.premiered || json.first_aired_date || year) + '').slice(0, 4)
+ };
+ //api.themoviedb.org/
+ else if('imdb_id' in (json = mr in json? json[mr].length > json[tr].length? json[mr]: json[tr]: json) || 'original_name' in json || 'original_title' in json)
+ data = {
+ imdb: iid || json.imdb_id || ei,
+ tmdb: mid || json.id | 0,
+ tvdb: tid || json.tvdb | 0,
+ title: json.title || json.name || title,
+ year: ((json.release_date || json.first_air_date || year) + '').slice(0, 4)
+ };
+ //omdbapi.com/
+ else if('imdbID' in json)
+ data = {
+ imdb: iid || json.imdbID || ei,
+ tmdb: mid || json.tmdbID | 0,
+ tvdb: tid || json.tvdbID | 0,
+ title: json.Title || json.Name || title,
+ year: json.Year || year
+ };
+ //theapache64.com/movie_db/
+ else if('data' in json)
+ data = {
+ imdb: iid || json.data.imdb_id || ei,
+ tmdb: mid || json.data.tmdb_id | 0,
+ tvdb: tid || json.data.tvdb_id | 0,
+ title: json.data.name || json.data.title || title,
+ year: json.data.year || year
+ };
+ //theimdbapi.org/
+ else if('imdb' in json)
+ data = {
+ imdb: iid || json.imdb || ei,
+ tmdb: mid || json.id | 0,
+ tvdb: tid || json.tvdb | 0,
+ title,
+ year
+ };
+ // given by the requesting service
+ else
+ data = {
+ imdb: iid || ei,
+ tmdb: mid | 0,
+ tvdb: tid | 0,
+ title,
+ year
+ };
- json = json && mr in json? json[mr].length > json[tr].length? json[mr]: json[tr]: json;
+ year = +((data.year + '').slice(0, 4)) || 0;
+ data.year = year;
- if(json instanceof Array)
- json = json[0];
+ let best = { title, year, data, type, rqut, score: json.score | 0 };
- if(!json)
- json = { IMDbID, TMDbID, TVDbID };
+ terminal.log('Best match:', url, { best, json });
- // Ombi, Radarr and Sonarr
- if(manable)
- data = {
- imdb: iid || json.imdbId || ei,
- tmdb: mid || json.tmdbId || json.theMovieDbId | 0,
- tvdb: tid || json.tvdbId || json.theTvDbId | 0,
- title: json.title || title,
- year: +(json.year || year)
- };
- //api.tvmaze.com/
- else if('externals' in (json = json.show || json))
- data = {
- imdb: iid || json.externals.imdb || ei,
- tmdb: mid || json.externals.themoviedb | 0,
- tvdb: tid || json.externals.thetvdb | 0,
- title: json.name || title,
- year: ((json.premiered || json.first_aired_date || year) + '').slice(0, 4)
- };
- //api.themoviedb.org/
- else if('imdb_id' in (json = mr in json? json[mr].length > json[tr].length? json[mr]: json[tr]: json) || 'original_name' in json || 'original_title' in json)
- data = {
- imdb: iid || json.imdb_id || ei,
- tmdb: mid || json.id | 0,
- tvdb: tid || json.tvdb | 0,
- title: json.title || json.name || title,
- year: ((json.release_date || json.first_air_date || year) + '').slice(0, 4)
- };
- //omdbapi.com/
- else if('imdbID' in json)
- data = {
- imdb: iid || json.imdbID || ei,
- tmdb: mid || json.tmdbID | 0,
- tvdb: tid || json.tvdbID | 0,
- title: json.Title || json.Name || title,
- year: json.Year || year
- };
- //theapache64.com/movie_db/
- else if('data' in json)
- data = {
- imdb: iid || json.data.imdb_id || ei,
- tmdb: mid || json.data.tmdb_id | 0,
- tvdb: tid || json.data.tvdb_id | 0,
- title: json.data.name || json.data.title || title,
- year: json.data.year || year
- };
- //theimdbapi.org/
- else if('imdb' in json)
- data = {
- imdb: iid || json.imdb || ei,
- tmdb: mid || json.id | 0,
- tvdb: tid || json.tvdb | 0,
- title,
- year
- };
- // given by the requesting service
- else
- data = {
- imdb: iid || ei,
- tmdb: mid | 0,
- tvdb: tid | 0,
- title,
- year
- };
+ if(best.data.imdb == ei && best.data.tmdb == 0 && best.data.tvdb == 0)
+ return terminal.log(`No information was found for "${ title } (${ year })"`), {};
- year = +((data.year + '').slice(0, 4)) || 0;
- data.year = year;
+ save(savename, data); // e.g. "Coco (0)" on Netflix before correction / no repeat searches
+ save(savename = `${title} (${year}).${rqut}`.toLowerCase(), data); // e.g. "Coco (2017)" on Netflix after correction / no repeat searches
+ save(`${title}.${rqut}`.toLowerCase(), year);
- let best = { title, year, data, type, rqut, score: json.score | 0 };
+ terminal.log(`Saved as "${ savename }"`, data);
- terminal.log('Best match:', url, { best, json });
+ rerun |= 0b00001;
- if(best.data.imdb == ei && best.data.tmdb == 0 && best.data.tvdb == 0)
- return terminal.log(`No information was found for "${ title } (${ year })"`), {};
+ return data;
+ }
- save(savename, data); // e.g. "Coco (0)" on Netflix before correction / no repeat searches
- save(savename = `${title} (${year}).${rqut}`.toLowerCase(), data); // e.g. "Coco (2017)" on Netflix after correction / no repeat searches
- save(`${title}.${rqut}`.toLowerCase(), year);
+ function $pushAddToCouchpotato(options) {
+ // TODO: this does not work anymore!
+ if(!options.IMDbID)
+ return new Notification(
+ 'warning',
+ 'Stopped adding to CouchPotato: No IMDb ID'
+ );
+
+ chrome.runtime.sendMessage(
+ {
+ type: 'VIEW_COUCHPOTATO',
+ url: `${ config.couchpotatoURL }/media.get`,
+ IMDbID: options.IMDbID,
+ TMDbID: options.TMDbID,
+ TVDbID: options.TVDbID,
+ basicAuth: config.couchpotatoBasicAuth,
+ },
+ response => {
+ let movieExists = response.success;
+ if(response.error) {
+ return new Notification(
+ 'warning',
+ 'CouchPotato request failed (see your console)'
+ ) ||
+ (!response.silent && terminal.error('Error viewing CouchPotato: ' + String(response.error)));
+ }
+ if(!movieExists) {
+ pushCouchPotatoRequest(options);
+ return;
+ }
+ new Notification(
+ 'warning',
+ `Movie already exists in CouchPotato (status: ${response.status})`
+ );
+ }
+ );
+ }
- terminal.log(`Saved as "${ savename }"`, data);
+ // Movies/TV Shows
+ function pushOmbiRequest(options) {
+ new Notification('info', `Adding "${ options.title }" to Ombi`, 3000);
- return data;
-}
+ if((!options.IMDbID && !options.TMDbID) && !options.TVDbID) {
+ return new Notification(
+ 'warning',
+ 'Stopped adding to Ombi: No content ID'
+ );
+ }
-function $pushAddToCouchpotato(options) {
- // TODO: this does not work anymore!
- if (!options.IMDbID)
- return new Notification(
- 'warning',
- 'Stopped adding to CouchPotato: No IMDb ID'
- );
-
- chrome.runtime.sendMessage(
- {
- type: 'VIEW_COUCHPOTATO',
- url: `${ config.couchpotatoURL }/media.get`,
- IMDbID: options.IMDbID,
- TMDbID: options.TMDbID,
- TVDbID: options.TVDbID,
- basicAuth: config.couchpotatoBasicAuth,
- },
- response => {
- let movieExists = response.success;
- if (response.error) {
- return new Notification(
- 'warning',
- 'CouchPotato request failed (see your console)'
- ) ||
- (!response.silent && terminal.error('Error viewing CouchPotato: ' + String(response.error)));
- }
- if (!movieExists) {
- pushCouchPotatoRequest(options);
- return;
- }
- new Notification(
- 'warning',
- `Movie already exists in CouchPotato (status: ${response.status})`
- );
- }
- );
-}
+ let contentType = (/movies?|film/i.test(options.type)? 'movie': 'tv');
-// Movies/TV Shows
-function pushOmbiRequest(options) {
- new Notification('info', `Adding "${ options.title }" to Ombi`, 3000);
+ chrome.runtime.sendMessage({
+ type: 'ADD_OMBI',
+ url: `${ config.ombiURL }api/v1/Request/${ contentType }`,
+ token: config.ombiToken,
+ title: options.title,
+ year: options.year,
+ imdbId: options.IMDbID,
+ tmdbId: options.TMDbID,
+ tvdbId: options.TVDbID,
+ contentType,
+ },
+ response => {
+ terminal.log('Pushing to Ombi', response);
+
+ if(response && response.error) {
+ return new Notification('warning', `Could not add "${ options.title }" to Ombi: ${ response.error }`) ||
+ (!response.silent && terminal.error('Error adding to Ombi: ' + String(response.error), response.location, response.debug));
+ } else if(response && response.success) {
+ let title = options.title.replace(/\&/g, 'and').replace(/\s+/g, '-').replace(/[^\w\-]+/g, '').replace(/\-{2,}/g, '-').toLowerCase();
- if ((!options.IMDbID && !options.TMDbID) && !options.TVDbID) {
- return new Notification(
- 'warning',
- 'Stopped adding to Ombi: No content ID'
+ terminal.log('Successfully pushed', options);
+ new Notification('update', `Added "${ options.title }" to Ombi`, 7000, () => window.open(config.ombiURL, '_blank'));
+ } else {
+ new Notification('warning', `Could not add "${ options.title }" to Ombi: Unknown Error`) ||
+ (!response.silent && terminal.error('Error adding to Ombi: ' + String(response)));
+ }
+ }
);
}
- let contentType = (/movies?|film/i.test(options.type)? 'movie': 'tv');
-
- chrome.runtime.sendMessage({
- type: 'ADD_OMBI',
- url: `${ config.ombiURL }api/v1/Request/${ contentType }`,
- token: config.ombiToken,
- title: options.title,
- year: options.year,
- imdbId: options.IMDbID,
- tmdbId: options.TMDbID,
- tvdbId: options.TVDbID,
- contentType,
- },
- response => {
- terminal.log('Pushing to Ombi', response);
+ // Movies/TV Shows
+ function pushCouchPotatoRequest(options) {
+ new Notification('info', `Adding "${ options.title }" to CouchPotato`, 3000);
+
+ chrome.runtime.sendMessage(
+ {
+ type: 'ADD_COUCHPOTATO',
+ url: `${ config.couchpotatoURL }/movie.add`,
+ IMDbID: options.IMDbID,
+ TMDbID: options.TMDbID,
+ TVDbID: options.TVDbID,
+ basicAuth: config.couchpotatoBasicAuth,
+ },
+ response => {
+ terminal.log('Pushing to CouchPotato', response);
+
+ if(response.error) {
+ return new Notification(
+ 'warning',
+ `Could not add "${ options.title }" to CouchPotato (see your console)`
+ ) ||
+ (!response.silent && terminal.error('Error adding to CouchPotato: ' + String(response.error), response.location, response.debug));
+ }
+ if(response.success) {
+ terminal.log('Successfully pushed', options);
+ new Notification('update', `Added "${ options.title }" to CouchPotato`);
+ } else {
+ new Notification('warning', `Could not add "${ options.title }" to CouchPotato`);
+ }
+ }
+ );
+ }
- if (response && response.error) {
- return new Notification('warning', `Could not add "${ options.title }" to Ombi: ${ response.error }`) ||
- (!response.silent && terminal.error('Error adding to Ombi: ' + String(response.error), response.location, response.debug));
- } else if (response && response.success) {
- let title = options.title.replace(/\&/g, 'and').replace(/\s+/g, '-').replace(/[^\w\-]+/g, '').replace(/\-{2,}/g, '-').toLowerCase();
+ // Movies
+ function pushWatcherRequest(options) {
+ new Notification('info', `Adding "${ options.title }" to Watcher`, 3000);
- terminal.log('Successfully pushed', options);
- new Notification('update', `Added "${ options.title }" to Ombi`, 7000, () => window.open(config.ombiURL, '_blank'));
- } else {
- new Notification('warning', `Could not add "${ options.title }" to Ombi: Unknown Error`) ||
- (!response.silent && terminal.error('Error adding to Ombi: ' + String(response)));
- }
+ if(!options.IMDbID && !options.TMDbID) {
+ return new Notification(
+ 'warning',
+ 'Stopped adding to Watcher: No IMDb/TMDb ID'
+ );
}
- );
-}
-
-// Movies/TV Shows
-function pushCouchPotatoRequest(options) {
- new Notification('info', `Adding "${ options.title }" to CouchPotato`, 3000);
-
- chrome.runtime.sendMessage(
- {
- type: 'ADD_COUCHPOTATO',
- url: `${ config.couchpotatoURL }/movie.add`,
- IMDbID: options.IMDbID,
- TMDbID: options.TMDbID,
- TVDbID: options.TVDbID,
- basicAuth: config.couchpotatoBasicAuth,
- },
- response => {
- terminal.log('Pushing to CouchPotato', response);
-
- if (response.error) {
- return new Notification(
- 'warning',
- `Could not add "${ options.title }" to CouchPotato (see your console)`
- ) ||
- (!response.silent && terminal.error('Error adding to CouchPotato: ' + String(response.error), response.location, response.debug));
- }
- if (response.success) {
- terminal.log('Successfully pushed', options);
- new Notification('update', `Added "${ options.title }" to CouchPotato`);
- } else {
- new Notification('warning', `Could not add "${ options.title }" to CouchPotato`);
- }
- }
- );
-}
-// Movies
-function pushWatcherRequest(options) {
- new Notification('info', `Adding "${ options.title }" to Watcher`, 3000);
-
- if (!options.IMDbID && !options.TMDbID) {
- return new Notification(
- 'warning',
- 'Stopped adding to Watcher: No IMDb/TMDb ID'
+ chrome.runtime.sendMessage({
+ type: 'ADD_WATCHER',
+ url: `${ config.watcherURL }api/`,
+ token: config.watcherToken,
+ StoragePath: config.watcherStoragePath,
+ basicAuth: config.watcherBasicAuth,
+ title: options.title,
+ year: options.year,
+ imdbId: options.IMDbID,
+ tmdbId: options.TMDbID,
+ },
+ response => {
+ terminal.log('Pushing to Watcher', response);
+
+ if(response && response.error) {
+ return new Notification('warning', `Could not add "${ options.title }" to Watcher: ${ response.error }`) ||
+ (!response.silent && terminal.error('Error adding to Watcher: ' + String(response.error), response.location, response.debug));
+ } else if(response && (response.success || (response.response + "") == "true")) {
+ let title = options.title.replace(/\&/g, 'and').replace(/\s+/g, '-').replace(/[^\w\-]+/g, '').replace(/\-{2,}/g, '-').toLowerCase(),
+ TMDbID = options.TMDbID || response.tmdbId;
+
+ terminal.log('Successfully pushed', options);
+ new Notification('update', `Added "${ options.title }" to Watcher`, 7000, () => window.open(`${config.watcherURL}library/status${TMDbID? `#${title}-${TMDbID}`: '' }`, '_blank'));
+ } else {
+ new Notification('warning', `Could not add "${ options.title }" to Watcher: Unknown Error`) ||
+ (!response.silent && terminal.error('Error adding to Watcher: ' + String(response)));
+ }
+ }
);
}
- chrome.runtime.sendMessage({
- type: 'ADD_WATCHER',
- url: `${ config.watcherURL }api/`,
- token: config.watcherToken,
- StoragePath: config.watcherStoragePath,
- basicAuth: config.watcherBasicAuth,
- title: options.title,
- year: options.year,
- imdbId: options.IMDbID,
- tmdbId: options.TMDbID,
- },
- response => {
- terminal.log('Pushing to Watcher', response);
-
- if (response && response.error) {
- return new Notification('warning', `Could not add "${ options.title }" to Watcher: ${ response.error }`) ||
- (!response.silent && terminal.error('Error adding to Watcher: ' + String(response.error), response.location, response.debug));
- } else if (response && (response.success || (response.response + "") == "true")) {
- let title = options.title.replace(/\&/g, 'and').replace(/\s+/g, '-').replace(/[^\w\-]+/g, '').replace(/\-{2,}/g, '-').toLowerCase(),
- TMDbID = options.TMDbID || response.tmdbId;
-
- terminal.log('Successfully pushed', options);
- new Notification('update', `Added "${ options.title }" to Watcher`, 7000, () => window.open(`${config.watcherURL}library/status${TMDbID? `#${title}-${TMDbID}`: '' }`, '_blank'));
- } else {
- new Notification('warning', `Could not add "${ options.title }" to Watcher: Unknown Error`) ||
- (!response.silent && terminal.error('Error adding to Watcher: ' + String(response)));
- }
- }
- );
-}
+ // Movies
+ function pushRadarrRequest(options, prompted) {
+ if(!options.IMDbID && !options.TMDbID)
+ return (!prompted)? new Notification(
+ 'warning',
+ 'Stopped adding to Radarr: No IMDb/TMDb ID'
+ ): null;
-// Movies
-function pushRadarrRequest(options) {
- new Notification('info', `Adding "${ options.title }" to Radarr`, 3000);
+ let PromptValues = {},
+ { PromptQuality, PromptLocation } = config;
- if (!options.IMDbID && !options.TMDbID) {
- return new Notification(
- 'warning',
- 'Stopped adding to Radarr: No IMDb/TMDb ID'
- );
- }
+ if(!prompted && (PromptQuality || PromptLocation))
+ return new Prompt('modify', options, refined => pushRadarrRequest(refined, true));
- let PromptValues = {},
- { PromptQuality, PromptLocation } = config;
-
- if(PromptQuality && +options.quality > 0)
- PromptValues.QualityID = +options.quality;
- if(PromptLocation && options.location)
- PromptValues.StoragePath = JSON.parse(config.radarrStoragePaths)[+options.location - 1].path.replace(/\\/g, '\\\\');
-
- chrome.runtime.sendMessage({
- type: 'ADD_RADARR',
- url: `${ config.radarrURL }api/movie/`,
- token: config.radarrToken,
- StoragePath: config.radarrStoragePath,
- QualityID: config.radarrQualityProfileId,
- basicAuth: config.radarrBasicAuth,
- title: options.title,
- year: options.year,
- imdbId: options.IMDbID,
- tmdbId: options.TMDbID,
- ...PromptValues
- },
- response => {
- terminal.log('Pushing to Radarr', response);
-
- if (response && response.error) {
- return new Notification('warning', `Could not add "${ options.title }" to Radarr: ${ response.error }`) ||
- (!response.silent && terminal.error('Error adding to Radarr: ' + String(response.error), response.location, response.debug));
- } else if (response && response.success) {
- let title = options.title.replace(/\&/g, 'and').replace(/\s+/g, '-').replace(/[^\w\-]+/g, '').replace(/\-{2,}/g, '-').toLowerCase(),
- TMDbID = options.TMDbID || response.tmdbId;
-
- terminal.log('Successfully pushed', options);
- new Notification('update', `Added "${ options.title }" to Radarr`, 7000, () => window.open(`${config.radarrURL}${TMDbID? `movies/${title}-${TMDbID}`: '' }`, '_blank'));
- } else {
- new Notification('warning', `Could not add "${ options.title }" to Radarr: Unknown Error`) ||
- (!response.silent && terminal.error('Error adding to Radarr: ' + String(response)));
- }
- }
- );
-}
+ if(PromptQuality && +options.quality > 0)
+ PromptValues.QualityID = +options.quality;
+ if(PromptLocation && options.location)
+ PromptValues.StoragePath = JSON.parse(config.radarrStoragePaths).map(item => item.id == options.location? item: null).filter(n => n)[0].path.replace(/\\/g, '\\\\');
-// TV Shows
-function pushSonarrRequest(options) {
- new Notification('info', `Adding "${ options.title }" to Sonarr`, 3000);
+ new Notification('info', `Adding "${ options.title }" to Radarr`, 3000);
- if (!options.TVDbID) {
- return new Notification(
- 'warning',
- 'Stopped adding to Sonarr: No TVDb ID'
+ chrome.runtime.sendMessage({
+ type: 'ADD_RADARR',
+ url: `${ config.radarrURL }api/movie/`,
+ token: config.radarrToken,
+ StoragePath: config.radarrStoragePath,
+ QualityID: config.radarrQualityProfileId,
+ basicAuth: config.radarrBasicAuth,
+ title: options.title,
+ year: options.year,
+ imdbId: options.IMDbID,
+ tmdbId: options.TMDbID,
+ ...PromptValues
+ },
+ response => {
+ terminal.log('Pushing to Radarr', response);
+
+ if(response && response.error) {
+ return new Notification('warning', `Could not add "${ options.title }" to Radarr: ${ response.error }`) ||
+ (!response.silent && terminal.error('Error adding to Radarr: ' + String(response.error), response.location, response.debug));
+ } else if(response && response.success) {
+ let title = options.title.replace(/\&/g, 'and').replace(/\s+/g, '-').replace(/[^\w\-]+/g, '').replace(/\-{2,}/g, '-').toLowerCase(),
+ TMDbID = options.TMDbID || response.tmdbId;
+
+ terminal.log('Successfully pushed', options);
+ new Notification('update', `Added "${ options.title }" to Radarr`, 7000, () => window.open(`${config.radarrURL}${TMDbID? `movies/${title}-${TMDbID}`: '' }`, '_blank'));
+ } else {
+ new Notification('warning', `Could not add "${ options.title }" to Radarr: Unknown Error`) ||
+ (!response.silent && terminal.error('Error adding to Radarr: ' + String(response)));
+ }
+ }
);
}
- let PromptValues = {},
- { PromptQuality, PromptLocation } = config;
-
- if(PromptQuality && +options.quality > 0)
- PromptValues.QualityID = +options.quality;
- if(PromptLocation && options.location)
- PromptValues.StoragePath = JSON.parse(config.sonarrStoragePaths)[+options.location - 1].path.replace(/\\/g, '\\\\');
-
- chrome.runtime.sendMessage({
- type: 'ADD_SONARR',
- url: `${ config.sonarrURL }api/series/`,
- token: config.sonarrToken,
- StoragePath: config.sonarrStoragePath,
- QualityID: config.sonarrQualityProfileId,
- basicAuth: config.sonarrBasicAuth,
- title: options.title,
- year: options.year,
- tvdbId: options.TVDbID,
- ...PromptValues
- },
- response => {
- terminal.log('Pushing to Sonarr', response);
+ // TV Shows
+ function pushSonarrRequest(options, prompted) {
+ if(!options.TVDbID)
+ return (!prompted)? new Notification(
+ 'warning',
+ 'Stopped adding to Sonarr: No TVDb ID'
+ ): null;
- if (response && response.error) {
- return new Notification('warning', `Could not add "${ options.title }" to Sonarr: ${ response.error }`) ||
- (!response.silent && terminal.error('Error adding to Sonarr: ' + String(response.error), response.location, response.debug));
- } else if (response && response.success) {
- let title = options.title.replace(/\&/g, 'and').replace(/\s+/g, '-').replace(/[^\w\-]+/g, '').replace(/\-{2,}/g, '-').toLowerCase();
+ let PromptValues = {},
+ { PromptQuality, PromptLocation } = config;
- terminal.log('Successfully pushed', options);
- new Notification('update', `Added "${ options.title }" to Sonarr`, 7000, () => window.open(`${config.sonarrURL}series/${title}`, '_blank'));
- } else {
- new Notification('warning', `Could not add "${ options.title }" to Sonarr: Unknown Error`) ||
- (!response.silent && terminal.error('Error adding to Sonarr: ' + String(response)));
- }
- }
- );
-}
+ if(!prompted && (PromptQuality || PromptLocation))
+ return new Prompt('modify', options, refined => pushSonarrRequest(refined, true));
-// make the button
-function renderPlexButton(persistent) {
- let existingButtons = document.querySelectorAll('.web-to-plex-button'),
- firstButton = existingButtons[0];
-
- if (existingButtons.length && !persistent)
- [].slice.call(existingButtons).forEach(button => button.remove());
- else if(persistent && firstButton !== null && firstButton !== undefined)
- return firstButton;
-
- //
+ }
- document.body.appendChild(button);
+ // make the button
+ let MASTER_BUTTON;
+ function renderPlexButton(persistent) {
+ let existingButtons = document.querySelectorAll('.web-to-plex-button'),
+ firstButton = existingButtons[0];
+
+ if(existingButtons.length && !persistent)
+ [].slice.call(existingButtons).forEach(button => button.remove());
+ else if(persistent && firstButton !== null && firstButton !== undefined)
+ return firstButton;
+
+ //
- t = t.join(', ');
- t = t.length > 24? t.slice(0, 21).replace(/\W+$/, '') + '...': t;
+ document.body.appendChild(button);
- element.ON_CLICK = e => {
- e.preventDefault();
+ return MASTER_BUTTON = button;
+ }
- let self = e.target, tv = /tv[\s-]?|shows?|series/i, fail = 0,
- options = JSON.parse(atob(button.getAttribute('saved_options')));
+ function modifyPlexButton(button, action, title, options = {}) {
+ let multiple = (action == 'multiple' || options instanceof Array),
+ element = button.querySelector('.w2p-action, .list-action'),
+ delimeter = '',
+ ty = 'Item', txt = 'title', hov = 'tooltip',
+ em = /^(tt|0)?$/i,
+ tv = /tv[\s-]?|shows?|series/i;
+
+ if(!element) {
+ element = button;
+ button = element.parentElement;
+ };
- for(let index = 0, length = options.length, option; index < length; index++) {
- option = options[index];
+ sendUpdate('SEARCH_FOR', { ...options, button });
- try {
- if(config.usingOmbi)
- pushOmbiRequest(option);
- else if (config.usingWatcher && !tv.test(option.type))
- pushWatcherRequest(option);
- else if (config.usingRadarr && !tv.test(option.type))
- pushRadarrRequest(option);
- else if (config.usingSonarr && tv.test(option.type))
- pushSonarrRequest(option);
- else if(config.usingCouchPotato)
- $pushAddToCouchpotato(option);
- } catch(error) {
- terminal.error(`Failed to get "${ option.title }" (Error #${ ++fail })`)
- }
- }
- NOTIFIED = false;
+ /* Handle a list of items */
+ if(multiple) {
+ options = [].slice.call(options);
- if (fail)
- new Notification('error', `Failed to grab ${ fail } item${fail==1?'':'s'}`);
- };
+ let saved_options = [], // a list of successful searches (not on Plex)
+ len = options.length,
+ s = (len == 1? '': 's'),
+ t = [];
- button.setAttribute('saved_options', btoa(JSON.stringify(saved_options)));
- element.addEventListener('click', e => (AUTO_GRAB.ENABLED && AUTO_GRAB.LIMIT > options.length)? element.ON_CLICK(e): new Prompt('select', options, o => { button.setAttribute('saved_options', btoa(JSON.stringify(o))); element.ON_CLICK(e) }));
+ for(let index = 0; index < len; index++) {
+ let option = options[index];
- element.setAttribute(hov, `Grab ${len} new item${s}: ${ t }`);
- button.classList.add(saved_options.length || len? 'wtp--download': 'wtp--error');
- } else {
- /* Handle a single item */
+ // Skip empty entries
+ if(!option || !option.type || !option.title) continue;
- if(!options || !options.type || !options.title) return;
+ // the action should be an array
+ // we'll give the button a list of links to engage, so make it snappy!
+ let url = `#${ option.imdb || 'tt' }-${ option.tmdb | 0 }-${ option.tvdb | 0 }`;
- let empty = (em.test(options.IMDbID) && em.test(options.TMDbID) && em.test(options.TVDbID)),
- nice_title = `${options.title.toCaps()}${options.year? ` (${options.year})`: ''}`;
+ /* Failed */
+ if(/#tt-0-0/i.test(url))
+ continue;
- if(options) {
- ty = (options.type == 'movie'? 'Movie': 'TV Show');
- txt = options.txt || txt;
- hov = options.hov || hov;
- }
+ saved_options.push(option);
+ t.push(option.title);
+ }
- if (action == 'found') {
- element.href = getPlexMediaURL(config.server.id, options.key);
- element.setAttribute(hov, `Watch "${options.title} (${options.year})" on Plex`);
- button.classList.add('wtp--found');
- } else if (action == 'downloader' || options.remote) {
-
- switch(options.remote) {
- /* GoStream */
- case 'oload':
- let href = options.href, path = '';
-
- if (config.usingOmbi) {
- path = '';
- } else if (config.usingWatcher && !tv.test(options.type)) {
- path = '';
- } else if (config.usingRadarr && !tv.test(options.type)) {
- path = config.radarrStoragePath;
- } else if (config.usingSonarr && tv.test(options.type)) {
- path = config.sonarrStoragePath;
- } else if(config.usingCouchPotato) {
- path = '';
+ t = t.join(', ');
+ t = t.length > 24? t.slice(0, 21).replace(/\W+$/, '') + '...': t;
+
+ element.ON_CLICK = e => {
+ e.preventDefault();
+
+ let self = e.target, tv = /tv[\s-]?|shows?|series/i, fail = 0,
+ options = JSON.parse(atob(button.getAttribute('saved_options')));
+
+ for(let index = 0, length = options.length, option; index < length; index++) {
+ option = options[index];
+
+ try {
+ if(config.usingOmbi)
+ pushOmbiRequest(option);
+ else if(config.usingWatcher && !tv.test(option.type))
+ pushWatcherRequest(option);
+ else if(config.usingRadarr && !tv.test(option.type))
+ pushRadarrRequest(option);
+ else if(config.usingSonarr && tv.test(option.type))
+ pushSonarrRequest(option);
+ else if(config.usingMedusa && tv.test(option.type))
+ pushMedusaRequest(option);
+ else if(config.usingCouchPotato)
+ $pushAddToCouchpotato(option);
+ } catch(error) {
+ terminal.error(`Failed to get "${ option.title }" (Error #${ ++fail })`)
}
+ }
+ NOTIFIED = false;
- element.href = `#${ options.IMDbID || 'tt' }-${ options.TMDbID | 0 }-${ options.TVDbID | 0 }`;
- button.classList.add('wtp--download');
- element.removeEventListener('click', element.ON_CLICK);
- element.addEventListener('click', element.ON_DOWNLOAD = e => {
- e.preventDefault();
+ if(fail)
+ new Notification('error', `Failed to grab ${ fail } item${fail==1?'':'s'}`);
+ };
- sendUpdate('DOWNLOAD_FILE', { ...options, button, href, path });
- new Notification('update', 'Opening prompt (may take a while)...');
- });
+ button.setAttribute('saved_options', btoa(JSON.stringify(saved_options)));
+ element.addEventListener('click', e => (AUTO_GRAB.ENABLED && AUTO_GRAB.LIMIT > options.length)? element.ON_CLICK(e): new Prompt('select', options, o => { button.setAttribute('saved_options', btoa(JSON.stringify(o))); element.ON_CLICK(e) }));
- element.setAttribute(hov, `Download "${ nice_title }" | ${ty}`);
- sendUpdate('SAVE_AS', { ...options, button, href, path });
- new Notification('update', `"${ nice_title }" can be downloaded`, 7000, e => element.click(e));
- return;
+ element.setAttribute(hov, `Grab ${len} new item${s}: ${ t }`);
+ button.classList.add(saved_options.length || len? 'wtp--download': 'wtp--error');
+ } else {
+ /* Handle a single item */
+ if(!options || !options.type || !options.title) return;
- /* Default & Error */
- default:
- let url = `#${ options.IMDbID || 'tt' }-${ options.TMDbID | 0 }-${ options.TVDbID | 0 }`;
-
- /* Failed */
- if(/#tt-0-0/i.test(url))
- return modifyPlexButton(button, 'notfound', title, options);
-
- element.href = url;
- button.classList.add('wtp--download');
- element.addEventListener('click', element.ON_CLICK = e => {
- e.preventDefault();
- if (config.usingOmbi) {
- pushOmbiRequest(options);
- } else if (config.usingWatcher && !tv.test(options.type)) {
- pushWatcherRequest(options);
- } else if (config.usingRadarr && !tv.test(options.type)) {
- pushRadarrRequest(options);
- } else if (config.usingSonarr && tv.test(options.type)) {
- pushSonarrRequest(options);
+ let empty = (em.test(options.IMDbID) && em.test(options.TMDbID) && em.test(options.TVDbID)),
+ nice_title = `${options.title.toCaps()}${options.year? ` (${options.year})`: ''}`;
+
+ if(options) {
+ ty = (options.type == 'movie'? 'Movie': 'TV Show');
+ txt = options.txt || txt;
+ hov = options.hov || hov;
+ }
+
+ if(action == 'found') {
+ element.href = getPlexMediaURL(config.server.id, options.key);
+ element.setAttribute(hov, `Watch "${options.title} (${options.year})" on Plex`);
+ button.classList.add('wtp--found');
+
+ new Notification('success', `Watch "${ nice_title }"`, 7000, e => element.click(e));
+ } else if(action == 'downloader' || options.remote) {
+
+ switch(options.remote) {
+ /* Vumoo */
+ case 'oload':
+ let href = options.href, path = '';
+
+ if(config.usingOmbi) {
+ path = '';
+ } else if(config.usingWatcher && !tv.test(options.type)) {
+ path = '';
+ } else if(config.usingRadarr && !tv.test(options.type)) {
+ path = config.radarrStoragePath;
+ } else if(config.usingSonarr && tv.test(options.type)) {
+ path = config.sonarrStoragePath;
+ } else if(config.usingMedusa && tv.test(options.type)) {
+ path = config.medusaStoragePath;
} else if(config.usingCouchPotato) {
- $pushAddToCouchpotato(options);
+ path = '';
}
- });
- }
- NOTIFIED = false;
- element.setAttribute(hov, `Add "${ nice_title }" | ${ty}`);
- element.style.removeProperty('display');
- } else if (action == 'notfound' || action == 'error' || empty) {
- element.removeAttribute('href');
+ element.href = `#${ options.IMDbID || 'tt' }-${ options.TMDbID | 0 }-${ options.TVDbID | 0 }`;
+ button.classList.add('wtp--download');
+ element.removeEventListener('click', element.ON_CLICK);
+ element.addEventListener('click', element.ON_DOWNLOAD = e => {
+ e.preventDefault();
- empty = !(options && options.title);
+ sendUpdate('DOWNLOAD_FILE', { ...options, button, href, path });
+ new Notification('update', 'Opening prompt (may take a while)...');
+ });
- if(empty)
- element.setAttribute(hov, `${ty || 'Item'} not found`);
- else
- element.setAttribute(hov, `"${ nice_title }" was not found`);
+ element.setAttribute(hov, `Download "${ nice_title }" | ${ty}`);
+ sendUpdate('SAVE_AS', { ...options, button, href, path });
+ new Notification('update', `"${ nice_title }" can be downloaded`, 7000, e => element.click(e));
+ return;
+
+
+ /* Default & Error */
+ default:
+ let url = `#${ options.IMDbID || 'tt' }-${ options.TMDbID | 0 }-${ options.TVDbID | 0 }`;
+
+ /* Failed */
+ if(/#tt-0-0/i.test(url))
+ return modifyPlexButton(button, 'notfound', title, options);
+
+ element.href = url;
+ button.classList.add('wtp--download');
+ element.addEventListener('click', element.ON_CLICK = e => {
+ e.preventDefault();
+ if(config.usingOmbi) {
+ pushOmbiRequest(options);
+ } else if(config.usingWatcher && !tv.test(options.type)) {
+ pushWatcherRequest(options);
+ } else if(config.usingRadarr && !tv.test(options.type)) {
+ pushRadarrRequest(options);
+ } else if(config.usingSonarr && tv.test(options.type)) {
+ pushSonarrRequest(options);
+ } else if(config.usingMedusa && tv.test(options.type)) {
+ pushMedusaRequest(options);
+ } else if(config.usingCouchPotato) {
+ $pushAddToCouchpotato(options);
+ }
+ });
+ }
+ NOTIFIED = false;
- button.classList.remove('wtp--found');
- button.classList.add('wtp--error');
- }
+ element.setAttribute(hov, `Add "${ nice_title }" | ${ty}`);
+ element.style.removeProperty('display');
+ } else if(action == 'notfound' || action == 'error' || empty) {
+ element.removeAttribute('href');
+
+ empty = !(options && options.title);
+
+ if(empty)
+ element.setAttribute(hov, `${ty || 'Item'} not found`);
+ else
+ element.setAttribute(hov, `"${ nice_title }" was not found`);
- element.id = options? `${options.IMDbID || 'tt'}-${options.TMDbID | 0}-${options.TVDbID | 0}`: 'tt-0-0';
+ button.classList.remove('wtp--found');
+ button.classList.add('wtp--error');
+ }
+
+ element.id = options? `${options.IMDbID || 'tt'}-${options.TMDbID | 0}-${options.TVDbID | 0}`: 'tt-0-0';
+ }
}
-}
-async function squabblePlexMedia(options, button) {
- if(!(options && options.length && button))
- return;
+ async function squabblePlexMedia(options, button) {
+ if(!(options && options.length && button))
+ return;
- let results = [],
- length = options.length,
- queries = (squabblePlexMedia.queries = squabblePlexMedia.queries || {});
+ let results = [],
+ length = options.length,
+ queries = (squabblePlexMedia.queries = squabblePlexMedia.queries || {});
- squabblePlexMedia.OPTIONS = options;
+ squabblePlexMedia.OPTIONS = options;
- let query = JSON.stringify(options);
+ let query = JSON.stringify(options);
- query = (queries[query] = queries[query] || {});
+ query = (queries[query] = queries[query] || {});
- if(query.running === true)
- return;
- else if(query.results) {
- let { results, multiple, items } = query;
+ if(query.running === true)
+ return;
+ else if(query.results) {
+ let { results, multiple, items } = query;
- new Notification('update', `Welcome back. ${ multiple } new ${ items } can be grabbed`, 7000, (event, target = button.querySelector('.list-action')) => target.click({ ...event, target }));
+ new Notification('update', `Welcome back. ${ multiple } new ${ items } can be grabbed`, 7000, (event, target = button.querySelector('.list-action')) => target.click({ ...event, target }));
- if (multiple)
- modifyPlexButton(button, 'multiple', `Download ${ multiple } ${ items }`, results);
+ if(multiple)
+ modifyPlexButton(button, 'multiple', `Download ${ multiple } ${ items }`, results);
- return;
- }
+ return;
+ }
- query.running = true;
+ query.running = true;
- new Notification('info', `Processing ${ length } item${ 's'[+(length === 1)] || '' }...`);
+ new Notification('info', `Processing ${ length } item${ 's'[+(length === 1)] || '' }...`);
- for(let index = 0, option, opt; index < length; index++) {
- let { IMDbID, TMDbID, TVDbID } = (option = await options[index]);
+ for(let index = 0, option, opt; index < length; index++) {
+ let { IMDbID, TMDbID, TVDbID } = (option = await options[index]);
- opt = { name: option.title, title: option.title, year: option.year, image: options.image, type: option.type, imdb: IMDbID, IMDbID, tmdb: TMDbID, TMDbID, tvdb: TVDbID, TVDbID };
+ opt = { name: option.title, title: option.title, year: option.year, image: options.image, type: option.type, imdb: IMDbID, IMDbID, tmdb: TMDbID, TMDbID, tvdb: TVDbID, TVDbID };
- try {
- await getPlexMediaRequest(option)
- .then(async({ found, key }) => {
- if (found) {
- // ignore found items, we only want new items
- } else {
- option.field = 'original_title';
+ try {
+ await getPlexMediaRequest(option)
+ .then(async({ found, key }) => {
+ if(found) {
+ // ignore found items, we only want new items
+ } else {
+ option.field = 'original_title';
- return await getPlexMediaRequest(option)
- .then(({ found, key }) => {
- if (found) {
- // ignore found items, we only want new items
- } else {
- let available = (config.usingOmbi || config.usingWatcher || config.usingRadarr || config.usingSonarr || config.usingCouchPotato),
- action = (available ? 'downloader' : 'notfound'),
- title = available ?
- 'Not on Plex (download available)':
- 'Not on Plex (download not available)';
+ return await getPlexMediaRequest(option)
+ .then(({ found, key }) => {
+ if(found) {
+ // ignore found items, we only want new items
+ } else {
+ let available = (config.usingOmbi || config.usingWatcher || config.usingRadarr || config.usingSonarr || config.usingMedusa || config.usingCouchPotato),
+ action = (available ? 'downloader' : 'notfound'),
+ title = available ?
+ 'Not on Plex (download available)':
+ 'Not on Plex (download not available)';
- results.push({ ...opt, found: false, status: action });
- }
- });
- }
- })
- } catch(error) {
- terminal.error('Request to Plex failed: ' + String(error));
- // new Notification('error', 'Failed to query item #' + (index + 1));
+ results.push({ ...opt, found: false, status: action });
+ }
+ });
+ }
+ })
+ } catch(error) {
+ terminal.error('Request to Plex failed: ' + String(error));
+ // new Notification('error', 'Failed to query item #' + (index + 1));
+ }
}
- }
- results = results.filter(v => v.status == 'downloader');
+ results = results.filter(v => v.status == 'downloader');
- let img = furnish('img', { title: 'Add to Plex It!', src: IMG_URL.plexit_icon_48, onmouseup: event => {let frame = document.querySelector('#plexit-bookmarklet-frame'); frame.src = frame.src.replace(/(#plexit:.*)?$/, '#plexit:' + event.target.parentElement.getAttribute('data'))} }),
- po, pi = furnish('li#plexit.list-item', { data: btoa(JSON.stringify(results)) }, img),
- op = document.querySelector('#wtp-plexit');
+ let img = furnish('img', { title: 'Add to Plex It!', src: IMG_URL.plexit_icon_48, onmouseup: event => {let frame = document.querySelector('#plexit-bookmarklet-frame'); frame.src = frame.src.replace(/(#plexit:.*)?$/, '#plexit:' + event.target.parentElement.getAttribute('data'))} }),
+ po, pi = furnish('li#plexit.list-item', { data: btoa(JSON.stringify(results)) }, img),
+ op = document.querySelector('#wtp-plexit');
- if(po = button.querySelector('#plexit'))
- po.remove();
- try {
- button.querySelector('ul').insertBefore(pi, op);
- } catch(e) { /* Don't do anything */ }
+ if(po = button.querySelector('#plexit'))
+ po.remove();
+ try {
+ button.querySelector('ul').insertBefore(pi, op);
+ } catch(e) { /* Don't do anything */ }
- let multiple = results.length,
- items = multiple == 1? 'item': 'items';
+ let multiple = results.length,
+ items = multiple == 1? 'item': 'items';
- new Notification('update', `Done. ${ multiple } new ${ items } can be grabbed`, 7000, (event, target = button.querySelector('.list-action')) => target.click({ ...event, target }));
+ new Notification('update', `Done. ${ multiple } new ${ items } can be grabbed`, 7000, (event, target = button.querySelector('.list-action')) => target.click({ ...event, target }));
- query.running = false;
- query.results = results;
- query.multiple = multiple;
- query.items = items;
+ query.running = false;
+ query.results = results;
+ query.multiple = multiple;
+ query.items = items;
- if (multiple)
- modifyPlexButton(button, 'multiple', `Download ${ multiple } ${ items }`, results);
-}
+ if(multiple)
+ modifyPlexButton(button, 'multiple', `Download ${ multiple } ${ items }`, results);
+ }
-function findPlexMedia(options) {
- if(!(options && options.title))
- return;
+ function findPlexMedia(options) {
+ if(!(options && options.title))
+ return;
- let { IMDbID, TMDbID, TVDbID } = options;
+ let { IMDbID, TMDbID, TVDbID } = options;
- TMDbID = +TMDbID;
- TVDbID = +TVDbID;
+ TMDbID = +TMDbID;
+ TVDbID = +TVDbID;
- let opt = { name: options.title, year: options.year, image: options.image || IMG_URL.nil, type: options.type, imdb: IMDbID, IMDbID, tmdb: TMDbID, TMDbID, tvdb: TVDbID, TVDbID },
- op = document.querySelector('#wtp-plexit'),
- img = (options.image)?
- furnish('div', { tooltip: 'Add to Plex It!', style: `background: url(${ IMG_URL.plexit_icon_16 }) top right/60% no-repeat, #0004 url(${ opt.image }) center/contain no-repeat; height: 48px; width: 34px;`, draggable: true, onmouseup: event => {let frame = document.querySelector('#plexit-bookmarklet-frame'); frame.src = frame.src.replace(/(#plexit:.*)?$/, '#plexit:' + event.target.parentElement.getAttribute('data'))} }):
- furnish('img', { title: 'Add to Plex It!', src: IMG_URL.plexit_icon_48, onmouseup: event => {let frame = document.querySelector('#plexit-bookmarklet-frame'); frame.src = frame.src.replace(/(#plexit:.*)?$/, '#plexit:' + event.target.parentElement.getAttribute('data'))} });
+ let opt = { name: options.title, year: options.year, image: options.image || IMG_URL.nil, type: options.type, imdb: IMDbID, IMDbID, tmdb: TMDbID, TMDbID, tvdb: TVDbID, TVDbID },
+ op = document.querySelector('#wtp-plexit'),
+ img = (options.image)?
+ furnish('div', { tooltip: 'Add to Plex It!', style: `background: url(${ IMG_URL.plexit_icon_16 }) top right/60% no-repeat, #0004 url(${ opt.image }) center/contain no-repeat; height: 48px; width: 34px;`, draggable: true, onmouseup: event => {let frame = document.querySelector('#plexit-bookmarklet-frame'); frame.src = frame.src.replace(/(#plexit:.*)?$/, '#plexit:' + event.target.parentElement.getAttribute('data'))} }):
+ furnish('img', { title: 'Add to Plex It!', src: IMG_URL.plexit_icon_48, onmouseup: event => {let frame = document.querySelector('#plexit-bookmarklet-frame'); frame.src = frame.src.replace(/(#plexit:.*)?$/, '#plexit:' + event.target.parentElement.getAttribute('data'))} });
- findPlexMedia.OPTIONS = options;
+ findPlexMedia.OPTIONS = options;
- try {
- return getPlexMediaRequest(options)
- .then(({ found, key }) => {
- if (found) {
- modifyPlexButton(options.button, 'found', 'On Plex', { ...options, key });
- opt = { ...opt, url: options.button.href, found: true, status: 'found' };
+ try {
+ return getPlexMediaRequest(options)
+ .then(({ found, key }) => {
+ if(found) {
+ modifyPlexButton(options.button, 'found', 'On Plex', { ...options, key });
+ opt = { ...opt, url: options.button.href, found: true, status: 'found' };
+
+ let po, pi = furnish('li#plexit.list-item', { data: btoa(JSON.stringify(opt)) }, img);
+
+ if(po = options.button.querySelector('#plexit'))
+ po.remove();
+ try {
+ options.button.querySelector('ul').insertBefore(pi, op);
+ } catch(e) { /* Don't do anything */ }
+ } else {
+ options.field = 'original_title';
- let po, pi = furnish('li#plexit.list-item', { data: btoa(JSON.stringify(opt)) }, img);
+ return getPlexMediaRequest(options)
+ .then(({ found, key }) => {
+ if(found) {
+ modifyPlexButton(options.button, 'found', 'On Plex', { ...options, key });
+ opt = { ...opt, url: options.button.href, found: true, status: 'found' };
- if(po = options.button.querySelector('#plexit'))
- po.remove();
- try {
- options.button.querySelector('ul').insertBefore(pi, op);
- } catch(e) { /* Don't do anything */ }
- } else {
- options.field = 'original_title';
+ let po, pi = furnish('li#plexit.list-item', { data: btoa(JSON.stringify(opt)) }, img);
- return getPlexMediaRequest(options)
- .then(({ found, key }) => {
- if (found) {
- modifyPlexButton(options.button, 'found', 'On Plex', { ...options, key });
- opt = { ...opt, url: options.button.href, found: true, status: 'found' };
+ if(po = options.button.querySelector('#plexit'))
+ po.remove();
+ try {
+ options.button.querySelector('ul').insertBefore(pi, op);
+ } catch(e) { /* Don't do anything */ }
+ } else {
+ let available = (config.usingOmbi || config.usingWatcher || config.usingRadarr || config.usingSonarr || config.usingMedusa || config.usingCouchPotato),
+ action = (available ? 'downloader' : 'notfound'),
+ title = available ?
+ 'Not on Plex (download available)':
+ 'Not on Plex (download not available)';
- let po, pi = furnish('li#plexit.list-item', { data: btoa(JSON.stringify(opt)) }, img);
+ modifyPlexButton(options.button, action, title, options);
+ opt = { ...opt, found: false, status: action };
- if(po = options.button.querySelector('#plexit'))
- po.remove();
- try {
- options.button.querySelector('ul').insertBefore(pi, op);
- } catch(e) { /* Don't do anything */ }
- } else {
- let available = (config.usingOmbi || config.usingWatcher || config.usingRadarr || config.usingSonarr || config.usingCouchPotato),
- action = (available ? 'downloader' : 'notfound'),
- title = available ?
- 'Not on Plex (download available)':
- 'Not on Plex (download not available)';
+ let po, pi = furnish('li#plexit.list-item', { data: btoa(JSON.stringify(opt)) }, img);
- modifyPlexButton(options.button, action, title, options);
- opt = { ...opt, found: false, status: action };
+ if(po = options.button.querySelector('#plexit'))
+ po.remove();
+ if(!!~[].slice.call(options.button.querySelector('ul').children).indexOf(op))
+ try {
+ options.button.querySelector('ul').insertBefore(pi, op);
+ } catch(e) { /* Don't do anything */ }
+ }
+ return found;
+ });
+ }
+ return found;
+ })
+ } catch(error) {
+ return modifyPlexButton(
+ options.button,
+ 'error',
+ 'Request to Plex Media Server failed',
+ options
+ ),
+ terminal.error(`Request to Plex failed: ${ String(error) }`),
+ false;
+ // new Notification('Failed to communicate with Plex');
+ }
+ }
- let po, pi = furnish('li#plexit.list-item', { data: btoa(JSON.stringify(opt)) }, img);
+ function getPlexMediaRequest(options) {
+ if(!(config.plexURL && config.plexToken) || config.DO_NOT_USE)
+ return new Promise((resolve, reject) => resolve({ found: false, key: null }));
- if(po = options.button.querySelector('#plexit'))
- po.remove();
- if(!!~[].slice.call(options.button.querySelector('ul').children).indexOf(op))
- try {
- options.button.querySelector('ul').insertBefore(pi, op);
- } catch(e) { /* Don't do anything */ }
- }
- return found;
- });
- }
- return found;
- })
- } catch(error) {
- return modifyPlexButton(
- options.button,
- 'error',
- 'Request to Plex Media Server failed',
- options
- ),
- terminal.error(`Request to Plex failed: ${ String(error) }`),
- false;
- // new Notification('Failed to communicate with Plex');
- }
-}
+ return new Promise((resolve, reject) => {
+ chrome.runtime.sendMessage({
+ type: 'SEARCH_PLEX',
+ options,
+ serverConfig: config.server
+ },
+ response =>
+ (response && response.error)?
+ reject(response.error):
+ (!response)?
+ reject(new Error('Unknown error')):
+ resolve(response)
+ );
+ });
+ }
-function getPlexMediaRequest(options) {
- if(!(config.plexURL && config.plexToken) || config.DO_NOT_USE)
- return new Promise((resolve, reject) => resolve({ found: false, key: null }));
+ function getPlexMediaURL(PlexUIID, key) {
+ return config.plexURL.replace(RegExp(`\/(${ config.server.id })?$`), `/web#!/server/` + PlexUIID) + `/details?key=${encodeURIComponent( key )}`;
+ }
- return new Promise((resolve, reject) => {
- chrome.runtime.sendMessage({
- type: 'SEARCH_PLEX',
- options,
- serverConfig: config.server
- },
- response =>
- (response && response.error)?
- reject(response.error):
- (!response)?
- reject(new Error('Unknown error')):
- resolve(response)
- );
- });
-}
+ /* Listen for events */
+ chrome.runtime.onMessage.addListener(async(request, sender) => {
+ terminal.log(`Listener event [${ request.instance_type }#${ request[request.instance_type.toLowerCase()] }]:`, request);
-function getPlexMediaURL(PlexUIID, key) {
- return config.plexURL.replace(RegExp(`\/(${ config.server.id })?$`), `/web#!/server/` + PlexUIID) + `/details?key=${encodeURIComponent( key )}`;
-}
+ let data = request.data,
+ LOCATION = `${ request.name || 'anonymous' } @ instance ${ request.instance }`,
+ PARSING_ERROR = `Can't parse missing information. ${ LOCATION }`,
+ BUTTON_ERROR = `The button failed to render. ${ LOCATION }`,
+ EMPTY_REQUEST = `The given request is empty. ${ LOCATION }`;
-/* Listen for events */
-chrome.runtime.onMessage.addListener(async(request, sender) => {
- terminal.log(`Listener event [${ request.instance_type }#${ request[request.instance_type] }]:`, request);
+ if(!data)
+ return terminal.warn(EMPTY_REQUEST);
+ let button = renderPlexButton();
- let data = request.data,
- LOCATION = `${ request.name || 'anonymous' } @ instance ${ request.instance }`,
- PARSING_ERROR = `Can't parse missing information. ${ LOCATION }`,
- BUTTON_ERROR = `The button failed to render. ${ LOCATION }`,
- EMPTY_REQUEST = `The given request is empty. ${ LOCATION }`;
+ if(!button)
+ return terminal.warn(BUTTON_ERROR);
- if(!data)
- return terminal.warn(EMPTY_REQUEST);
- let button = renderPlexButton();
+ switch(request.type) {
+ case 'POPULATE':
+
+ if(data instanceof Array) {
+ for(let index = 0, length = data.length, item; index < length; index++)
+ if(!(item = data[index]) || !item.type)
+ data.splice(index, 1, null);
- if(!button)
- return terminal.warn(BUTTON_ERROR);
+ data = data.filter(value => value !== null && value !== undefined);
- switch(request.type) {
- case 'POPULATE':
+ for(let index = 0, length = data.length, item; index < length; index++) {
+ let { image, type, title, year, IMDbID, TMDbID, TVDbID } = (item = data[index]);
- if(data instanceof Array) {
- for(let index = 0, length = data.length, item; index < length; index++)
- if(!(item = data[index]) || !item.type)
- data.splice(index, 1, null);
+ if(!item.title || !item.type)
+ continue;
- data = data.filter(value => value !== null && value !== undefined);
+ let Db = await getIDs(item);
- for(let index = 0, length = data.length, item; index < length; index++) {
- let { image, type, title, year, IMDbID, TMDbID, TVDbID } = (item = data[index]);
+ IMDbID = IMDbID || Db.imdb || 'tt';
+ TMDbID = TMDbID || Db.tmdb || 0;
+ TVDbID = TVDbID || Db.tvdb || 0;
- if(!item.title || !item.type)
- continue;
+ title = title || Db.title;
+ year = +(year || Db.year || 0);
- let Db = await getIDs(item);
+ data.splice(index, 1, { type, title, year, image, button, IMDbID, TMDbID, TVDbID });
+ }
+
+ if(!data.length)
+ return terminal.error(PARSING_ERROR);
+ else
+ squabblePlexMedia(data, button);
+ } else {
+ if(!data || !data.title || !data.type)
+ return terminal.error(PARSING_ERROR);
+
+ let { image, type, title, year, IMDbID, TMDbID, TVDbID } = data;
+ let Db = await getIDs(data);
IMDbID = IMDbID || Db.imdb || 'tt';
TMDbID = TMDbID || Db.tmdb || 0;
@@ -1738,91 +1982,104 @@ chrome.runtime.onMessage.addListener(async(request, sender) => {
title = title || Db.title;
year = +(year || Db.year || 0);
- data.splice(index, 1, { type, title, year, image, button, IMDbID, TMDbID, TVDbID });
+ let found = await findPlexMedia({ type, title, year, image, button, IMDbID, TMDbID, TVDbID });
+ sendUpdate('FOUND', { ...request, found }, true);
}
+ return true;
- if(!data.length)
- return terminal.error(PARSING_ERROR);
- else
- squabblePlexMedia(data, button);
- } else {
- if(!data || !data.title || !data.type)
- return terminal.error(PARSING_ERROR);
-
- let { image, type, title, year, IMDbID, TMDbID, TVDbID } = data;
- let Db = await getIDs(data);
+ default:
+ // terminal.warn(`Unknown event [${ request.type }]`);
+ return false;
+ }
+ });
- IMDbID = IMDbID || Db.imdb || 'tt';
- TMDbID = TMDbID || Db.tmdb || 0;
- TVDbID = TVDbID || Db.tvdb || 0;
+ /* Listen for Window events - from iframes, etc. */
+ top.addEventListener('message', request => {
+ try {
+ request = request.data;
- title = title || Db.title;
- year = +(year || Db.year || 0);
+ switch(request.type) {
+ case 'SEND_VIDEO_LINK':
+ let options = { ...findPlexMedia.OPTIONS, href: request.href, remote: request.from };
- let found = await findPlexMedia({ type, title, year, image, button, IMDbID, TMDbID, TVDbID });
- sendUpdate('FOUND', { ...request, found });
- }
- return true;
+ terminal.log('oload Event:', options);
- default:
-// terminal.warn(`Unknown event [${ request.type }]`);
- return false;
- }
-});
+ modifyPlexButton(MASTER_BUTTON, 'downloader', 'Download', options);
+ return true;
-/* Listen for Window events - from iframes, etc. */
-top.addEventListener('message', request => {
- try {
- request = request.data;
+ case 'NOTIFICATION':
+ let { state, text, timeout = 7000, callback = () => {}, requiresClick = true } = request.data;
+ new Notification(state, text, timeout, callback, requiresClick);
+ return true;
- switch(request.type) {
- case 'SEND_VIDEO_LINK':
- let options = { ...findPlexMedia.OPTIONS, href: request.href, remote: request.from };
+ default:
+ // terminal.warn(`Unknown event [${ request.type }]`);
+ return false;
+ }
+ } catch(error) {
+ new Notification('error', `Unable to use downloader: ${ String(error) }`);
+ throw error
+ }
+ });
- modifyPlexButton(options.button, 'downloader', 'Download', options);
- return true;
+})(new Date);
- default:
- // terminal.warn(`Unknown event [${ request.type }]`);
- return false;
- }
- } catch(error) {
- new Notification('error', `Unable to use downloader: ${ String(error) }`);
- throw error
- }
-});
+/* Helpers */
function wait(on, then) {
- if (on && on())
+ if(on && ((on instanceof Function && on()) || true))
then && then();
else
setTimeout(() => wait(on, then), 50);
}
// the custom "on location change" event
-let locationchangecallbacks = [];
-
function watchlocationchange(subject) {
+ let locationchangecallbacks = watchlocationchange.locationchangecallbacks;
+
watchlocationchange[subject] = watchlocationchange[subject] || location[subject];
- if (watchlocationchange[subject] != location[subject]) {
+ if(watchlocationchange[subject] != location[subject]) {
+ let from = watchlocationchange[subject],
+ to = location[subject],
+ properties = { from, to },
+ sign = code => (code + '').replace(/\s+/g, '');
+
watchlocationchange[subject] = location[subject];
- for(let index = 0, length = locationchangecallbacks.length, callback; index < length; index++) {
+ for(let index = 0, length = locationchangecallbacks.length, callback, called; length > 0 && index < length; index++) {
callback = locationchangecallbacks[index];
+ called = locationchangecallbacks.called[sign(callback)];
+
+ let event = new Event('locationchange', { bubbles: true });
+
+ if(!called && callback && typeof callback == 'function') {
+ locationchangecallbacks.called[sign(callback)] = true;
+ window.addEventListener('beforeunload', event => {
+ event.preventDefault(false);
+
+ callback({ event, ...properties });
+ });
- if(callback && typeof callback == 'function')
- callback(new Event('locationchange', { bubbles: true }));
+ callback({ event, ...properties });
+
+ open(to, '_self');
+ } else {
+ return /* The eventlistener was already called */;
+ }
}
}
}
+watchlocationchange.locationchangecallbacks = watchlocationchange.locationchangecallbacks || [];
+watchlocationchange.locationchangecallbacks.called = watchlocationchange.locationchangecallbacks.called || {};
if(!('onlocationchange' in window))
Object.defineProperty(window, 'onlocationchange', {
- set: callback => locationchangecallbacks.push(callback)
+ set: callback => (typeof callback == 'function'? watchlocationchange.locationchangecallbacks.push(callback): null),
+ get: () => watchlocationchange.locationchangecallbacks
});
-watchlocationchange.interval = watchlocationchange.interval || setInterval(() => watchlocationchange('href'), 1000);
+watchlocationchange.onlocationchangeinterval = watchlocationchange.onlocationchangeinterval || setInterval(() => watchlocationchange('href'), 1);
// at least 1s is needed to properly fire the event ._.
String.prototype.toCaps = String.prototype.toCaps || function toCaps(all) {
@@ -1835,7 +2092,8 @@ String.prototype.toCaps = String.prototype.toCaps || function toCaps(all) {
titles = /(?!^|(?:an?|the)\s+)\b(a([st]|nd?|cross|fter|lthough)?|b(e(cause|fore|tween)?|ut|y)|during|from|in(to)?|[io][fn]|[fn]?or|the|[st]o|through|under|with(out)?|yet)(?!\s*$)\b/gi,
cap_exceptions = /([\|\"\(]\s*[a-z]|[\:\.\!\?]\s+[a-z]|(?:^\b|[^\'\-\+]\b)[^aeiouy\d\W]+\b)/gi, // Punctuation exceptions, e.g. "And not I"
all_exceptions = /\b((?:ww)?(?:m{1,4}(?:c?d(?:c{0,3}(?:x?l(?:x{0,3}(?:i?vi{0,3})?)?)?)?)?|c?d(?:c{0,3}(?:x?l(?:x{0,3}(?:i?vi{0,3})?)?)?)?|c{1,3}(?:x?l(?:x{0,3}(?:i?vi{0,3})?)?)?|x?l(?:x{0,3}(?:i?vi{0,3})?)?|x{1,3}(?:i?vi{0,3})?|i?vi{0,3}|i{1,3}))\b/gi, // Roman Numberals
- cam_exceptions = /\b((?:mr?s|[sdjm]r|mx)|(?:adm|cm?dr?|chf|c[op][lmr]|cpt|gen|lt|mjr|sgt)|doc|hon|prof)(?:\.|\b)/gi; // Titles (Most Common?)
+ cam_exceptions = /\b((?:mr?s|[sdjm]r|mx)|(?:adm|cm?dr?|chf|c[op][lmr]|cpt|gen|lt|mjr|sgt)|doc|hon|prof)(?:\.|\b)/gi, // Titles (Most Common?)
+ low_exceptions = /'([\w]+)/gi; // Apostrphe cases
array = array.split(/\s+/);
@@ -1851,10 +2109,11 @@ String.prototype.toCaps = String.prototype.toCaps || function toCaps(all) {
if(!all)
string = string
- .replace(titles, ($0, $1, $$, $_) => $1.toLowerCase())
- .replace(cap_exceptions, ($0, $1, $$, $_) => $1.toUpperCase())
- .replace(all_exceptions, ($0, $1, $$, $_) => $1.toUpperCase())
- .replace(cam_exceptions, ($0, $1, $$, $_) => $1[0].toUpperCase() + $1.slice(1, $1.length).toLowerCase() + '.');
+ .replace(titles, ($0, $1, $$, $_) => $1.toLowerCase())
+ .replace(all_exceptions, ($0, $1, $$, $_) => $1.toUpperCase())
+ .replace(cap_exceptions, ($0, $1, $$, $_) => $1.toUpperCase())
+ .replace(low_exceptions, ($0, $1, $$, $_) => $0.toLowerCase())
+ .replace(cam_exceptions, ($0, $1, $$, $_) => $1[0].toUpperCase() + $1.slice(1, $1.length).toLowerCase() + '.');
return string;
};
@@ -1995,7 +2254,7 @@ String.prototype.toCaps = String.prototype.toCaps || function toCaps(all) {
else if(t == '.')
attributes.classList = [].slice.call(attributes.classList || []).concat(v);
else if(/\[(.+)\]/.test(n[i]))
- R.$1.split('][').forEach(N => attributes[(N = N.split('=', 2))[0]] = N[1] || '');
+ R.$1.split('][').forEach(N => attributes[(N = N.replace(/\s*=\s*(?:("?)([^]*)\1)?/, '=$2').split('=', 2))[0]] = N[1] || '');
name = name[0];
let element = document.createElement(name, options);
@@ -2005,6 +2264,20 @@ String.prototype.toCaps = String.prototype.toCaps || function toCaps(all) {
Object.entries(attributes).forEach(
([name, value]) => (/^(on|(?:(?:inner|outer)(?:HTML|Text)|textContent|class(?:List|Name)|value)$)/.test(name))?
+ (typeof value == 'string' && /^on/.test(name))?
+ (() => {
+ try {
+ /* Can't make a new function(eval) */
+ element[name] = new Function('', value);
+ } catch (__error) {
+ try {
+ /* Not a Chrome (extension) state */
+ chrome.tabs.getCurrent(tab => chrome.tabs.executeScript(tab.id, { code: `document.furnish.__cache__ = () => {${ value }}` }, __cache__ => element[name] = __cache__[0] || parent.furnish.__cache__ || value));
+ } catch (_error) {
+ throw __error, _error;
+ }
+ }
+ })():
element[name] = value:
element.setAttribute(name, value)
);
|