';
+
+/* Start Injected */
+${ code }
+/* End Injected */
+
+let ScriptReadyState;
+
+top.addEventListener('popstate', script.init);
+top.addEventListener('pushstate-changed', script.init);
+
+return (script.RegExp = RegExp(
+ script.url
+ // .replace(/\\|.*?(\\)|$)/g,'')
+ .replace(/^\\*\\:/,'\\\\w{3,}:')
+ .replace(/\\*\\./g,'([^\\\\.]+\\\\.)?')
+ .replace(/\\/\\*/g,'/[^$]*'),'i')
+).test
+(location.href)?
+/* URL matches pattern */
+ script.ready?
+ /* Script has the "ready" property */
+ (ScriptReadyState =
+ script.ready.constructor.name == 'AsyncFunction'?
+ /* "ready" is an async function */
+ script.ready():
+ /* "ready" is a sync (normal) function */
+ script.ready()
+ )?
+ /* Script is ready */
+ script.init( ScriptReadyState ):
+ /* Script isn't ready */
+ (script.timeout || 1000):
+ /* Script doesn't have the "ready" property */
+ script.init():
+/* URL doesn't match pattern */
+(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))
+ })
+ })
+ .then(() => running.push(id, instance))
+ .catch(error => { throw error });
+ break;
+
+ case '_INIT_':
+ 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;
+
+ default:
+ instance = RandomName();
+ return false;
+ };
}
-};
+
+ return true;
+});
// this doesn't actually work...
-//chrome.tabs.onActiveChanged.addListener(tabchange);
+chrome.tabs.onActiveChanged.addListener(tabchange);
// workaround for the above
-setInterval(() =>
- chrome.tabs.query({
- active: true,
- currentWindow: true,
- }, tabchange)
-, 1000);
+chrome.tabs.onActivated.addListener(change => {
+ instance = RandomName();
+
+ chrome.tabs.get(change.tabId, tab => tabchange([ tab ]));
+});
+
+let refresh;
+
+chrome.tabs.onUpdated.addListener(refresh = (ID, change, tab) => {
+ instance = RandomName();
+
+ if(change.status == 'complete' && !tab.discarded)
+ tabchange([ tab ]);
+ else if(!tab.discarded)
+ setTimeout(() => refresh(ID, change, tab), 1000);
+});
diff --git a/src/popup/index.html b/src/popup/index.html
index f8adb7b..cb5512b 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 {
@@ -180,6 +180,30 @@
box-shadow: 0 10px 128px inset #7A314E;
}
+ #justwatch:hover {
+ box-shadow: 0 10px 128px inset #0E202C;
+ }
+
+ #moviemeter:hover {
+ box-shadow: 0 10px 128px inset #000000;
+ }
+
+ #allocine:hover {
+ 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;
+ }
+
+ #webtoplex:hover {
+ box-shadow: 0 10px 128px inset #CC7B19;
+ }
+
#local-plex:hover {
box-shadow: 0 10px 128px inset #F9BD03;
}
@@ -204,6 +228,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;
@@ -229,7 +257,7 @@
float: right;
}
- [is-shy] label:after {
+ [is-shy] label:after, [is-dead] label:after {
content: " \1f910";
float: right;
}
@@ -238,6 +266,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;
@@ -289,7 +322,7 @@
-
+ |
@@ -304,13 +337,13 @@
-
+ |
|
-
+ |
@@ -325,27 +358,33 @@
|
+
+
+
+
+
+ |
|
-
+ |
|
+
+
+
|
-
-
-
@@ -358,33 +397,27 @@
|
+
+
+
|
-
-
-
|
-
+ |
|
-
-
-
-
-
- |
@@ -394,42 +427,48 @@
+
+
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
|
-
-
-
-
|
+
+
+
|
-
-
-
-
-
- |
-
-
-
@@ -442,12 +481,48 @@
|
-
+ |
+
+
+
|
+
+
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+
+
+
+ |
+
+
+
+
+
+ |
+
+
+
+
+
+ |
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/__layout__.js b/src/sites/__layout__.js
new file mode 100644
index 0000000..9831075
--- /dev/null
+++ b/src/sites/__layout__.js
@@ -0,0 +1,2 @@
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: '< Page Alias >' }))();
diff --git a/src/sites/__test__.js b/src/sites/__test__.js
new file mode 100644
index 0000000..68ee2d5
--- /dev/null
+++ b/src/sites/__test__.js
@@ -0,0 +1,2 @@
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: '__test__' }))();
diff --git a/src/sites/flenix/index.css b/src/sites/allocine/index.css
similarity index 100%
rename from src/sites/flenix/index.css
rename to src/sites/allocine/index.css
diff --git a/src/sites/allocine/index.js b/src/sites/allocine/index.js
new file mode 100644
index 0000000..850536c
--- /dev/null
+++ b/src/sites/allocine/index.js
@@ -0,0 +1,2 @@
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'allocine' }))();
diff --git a/src/sites/amazon/index.js b/src/sites/amazon/index.js
index adc5d72..70796df 100644
--- a/src/sites/amazon/index.js
+++ b/src/sites/amazon/index.js
@@ -1,58 +1,2 @@
-/* global findPlexMedia, parseOptions, modifyPlexButton */
-function isMovie() {
- return !isShow();
-}
-
-function isShow() {
- return document.querySelector('[data-automation-id*="seasons"], [class*="seasons"], [class*="episodes"], [class*="series"]');
-}
-
-function isPageReady() {
- return document.querySelector('[data-automation-id="imdb-rating-badge"], #most-recent-reviews-content > *:first-child');
-}
-
-async function init() {
- if (isPageReady())
- await initPlexThingy(isShow()? 'tv': 'movie');
- else
- // This almost never happens, but sometimes the page is too slow so we need to wait a bit.
- setTimeout(init, 1000);
-}
-
-async function initPlexThingy(type) {
- let button = renderPlexButton(),
- R = RegExp;
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- let $title = document.querySelector('[data-automation-id="title"], #aiv-content-title, .dv-node-dp-title'),
- $year = document.querySelector('[data-automation-id="release-year-badge"], .release-year'),
- $image = document.querySelector('.av-bgimg__div, div[style*="sgp-catalog-images"]');
-
- if (!$title)
- return modifyPlexButton(
- button,
- 'error',
- 'Could not extract title from Amazon'
- ),
- null;
-
- let title = $title.textContent.replace(/(?:\(.+?\)|(\d+)|\d+\s+seasons?\s+(\d+))\s*$/gi, '').trim(),
- year = $year? $year.textContent.trim(): R.$1 || R.$2 || YEAR,
- image = getComputedStyle($image).backgroundImage;
-
- let Db = await getIDs({ title, year, type }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = Db.title;
- year = Db.year;
-
- findPlexMedia({ type, title, year, image, button, IMDbID, TMDbID, TVDbID });
-}
-
-if (isMovie() || isShow()) {
- parseOptions().then(async() => await init());
-}
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'amazon' }))();
diff --git a/src/sites/common.css b/src/sites/common.css
index e29cb44..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,7 +223,17 @@
max-width: 60% !important;
}
-.web-to-plex-prompt-option.mutable > *:last-child {
+.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 {
background: #ffffff40 !important;
border-radius: 3px !important;
transition: all 0.1s !important;
@@ -228,18 +243,30 @@
float: right !important;
margin-right: -9px !important;
- margin-top: -9px !important;
+ margin-top: -42px !important;
padding: 0 !important;
}
-.web-to-plex-prompt-option.mutable > *:last-child:hover {
+.web-to-plex-prompt-option.mutable > .remove:hover {
background: #ffffff4d !important;
}
-.web-to-plex-prompt-option.mutable > *:last-child:after {
+.web-to-plex-prompt-option.mutable > .remove:after {
content: '\00d7' !important;
}
+.web-to-plex-prompt-option.mutable > .quality {
+ width: 50% !important;
+}
+
+.web-to-plex-prompt-option.mutable > .location {
+ width: 90% !important;
+}
+
+.web-to-plex-prompt-option.mutable > .location:last-child:not(:first-child) {
+ margin-top: 5px !important;
+}
+
.web-to-plex-prompt-footer {
text-align: right !important;
border-bottom-left-radius: 3px !important;
@@ -302,6 +329,8 @@
position: fixed !important;
z-index: 999999 !important;
+ min-height: 0 !important;
+ min-width: 0 !important;
height: 72px !important;
width: 180px !important;
@@ -485,3 +514,88 @@
padding: 3px 6px !important;
position: absolute !important;
}
+
+/* bbodine @CodePen - https://codepen.io/bbodine1/pen/novBm */
+.checkbox {
+ width: 80px;
+ height: 26px;
+ background: #000;
+ margin: 15px 0;
+ position: relative;
+ border-radius: 50px;
+ box-shadow: inset 0px 1px 1px rgba(0, 0, 0, 0.5), 0px 1px 0px rgba(255, 255, 255, 0.2);
+}
+
+span.checkbox {
+ display: inline-block;
+
+ margin: 0;
+ vertical-align: text-bottom;
+}
+
+.checkbox:after {
+ content: 'OFF';
+ color: #666;
+ position: absolute;
+ right: 10px;
+ z-index: 0;
+ font: 12px/26px Arial, sans-serif;
+ font-weight: bold;
+ text-shadow: 1px 1px 0px rgba(255, 255, 255, 0.15);
+}
+
+.checkbox:before {
+ content: 'ON';
+ color: #cc7b19;
+ position: absolute;
+ left: 10px;
+ z-index: 0;
+ font: 12px/26px Arial, sans-serif;
+ font-weight: bold;
+}
+
+.checkbox[prompt-yes]:before {
+ content: attr(prompt-yes);
+ text-transform: uppercase;
+}
+
+.checkbox[prompt-no]:after {
+ content: attr(prompt-no);
+ text-transform: uppercase;
+}
+
+.checkbox[prompt="y/n"i]:before {
+ content: 'YES';
+}
+
+.checkbox[prompt="y/n"i]:after {
+ content: 'NO';
+}
+
+.checkbox label {
+ display: block;
+ width: 34px;
+ height: 20px;
+ cursor: pointer;
+ position: absolute;
+ top: 3px;
+ left: 3px;
+ z-index: 1;
+ background: #666;
+ border-radius: 50px;
+ transition: all 0.4s ease;
+ box-shadow: 0px 2px 5px 0px rgba(0, 0, 0, 0.3);
+}
+
+.checkbox input[type=checkbox] {
+ visibility: hidden;
+}
+
+.checkbox input[type=checkbox]:checked + label {
+ left: 43px;
+ background: #cc7b19;
+}
+
+.checkbox[disabled] {
+ opacity: 0.25 !important;
+}
diff --git a/src/sites/couchpotato/index.js b/src/sites/couchpotato/index.js
index 64a9de5..7ef46cf 100644
--- a/src/sites/couchpotato/index.js
+++ b/src/sites/couchpotato/index.js
@@ -1,52 +1,2 @@
-/* global wait, modifyPlexButton, parseOptions, findPlexMedia */
-function init() {
- wait(
- () => document.querySelector('.media-body .clearfix') && document.querySelector('.media-body .clearfix').children.length > 1,
- () => initPlexThingy(isMovie()? 'movie': isShow()? 'show': null)
- );
-}
-
-function isMovie() {
- return /^\/movies?\//.test(window.location.pathname);
-}
-
-function isShow() {
- return /^\/shows?\//.test(window.location.pathname);
-}
-
-function initPlexThingy(type) {
- let button = renderPlexButton();
-
- if (!button || !type)
- return /* Fatal Error: Fail Silently */;
-
- let $title = document.querySelector('[itemprop="description"]'),
- $date = $title.previousElementSibling,
- $image = document.querySelector('img[src*="wp-content"]');
-
- if (!$title || !$date)
- return modifyPlexButton(
- $button,
- 'error',
- 'Could not extract title or year from CouchPotato'
- );
-
- let title = $title.textContent.trim(),
- year = $date.textContent.trim(),
- image = ($image || {}).src,
- IMDbID = getIMDbID();
-
- findPlexMedia({ title, year, image, button, type, IMDbID });
-}
-
-function getIMDbID() {
- let $link = document.querySelector(
- '[href*="imdb.com/title/tt"]'
- );
- if ($link) {
- let link = $link.href.replace(/^.*imdb\.com\/title\//, '');
- return link.replace(/\/(?:maindetails\/?)?$/, '');
- }
-}
-
-parseOptions().then(init);
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'couchpotato' }))();
diff --git a/src/sites/fandango/index.js b/src/sites/fandango/index.js
index 89e6444..366bb50 100644
--- a/src/sites/fandango/index.js
+++ b/src/sites/fandango/index.js
@@ -1,42 +1,2 @@
-/* global findPlexMedia, parseOptions, modifyPlexButton */
-function isMovie() {
- return /\/movie-overview\/?$/.test(window.location.pathname);
-}
-
-async function initPlexThingy(type) {
- let $parent = document.querySelector('.subnav ul'),
- button = renderPlexButton();
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- let $title = document.querySelector('.subnav__title'),
- $year = document.querySelector('.movie-details__release-date'),
- $image = document.querySelector('.movie-details__movie-img');
-
- if (!$title || !$year)
- return modifyPlexButton(
- button,
- 'error',
- 'Could not extract title or year from Fandango'
- ),
- null;
-
- let title = $title.textContent.trim().split(/\n+/)[0].trim(),
- year = $year.textContent.replace(/.*(\d{4}).*/, '$1').trim(),
- image = ($image || {}).src;
-
- let Db = await getIDs({ title, year, type }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = Db.title;
- year = Db.year;
-
- findPlexMedia({ type, title, year, image, button, IMDbID, TMDbID, TVDbID });
-}
-
-if (isMovie()) {
- parseOptions().then(async() => await initPlexThingy('movie'));
-}
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'fandango' }))();
diff --git a/src/sites/flenix/index.js b/src/sites/flenix/index.js
deleted file mode 100644
index 229a5f5..0000000
--- a/src/sites/flenix/index.js
+++ /dev/null
@@ -1,62 +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' });
-}
diff --git a/src/sites/flickmetrix/index.js b/src/sites/flickmetrix/index.js
index 1119254..4cbc2f3 100644
--- a/src/sites/flickmetrix/index.js
+++ b/src/sites/flickmetrix/index.js
@@ -1,106 +1,2 @@
-/* global wait, modifyPlexButton, parseOptions, findPlexMedia */
-function isMovie() {
- return !!document.querySelector('#singleFilm') || /\bid=\d+\b/i.test(window.location.search);
-}
-
-function isList() {
- return !isMovie();
-}
-
-let START = +(new Date);
-
-function init() {
- if(/\/(?=((?:watchlist|seen|favourites|trash)\b|$))/i.test(window.location.pathname))
- wait(
- () => (!document.querySelector('#loadingOverlay > *')),
- () => (isList()? initList: initPlexThingy)()
- );
-}
-
-function initPlexThingy() {
- let button = renderPlexButton();
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- let $element = document.querySelector('#singleFilm'),
- $title = $element.querySelector('.title'),
- $date = $element.querySelector('.title + *'),
- $image = $element.querySelector('img');
-
- if (!$title || !$date)
- return modifyPlexButton(
- button,
- 'error',
- 'Could not extract title or year from Flickmetrix'
- );
-
- let title = $title.textContent.trim(),
- year = $date.textContent.replace(/^\(|\)$/g, '').trim(),
- image = ($image || {}).src,
- IMDbID = getIMDbID($element);
-
- findPlexMedia({ title, year, button, type: 'movie', IMDbID });
-}
-
-function getIMDbID(element) {
- let $link = element.querySelector(
- '[href*="imdb.com/title/tt"]'
- );
- if ($link) {
- let link = $link.href.replace(/^.*imdb\.com\/title\//, '');
- return link.replace(/\/(?:maindetails\/?)?$/, '');
- }
-}
-
-async function addInListItem(element) {
- let $title = element.querySelector('.title'),
- $date = element.querySelector('.title + *'),
- $image = element.querySelector('img');
-
- if (!$title)
- return;
-
- let title = $title.textContent.trim(),
- year = +$date.textContent.replace(/^\(|\)$/g, '').trim(),
- image = $image.src,
- type = 'movie',
- IMDbID = getIMDbID(element);
-
- let Db = await getIDs({ type, title, year, IMDbID }),
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = title || Db.title;
- year = year || Db.year;
- IMDbID = IMDbID || Db.IMDbID;
-
- return { type, title, year, image, IMDbID, TMDbID, TVDbID };
-}
-
-function initList() {
- let $listItems = document.querySelectorAll('.film'),
- button = renderPlexButton(),
- options = [], length = $listItems.length - 1;
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- $listItems.forEach(async(element, index, array) => {
- let option = await addInListItem(element);
-
- if(option)
- options.push(option);
-
- if(index == length)
- setTimeout(() => {
- terminal.log(options)
- if (!options.length)
- new Notification('error', 'Failed to process list');
- else
- squabblePlex(options, button);
- }, 50);
- });
-}
-
-parseOptions().then(window.onlocationchange = init);
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'flickmetrix' }))();
diff --git a/src/sites/google/index.js b/src/sites/google/index.js
index db88def..1e3c09f 100644
--- a/src/sites/google/index.js
+++ b/src/sites/google/index.js
@@ -1,72 +1,2 @@
-function isMovie() {
- return document.querySelector('#media_result_group, [href*="themoviedb.org/tv/"], [href*="imdb.com/title/tt"]');
-}
-
-function isShow() {
- return document.queryBy('[href*="thetvdb.com/"][href*="id="], [href*="thetvdb.com/series/"], [href*="themoviedb.org/tv/"], [href*="imdb.com/title/tt"][href$="externalsites"]').first;
-}
-
-function init() {
- if(isMovie() || isShow())
- initPlexThingy(isMovie()? 'movie': isShow()? 'show': null);
-}
-
-async function initPlexThingy(type) {
- let $title, $type, $date, $image;
-
- let button = renderPlexButton();
- if(!button || !type)
- return /* Fail silently */;
-
- if(type == 'movie') {
- $title = document.querySelector('.kno-ecr-pt');
- $type = document.querySelector('.kno-ecr-pt + *'); // in case a tv show is incorrectly identified
- $date = document.querySelector('.kno-fb-ctx:not([data-local-attribute]) span');
- $image = document.querySelector('#media_result_group img');
- } else {
- $title = isShow().querySelector('*');
- $date = { textContent: '' };
- $image = { src: '' };
- }
-
- if(!$title || !$date)
- return modifyPlexButton(button, 'error', 'Could not extract title or year from Google');
-
- if($type) {
- type = $type.textContent;
-
- type = /\b(tv|show|series)\b/i.test(type)? 'show': /\b(movie|film|cinema|(?:\d+h\s+)?\d+m)\b/i.test(type)? 'movie': 'error';
- $date = (type == 'show'? document.querySelector('.kno-fv') || $date: $date) || { textContent: '' };
- }
-
- if(type == 'error')
- return;
-
- let date = $date.textContent.replace(/(\d{4})/),
- year = +RegExp.$1,
- title = $title.textContent.replace((type == 'movie'? /^(.+)$/: /(.+)(?:(?:\:\s*series\s+info|\-\s*(?:all\s+episodes|season)).+)$/i), '$1').trim(),
- image = ($image || {}).src;
-
- year = year > 999? year: 0;
-
- let IMDbID = getIMDbID(),
- Db = await getIDs({ title, year, type, IMDbID }),
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- findPlexMedia({ type, title, year, image, button, IMDbID, TMDbID, TVDbID });
-}
-
-function getIMDbID() {
- let link = document.querySelector('a._hvg[href*="imdb.com/title/tt"]');
-
- if(link)
- return link.href.replace(/.*(tt\d+).*/, '$1');
-}
-
-parseOptions()
- .then(() => {
- window.addEventListener('popstate', init);
- window.addEventListener('pushstate-changed', init);
- init();
- });
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'google' }))();
diff --git a/src/sites/google/play.js b/src/sites/google/play.js
index 7c59064..0d53cab 100644
--- a/src/sites/google/play.js
+++ b/src/sites/google/play.js
@@ -1,50 +1,2 @@
-/* global wait, modifyPlexButton, parseOptions, findPlexMedia */
-function isMoviePage() {
- return window.location.pathname.startsWith('/store/movies/');
-}
-
-function isShowPage() {
- return window.location.pathname.startsWith('/store/tv/');
-}
-
-function init() {
- if (isMoviePage() || isShowPage()) {
- wait(
- () => document.querySelector('c-wiz span > button.id-track-click'),
- () => initPlexThingy(isMoviePage() ? 'movie' : 'tv')
- );
- }
-}
-
-async function initPlexThingy(type) {
- let button = renderPlexButton();
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- let $title = document.querySelector('h1'),
- $year = document.querySelector(`h1 ~ div span:${ type === 'movie'? 'first': 'last' }-of-type`),
- $image = document.querySelector('img[alt="cover art" i]');
-
- if (!$title || !$year)
- return modifyPlexButton(button, 'error', `Could not extract ${ !$title? 'title': 'year' } from Google`);
-
- let title = $title.textContent.replace(/\(\s*(\d{4})\s*\).*?$/, '').trim(),
- year = (RegExp.$1 || $year.textContent).replace(/^.*?(\d+)/, '$1').trim(),
- image = ($image || {}).src,
- Db = await getIDs({ title, year, type }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = Db.title;
- year = Db.year;
-
- findPlexMedia({ type, title, year, button, IMDbID, TMDbID, TVDbID });
-}
-
-parseOptions().then(() => {
- window.addEventListener('popstate', init);
- window.addEventListener('pushstate-changed', init);
- init();
-});
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'google.play' }))();
diff --git a/src/sites/gostream/index.js b/src/sites/gostream/index.js
index b8d1f3b..cc74b85 100644
--- a/src/sites/gostream/index.js
+++ b/src/sites/gostream/index.js
@@ -1,63 +1,2 @@
-/* global parseOptions, modifyPlexButton, findPlexMedia */
-function isMoviePage() {
- // An example movie page: /movies/3030-the-1517-to-paris.html
- return /\/(?!genre|most-viewed|top-imdb|contact)\b/.test(window.location.pathname);
-}
-
-function isMoviePageReady() {
- let e = document.querySelector('.movieplay iframe, .desc iframe');
- return !!e && e.src != '' && document.readyState == 'complete';
-}
-
-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 /* an error occurred, fail silently */;
-
- let $title = document.querySelector('[itemprop="name"]:not(meta)'),
- $year = document.querySelector('.mvic-desc [href*="year/"]'),
- $image, start = +(new Date);
-
- wait(() => (+(new Date) - start > 5000) || ($image = document.querySelector('.hiddenz, [itemprop="image"]')));
-
- if (!$title)
- return modifyPlexButton(
- button,
- 'error',
- 'Could not extract title from GoStream'
- ),
- null;
-
- new Notification('update', 'Select the Openload (OL) server to download');
-
- let title = $title.innerText.trim(),
- year = ($year? $year.innerText.trim(): 0),
- image = ($image? $image.src: null),
- 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, image, button, IMDbID, TMDbID, TVDbID, type });
-}
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'gostream' }))();
diff --git a/src/sites/hulu/index.js b/src/sites/hulu/index.js
index 6e1d3e0..9b777ed 100644
--- a/src/sites/hulu/index.js
+++ b/src/sites/hulu/index.js
@@ -1,39 +1,2 @@
-/* global findPlexMedia, parseOptions, modifyPlexButton */
-function isReady() {
- return $$('#content [class$="__meta"]');
-}
-
-function isMovie() {
- return window.location.pathname.startsWith('/movie/'); // /movies/ is STRICTLY for a collection of movies (e.g. the line-up)
-}
-
-function isShow() {
- return window.location.pathname.startsWith('/series/'); // /tv/ is STRICTLY for a collection of movies (e.g. the line-up)
-}
-
-let $$ = selector => document.querySelector(selector);
-
-async function initPlexThingy(type) {
- let button = renderPlexButton();
-
- if (!button || !type)
- return /* Fatal Error: Fail Silently */;
-
- let $title = $$('#content [class$="__name"]'),
- $year = $$('#content [class$="__meta"] [class$="segment"]:last-child'),
- title = $title.innerText.replace(/^\s+|\s+$/g, '').toCaps(),
- year = +$year.textContent.replace(/.*\((\d{4})\).*/, '$1'),
- Db = await getIDs({ title, year, type }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = Db.title;
- year = Db.year;
-
- findPlexMedia({ type, title, year, button, IMDbID, TMDbID, TVDbID });
-}
-
-(window.onlocationchange = () =>
- wait(isReady, () => parseOptions().then(async() => await initPlexThingy(isMovie()? 'movie': isShow()? 'tv': null)))
-)();
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'hulu' }))();
diff --git a/src/sites/imdb/index.js b/src/sites/imdb/index.js
index 24ec8f7..1c4ef9a 100644
--- a/src/sites/imdb/index.js
+++ b/src/sites/imdb/index.js
@@ -1,183 +1,2 @@
-/* global findPlexMedia, parseOptions, modifyPlexButton */
-function isMovie() {
- let tag = $$('meta[property="og:type"]');
- return tag && tag.content === 'video.movie';
-}
-
-function isShow() {
- let tag = $$('meta[property="og:type"]');
- return tag && tag.content === 'video.tv_show';
-}
-
-function isList() {
- return window.location.pathname.startsWith('/list/');
-}
-
-function getIMDbID() {
- let tag = $$('meta[property="pageId"]');
- return tag ? tag.content : undefined;
-}
-
-let $$ = (selector, index = 0) => document.queryBy(selector)[index],
- IMDbID = getIMDbID(),
- usa = /\b(USA?|United\s+States)\b/i;
-
-function cleanYear(year) {
- // The year can contain `()`, so we need to strip it out.
- return year.replace(/^\(|\)$/g, '').trim();
-}
-
-async function initPlexMovie() {
- let $parent = $$('.plot_summary'),
- button = renderPlexButton(),
- type = 'movie';
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- let $title = $$('.originalTitle, .title_wrapper h1'),
- $altname = $$('.title_wrapper h1'),
- $date = $$('.title_wrapper [href*="/releaseinfo"]'),
- $year = $$('.title_wrapper #titleYear'),
- $image = $$('img[alt$="poster" i]'),
- // TODO: Hmm there should be a less risky way...
- title = $title.childNodes[0].textContent.trim(),
- altname = ($altname == $title? title: $altname.childNodes[0].textContent.trim()),
- country = $date.innerText.replace(/[^]+\((\w+)\)[^]*?$/, '$1'),
- year = +cleanYear($year.textContent),
- image = ($image || {}).src;
- title = usa.test(country)? title: altname;
-
- let Db = await getIDs({ title, year, type, IMDbID }),
- TMDbID = +Db.tmdb,
- TVDbID = +Db.tvdb;
-
- IMDbID = IMDbID || Db.imdb;
- title = Db.title;
- year = Db.year;
-
- findPlexMedia({ type, title, year, image, button, IMDbID, TMDbID, TVDbID });
-}
-
-async function initPlexShow() {
- let $parent = $$('.plot_summary'),
- button = renderPlexButton(),
- type = 'show';
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- let $title = $$('.originalTitle, .title_wrapper h1'),
- $altname = $$('.title_wrapper h1'),
- $date = $$('.title_wrapper [href*="/releaseinfo"]'),
- date = $$('title').textContent,
- dateMatch = date.match(/Series\s*\(?(\d{4})(?:[^\)]+\))?/i),
- $image = $$('img[alt$="poster" i]');
-
- if (!($title || $altname) || !dateMatch)
- return modifyPlexButton(button, 'error', `Could not extract ${ !($title || $altname)? 'title': 'year' } from IMDb`);
-
- let title = $title.textContent.trim(),
- altname = ($altname == $title? title: $altname.childNodes[0].textContent.trim()),
- country = $date.innerText.replace(/[^]+\((\w+)\)[^]*?$/, '$1'),
- year = parseInt(dateMatch[1]),
- image = ($image || {}).src;
- title = usa.test(country)? title: altname;
-
- let Db = await getIDs({ title, year, type, IMDbID }),
- TMDbID = +Db.tmdb,
- TVDbID = +Db.tvdb;
-
- IMDbID = IMDbID || Db.imdb;
- title = Db.title;
- year = Db.year;
-
- let savename = title.toLowerCase(),
- cached = await load(`${savename}.imdb`);
-
- if(!cached) {
- save(`${savename} (${year}).imdb`, { type, title, year, imdb: IMDbID, tmdb: TMDbID, tvdb: TVDbID });
- save(`${savename}.imdb`, +year);
- terminal.log(`Saved as "${savename} (${year}).imdb"`);
- }
-
- findPlexMedia({ type, title, year, button, IMDbID, TMDbID, TVDbID });
-}
-
-async function addInListItem(element) {
- let $title = element.querySelector('.col-title a'),
- $date = element.querySelector('.col-title a + *'),
- $image = element.querySelector('img.loadlate, img[data-tconst]'),
- $IMDbID = $title;
-
- if (!$title || !$date)
- return;
-
- let title = $title.textContent.trim(),
- year = cleanYear($date.textContent),
- image = $image.src,
- IMDbID = $IMDbID.href.replace(/.*\/(tt\d+)\b.*$/, '$1'),
- type = (/[\-\u2010-\u2015]/.test(year)? 'show': 'movie');
-
- year = parseInt(year);
-
- let Db = await getIDs({ type, title, year, IMDbID }),
- TMDbID = +Db.tmdb,
- TVDbID = +Db.tvdb;
-
- title = title || Db.title;
- year = year || Db.year;
-
- let savename = title.toLowerCase(),
- cached = await load(`${savename}.imdb`);
-
- if(!cached) {
- save(`${savename} (${year}).imdb`, { type, title, year, imdb: IMDbID, tmdb: TMDbID, tvdb: TVDbID });
- save(`${savename}.imdb`, +year);
- terminal.log(`Saved as "${savename} (${year}).imdb"`);
- }
-
- return { type, title, year, image, IMDbID, TMDbID, TVDbID };
-}
-
-function initList() {
- let $listItems = document.querySelectorAll('#main .lister-item'),
- button = renderPlexButton(true),
- options = [], length = $listItems.length - 1;
-
- if (!/&mode=simple/i.test(location.search))
- return location.search = location.search.replace(/&mode=\w+/, '&mode=simple');
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- $listItems.forEach(async(element, index, array) => {
- let option = await addInListItem(element);
-
- if(option)
- options.push(option);
-
- if(index == length)
- setTimeout(() => {
- if (!options.length)
- new Notification('error', 'Failed to process list');
- else
- squabblePlex(options, button);
- }, 50);
- });
-}
-
-let init = () => {
- if (((isMovie() || isShow()) && IMDbID) || isList()) {
- parseOptions().then(async() => {
- if (isMovie())
- await initPlexMovie();
- else if (isShow())
- await initPlexShow();
- else if(isList())
- await initList();
- });
- }
-}
-
-init();
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'imdb' }))();
diff --git a/src/sites/itunes/index.js b/src/sites/itunes/index.js
index 3bfe34e..4f9ec1b 100644
--- a/src/sites/itunes/index.js
+++ b/src/sites/itunes/index.js
@@ -1,45 +1,2 @@
-/* global findPlexMedia, parseOptions, modifyPlexButton */
-function isMovie() {
- return /^(\/\w+)?\/movie\//.test(window.location.pathname);
-}
-
-function isShow() {
- return /^(\/\w+)?\/tv-season\//.test(window.location.pathname);
-}
-
-let $$ = selector => document.querySelector(selector);
-
-async function initPlexThingy(type) {
- let title, year, image, button = renderPlexButton();
-
- if (!button || !type)
- return /* Fatal Error: Fail Silently */;
-
- if(type == 'movie') {
- let $title = $$('[class*="movie-header__title"]'),
- $year = $$('[datetime]'),
- $image = $$('[class*="product"] ~ * picture img');
-
- title = $title.textContent;
- year = +$year.textContent;
- image = ($image || {}).src;
- } else {
- let meta = [$$('h1[itemprop="name"], h1'), $$('.release-date > *:last-child'), $$('[class*="product"] ~ * picture img')];
-
- title = meta[0].textContent.replace(/\s*\((\d+)\)\s*/, '').trim();
- year = meta[1].textContent.replace(/[^]*(\d{4})[^]*?$/g, '$1').trim();
- image = meta[2].src;
- }
-
- let Db = await getIDs({ title, year, type }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = Db.title;
- year = Db.year;
-
- findPlexMedia({ type, title, year, image, button, IMDbID, TMDbID, TVDbID });
-}
-
-parseOptions().then(async() => await initPlexThingy(isMovie()? 'movie': isShow()? 'tv': null));
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'itunes' }))();
diff --git a/src/sites/justwatch/index.css b/src/sites/justwatch/index.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/sites/justwatch/index.js b/src/sites/justwatch/index.js
new file mode 100644
index 0000000..b5dff44
--- /dev/null
+++ b/src/sites/justwatch/index.js
@@ -0,0 +1,2 @@
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'justwatch' }))();
diff --git a/src/sites/layout.js b/src/sites/layout.js
deleted file mode 100644
index ea23844..0000000
--- a/src/sites/layout.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/* Your file should look something similar to this */
-/* required: function init */
-function init( type ) {
- if ( PageReady() )
- startWebtoPlex( type );
- else
- setTimeout(init, 1000);
-}
-
-function PageReady() {
- // should return a boolen/object to indicate the page is finished loading
- return document.readyState == 'complete';
-}
-
-async function startWebtoPlex(type) {
- let button = renderButton();
- if (!button)
- return /* Silent error */;
-
- let title = document.querySelector('#title').textContent,
- year = document.querySelector('#year').textContent,
- image = document.querySelector('#poster').textContent;
-
- let Db = await getIDs({ title, year, type }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- findPlexMedia({ title, year, image, button, IMDbID, TMDbID, TVDbID, type });
-}
-
-parseOptions().then(() => {
- init( location.pathname.startsWith('/movie')? 'movie': 'show' )
-});
diff --git a/src/sites/letterboxd/index.js b/src/sites/letterboxd/index.js
index 60d719a..80cd852 100644
--- a/src/sites/letterboxd/index.js
+++ b/src/sites/letterboxd/index.js
@@ -1,99 +1,2 @@
-/* global wait, modifyPlexButton, parseOptions, findPlexMedia */
-function isList() {
- return /\/list\//i.test(window.location.pathname);
-}
-
-let START = +(new Date);
-
-function init() {
- if(/\/(film|list)\//i.test(window.location.pathname))
- wait(
- () => (isList()? +(new Date) - START > 500: document.querySelector('.js-watch-panel')),
- () => ((isList()? initList: initPlexThingy)())
- );
-}
-
-function initPlexThingy() {
- let button = renderPlexButton();
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- let $title = document.querySelector('.headline-1[itemprop="name"]'),
- $date = document.querySelector('small[itemprop="datePublished"]'),
- $image = document.querySelector('.image');
-
- if (!$title || !$date)
- return modifyPlexButton(
- button,
- 'error',
- 'Could not extract title or year from Letterboxd'
- );
-
- let title = $title.textContent.trim(),
- year = $date.textContent.trim(),
- image = ($image || {}).src,
- IMDbID = getIMDbID();
-
- findPlexMedia({ title, year, button, type: 'movie', IMDbID });
-}
-
-function getIMDbID() {
- let $link = document.querySelector(
- '.track-event[href*="imdb.com/title/tt"]'
- );
- if ($link) {
- let link = $link.href.replace(/^.*imdb\.com\/title\//, '');
- return link.replace(/\/(?:maindetails\/?)?$/, '');
- }
-}
-
-async function addInListItem(element) {
- let $title = element.querySelector('.frame-title'),
- $image = element.querySelector('img');
-
- if (!$title)
- return;
-
- let title = $title.textContent.replace(/\((\d+)\)/, '').trim(),
- year = +RegExp.$1,
- image = $image.src,
- type = 'movie';
-
- let Db = await getIDs({ type, title, year }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = title || Db.title;
- year = year || Db.year;
-
- return { type, title, year, image, IMDbID, TMDbID, TVDbID };
-}
-
-function initList() {
- let $listItems = document.querySelectorAll('.poster-list .poster-container'),
- button = renderPlexButton(true),
- options = [], length = $listItems.length - 1;
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- $listItems.forEach(async(element, index, array) => {
- let option = await addInListItem(element);
-
- if(option)
- options.push(option);
-
- if(index == length)
- setTimeout(() => {
- terminal.log(options)
- if (!options.length)
- new Notification('error', 'Failed to process list');
- else
- squabblePlex(options, button);
- }, 50);
- });
-}
-
-parseOptions().then(init);
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'letterboxd' }))();
diff --git a/src/sites/metacritic/index.js b/src/sites/metacritic/index.js
index 07eb064..be3e7c5 100644
--- a/src/sites/metacritic/index.js
+++ b/src/sites/metacritic/index.js
@@ -1,62 +1,2 @@
-/* global wait, modifyPlexButton, parseOptions, findPlexMedia */
-function init() {
- wait(
- () => document.readyState === 'complete',
- () => initPlexThingy(isMovie()? 'movie': isShow()? 'tv': null) || isList()? initList(): null
- );
-}
-
-function isMovie() {
- return /^\/movie\//i.test(window.location.pathname);
-}
-
-function isShow() {
- return /^\/tv\//i.test(window.location.pathname);
-}
-
-function isList() {
- return /(^\/list\/)/i.test(window.location.pathname);
-}
-
-async function initPlexThingy(type) {
- let button = renderPlexButton();
-
- if (!button || !type)
- return /* Fatal Error: Fail Silently */;
-
- let $title = document.querySelector('.product_page_title > *, .product_title'),
- $date = document.querySelector('.product_page_title > .release_year, .product_data .release_data'),
- $image = document.querySelector('.summary_img');
-
- if (!$title || !$date)
- return console.log('failed'), modifyPlexButton(
- button,
- 'error',
- `Could not extract ${ !$title? 'title': 'year' } from Metacritic`
- );
-
- let title = $title.textContent.replace(/\s+/g, ' ').trim(),
- year = $date.textContent.replace(/\s+/g, ' ').replace(/.*(\d{4}).*$/, '$1').trim(),
- image = ($image || {}).src;
-
- let Db = await getIDs({ title, year, type }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = Db.title;
- year = Db.year;
-
- type = type === 'tv'? 'show': type;
-
- findPlexMedia({ title, year, button, type, IMDbID, TMDbID, TVDbID });
-}
-
-async function initList() {
- /* Not implemented... Metacritic has too much sh*t loading to even try to open a console */
- /* Targeted for v5/v6 */
-}
-
-parseOptions().then(() => {
- init();
-});
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'metacritic' }))();
diff --git a/src/sites/moviemeter/index.css b/src/sites/moviemeter/index.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/sites/moviemeter/index.js b/src/sites/moviemeter/index.js
new file mode 100644
index 0000000..4bf6acf
--- /dev/null
+++ b/src/sites/moviemeter/index.js
@@ -0,0 +1,2 @@
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'moviemeter' }))();
diff --git a/src/sites/movieo/index.js b/src/sites/movieo/index.js
index fd7977c..7738986 100644
--- a/src/sites/movieo/index.js
+++ b/src/sites/movieo/index.js
@@ -1,128 +1,2 @@
-/* global parseOptions, modifyPlexButton, findPlexMedia */
-function isMoviePage() {
- let path = window.location.pathname;
-
- if (!path.startsWith('/movies/'))
- return false;
-
- // An example movie page: /movies/juno-hpsgt (can also have trailing slash!)
- // Example non-movie page: /movies/watchlist/gbdx
- // So if there is one slash extra (trailing slash not included), it's not a movie page.
- let jup = path.replace('/movies/', '').slice(0, -1);
- return !jup.includes('/');
-}
-
-function isList() {
- let path = window.location.pathname;
-
- return /\/(black|seen|watch)?lists?\//i.test(path);
-}
-
-function isPageReady() {
- return !!document.querySelector('.share-box, .zopim');
-}
-
-function init() {
- if (isMoviePage()) {
- if (isPageReady()) {
- initPlexThingy();
- } else {
- // This almost never happens, but sometimes the page is too slow so we need to wait a bit.
- // I could reproduce this by clicking on a movie in the movie watchlist,
- // going back in history and then going forward in history.
- setTimeout(init, 1000);
- }
- } else if (isList()) {
- if (isPageReady()) {
- initList();
- } else {
- setTimeout(init, 1000);
- }
- }
-}
-
-parseOptions().then(() => {
- window.addEventListener('popstate', init);
- window.addEventListener('pushstate-changed', init);
- init();
-});
-
-function initPlexThingy() {
- let button = renderPlexButton();
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- let $title = document.querySelector('#doc_title'),
- $date = document.querySelector('meta[itemprop="datePublished"]'),
- $image = document.querySelector('img.poster');
-
- if (!$title || !$date)
- return modifyPlexButton(
- button,
- 'error',
- `Could not extract ${ !$title? 'title': 'year' } from Movieo`
- );
-
- let title = $title.dataset.title.trim(),
- year = $date.content.slice(0, 4),
- image = ($image || {}).src,
- IMDbID = getIMDbID();
-
- findPlexMedia({ title, year, button, image, type: 'movie', IMDbID });
-}
-
-function getIMDbID() {
- let $link = document.querySelector(
- '.tt-parent[href*="imdb.com/title/tt"]'
- );
- if ($link)
- return $link.href.replace(/^[^]*\/title\//, '');
-}
-
-async function addInListItem(element) {
- let $title = element.querySelector('.title'),
- $image = element.querySelector('.poster-cont');
-
- if (!$title)
- return;
-
- let title = $title.textContent.trim().replace(/\s*\((\d{4})\)/, ''),
- year = +RegExp.$1,
- image = $image.getAttribute('data-src'),
- type = 'movie';
-
- let Db = await getIDs({ type, title, year }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = title || Db.title;
- year = year || Db.year;
-
- return { type, title, year, image, IMDbID, TMDbID, TVDbID };
-}
-
-function initList() {
- let $listItems = document.querySelectorAll('[data-title][data-id]'),
- button = renderPlexButton(true),
- options = [], length = $listItems.length - 1;
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- $listItems.forEach(async(element, index, array) => {
- let option = await addInListItem(element);
-
- if(option)
- options.push(option);
-
- if(index == length)
- setTimeout(() => {
- if (!options.length)
- new Notification('error', 'Failed to process list');
- else
- squabblePlex(options, button);
- }, 50);
- });
-}
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'movieo' }))();
diff --git a/src/sites/netflix/index.js b/src/sites/netflix/index.js
index 72bded2..5bc66c2 100644
--- a/src/sites/netflix/index.js
+++ b/src/sites/netflix/index.js
@@ -1,42 +1,2 @@
-/* global findPlexMedia, parseOptions, modifyPlexButton */
-function isReady() {
- let element = $$('[class$="__time"]');
-
- return document.readyState == 'complete' && element && !/^([0:]+|null|undefined)?$/.test(element.textContent);
-}
-
-function isMovie() {
- return !isShow();
-}
-
-function isShow() {
- return $$('[class*="playerEpisodes"]');
-}
-
-let $$ = selector => document.querySelector(selector);
-
-async function initPlexThingy(type) {
- let button = renderPlexButton();
-
- if (!button || !type)
- return /* Fatal Error: Fail Silently */;
-
- let $title = $$('.video-title h4'),
- title = $title.innerText.replace(/^\s+|\s+$/g, '').toCaps() || sessionStorage.getItem(`last-${type}-title`),
- year = 0,
- Db = await getIDs({ title, year, type }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = Db.title;
- year = Db.year;
-
- sessionStorage.setItem(`last-${type}-title`, title);
-
- findPlexMedia({ type, title, year, button, IMDbID, TMDbID, TVDbID });
-}
-
-(window.onlocationchange = () =>
- wait(isReady, () => parseOptions().then(async() => await initPlexThingy(isMovie()? 'movie': isShow()? 'tv': null)))
-)();
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'netflix' }))();
diff --git a/src/sites/rottentomatoes/index.js b/src/sites/rottentomatoes/index.js
index 28be81e..6fb798c 100644
--- a/src/sites/rottentomatoes/index.js
+++ b/src/sites/rottentomatoes/index.js
@@ -1,104 +1,2 @@
-/* global wait, modifyPlexButton, parseOptions, findPlexMedia */
-function init() {
- wait(
- () => (isList()? document.readyState === 'complete': document.querySelector('#reviews')),
- () => (initPlexThingy(isMovie()? 'movie': isShow()? 'show': null) || isList()? initList(): null)
- );
-}
-
-function isMovie() {
- return /^\/m/.test(window.location.pathname);
-}
-
-function isShow() {
- return /^\/t/.test(window.location.pathname);
-}
-
-function isList() {
- return /^\/browse\//i.test(window.location.pathname);
-}
-
-async function initPlexThingy(type) {
- let button = renderPlexButton();
-
- if (!button || !type)
- return /* Fatal Error: Fail Silently */;
-
- let $title = document.querySelector('.playButton + .title, [itemprop="name"]'),
- $year = (type == 'movie'? $title.nextElementSibling: $title.querySelector('.subtle')),
- $image = document.querySelector('[class*="posterimage" i]');
-
- if (!$title || !$year)
- return modifyPlexButton(
- button,
- 'error',
- 'Could not extract title or year from Rotten Tomatoes'
- );
-
- let title = $title.textContent.trim().replace(/(.+)\:[^]*$/, type == 'movie'? '$&': '$1'),
- year = $year.textContent.replace(/\D+/g, '').trim(),
- image = ($image || {}).srcset,
- Db = await getIDs({ title, year, type }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- if (image)
- image = image.replace(/([^\s]+)[^]*/, '$1');
-
- findPlexMedia({ title, year, image, button, type, IMDbID, TMDbID, TVDbID });
-}
-
-async function addInListItem(element) {
- let $title = element.querySelector('.movieTitle'),
- $image = element.querySelector('.poster'),
- $type = element.querySelector('[href^="/m/"], [href^="/t/"]');
-
- if (!$title)
- return;
-
- let title = $title.textContent.trim(),
- image = $image.src,
- type = /\/([mt])\//i.test($type.href)? RegExp.$1 == 'm'? 'movie': 'show': null;
-
- if(!type)
- return {};
- if(type == 'show')
- title = title.replace(/\s*\:\s*seasons?\s+\d+\s*/i, '');
-
- let Db = await getIDs({ type, title }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb,
- year = Db.year;
-
- title = title || Db.title;
-
- return { type, title, year, image, IMDbID, TMDbID, TVDbID };
-}
-
-function initList() {
- let $listItems = document.querySelectorAll('.mb-movie'),
- button = renderPlexButton(true),
- options = [], length = $listItems.length - 1;
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- $listItems.forEach(async(element, index, array) => {
- let option = await addInListItem(element);
-
- if(option)
- options.push(option);
-
- if(index == length)
- setTimeout(() => {
- if (!options.length)
- new Notification('error', 'Failed to process list');
- else
- squabblePlex(options, button);
- }, 50);
- });
-}
-
-parseOptions().then(init);
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'rottentomatoes' }))();
diff --git a/src/sites/tmdb/index.js b/src/sites/tmdb/index.js
index 9ca127a..6989aed 100644
--- a/src/sites/tmdb/index.js
+++ b/src/sites/tmdb/index.js
@@ -1,125 +1,2 @@
-/* global wait, modifyPlexButton, parseOptions, findPlexMedia */
-function init() {
- wait(
- () => document.readyState === 'complete',
- () => (initPlexThingy(isMovie()? 'movie': isShow()? 'tv': null) || isList()? initList(): null)
- );
-}
-
-function isMovie() {
- return /\/movie\/\d+/i.test(window.location.pathname);
-}
-
-function isShow() {
- return /\/tv\/\d+/i.test(window.location.pathname);
-}
-
-function isList() {
- return /(^\/discover\/|\/(movie|tv)\/([^\d]+|\B))/i.test(window.location.pathname);
-}
-
-async function initPlexThingy(type) {
- let button = renderPlexButton();
-
- if (!button || !type)
- return /* Fatal Error: Fail Silently */;
-
- let $title = document.querySelector('.title > span > *:not(.release_date)'),
- $date = document.querySelector('.title .release_date'),
- $image = document.querySelector('img.poster');
-
- if (!$title || !$date)
- return modifyPlexButton(
- button,
- 'error',
- `Could not extract ${ !$title? 'title': 'year' } from TheMovieDb`
- );
-
- let title = $title.textContent.trim(),
- year = $date.textContent.trim(),
- image = ($image || {}).src,
- apid = window.location.pathname.replace(/\/(?:movie|tv)\/(\d+).*/, '$1');
-
- type = type == 'movie'? 'movie': 'show';
-
- let Db = await getIDs({ title, year, TMDbID: apid, APIType: type, APIID: apid }),
- IMDbID = Db.imdb,
- TMDbID = +Db.tmdb,
- TVDbID = +Db.tvdb;
-
- title = Db.title;
- year = Db.year;
-
- let savename = title.toLowerCase(),
- cached = await load(`${savename}.tmdb`);
-
- if(!cached) {
- save(`${savename} (${year}).tmdb`, { type, title, year, imdb: IMDbID, tmdb: TMDbID, tvdb: TVDbID });
- save(`${savename}.tmdb`, +year);
- terminal.log(`Saved as "${savename} (${year}).tmdb"`);
- }
-
- findPlexMedia({ title, year, image, button, type, IMDbID, TMDbID, TVDbID });
-}
-
-async function addInListItem(element) {
- let $title = element.querySelector('.title'),
- $date = element.querySelector('.title + *'),
- $image = element.querySelector('.poster'),
- $type = $title.id.split('_');
-
- if (!$title || !$date)
- return;
-
- let title = $title.textContent.trim(),
- year = $date.textContent,
- image = $image.src,
- type = ($type[0] == 'movie'? 'movie': 'show'),
- TMDbID = +$type[1];
-
- let Db = await getIDs({ type, title, year, TMDbID, APIType: type, APIID: TMDbID }),
- IMDbID = Db.imdb,
- TVDbID = +Db.tvdb;
-
- title = title || Db.title;
- year = +year || Db.year;
-
- let savename = title.toLowerCase(),
- cached = await load(`${savename}.tmdb`);
-
- if(!cached) {
- save(`${savename} (${year}).tmdb`, { type, title, year, imdb: IMDbID, tmdb: TMDbID, tvdb: TVDbID });
- save(`${savename}.tmdb`, +year);
- terminal.log(`Saved as "${savename} (${year}).tmdb"`);
- }
-
- return { type, title, year, image, IMDbID, TMDbID, TVDbID };
-}
-
-function initList() {
- let $listItems = document.querySelectorAll('.item.card'),
- button = renderPlexButton(true), /* see if a button was already created */
- options = [], length = $listItems.length - 1;
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- $listItems.forEach(async(element, index, array) => {
- let option = await addInListItem(element);
-
- if(option)
- options.push(option);
-
- if(index == length)
- setTimeout(() => {
- if (!options.length)
- new Notification('error', 'Failed to process list');
- else
- squabblePlex(options, button);
- }, 50);
- });
-}
-
-parseOptions().then(() => {
- init();
-});
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'tmdb' }))();
diff --git a/src/sites/trakt/index.js b/src/sites/trakt/index.js
index fa16184..4e035f8 100644
--- a/src/sites/trakt/index.js
+++ b/src/sites/trakt/index.js
@@ -1,193 +1,2 @@
-/* global wait, modifyPlexButton, parseOptions, findPlexMedia */
-let $$ = (element, all = false) => (element = document.querySelectorAll(element)).length > 1 && all? element: element[0];
-
-function isMoviePage() {
- return !isDash() && window.location.pathname.startsWith('/movies/');
-}
-
-function isShowPage() {
- return !isDash() && window.location.pathname.startsWith('/shows/');
-}
-
-function isDash() {
- return /^\/(dashboard|calendars|people|search|(?:movies|shows)\/(?:trending|popular|watched|collected|anticipated|boxoffice)|$)/i.test(window.location.pathname);
-}
-
-function getIMDbID() {
- let $link = $$(
- // HTTPS and HTTP
- '[href*="imdb.com/title/tt"]'
- );
-
- if ($link)
- return $link.href.replace(/^.*?imdb\.com\/.+\b(tt\d+)\b/, '$1');
-}
-
-function getTVDbID() {
- let $link = $$(
- // HTTPS and HTTP
- '[href*="thetvdb.com/"]'
- );
-
- if ($link)
- return $link.href.replace(/^.*?thetvdb.com\/.+(?:(?:series\/?(?:\?id=)?)(\d+)\b).*?$/, '$1');
-}
-
-function getTMDbID() {
- let $link = $$(
- // HTTPS and HTTP
- '[href*="themoviedb.org/"]'
- );
-
- if ($link)
- return $link.href.replace(/^.*?themoviedb.org\/(?:movie|tv|shows?|series)\/(\d+).*?$/, '$1');
-}
-
-function init() {
- if (isMoviePage() || isShowPage() || isDash()) {
- wait(
- () => ($$('#info-wrapper ul.external, .format-date') || document.readyState == 'complete'),
- () => (isDash()? initDash(): initPlexThingy(isMoviePage() ? 'movie' : 'show'))
- );
- }
-}
-
-async function initPlexThingy(type) {
- let button = renderPlexButton();
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- let $title = $$('.mobile-title'),
- $year = $$('.mobile-title .year'),
- $image = $$('.poster img.real[alt="poster" i]');
-
- if (!$title || !$year)
- return modifyPlexButton(button, 'error', `Could not extract ${ !$title? 'title': 'year' } from Trakt`);
-
- let title = $title.textContent.replace(/(.+)(\d{4}).*?$/, '$1').replace(/\s*\:\s*Season.*$/i, '').trim(),
- year = +(RegExp.$2 || $year.textContent).trim(),
- image = ($image || {}).src,
- IMDbID = getIMDbID(),
- TMDbID = getTMDbID(),
- TVDbID = getTVDbID();
-
- if(!IMDbID && !TMDbID && !TVDbID) {
- let Db = await getIDs({ title, year, type, IMDbID, TMDbID, TVDbID });
-
- IMDbID = IMDbID || Db.imdb,
- TMDbID = TMDbID || Db.tmdb,
- TVDbID = TVDbID || Db.tvdb;
- title = Db.title;
- year = Db.year;
- }
-
- let o = (type == 'movie')? { im: IMDbID, tm: +TMDbID }: { im: IMDbID, tm: +TMDbID, tv: +TVDbID };
-
- /* use Trakt as a caching service when applicable */
- /* yes, Trakt asks not to scrape their site, and we're not saving this to a server, so I'm gonna say OK */
- let savename = title.toLowerCase(),
- cached = {};
-
- if(type == 'movie') {
- cached.tmdb = await load(`${savename}.tmdb`);
- cached.imdb = await load(`${savename}.imdb`);
-
- if(!cached.tmdb) {
- save(`${title} (${year}).tmdb`, { title, year, imdb: o.im, tmdb: o.tm });
- save(`${title}.tmdb`, year);
- }
-
- if(!cached.imdb) {
- save(`${title} (${year}).imdb`, { title, year, imdb: o.im });
- save(`${title}.imdb`, year);
- }
- } else {
- cached.tvdb = await load(`${savename}.tvdb`);
- cached.tmdb = await load(`${savename}.tmdb`);
- cached.imdb = await load(`${savename}.imdb`);
-
- if(!cached.tvdb) {
- save(`${title} (${year}).tvdb`, { title, year, tvdb: o.tv, tmdb: o.tm, imdb: o.im });
- save(`${title}.tvdb`, year);
- }
-
- if(!cached.tmdb) {
- save(`${title} (${year}).tmdb`, { title, year, imdb: o.im, tmdb: o.tm });
- save(`${title}.tmdb`, year);
- }
-
- if(!cached.imdb) {
- save(`${title} (${year}).imdb`, { title, year, imdb: o.im });
- save(`${title}.imdb`, year);
- }
- }
-
- findPlexMedia({ type, title, year, image, button, IMDbID, TMDbID, TVDbID });
-}
-
-async function initDash() {
- let buttons = $$(".btn-watch-now, .quick-icons .watch-now", true);
-
- buttons.forEach((element, index, array) => {
- element.preclick = element.preclick || element.onclick || (() => {});
-
- element.onclick = async(event, rerun) => {
- event.path.filter((v, i, a) => !!~[].slice.call(buttons).indexOf(v)).forEach((e, i, a) => e.preclick(event));
-
- let ready = /^[^]+$/.test($$('#watch-now-content').innerText);
-
- if(!ready || !rerun)
- return setTimeout( () => element.onclick(event, true), 5 );
-
- let title = $$("#watch-now-content h3").innerText.replace(/^\s*where\s+to\s+watch\s*/i, ''),
- image = $$('.poster img.real[alt="poster" i]'),
- type = 'show',
- year = YEAR,
- button = $$(".w2p-channel");
-
- if(title == '')
- title = $$("#watch-now-content h1").innerText.replace(/^\s*(.+)\s+(\d+)\s*$/, '$1'),
- year = RegExp.$2,
- type = 'movie';
-
- title = title.toCaps();
-
- if(!button) {
- $$("#watch-now-content .streaming-links").innerHTML +=
-`
-ondemand
-
-`;
- wait(() => button = $$(".w2p-channel"), () => {});
- }
-
- let Db = await getIDs({ title, year, type }),
- IMDbID = Db.imdb,
- TMDbID = +Db.tmdb,
- TVDbID = +Db.tvdb;
-
- title = Db.title;
- year = Db.year;
-
- findPlexMedia({ type, title, year, button, IMDbID, TMDbID, TVDbID, txt: 'title', hov: 'null' });
- };
- });
-}
-
-parseOptions().then(() => {
- window.addEventListener('popstate', init);
- window.addEventListener('pushstate-changed', init);
- init();
-});
-
-window.onlocationchange = (event) => {
- init();
-};
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'trakt' }))();
diff --git a/src/sites/tubi/index.css b/src/sites/tubi/index.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/sites/tubi/index.js b/src/sites/tubi/index.js
new file mode 100644
index 0000000..fbe9b35
--- /dev/null
+++ b/src/sites/tubi/index.js
@@ -0,0 +1,2 @@
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'tubi' }))();
diff --git a/src/sites/tvdb/index.js b/src/sites/tvdb/index.js
index 447b099..8486015 100644
--- a/src/sites/tvdb/index.js
+++ b/src/sites/tvdb/index.js
@@ -1,75 +1,2 @@
-/* global findPlexMedia, parseOptions, modifyPlexButton */
-function isShowPage() {
- // An example movie page: /series/gravity-falls
- return window.location.pathname.startsWith('/series/');
-}
-
-function isShowPageReady() {
- return !!document.querySelector('#series_basic_info');
-}
-
-function init() {
- if (isShowPage())
- if (isShowPageReady())
- 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('#series_title'),
- $image = document.querySelector('img[src*="/posters/"]');
-
- if (!$title)
- return modifyPlexButton(
- button,
- 'error',
- `Could not extract title from TheTVDb`
- ),
- null;
-
- let title = $title.innerText.trim(),
- year,
- image = ($image || {}).src,
- d = '', o = {},
- Db = document.querySelector('#series_basic_info')
- .textContent
- .replace(/^\s+|\s+$/g, '')
- .replace(/^\s+$/gm, d)
- .replace(/^\s+(\S)/gm, '$1')
- .split(RegExp(`\\n*${d}\\n*`))
- .forEach(value => {
- value = value.split(/\n+/, 2);
-
- let n = value[0], v = value[1];
-
- n = n.replace(/^([\w\s]+).*$/, '$1').replace(/\s+/g, '_').toLowerCase();
-
- o[n] = /,/.test(v)? v.split(/\s*,\s*/): v;
- });
-
- year = +(((o.first_aired || YEAR) + "").slice(0, 4));
-
- let savename = title.toLowerCase(),
- cached = await load(`${savename}.tvdb`);
-
- if(!cached) {
- save(`${savename} (${year}).tvdb`, { title, year, tvdb: +o.thetvdb, imdb: o.imdb });
- save(`${savename}.tvdb`, +year);
- terminal.log(`Saved as "${savename} (${year}).tvdb"`);
- }
-
- findPlexMedia({ title, year, image, button, type: 'show', IMDbID: o.imdb, TVDbID: +o.thetvdb });
-}
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'tvdb' }))();
diff --git a/src/sites/tvmaze/index.js b/src/sites/tvmaze/index.js
index 084580d..d704d5c 100644
--- a/src/sites/tvmaze/index.js
+++ b/src/sites/tvmaze/index.js
@@ -1,57 +1,2 @@
-/* global findPlexMedia, parseOptions, modifyPlexButton */
-function isShowPage() {
- // An example movie page: /shows/2757/colony
- return window.location.pathname.startsWith('/shows/');
-}
-
-function isShowPageReady() {
- return !!document.querySelector('#general-info-panel .rateit');
-}
-
-async function init() {
- if (isShowPage())
- if (isShowPageReady())
- await 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(async() => {
- window.addEventListener('popstate', init);
- window.addEventListener('pushstate-changed', init);
- await init();
-});
-
-async function initPlexThingy() {
- let button = renderPlexButton();
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- let $title = document.querySelector('header.columns > h1'),
- $date = document.querySelector('#year'),
- $image = document.querySelector('figure img'),
- $apid = window.location.pathname.replace(/\/shows\/(\d+).*/, '$1');
-
- if (!$title || !$date)
- return modifyPlexButton(
- button,
- 'error',
- `Could not extract ${ !$title? 'title': 'year' } from TV Maze`
- ),
- null;
-
- let title = $title.innerText.trim(),
- year = $date.innerText.replace(/\((\d+).+\)/, '$1'),
- image = ($image || {}).src,
- Db = await getIDs({ title, year, type: 'tv', APIID: $apid }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = Db.title;
- year = Db.year;
-
- findPlexMedia({ title, year, button, type: 'tv', IMDbID, TMDbID, TVDbID });
-}
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'tvmaze' }))();
diff --git a/src/sites/verizon/index.js b/src/sites/verizon/index.js
index a922c25..b7294b1 100644
--- a/src/sites/verizon/index.js
+++ b/src/sites/verizon/index.js
@@ -1,70 +1,2 @@
-/* global wait, modifyPlexButton, parseOptions, findPlexMedia */
-function isMoviePage() {
- return /\bmovies?\b/i.test(window.location.pathname);
-}
-
-function isShowPage() {
- return /\bseries\b/i.test(window.location.pathname);
-}
-
-function isOnDemand() {
- return /ondemand/i.test(window.location.pathname);
-}
-
-function init() {
- if (isMoviePage() || isShowPage()) {
- wait(
- () => document.querySelector('.container .btn-with-play, .moredetails, .more-like'),
- () => initPlexThingy(isMoviePage() ? 'movie' : 'tv')
- );
- }
-}
-
-async function initPlexThingy(type) {
- let button = renderPlexButton();
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- let $title, $year, $image = document.querySelector('.cover img');
-
- if(isOnDemand()) {
- if(isMoviePage()) {
- $title = document.querySelector('.detail *');
- $year = document.querySelector('.rating *');
- } else {
- $title = {textContent: window.location.pathname.replace(/\/ondemand\/tvshows?\/([^\/]+?)\/.*/i)};
- $year = document.querySelector('#showDetails > * > *:nth-child(4) *:last-child');
-
- $title.textContent = decodeURL($title.textContent).toCpas();
- }
- } else {
- $title = document.querySelector('.copy > .title');
- $year = (type === 'movie')?
- document.querySelector('.copy > .details'):
- document.querySelector('.summary ~ .title ~ *');
- }
-
- if (!$title || !$year)
- return modifyPlexButton(button, 'error', `Could not extract ${ !$title? 'title': 'year' } from Verizon`);
-
- let title = $title.textContent.trim(),
- year = $year.textContent.slice(0, 4).trim(),
- image = ($image || {}).src;
-
- let Db = await getIDs({ title, year, type }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = Db.title;
- year = Db.year;
-
- findPlexMedia({ type, title, year, image, button, IMDbID, TMDbID, TVDbID });
-}
-
-parseOptions().then(() => {
- window.addEventListener('popstate', init);
- window.addEventListener('pushstate-changed', init);
- init();
-});
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'verizon' }))();
diff --git a/src/sites/vrv/index.js b/src/sites/vrv/index.js
index 0901970..c4a2052 100644
--- a/src/sites/vrv/index.js
+++ b/src/sites/vrv/index.js
@@ -1,119 +1,2 @@
-/* global findPlexMedia, parseOptions, modifyPlexButton */
-function isShow() {
- // An example movie page: /series/GR75MN7ZY/Deep-Space-69-Unrated
- return /^\/(?:series)\//.test(window.location.pathname) || (/^\/(?:watch)\//.test(window.location.pathname) && document.querySelector('.content .series'));
-}
-
-function isMovie() {
- return /^\/(?:watch)\//.test(window.location.pathname) && !document.querySelector('.content .series');
-}
-
-function isPageReady() {
- let img = document.querySelector('.h-thumbnail > img'),
- pre = document.querySelector('#content .content .card');
- return isList()? pre && pre.textContent: img && img.src;
-}
-
-function isList() {
- return /\/(watchlist)\b/i.test(window.location.pathname);
-}
-
-function init() {
- if (isPageReady()) {
- if (isShow())
- initPlexThingy('show');
- else if (isMovie())
- initPlexThingy('movie');
- else if(isList())
- initList();
- } 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);
- (window.onlocationchange = init)();
-});
-
-async function initPlexThingy(type) {
- let button = renderPlexButton();
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- let $title = document.querySelector('.series, [class*="video"] .title, [class*="series"] .title'),
- $year = document.querySelector('.additional-information-item'),
- $image = document.querySelector('[class*="poster"][class*="wrapper"] img');
-
- if (!$title)
- return modifyPlexButton(
- button,
- 'error',
- `Could not extract title from VRV`
- ),
- null;
-
- let title = $title.innerText.replace(/(unrated|mature|tv-?\d{1,2})\s*$/i, '').trim(),
- year = $year? $year.textContent.replace(/.+(\d{4}).*/, '$1').trim(): 0,
- image = ($image || {}).src,
- Db = await getIDs({ type, title, year }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = title || Db.title;
- year = year || Db.year;
-
- findPlexMedia({ type, title, year, image, button, IMDbID, TMDbID, TVDbID });
-}
-
-async function addInListItem(element) {
- let $title = element.querySelector('.info > *'),
- $image = element.querySelector('.poster-image img'),
- $type = element.querySelector('.info [class*="series"], .info [class*="movie"]');
-
- if (!$title || !$type)
- return;
-
- let title = $title.textContent.trim(),
- image = $image.src,
- type = $type.getAttribute('class').replace(/[^]*(movie|series)[^]*/, '$1'),
- year;
-
- let Db = await getIDs({ type, title }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = +Db.tvdb;
-
- title = title || Db.title;
- year = Db.year;
-
- return { type, title, year, image, IMDbID, TMDbID, TVDbID };
-}
-
-function initList() {
- let $listItems = document.querySelectorAll('#content .content .card'),
- button = renderPlexButton(),
- options = [], length = $listItems.length - 1;
-
- if (!button)
- return /* Fatal Error: Fail Silently */;
-
- $listItems.forEach(async(element, index, array) => {
- let option = await addInListItem(element);
-
- if(option)
- options.push(option);
-
- if(index == length)
- setTimeout(() => {
- if (!options.length)
- new Notification('error', 'Failed to process list');
- else
- squabblePlex(options, button);
- }, 50);
- });
-}
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'vrv' }))();
diff --git a/src/sites/vudu/index.js b/src/sites/vudu/index.js
index bf346af..303f5ba 100644
--- a/src/sites/vudu/index.js
+++ b/src/sites/vudu/index.js
@@ -1,59 +1,2 @@
-/* global findPlexMedia, parseOptions, modifyPlexButton */
-function isMovie() {
- return !isShow();
-}
-
-function isShow() {
- return /(?:Season-\d+\/\d+)$/i.test(window.location.pathname);
-}
-
-function isPageReady() {
- return !!document.querySelector('img[src*="poster" i]');
-}
-
-async function init() {
- if (isPageReady())
- await initPlexThingy(isMovie()? 'movie': isShow()? 'tv': null);
- else
- // This almost never happens, but sometimes the page is too slow so we need to wait a bit.
- setTimeout(init, 1000);
-}
-
-async function initPlexThingy(type) {
- let button = renderPlexButton();
-
- if (!button || !type)
- return /* Fatal Error: Fail Silently */;
-
- let $title = document.querySelector('.head-big'),
- $date = document.querySelector('.container .row:first-child .row ~ * > .row span'),
- $image = document.querySelector('img[src*="poster" i]');
-
- if (!$title)
- return modifyPlexButton(
- button,
- 'error',
- `Could not extract title from Vudu`
- );
-
- let title = $title.textContent.replace(/\((\d{4})\)/, '').trim(),
- year = $date? $date.textContent.split(/\s*\|\s*/): RegExp.$1,
- image = ($image || {}).src;
-
- year = +year[year.length - 1].slice(0, 4);
- year |= 0;
-
- let Db = await getIDs({ title, year, type }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = Db.title;
- year = Db.year;
-
- findPlexMedia({ type, title, year, image, button, IMDbID, TMDbID, TVDbID });
-}
-
-if (isMovie() || isShow()) {
- parseOptions().then(async() => await (window.onlocationchange = init)());
-}
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'vudu' }))();
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..2793ee0
--- /dev/null
+++ b/src/sites/vumoo/index.js
@@ -0,0 +1,2 @@
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'vumoo' }))();
diff --git a/src/sites/webtoplex/index.css b/src/sites/webtoplex/index.css
new file mode 100644
index 0000000..e69de29
diff --git a/src/sites/webtoplex/index.js b/src/sites/webtoplex/index.js
new file mode 100644
index 0000000..291f462
--- /dev/null
+++ b/src/sites/webtoplex/index.js
@@ -0,0 +1,2 @@
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'webtoplex' }))();
diff --git a/src/sites/youtube/index.js b/src/sites/youtube/index.js
index 0e215b7..7512c79 100644
--- a/src/sites/youtube/index.js
+++ b/src/sites/youtube/index.js
@@ -1,54 +1,2 @@
-let $$ = selector => document.querySelector(selector);
-
-function isMovie(owner) {
- return /\byoutube movies\b/i.test(owner);
-}
-
-function isShow() {
- let __title__ = $$('.super-title');
-
- return __title__ && /\bs\d+\b.+\be\d+\b/i.test(__title__.textContent);
-}
-
-async function init() {
- let owner = $$('#owner-container').textContent.replace(/^\s+|\s+$/g, '');
-
- $$('.more-button').click(); // show the year and other information, fails otherwise
-
- if(isMovie(owner) || isShow())
- await initPlexThingy(isMovie(owner)? 'movie': isShow()? 'show': null);
-
- $$('.less-button').click(); // close the meta-information
-}
-
-async function initPlexThingy(type) {
- let button = renderPlexButton();
- if(!button || !type)
- return /* Fail silently */;
-
- let $title = (type == 'movie'? $$('.title'): $$('#owner-container')),
- $date = $$('#content ytd-expander');
-
- if(!$title || !$date)
- return modifyPlexButton(button, 'error', 'Could not extract title or year from YouTube');
-
- let title = $title.textContent.trim(),
- year = +$date.textContent.replace(/[^]*(?:release|air) date\s+(?:(?:\d+\/\d+\/)?(\d{2,4}))[^]*/i, ($0, $1, $$, $_) => +$1 < 1000? 2000 + +$1: $1);
-
- let Db = await getIDs({ title, year, type }),
- IMDbID = Db.imdb,
- TMDbID = Db.tmdb,
- TVDbID = Db.tvdb;
-
- title = Db.title;
- year = Db.year;
-
- findPlexMedia({ type, title, year, button, IMDbID, TMDbID, TVDbID });
-}
-
-parseOptions()
- .then(() => {
- window.addEventListener('popstate', init);
- window.addEventListener('pushstate-changed', init);
- wait(() => $$('#owner-container'), init)
- });
+/* global Update(type:string, details:object) */
+(init = () => Update('SCRIPT', { script: 'youtube' }))();
diff --git a/src/utils.js b/src/utils.js
index a276266..b9590be 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -1,1694 +1,2124 @@
/* eslint-disable no-unused-vars */
-/* global config */
-function wait(on, then) {
- if (on())
- then && then();
- else
- setTimeout(() => wait(on, then), 50);
-}
+/* global configuration, init, Update, "Helpers" */
+
+let configuration, init, Update;
+
+(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'),
+ };
-let NO_DEBUGGER = false;
-
-let date = (new Date),
- terminal =
- NO_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();
-
-let getURL = url => chrome.extension.getURL(url);
-
-let IMG_URL = {
- 'i16': getURL('img/16.png'),
- 'i48': getURL('img/48.png'),
- '_16': getURL('img/_16.png'),
- '_48': getURL('img/_48.png'),
- 'o16': getURL('img/o16.png'),
- 'o48': getURL('img/o48.png'),
- 'h16': getURL('img/hide.16.png'),
- 'h48': getURL('img/hide.48.png'),
- 'j16': getURL('img/show.16.png'),
- 'j48': getURL('img/show.48.png'),
- 'p16': getURL('img/plexit.16.png'),
- 'p48': getURL('img/plexit.48.png'),
- 'r16': getURL('img/reload.16.png'),
- 'r48': getURL('img/reload.48.png'),
- 'x16': getURL('img/close.16.png'),
- 'x48': getURL('img/close.48.png'),
- 's16': getURL('img/settings.16.png'),
- 's48': getURL('img/settings.48.png'),
- 'noi': getURL('img/noise.png'),
- 'nil': getURL('img/null.png'),
-};
+ // the storage - priority to sync
+ const UTILS_STORAGE = chrome.storage.sync || chrome.storage.local;
-// the custom "on location change" event
-let locationchangecallbacks = [];
+ async function load(name = '') {
+ if(!name)
+ return /* invalid name */;
-function watchlocationchange(subject) {
- watchlocationchange[subject] = watchlocationchange[subject] || location[subject];
+ name = 'Cache-Data/' + btoa(name.toLowerCase().replace(/\s+/g, ''));
- if (watchlocationchange[subject] != location[subject]) {
- watchlocationchange[subject] = location[subject];
+ return new Promise((resolve, reject) => {
+ function LOAD(DISK) {
+ let data = JSON.parse(DISK[name] || null);
- for(let index = 0, length = locationchangecallbacks.length, callback; index < length; index++) {
- callback = locationchangecallbacks[index];
+ return resolve(data);
+ }
- if(callback && typeof callback == 'function')
- callback(new Event('locationchange', { bubbles: true }));
- }
+ UTILS_STORAGE.get(null, DISK => {
+ if(chrome.runtime.lastError)
+ chrome.storage.local.get(null, LOAD);
+ else
+ LOAD(DISK);
+ });
+ });
}
-}
-Object.defineProperty(window, 'onlocationchange', {
- set: callback => locationchangecallbacks.push(callback)
-});
+ async function save(name = '', data) {
+ if(!name)
+ return /* invalid name */;
-setInterval(() => watchlocationchange('pathname'), 1000); // at least 1s is needed to properly fire the event ._.
+ name = 'Cache-Data/' + btoa(name.toLowerCase().replace(/\s+/g, ''));
+ data = JSON.stringify(data);
-// the storage
-const storage = chrome.storage.sync || chrome.storage.local;
+ // erase entries after 400-500 have been made
+ UTILS_STORAGE.get(null, items => {
+ let array = [], bytes = 0;
-async function load(name = '') {
- if(!name) return;
+ for(let item in items) {
+ let object = items[item];
- name = 'Cache-Data/' + btoa(name.toLowerCase().replace(/\s+/g, ''));
+ array.push(item);
+ bytes += (typeof object == 'string'? object.length * 8: typeof object == 'boolean'? 8: JSON.stringify(object).length * 8)|0;
+ }
- return new Promise((resolve, reject) => {
- function LOAD(DISK) {
- let data = JSON.parse(DISK[name] || null);
+ if((UTILS_STORAGE.MAX_ITEMS && array.length >= UTILS_STORAGE.MAX_ITEMS) || bytes >= UTILS_STORAGE.QUOTA_BYTES)
+ for(let item in items)
+ if(/^cache-data\//i.test(item))
+ UTILS_STORAGE.remove(item);
+ });
- return resolve(data);
- }
+ await UTILS_STORAGE.set({[name]: data}, () => data);
- storage.get(null, DISK => {
- if (chrome.runtime.lastError)
- chrome.storage.local.get(null, LOAD);
- else
- LOAD(DISK);
- });
- });
-}
+ return name;
+ }
-async function save(name = '', data) {
- if(!name) return;
+ async function remove(name) {
+ if(!name)
+ return /* invalid name */;
- name = 'Cache-Data/' + btoa(name.toLowerCase().replace(/\s+/g, ''));
- data = JSON.stringify(data);
+ return await UTILS_STORAGE.remove(['Cache-Data/' + btoa(name.toLowerCase().replace(/\s+/g, ''))]);
+ }
- await storage.set({[name]: data}, () => data);
+ /* 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') && configuration.NotifyNewOnly && /\balready\s+(exists?|(been\s+)?added)\b/.test(text)) || (configuration.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]);
+
+ document.body.appendChild(element);
+
+ return queue[element.id];
+ }
+ }
- return name;
-}
+ 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(
+ configuration.usingRadarr?
+ configuration.radarrQualities:
+ configuration.usingWatcher?
+ configuration.watcherQualities:
+ '[]'
+ ),
+ show: JSON.parse(
+ configuration.usingSonarr?
+ configuration.sonarrQualities:
+ configuration.usingMedusa?
+ configuration.medusaQualities:
+ '[]'
+ )
+ },
+ locations = {
+ movie: JSON.parse(
+ configuration.usingRadarr?
+ configuration.radarrStoragePaths:
+ configuration.usingWatcher?
+ configuration.watcherStoragePaths:
+ '[]'
+ ),
+ show: JSON.parse(
+ configuration.usingSonarr?
+ configuration.sonarrStoragePaths:
+ configuration.usingMedusa?
+ configuration.medusaStoragePaths:
+ '[]'
+ )
+ },
+ defaults = {
+ movie: (
+ configuration.usingRadarr?
+ { quality: configuration.__radarrQuality, location: configuration.__radarrStoragePath }:
+ {}
+ ),
+ show: (
+ configuration.usingSonarr?
+ { quality: configuration.__sonarrQuality, location: configuration.__sonarrStoragePath }:
+ configuration.usingMedusa?
+ { quality: configuration.__medusaQuality, location: configuration.__medusaStoragePath }:
+ {}
+ )
+ };
-async function kill(name) {
- return storage.remove(['Cache-Data/' + btoa(name.toLowerCase().replace(/\s+/g, ''))]);
-}
+ 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() } }),
+ (
+ configuration.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))):
+ ''
+ ),(
+ configuration.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;
+ }
-// 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];
+ 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 Identify({ 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 Identify({ 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')
+ )
+ )
+ );
+ 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() } }),
+ (
+ configuration.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))):
+ ''
+ ),(
+ configuration.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 (last && last.done === false)
- return (last => setTimeout(() => new Notification(state, text, timeout, callback, requiresClick), +(new Date) - last.start))(last);
+ return elements
+ })(array)
+ ),
- let element = document.furnish(`div.web-to-plex-notification.${state}`, {
- onclick: event => {
- let notification = Notification.queue[event.target.id],
- element = notification.element;
+ // 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}`: '/' }` }),
+ (
+ configuration.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'),
+ (
+ configuration.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')
+ )
+ )
+ );
- notification.done = true;
- Notification.queue.list.splice(notification.index, 1);
- clearTimeout(notification.job);
- element.remove();
+ if(P_QUA) P_QUA.value = defaults[type].quality;
+ if(P_LOC) P_LOC.value = defaults[type].location;
- let removed = delete Notification.queue[notification.id];
+ P_QUA = P_LOC = null;
+ break;
- return (event.requiresClick)? null: notification.callback(removed);
+ default:
+ return UTILS_TERMINAL.warn(`Unknown prompt type "${ prompt_type }"`);
+ break;
}
- }, text);
-
- queue[element.id = +(new Date)] = {
- start: +element.id,
- stop: +element.id + timeout,
- span: +timeout,
- done: false,
- index: queue.list.length,
- job: setTimeout(() => element.onclick({ target: element, requiresClick }), timeout),
- id: +element.id,
- callback, element
- };
- queue.list.push(queue[element.id]);
- document.body.appendChild(element);
+ return container.append(prompt), prompt;
+ }
+ }
- return queue[element.id];
+ // open up the options page
+ function Options() {
+ chrome.runtime.sendMessage({
+ type: 'OPEN_OPTIONS'
+ });
}
-}
-class Prompt {
- constructor(prompt_type, options, callback = () => {}, container = document.body) {
- let prompt, remove,
- array = (options instanceof Array? options: [].slice.call(options)),
- data = [...array];
-
- switch(prompt_type) {
- /* Allows the user to add and remove items from a list */
- case 'prompt':
- case 'input':
- remove = element => {
- let prompter = document.queryBy('.web-to-plex-prompt').first,
- header = document.queryBy('.web-to-plex-prompt-header').first,
- counter = document.queryBy('.web-to-plex-prompt-options').first;
-
- if(element === true)
- return prompter.remove();
- else
- element.remove();
+ // Send an update query to background.js
+ Update = (type, options = {}, postToo) => {
+ if(configuration)
+ UTILS_TERMINAL.log(`Requesting update: ${ type }`, options);
- data.splice(+element.value, 1, null);
- header.innerText = 'Approve ' + counter.children.length + (counter.children.length == 1?' item': ' items');
- };
+ chrome.runtime.sendMessage({
+ type,
+ options
+ });
- prompt = document.furnish('div.web-to-plex-prompt', {},
- document.furnish('div.web-to-plex-prompt-body', {},
- // The prompt's title
- document.furnish('h1.web-to-plex-prompt-header', {}, 'Approve ' + array.length + (array.length == 1? ' item': ' items')),
-
- // The prompt's items
- document.furnish('div.web-to-plex-prompt-options', {},
- ...(ITEMS => {
- let elements = [];
-
- for(let index = 0, length = ITEMS.length, ITEM; index < length; index++)
- ITEM = ITEMS[index],
- elements.push(
- document.furnish('li.web-to-plex-prompt-option.mutable', { value: index, innerHTML: `${ ITEM.title }${ ITEM.year? ` (${ ITEM.year })`: '' } \u2014 ${ ITEM.type }` },
- document.furnish('button', { title: `Remove "${ ITEM.title }"`, onclick: event => { remove(event.target.parentElement); event.target.remove() } })
- ),
- );
-
- return elements
- })(array)
- ),
-
- // The engagers
- document.furnish('div.web-to-plex-prompt-footer', {},
- document.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;
- }
+ if(postToo)
+ top.postMessage(options);
+ };
- 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 }"`);
- }
- }
- } }),
- document.furnish('button.web-to-plex-prompt-decline', { onclick: event => { remove(true); callback([]) } }, 'Close'),
- document.furnish('button.web-to-plex-prompt-accept', { onclick: event => { remove(true); new Prompt(prompt_type, options, callback, container) } }, 'Reset'),
- document.furnish('button.web-to-plex-prompt-accept', { onclick: event => { remove(true); callback(data.filter(value => value !== null && value !== undefined)) } }, 'Continue')
- )
- )
- );
- break;
+ // get the saved options
+ function options() {
+ 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;
+ }
- /* Allows the user to remove predetermined items */
- case 'select':
- remove = element => {
- let prompter = document.queryBy('.web-to-plex-prompt').first,
- header = document.queryBy('.web-to-plex-prompt-header').first,
- counter = document.queryBy('.web-to-plex-prompt-options').first;
+ 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
+ }
- if(element === true)
- return prompter.remove();
- else
- element.remove();
+ if(o.usingCouchPotato && o.couchpotatoURLRoot && o.couchpotatoToken) {
+ o.couchpotatoURL = `${ items.couchpotatoURLRoot }/api/${encodeURIComponent(o.couchpotatoToken)}`;
+ } else {
+ delete o.couchpotatoURL; // prevent variable ghosting
+ }
- data.splice(+element.value, 1, null);
- header.innerText = 'Approve ' + counter.children.length + (counter.children.length == 1?' item': ' items');
- };
+ if(o.usingWatcher && o.watcherURLRoot && o.watcherToken) {
+ o.watcherURL = o.watcherURLRoot;
+ } else {
+ delete o.watcherURL; // prevent variable ghosting
+ }
- prompt = document.furnish('div.web-to-plex-prompt', {},
- document.furnish('div.web-to-plex-prompt-body', {},
- // The prompt's title
- document.furnish('h1.web-to-plex-prompt-header', {}, 'Approve ' + array.length + (array.length == 1? ' item': ' items')),
-
- // The prompt's items
- document.furnish('div.web-to-plex-prompt-options', {},
- ...(ITEMS => {
- let elements = [];
-
- for(let index = 0, length = ITEMS.length, ITEM; index < length; index++)
- ITEM = ITEMS[index],
- elements.push(
- document.furnish('li.web-to-plex-prompt-option.mutable', { value: index, innerHTML: `${ ITEM.title }${ ITEM.year? ` (${ ITEM.year })`: '' } \u2014 ${ ITEM.type }` },
- document.furnish('button', { title: `Remove "${ ITEM.title }"`, onclick: event => { remove(event.target.parentElement); event.target.remove() } })
- ),
- );
-
- return elements
- })(array)
- ),
-
- // The engagers
- document.furnish('div.web-to-plex-prompt-footer', {},
- document.furnish('button.web-to-plex-prompt-decline', { onclick: event => { remove(true); callback([]) } }, 'Close'),
- document.furnish('button.web-to-plex-prompt-accept', { onclick: event => { remove(true); new Prompt(prompt_type, options, callback, container) } }, 'Reset'),
- document.furnish('button.web-to-plex-prompt-accept', { onclick: event => { remove(true); callback(data.filter(value => value !== null && value !== undefined)) } }, 'Continue')
- )
- )
- );
- break;
+ if(o.usingRadarr && o.radarrURLRoot && o.radarrToken) {
+ o.radarrURL = o.radarrURLRoot;
+ } else {
+ delete o.radarrURL; // prevent variable ghosting
+ }
- default:
- return terminal.warn(`Unknown prompt type "${ prompt_type }"`);
- break;
- }
+ if(o.usingSonarr && o.sonarrURLRoot && o.sonarrToken) {
+ o.sonarrURL = o.sonarrURLRoot;
+ } else {
+ delete o.sonarrURL; // prevent variable ghosting
+ }
- return container.append(prompt), prompt;
- }
-}
+ if(o.usingMedusa && o.medusaURLRoot && o.medusaToken) {
+ o.medusaURL = o.medusaURLRoot;
+ } else {
+ delete o.medusaURL; // prevent variable ghosting
+ }
-// Send an update query to background.js
-function sendUpdate(type, options = {}) {
- terminal.log(`Requesting update: ${ type }`, options);
+ resolve(o);
+ }
- chrome.runtime.sendMessage({
- type,
- options
- });
-}
+ UTILS_STORAGE.get(null, options => {
+ if(chrome.runtime.lastError)
+ chrome.storage.local.get(null, handleOptions);
+ else
+ handleOptions(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
- };
+ // self explanatory, returns an object; sets the configuration variable
+ function ParsedOptions() {
+ return options()
+ .then(
+ options => (configuration = options),
+ error => {
+ new Notification(
+ 'warning',
+ 'Fill in missing Web to Plex options',
+ 15000,
+ Options
+ );
+ throw error;
+ }
+ );
+ }
- options.plexURL = o.plexURL?
- `${ o.plexURL }web#!/server/${ o.server.id }/`:
- `https://app.plex.tv/web/app#!/server/${ o.server.id }/`;
- } else {
- o = options;
- }
+ await ParsedOptions();
- if (o.couchpotatoBasicAuthUsername)
- o.couchpotatoBasicAuth = {
- username: o.couchpotatoBasicAuthUsername,
- password: o.couchpotatoBasicAuthPassword
- };
+ let AUTO_GRAB = {
+ ENABLED: configuration.UseAutoGrab,
+ LIMIT: configuration.AutoGrabLimit,
+ },
+ UTILS_DEVELOPER = configuration.ExtensionBranchType, // = { true: Developer Mode, fase: Standard Mode }
+ UTILS_TERMINAL =
+ UTILS_DEVELOPER?
+ console:
+ { error: m => m, info: m => m, log: m => m, warn: m => m, group: m => m, groupEnd: m => m };
- // TODO: stupid copy/pasta
- if (o.watcherBasicAuthUsername)
- o.watcherBasicAuth = {
- username: o.watcherBasicAuthUsername,
- password: o.watcherBasicAuthPassword
- };
+ UTILS_TERMINAL.log('UTILS_DEVELOPER:', UTILS_DEVELOPER, configuration);
- if (o.radarrBasicAuthUsername)
- o.radarrBasicAuth = {
- username: o.radarrBasicAuthUsername,
- password: o.radarrBasicAuthPassword
- };
+ // parse the formatted headers and URL
+ function HandleProxyHeaders(Headers = "", URL = "") {
+ let headers = {};
- if (o.sonarrBasicAuthUsername)
- o.sonarrBasicAuth = {
- username: o.sonarrBasicAuthUsername,
- password: o.sonarrBasicAuthPassword
- };
+ Headers.replace(/^[ \t]*([^\=\s]+)[ \t]*=[ \t]*((["'`])(?:[^\\\3]*|\\.)\3|[^\f\n\r\v]*)/gm, ($0, $1, $2, $3, $$, $_) => {
+ let string = !!$3;
- if (o.ombiURLRoot && o.ombiToken) {
- o.ombiURL = o.ombiURLRoot;
+ if(string) {
+ headers[$1] = $2.replace(RegExp(`^${ $3 }|${ $3 }$`, 'g'), '');
} else {
- delete o.ombiURL; // prevent variable ghosting
- }
+ $2 = $2.replace(/@([\w\.]+)/g, (_0, _1, _$, __) => {
+ let path = _1.split('.'), property = top;
- if (o.couchpotatoURLRoot && o.couchpotatoToken) {
- o.couchpotatoURL = `${ items.couchpotatoURLRoot }/api/${encodeURIComponent(o.couchpotatoToken)}`;
- } else {
- delete o.couchpotatoURL; // prevent variable ghosting
- }
+ for(let index = 0, length = path.length; index < length; index++)
+ property = property[path[index]];
- if (o.watcherURLRoot && o.watcherToken) {
- o.watcherURL = o.watcherURLRoot;
- } else {
- delete o.watcherURL; // prevent variable ghosting
+ headers[$1] = property;
+ })
+ .replace(/@\{b(ase-?)?64-url\}/gi, btoa(URL))
+ .replace(/@\{enc(ode)?-url\}/gi, encodeURIComponent(URL))
+ .replace(/@\{(raw-)?url\}/gi, URL);
}
+ });
- if (o.radarrURLRoot && o.radarrToken) {
- o.radarrURL = o.radarrURLRoot;
- } else {
- delete o.radarrURL; // prevent variable ghosting
- }
+ return headers;
+ }
- if (o.sonarrURLRoot && o.sonarrToken) {
- o.sonarrURL = o.sonarrURLRoot;
- } else {
- delete o.sonarrURL; // prevent variable ghosting
- }
+ // 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 Identify({ title, alttitle, year, type, IMDbID, TMDbID, TVDbID, APIType, APIID, meta, rerun }) {
+ let json = {}, // returned object
+ data = {}, // mutated object
+ promise, // query promise
+ api = {
+ tmdb: configuration.TMDbAPI || 'bcb95f026f9a01ffa707fcff71900e94',
+ omdb: configuration.OMDbAPI || 'PlzBanMe',
+ ombi: configuration.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 = configuration.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 && (configuration.usingOmbi || (configuration.usingRadarr && rqut == 'tmdb') || ((configuration.usingSonarr || configuration.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 (tilde from anime results by TMDb)
+ .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;
+ savename = `${title} (${year}).${rqut}`.toLowerCase();
+ local = await load(savename);
+ }
- resolve(o);
+ if(local) {
+ UTILS_TERMINAL.log('[LOCAL] Search results', local);
+ return local;
}
- storage.get(null, options => {
- if (chrome.runtime.lastError)
- chrome.storage.local.get(null, handleOptions);
- else
- handleOptions(options);
- });
- });
-}
+ /* the rest of this function is a beautiful mess that will need to be dealt with later... but it works */
+ let url =
+ (manable && title && configuration.usingOmbi)?
+ `${ configuration.ombiURLRoot }api/v1/Search/${ (rqut == 'imdb' || rqut == 'tmdb' || apit == 'movie')? 'movie': 'tv' }/${ plus(title, '%20') }/?apikey=${ api.ombi }`:
+ (manable && (configuration.usingRadarr || configuration.usingSonarr || configuration.usingMedusa))?
+ (configuration.usingRadarr && (rqut == 'imdb' || rqut == 'tmdb'))?
+ (mid)?
+ `${ configuration.radarrURLRoot }api/movie/lookup/tmdb?tmdbId=${ mid }&apikey=${ configuration.radarrToken }`:
+ (iid)?
+ `${ configuration.radarrURLRoot }api/movie/lookup/imdb?imdbId=${ iid }&apikey=${ configuration.radarrToken }`:
+ `${ configuration.radarrURLRoot }api/movie/lookup?term=${ plus(title, '%20') }&apikey=${ configuration.radarrToken }`:
+ (configuration.usingSonarr)?
+ (tid)?
+ `${ configuration.sonarrURLRoot }api/series/lookup?term=tvdb:${ tid }&apikey=${ configuration.sonarrToken }`:
+ `${ configuration.sonarrURLRoot }api/series/lookup?term=${ plus(title, '%20') }&apikey=${ configuration.sonarrToken }`:
+ (configuration.usingMedusa)?
+ (tid)?
+ `${ configuration.medusarURLRoot }api/v2/series/tvdb${ tid }?detailed=true&${ tid }&api_key=${ configuration.medusaToken }`:
+ `${ configuration.medusaURLRoot }api/v2/internal/searchIndexersForShowName?query=${ plus(title) }&indexerId=0&api_key=${ configuration.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 = configuration.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);
+
+ UTILS_TERMINAL.log({ proxy, url, headers });
+ }
-// self explanatory
-function openOptionsPage() {
- chrome.runtime.sendMessage({
- type: 'OPEN_OPTIONS'
- });
-}
+ UTILS_TERMINAL.log(`Searching for "${ title } (${ year })" in ${ type || apit }/${ rqut }${ proxy.enabled? '[PROXY]': '' } => ${ url }`);
-// 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
- );
+ 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) {
+ UTILS_TERMINAL.error(`Failed to parse JSON: "${ data }"`);
+ }
+ })
+ .catch(error => {
throw error;
- }
- );
-}
+ });
-let config = parseOptions(),
- AUTO_GRAB = {
- ENABLED: config.UseAutoGrab,
- LIMIT: config.AutoGrabLimit,
- };
+ UTILS_TERMINAL.log('Search results', { title, year, url, json });
+
+ if('results' in json)
+ json = json.results;
+
+ 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 = "") => {
+
+ 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, '|'],
+ [/[\u2010-\u2015]/g, '-'], // fancy hyphen
+ [/[\u201a\u275f]/g, ','], // fancy comma
+ [/[\u2018\u2019\u201b\u275b\u275c`]/g, "'"], // fancy apostrophe (tilde from anime results by TMDb)
+ [/[\u201c-\u201f\u275d\u275e]/g, '"'], // fancy quotation marks
+ [/'(?=\B)|\B'/g, '']
+ ];
+
+ for(let i = 0; i < r.length; i++) {
+ if(/^([\(\|\)]+)?$/.test(s)) return "";
+
+ s = s.replace(r[i][0], r[i][1]);
+ }
-function HandleProxyHeaders(Headers = "", URL = "") {
- let headers = {};
+ return c(s);
+ },
+ R = (s = "", S = "", n = !0) => {
+ let l = s.split(' ').length, L = S.split(' ').length, E,
+ score = 100 * (((S.match(E = RegExp(`\\b(${k(s)})\\b`, 'gi')) || [null]).length) / (L || 1)),
+ passing = configuration.UseLooseScore | 0;
- Headers.replace(/^[ \t]*([^\=\s]+)[ \t]*=[ \t]*((["'`])(?:[^\\\3]*|\\.)\3|[^\f\n\r\v]*)/gm, ($0, $1, $2, $3, $$, $_) => {
- let string = !!$3;
+ UTILS_TERMINAL.log(`\tQuick Match => "${ s }"/"${ S }" = ${ score }% (${ E })`);
+ score *= (l > L? (L||1)/l: L > l? (l||1)/L: 1);
+ UTILS_TERMINAL.log(`\tActual Match (${ passing }% to pass) ~> ... = ${ score }%`);
- if(string) {
- headers[$1] = $2.replace(RegExp(`^${ $3 }|${ $3 }$`, 'g'), '');
- } else {
- $2 = $2.replace(/@([\w\.]+)/g, (_0, _1, _$, __) => {
- let path = _1.split('.'), property = top;
+ 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(configuration.usingMedusa && $data instanceof Array)
+ found = ((t($data[4]) == t(title) || $alt) && +year === +$data[5].slice(0, 4))?
+ $alt || $data:
+ found;
+ // Radarr & Sonarr
+ else if(configuration.usingRadarr || configuration.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;
- for(let index = 0, length = path.length; index < length; index++)
- property = property[path[index]];
+ UTILS_TERMINAL.log(`Strict Matching: ${ !!found }`, !!found? found: null);
+ }
- headers[$1] = property;
- })
- .replace(/@\{b(ase-?)?64-url\}/gi, btoa(URL))
- .replace(/@\{enc(ode)?-url\}/gi, encodeURIComponent(URL))
- .replace(/@\{(raw-)?url\}/gi, URL);
- }
- });
+ // 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(configuration.usingMedusa && $data instanceof Array)
+ found = (c($data[4]) == c(title) || $alt)?
+ $alt || $data:
+ found;
+ // Radarr & Sonarr
+ if(configuration.usingRadarr || configuration.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:
+ found;
- return headers;
-}
+ UTILS_TERMINAL.log(`Title Matching: ${ !!found }`, !!found? found: null);
+ }
-// 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)/i.test(rqut)?
- 'tmdb':
- rqut || '*';
- manable = manable && (config.ombiURL || (config.radarrURL && rqut == 'tmdb') || (config.sonarrURL && rqut == 'tvdb'));
- title = (title? title.replace(/\s*[\:,]\s*Season\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);
- }
+ // 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; configuration.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(configuration.usingMedusa && $data instanceof Array)
+ found = (R($data[4], title) || $alt)?
+ $alt || $data:
+ found;
+ // Radarr & Sonarr
+ if(configuration.usingRadarr || configuration.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) || UTILS_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:
+ found;
- if(local) {
- terminal.log('[LOCAL] Search results', local);
- return local;
+ UTILS_TERMINAL.log(`Loose Matching: ${ !!found }`, !!found? found: null);
+ }
+
+ json = found;
+ }
+
+ if((json === undefined || json === null || json === false) && !(rerun & 0b0001))
+ return UTILS_TERMINAL.warn(`Trying to find "${ title }" again (as "${ (alttitle || title) }")`), rerun |= 0b0001, json = Identify({ title: (alttitle || 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 && (!configuration.usingMedusa? true: (configuration.usingSonarr || configuration.usingOmbi)))
+ json = json[0];
+
+ if(!json)
+ json = { IMDbID, TMDbID, TVDbID };
+
+ // Ombi, Medusa, Radarr and Sonarr
+ if(manable)
+ data = (
+ (configuration.usingMedusa && !(configuration.usingSonarr || configuration.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
+ };
+
+ year = +((data.year + '').slice(0, 4)) || 0;
+ data.year = year;
+
+ let best = { title, year, data, type, rqut, score: json.score | 0 };
+
+ UTILS_TERMINAL.log('Best match:', url, { best, json });
+
+ if(best.data.imdb == ei && best.data.tmdb == 0 && best.data.tvdb == 0)
+ return UTILS_TERMINAL.log(`No information was found for "${ title } (${ 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);
+
+ UTILS_TERMINAL.log(`Saved as "${ savename }"`, data);
+
+ rerun |= 0b00001;
+
+ return data;
}
- /* 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.ombiURLRoot)?
- `${ config.ombiURLRoot }api/v1/Search/${ (rqut == 'imdb' || rqut == 'tmdb' || apit == 'movie')? 'movie': 'tv' }/${ plus(title, '%20') }/?apikey=${ api.ombi }`:
- (manable && (config.radarrURLRoot || config.sonarrURLRoot))?
- (config.radarrURLRoot && (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 });
+ function __Request_CouchPotato__(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: `${ configuration.couchpotatoURL }/media.get`,
+ IMDbID: options.IMDbID,
+ TMDbID: options.TMDbID,
+ TVDbID: options.TVDbID,
+ basicAuth: configuration.couchpotatoBasicAuth,
+ },
+ response => {
+ let movieExists = response.success;
+ if(response.error) {
+ return new Notification(
+ 'warning',
+ 'CouchPotato request failed (see your console)'
+ ) ||
+ (!response.silent && UTILS_TERMINAL.error('Error viewing CouchPotato: ' + String(response.error)));
+ }
+ if(!movieExists) {
+ Request_CouchPotato(options);
+ return;
+ }
+ new Notification(
+ 'warning',
+ `Movie already exists in CouchPotato (status: ${response.status})`
+ );
+ }
+ );
}
- terminal.log(`Searching for "${ title } (${ year })" in ${ type || apit }/${ rqut }${ proxy.enabled? '[PROXY]': '' } => ${ url }`);
+ // Movies/TV Shows
+ function Request_Ombi(options) {
+ new Notification('info', `Adding "${ options.title }" to Ombi`, 3000);
- 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((!options.IMDbID && !options.TMDbID) && !options.TVDbID) {
+ return new Notification(
+ 'warning',
+ 'Stopped adding to Ombi: No content ID'
+ );
+ }
- terminal.log('Search results', { title, year, url, json });
+ let contentType = (/movies?|film/i.test(options.type)? 'movie': 'tv');
- if('results' in json)
- json = json.results;
+ chrome.runtime.sendMessage({
+ type: 'PUSH_OMBI',
+ url: `${ configuration.ombiURL }api/v1/Request/${ contentType }`,
+ token: configuration.ombiToken,
+ title: options.title,
+ year: options.year,
+ imdbId: options.IMDbID,
+ tmdbId: options.TMDbID,
+ tvdbId: options.TVDbID,
+ contentType,
+ },
+ response => {
+ UTILS_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 && UTILS_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();
+
+ UTILS_TERMINAL.log('Successfully pushed', options);
+ new Notification('update', `Added "${ options.title }" to Ombi`, 7000, () => window.open(configuration.ombiURL, '_blank'));
+ } else {
+ new Notification('warning', `Could not add "${ options.title }" to Ombi: Unknown Error`) ||
+ (!response.silent && UTILS_TERMINAL.error('Error adding to Ombi: ' + String(response)));
+ }
+ }
+ );
+ }
- 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 = "") => {
+ // Movies/TV Shows
+ function Request_CouchPotato(options) {
+ new Notification('info', `Adding "${ options.title }" to CouchPotato`, 3000);
+
+ chrome.runtime.sendMessage(
+ {
+ type: 'PUSH_COUCHPOTATO',
+ url: `${ configuration.couchpotatoURL }/movie.add`,
+ IMDbID: options.IMDbID,
+ TMDbID: options.TMDbID,
+ TVDbID: options.TVDbID,
+ basicAuth: configuration.couchpotatoBasicAuth,
+ },
+ response => {
+ UTILS_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 && UTILS_TERMINAL.error('Error adding to CouchPotato: ' + String(response.error), response.location, response.debug));
+ }
+ if(response.success) {
+ UTILS_TERMINAL.log('Successfully pushed', options);
+ new Notification('update', `Added "${ options.title }" to CouchPotato`);
+ } else {
+ new Notification('warning', `Could not add "${ options.title }" to CouchPotato`);
+ }
+ }
+ );
+ }
- 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, '|']
- ];
+ // Movies
+ function Request_Watcher(options) {
+ new Notification('info', `Adding "${ options.title }" to Watcher`, 3000);
- for(let i = 0; i < r.length; i++) {
- if(/^([\(\|\)]+)?$/.test(s)) return "";
+ if(!options.IMDbID && !options.TMDbID) {
+ return new Notification(
+ 'warning',
+ 'Stopped adding to Watcher: No IMDb/TMDb ID'
+ );
+ }
- s = s.replace(r[i][0], r[i][1]);
+ chrome.runtime.sendMessage({
+ type: 'PUSH_WATCHER',
+ url: `${ configuration.watcherURL }api/`,
+ token: configuration.watcherToken,
+ StoragePath: configuration.watcherStoragePath,
+ basicAuth: configuration.watcherBasicAuth,
+ title: options.title,
+ year: options.year,
+ imdbId: options.IMDbID,
+ tmdbId: options.TMDbID,
+ },
+ response => {
+ UTILS_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 && UTILS_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;
+
+ UTILS_TERMINAL.log('Successfully pushed', options);
+ new Notification('update', `Added "${ options.title }" to Watcher`, 7000, () => window.open(`${configuration.watcherURL}library/status${TMDbID? `#${title}-${TMDbID}`: '' }`, '_blank'));
+ } else {
+ new Notification('warning', `Could not add "${ options.title }" to Watcher: Unknown Error`) ||
+ (!response.silent && UTILS_TERMINAL.error('Error adding to Watcher: ' + String(response)));
}
+ }
+ );
+ }
- 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;
+ // Movies
+ function Request_Radarr(options, prompted) {
+ if(!options.IMDbID && !options.TMDbID)
+ return (!prompted)? new Notification(
+ 'warning',
+ 'Stopped adding to Radarr: No IMDb/TMDb ID'
+ ): null;
- 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);
- }
+ let PromptValues = {},
+ { PromptQuality, PromptLocation } = configuration;
- // 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))?
- $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);
- }
+ if(!prompted && (PromptQuality || PromptLocation))
+ return new Prompt('modify', options, refined => Request_Radarr(refined, true));
- // 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)))?
- $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);
- }
+ if(PromptQuality && +options.quality > 0)
+ PromptValues.QualityID = +options.quality;
+ if(PromptLocation && options.location)
+ PromptValues.StoragePath = JSON.parse(configuration.radarrStoragePaths).map(item => item.id == options.location? item: null).filter(n => n)[0].path.replace(/\\/g, '\\\\');
- json = found;
+ new Notification('info', `Adding "${ options.title }" to Radarr`, 3000);
+
+ chrome.runtime.sendMessage({
+ type: 'PUSH_RADARR',
+ url: `${ configuration.radarrURL }api/movie/`,
+ token: configuration.radarrToken,
+ StoragePath: configuration.radarrStoragePath,
+ QualityID: configuration.radarrQualityProfileId,
+ basicAuth: configuration.radarrBasicAuth,
+ title: options.title,
+ year: options.year,
+ imdbId: options.IMDbID,
+ tmdbId: options.TMDbID,
+ ...PromptValues
+ },
+ response => {
+ UTILS_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 && UTILS_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;
+
+ UTILS_TERMINAL.log('Successfully pushed', options);
+ new Notification('update', `Added "${ options.title }" to Radarr`, 7000, () => window.open(`${configuration.radarrURL}${TMDbID? `movies/${title}-${TMDbID}`: '' }`, '_blank'));
+ } else {
+ new Notification('warning', `Could not add "${ options.title }" to Radarr: Unknown Error`) ||
+ (!response.silent && UTILS_TERMINAL.error('Error adding to Radarr: ' + String(response)));
+ }
+ }
+ );
}
- 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 };
+ // TV Shows
+ function Request_Sonarr(options, prompted) {
+ if(!options.TVDbID)
+ return (!prompted)? new Notification(
+ 'warning',
+ 'Stopped adding to Sonarr: No TVDb ID'
+ ): null;
- let ei = 'tt',
- mr = 'movie_results',
- tr = 'tv_results';
+ let PromptValues = {},
+ { PromptQuality, PromptLocation } = configuration;
- json = json && mr in json? json[mr].length > json[tr].length? json[mr]: json[tr]: json;
+ if(!prompted && (PromptQuality || PromptLocation))
+ return new Prompt('modify', options, refined => Request_Sonarr(refined, true));
- if(json instanceof Array)
- json = json[0];
+ if(PromptQuality && +options.quality > 0)
+ PromptValues.QualityID = +options.quality;
+ if(PromptLocation && options.location)
+ PromptValues.StoragePath = JSON.parse(configuration.sonarrStoragePaths).map(item => item.id == options.location? item: null).filter(n => n)[0].path.replace(/\\/g, '\\\\');
- if(!json)
- json = { IMDbID, TMDbID, TVDbID };
+ new Notification('info', `Adding "${ options.title }" to Sonarr`, 3000);
- // 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
- };
+ chrome.runtime.sendMessage({
+ type: 'PUSH_SONARR',
+ url: `${ configuration.sonarrURL }api/series/`,
+ token: configuration.sonarrToken,
+ StoragePath: configuration.sonarrStoragePath,
+ QualityID: configuration.sonarrQualityProfileId,
+ basicAuth: configuration.sonarrBasicAuth,
+ title: options.title,
+ year: options.year,
+ tvdbId: options.TVDbID,
+ ...PromptValues
+ },
+ response => {
+ UTILS_TERMINAL.log('Pushing to Sonarr', response);
- year = +((data.year + '').slice(0, 4)) || 0;
- data.year = year;
+ if(response && response.error) {
+ return new Notification('warning', `Could not add "${ options.title }" to Sonarr: ${ response.error }`) ||
+ (!response.silent && UTILS_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 best = { title, year, data, type, rqut, score: json.score | 0 };
+ UTILS_TERMINAL.log('Successfully pushed', options);
+ new Notification('update', `Added "${ options.title }" to Sonarr`, 7000, () => window.open(`${configuration.sonarrURL}series/${title}`, '_blank'));
+ } else {
+ new Notification('warning', `Could not add "${ options.title }" to Sonarr: Unknown Error`) ||
+ (!response.silent && UTILS_TERMINAL.error('Error adding to Sonarr: ' + String(response)));
+ }
+ }
+ );
+ }
- terminal.log('Best match:', url, { best, json });
+ // TV Shows
+ function Request_Medusa(options, prompted) {
+ if(!options.TVDbID)
+ return (!prompted)? new Notification(
+ 'warning',
+ 'Stopped adding to Medusa: No TVDb ID'
+ ): null;
- if(best.data.imdb == ei && best.data.tmdb == 0 && best.data.tvdb == 0)
- return terminal.log(`No information was found for "${ title } (${ year })"`), {};
+ let PromptValues = {},
+ { PromptQuality, PromptLocation } = configuration;
- 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);
+ if(!prompted && (PromptQuality || PromptLocation))
+ return new Prompt('modify', options, refined => Request_Medusa(refined, true));
- terminal.log(`Saved as "${ savename }"`, data);
+ if(PromptQuality && +options.quality > 0)
+ PromptValues.QualityID = +options.quality;
+ if(PromptLocation && options.location)
+ PromptValues.StoragePath = JSON.parse(configuration.medusaStoragePaths).map(item => item.id == options.location? item: null).filter(n => n)[0].path.replace(/\\/g, '\\\\');
- return data;
-}
+ new Notification('info', `Adding "${ options.title }" to Medusa`, 3000);
-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 is already in CouchPotato (status: ${response.status})`
- );
- }
- );
-}
+ chrome.runtime.sendMessage({
+ type: 'PUSH_MEDUSA',
+ url: `${ configuration.medusaURL }api/v2/series`,
+ root: `${ configuration.medusaURL }api/v2/`,
+ token: configuration.medusaToken,
+ StoragePath: configuration.medusaStoragePath,
+ QualityID: configuration.medusaQualityProfileId,
+ basicAuth: configuration.medusaBasicAuth,
+ title: options.title,
+ year: options.year,
+ tvdbId: options.TVDbID,
+ ...PromptValues
+ },
+ response => {
+ UTILS_TERMINAL.log('Pushing to Medusa', response);
-// Movies/TV Shows
-function pushOmbiRequest(options) {
- new Notification('info', `Adding "${ options.title }" to Ombi`, 3000);
+ if(response && response.error) {
+ return new Notification('warning', `Could not add "${ options.title }" to Medusa: ${ response.error }`) ||
+ (!response.silent && UTILS_TERMINAL.error('Error adding to Medusa: ' + 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'
+ UTILS_TERMINAL.log('Successfully pushed', options);
+ new Notification('update', `Added "${ options.title }" to Medusa`, 7000, () => window.open(`${configuration.medusaURL}home/displayShow?indexername=tvdb&seriesid=${options.TVDbID}`, '_blank'));
+ } else {
+ new Notification('warning', `Could not add "${ options.title }" to Medusa: Unknown Error`) ||
+ (!response.silent && UTILS_TERMINAL.error('Error adding to Medusa: ' + 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);
+ // make the button
+ let MASTER_BUTTON;
+ function RenderButton(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;
+
+ //
-// TV Shows
-function pushSonarrRequest(options) {
- new Notification('info', `Adding "${ options.title }" to Sonarr`, 3000);
+ document.body.appendChild(button);
- if (!options.TVDbID) {
- return new Notification(
- 'warning',
- 'Stopped adding to Sonarr: No TVDb ID'
- );
+ return MASTER_BUTTON = button;
}
- 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,
- },
- response => {
- terminal.log('Pushing to Sonarr', response);
+ function UpdateButton(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;
+ };
- 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();
+ Update('SEARCH_FOR', { ...options, button });
- 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)));
- }
- }
- );
-}
+ /* Handle a list of items */
+ if(multiple) {
+ options = [].slice.call(options);
-// 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;
-
- //
+ if(!options || !options.type || !options.title) return;
- document.body.appendChild(button);
+ let empty = (em.test(options.IMDbID) && em.test(options.TMDbID) && em.test(options.TVDbID)),
+ nice_title = `${options.title.toCaps()}${options.year? ` (${options.year})`: ''}`;
- return button;
-}
+ if(options) {
+ ty = (options.type == 'movie'? 'Movie': 'TV Show');
+ txt = options.txt || txt;
+ hov = options.hov || hov;
+ }
-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;
- };
+ if(action == 'found') {
+ element.href = Request_PlexURL(configuration.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 & GoStream */
+ case 'oload':
+ case 'consistent':
+ let href = options.href, path = '';
+
+ if(configuration.usingOmbi) {
+ path = '';
+ } else if(configuration.usingWatcher && !tv.test(options.type)) {
+ path = '';
+ } else if(configuration.usingRadarr && !tv.test(options.type)) {
+ path = configuration.radarrStoragePath;
+ } else if(configuration.usingSonarr && tv.test(options.type)) {
+ path = configuration.sonarrStoragePath;
+ } else if(configuration.usingMedusa && tv.test(options.type)) {
+ path = configuration.medusaStoragePath;
+ } else if(configuration.usingCouchPotato) {
+ path = '';
+ }
- sendUpdate('SEARCH_FOR', { ...options, button });
+ 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();
- /* Handle a list of items */
- if(multiple) {
- options = [].slice.call(options);
+ Update('DOWNLOAD_FILE', { ...options, button, href, path });
+ new Notification('update', 'Opening prompt (may take a while)...');
+ });
- let saved_options = [], // a list of successful searches (not on Plex)
- len = options.length,
- s = (len == 1? '': 's'),
- t = [];
+ element.setAttribute(hov, `Download "${ nice_title }" | ${ty}`);
+ Update('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 UpdateButton(button, 'notfound', title, options);
+
+ element.href = url;
+ button.classList.add('wtp--download');
+ element.addEventListener('click', element.ON_CLICK = e => {
+ e.preventDefault();
+ if(configuration.usingOmbi) {
+ Request_Ombi(options);
+ } else if(configuration.usingWatcher && !tv.test(options.type)) {
+ Request_Watcher(options);
+ } else if(configuration.usingRadarr && !tv.test(options.type)) {
+ Request_Radarr(options);
+ } else if(configuration.usingSonarr && tv.test(options.type)) {
+ Request_Sonarr(options);
+ } else if(configuration.usingMedusa && tv.test(options.type)) {
+ Request_Medusa(options);
+ } else if(configuration.usingCouchPotato) {
+ __Request_CouchPotato__(options);
+ }
+ });
+ }
+ NOTIFIED = false;
- for(let index = 0; index < len; index++) {
- let option = options[index];
+ element.setAttribute(hov, `Add "${ nice_title }" | ${ty}`);
+ element.style.removeProperty('display');
+ } else if(action == 'notfound' || action == 'error' || empty) {
+ element.removeAttribute('href');
- // Skip empty entries
- if(!option || !option.type || !option.title) continue;
+ empty = !(options && options.title);
- // 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 }`;
+ if(empty)
+ element.setAttribute(hov, `${ty || 'Item'} not found`);
+ else
+ element.setAttribute(hov, `"${ nice_title }" was not found`);
- /* Failed */
- if(/#tt-0-0/i.test(url))
- continue;
+ button.classList.remove('wtp--found');
+ button.classList.add('wtp--error');
+ }
- saved_options.push(option);
- t.push(option.title);
+ element.id = options? `${options.IMDbID || 'tt'}-${options.TMDbID | 0}-${options.TVDbID | 0}`: 'tt-0-0';
}
+ }
- t = t.join(', ');
- t = t.length > 24? t.slice(0, 21).replace(/\W+$/, '') + '...': t;
-
- element.ON_CLICK = e => {
- e.preventDefault();
+ // Find media on Plex
+ async function FindMediaItems(options, button) {
+ if(!(options && options.length && button))
+ return;
- let self = e.target, tv = /tv[\s-]?|shows?|series/i, fail = 0,
- options = JSON.parse(atob(button.getAttribute('saved_options')));
+ let results = [],
+ length = options.length,
+ queries = (FindMediaItems.queries = FindMediaItems.queries || {});
- for(let index = 0, length = options.length, option; index < length; index++) {
- option = options[index];
+ FindMediaItems.OPTIONS = options;
- try {
- if(config.ombiURL)
- pushOmbiRequest(option);
- else if (config.watcherURL && !tv.test(option.type))
- pushWatcherRequest(option);
- else if (config.radarrURL && !tv.test(option.type))
- pushRadarrRequest(option);
- else if (config.sonarrURL && tv.test(option.type))
- pushSonarrRequest(option);
- else if(config.couchpotatoURL && tv.test(option.type))
- $pushAddToCouchpotato(option);
- } catch(error) {
- terminal.error(`Failed to get "${ option.title }" (Error #${ ++fail })`)
- }
- }
+ let query = JSON.stringify(options);
- if (fail)
- new Notification('error', `Failed to grab ${ fail } item${fail==1?'':'s'}`);
- };
+ query = (queries[query] = queries[query] || {});
- 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) }));
+ if(query.running === true)
+ return;
+ else if(query.results) {
+ let { results, multiple, items } = query;
- 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 */
+ new Notification('update', `Welcome back. ${ multiple } new ${ items } can be grabbed`, 7000, (event, target = button.querySelector('.list-action')) => target.click({ ...event, target }));
- if(!options || !options.type || !options.title) return;
+ if(multiple)
+ UpdateButton(button, 'multiple', `Download ${ multiple } ${ items }`, results);
- 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;
+ return;
}
- 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.ombiURL) {
- path = '';
- } else if (config.watcherURL && !tv.test(options.type)) {
- path = '';
- } else if (config.radarrURL && !tv.test(options.type)) {
- path = config.radarrStoragePath;
- } else if (config.sonarrURL && tv.test(options.type)) {
- path = config.sonarrStoragePath;
- } else if(config.couchpotatoURL && tv.test(options.type)) {
- path = '';
- }
+ query.running = true;
- 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();
+ new Notification('info', `Processing ${ length } item${ 's'[+(length === 1)] || '' }...`);
- sendUpdate('DOWNLOAD_FILE', { ...options, button, href, path });
- new Notification('update', 'Opening prompt (may take a while)...');
- });
+ for(let index = 0, option, opt; index < length; index++) {
+ let { IMDbID, TMDbID, TVDbID } = (option = await options[index]);
- 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;
+ 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 Request_Plex(option)
+ .then(async({ found, key }) => {
+ if(found) {
+ // ignore found items, we only want new items
+ } else {
+ option.field = 'original_title';
- /* 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.ombiURL) {
- pushOmbiRequest(options);
- } else if (config.watcherURL && !tv.test(options.type)) {
- pushWatcherRequest(options);
- } else if (config.radarrURL && !tv.test(options.type)) {
- pushRadarrRequest(options);
- } else if (config.sonarrURL && tv.test(options.type)) {
- pushSonarrRequest(options);
- } else if(config.couchpotatoURL && tv.test(options.type)) {
- $pushAddToCouchpotato(options);
+ return await Request_Plex(option)
+ .then(({ found, key }) => {
+ if(found) {
+ // ignore found items, we only want new items
+ } else {
+ let available = (configuration.usingOmbi || configuration.usingWatcher || configuration.usingRadarr || configuration.usingSonarr || configuration.usingMedusa || configuration.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) {
+ UTILS_TERMINAL.error('Request to Plex failed: ' + String(error));
+ // new Notification('error', 'Failed to query item #' + (index + 1));
}
+ }
- element.setAttribute(hov, `Add "${ nice_title }" | ${ty}`);
- element.style.removeProperty('display');
- } else if (action == 'notfound' || action == 'error' || empty) {
- element.removeAttribute('href');
+ results = results.filter(v => v.status == 'downloader');
- empty = !(options && options.title);
+ 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(empty)
- element.setAttribute(hov, `${ty || 'Item'} not found`);
- else
- element.setAttribute(hov, `"${ nice_title }" was not found`);
+ if(po = button.querySelector('#plexit'))
+ po.remove();
+ try {
+ button.querySelector('ul').insertBefore(pi, op);
+ } catch(e) { /* Don't do anything */ }
- button.classList.remove('wtp--found');
- button.classList.add('wtp--error');
- }
+ let multiple = results.length,
+ items = multiple == 1? 'item': 'items';
- element.id = options? `${options.IMDbID || 'tt'}-${options.TMDbID | 0}-${options.TVDbID | 0}`: 'tt-0-0';
- }
-}
+ new Notification('update', `Done. ${ multiple } new ${ items } can be grabbed`, 7000, (event, target = button.querySelector('.list-action')) => target.click({ ...event, target }));
-async function squabblePlex(options, button) {
- if(!(options && options.length && button))
- return;
+ query.running = false;
+ query.results = results;
+ query.multiple = multiple;
+ query.items = items;
- let results = [],
- length = options.length;
+ if(multiple)
+ UpdateButton(button, 'multiple', `Download ${ multiple } ${ items }`, results);
+ }
+
+ async function FindMediaItem(options) {
+ if(!(options && options.title))
+ return;
- squabblePlex.OPTIONS = options;
+ let { IMDbID, TMDbID, TVDbID } = options;
- new Notification('info', `Processing ${ length } item${ 's'[+(length === 1)] || '' }...`);
+ TMDbID = +TMDbID;
+ TVDbID = +TVDbID;
- for(let index = 0, option, opt; index < length; index++) {
- let { IMDbID, TMDbID, TVDbID } = (option = await options[index]);
+ 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'))} });
- 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 };
+ FindMediaItem.OPTIONS = options;
try {
- await getPlexMediaRequest(option)
- .then(async({ found, key }) => {
- if (found) {
- // ignore found items, we only want new items
+ return Request_Plex(options)
+ .then(({ found, key }) => {
+ if(found) {
+ UpdateButton(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 {
- option.field = 'original_title';
+ options.field = 'original_title';
- return await getPlexMediaRequest(option)
+ return Request_Plex(options)
.then(({ found, key }) => {
- if (found) {
- // ignore found items, we only want new items
+ if(found) {
+ UpdateButton(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 {
- let available = (config.ombiURL || config.watcherURL || config.radarrURL || config.sonarrURL || config.couchpotatoURL),
+ let available = (configuration.usingOmbi || configuration.usingWatcher || configuration.usingRadarr || configuration.usingSonarr || configuration.usingMedusa || configuration.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 });
+ UpdateButton(options.button, action, title, options);
+ opt = { ...opt, found: false, status: action };
+
+ let po, pi = furnish('li#plexit.list-item', { data: btoa(JSON.stringify(opt)) }, img);
+
+ 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) {
- terminal.error('Request to Plex failed: ' + String(error));
- // new Notification('error', 'Failed to query item #' + (index + 1));
- }
+ } catch(error) {
+ return UpdateButton(
+ options.button,
+ 'error',
+ 'Request to Plex Media Server failed',
+ options
+ ),
+ UTILS_TERMINAL.error(`Request to Plex failed: ${ String(error) }`),
+ false;
+ // new Notification('Failed to communicate with Plex');
+ }
}
- results = results.filter(v => v.status == 'downloader');
+ function Request_Plex(options) {
+ if(!(configuration.plexURL && configuration.plexToken) || configuration.DO_NOT_USE)
+ return new Promise((resolve, reject) => resolve({ found: false, key: null }));
- let img = furnish('img', { title: 'Add to Plex It!', src: IMG_URL.p48, onclick: 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');
+ return new Promise((resolve, reject) => {
+ chrome.runtime.sendMessage({
+ type: 'SEARCH_PLEX',
+ options,
+ serverConfig: configuration.server
+ },
+ response =>
+ (response && response.error)?
+ reject(response.error):
+ (!response)?
+ reject(new Error('Unknown error')):
+ resolve(response)
+ );
+ });
+ }
- if(po = button.querySelector('#plexit'))
- po.remove();
- button.querySelector('ul').insertBefore(pi, op);
+ function Request_PlexURL(PlexUIID, key) {
+ return configuration.plexURL.replace(RegExp(`\/(${ configuration.server.id })?$`), `/web#!/server/` + PlexUIID) + `/details?key=${encodeURIComponent( key )}`;
+ }
- let multiple = results.length,
- items = multiple == 1? 'item': 'items';
+ /* Listen for events */
+ chrome.runtime.onMessage.addListener(async(request, sender) => {
+ UTILS_TERMINAL.log(`Listener event [${ request.instance_type }#${ request[request.instance_type.toLowerCase()] }]:`, request);
- new Notification('update', `Done. ${ multiple } new ${ items } can be grabbed`, 7000, (event, target = button.querySelector('.list-action')) => target.click({ ...event, target }));
+ 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 (multiple)
- modifyPlexButton(button, 'multiple', `Download ${ multiple } ${ items }`, results);
-}
+ if(!data)
+ return UTILS_TERMINAL.warn(EMPTY_REQUEST);
+ let button = RenderButton();
-function findPlexMedia(options) {
- if(!(options && options.title))
- return;
+ if(!button)
+ return UTILS_TERMINAL.warn(BUTTON_ERROR);
- let { IMDbID, TMDbID, TVDbID } = options;
+ switch(request.type) {
+ case 'POPULATE':
- TMDbID = +TMDbID;
- TVDbID = +TVDbID;
+ 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);
- 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.p16 }) top right/60% no-repeat, #0004 url(${ opt.image }) center/contain no-repeat; height: 48px; width: 34px;`, draggable: true, onclick: 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.p48, onclick: event => {let frame = document.querySelector('#plexit-bookmarklet-frame'); frame.src = frame.src.replace(/(#plexit:.*)?$/, '#plexit:' + event.target.parentElement.getAttribute('data'))} });
+ data = data.filter(value => value !== null && value !== undefined);
- findPlexMedia.OPTIONS = options;
+ for(let index = 0, length = data.length, item; index < length; index++) {
+ let { image, type, title, year, IMDbID, TMDbID, TVDbID } = (item = data[index]);
- try {
- 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(!item.title || !item.type)
+ continue;
- let po, pi = furnish('li#plexit.list-item', { data: btoa(JSON.stringify(opt)) }, img);
+ let Db = await Identify(item);
- if(po = options.button.querySelector('#plexit'))
- po.remove();
- options.button.querySelector('ul').insertBefore(pi, op);
- } else {
- options.field = 'original_title';
+ IMDbID = IMDbID || Db.imdb || 'tt';
+ TMDbID = TMDbID || Db.tmdb || 0;
+ TVDbID = TVDbID || Db.tvdb || 0;
- 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' };
+ title = title || Db.title;
+ year = +(year || Db.year || 0);
- let po, pi = furnish('li#plexit.list-item', { data: btoa(JSON.stringify(opt)) }, img);
+ data.splice(index, 1, { type, title, year, image, button, IMDbID, TMDbID, TVDbID });
+ }
- if(po = options.button.querySelector('#plexit'))
- po.remove();
- options.button.querySelector('ul').insertBefore(pi, op);
- } else {
- let available = (config.ombiURL || config.watcherURL || config.radarrURL || config.sonarrURL || config.couchpotatoURL),
- action = (available ? 'downloader' : 'notfound'),
- title = available ?
- 'Not on Plex (download available)':
- 'Not on Plex (download not available)';
+ if(!data.length)
+ return UTILS_TERMINAL.error(PARSING_ERROR);
+ else
+ FindMediaItems(data, button);
+ } else {
+ if(!data || !data.title || !data.type)
+ return UTILS_TERMINAL.error(PARSING_ERROR);
- modifyPlexButton(options.button, action, title, options);
- opt = { ...opt, found: false, status: action };
+ let { image, type, title, year, IMDbID, TMDbID, TVDbID } = data;
+ let Db = await Identify(data);
- let po, pi = furnish('li#plexit.list-item', { data: btoa(JSON.stringify(opt)) }, img);
+ IMDbID = IMDbID || Db.imdb || 'tt';
+ TMDbID = TMDbID || Db.tmdb || 0;
+ TVDbID = TVDbID || Db.tvdb || 0;
- if(po = options.button.querySelector('#plexit'))
- po.remove();
- if(!!~[].slice.call(options.button.querySelector('ul').children).indexOf(op))
- options.button.querySelector('ul').insertBefore(pi, op);
- }
- });
+ title = title || Db.title;
+ year = +(year || Db.year || 0);
+
+ let found = await FindMediaItem({ type, title, year, image, button, IMDbID, TMDbID, TVDbID });
+ Update('FOUND', { ...request, found }, true);
}
- })
- } catch(error) {
- return modifyPlexButton(
- options.button,
- 'error',
- 'Request to Plex Media Server failed',
- options
- ),
- terminal.error(`Request to Plex failed: ${ String(error) }`);
- // new Notification('Failed to communicate with Plex');
+ return true;
+
+ default:
+ // UTILS_TERMINAL.warn(`Unknown event [${ request.type }]`);
+ return false;
}
-}
+ });
-function getPlexMediaRequest(options) {
- if(!(config.plexURL && config.plexToken) || config.DO_NOT_USE)
- return new Promise((resolve, reject) => resolve({ found: false, key: null }));
+ /* Listen for Window events - from iframes, etc. */
+ top.addEventListener('message', request => {
+ try {
+ request = request.data;
- 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)
- );
- });
-}
+ switch(request.type) {
+ case 'SEND_VIDEO_LINK':
+ let options = { ...FindMediaItem.OPTIONS, href: request.href, remote: request.from };
-function getPlexMediaURL(PlexUIID, key) {
- return config.plexURL.replace(RegExp(`\/(${ config.server.id })?$`), `/web#!/server/` + PlexUIID) + `/details?key=${encodeURIComponent( key )}`;
-}
+ UTILS_TERMINAL.log(`Download Event [${ options.remote }]:`, options);
-/* Listen for Plugin events */
-chrome.runtime.onMessage.addListener(async(request, sender) => {
- terminal.log(`Plugin event [${ request.plugin }]:`, request);
+ UpdateButton(MASTER_BUTTON, 'downloader', 'Download', options);
+ return true;
- switch(request.type) {
- case 'POPULATE':
- let button = renderPlexButton(),
- data = request.data,
- PARSING_ERROR = `Can't parse missing information. ${ request.name } @ instance ${ request.instance }`,
- BUTTON_ERROR = `The button failed to render. ${ request.name } @ instance ${ request.instance }`;
+ case 'NOTIFICATION':
+ let { state, text, timeout = 7000, callback = () => {}, requiresClick = true } = request.data;
+ new Notification(state, text, timeout, callback, requiresClick);
+ return true;
- if(!button)
- return terminal.warn(BUTTON_ERROR);
+ default:
+ // UTILS_TERMINAL.warn(`Unknown event [${ request.type }]`);
+ return false;
+ }
+ } catch(error) {
+ new Notification('error', `Unable to use downloader: ${ String(error) }`);
+ throw error
+ }
+ });
- 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);
+})(new Date);
- data = data.filter(value => value !== null && value !== undefined);
+/* Helpers */
- for(let index = 0, length = data.length, item; index < length; index++) {
- let { image, type, title, year, IMDbID, TMDbID, TVDbID } = (item = data[index]);
+function wait(on, then) {
+ if(on && ((on instanceof Function && on()) || true))
+ then && then();
+ else
+ setTimeout(() => wait(on, then), 50);
+}
- if(!item.title || !item.type)
- continue;
+// the custom "on location change" event
+function watchlocationchange(subject) {
+ let locationchangecallbacks = watchlocationchange.locationchangecallbacks;
- let Db = await getIDs(item);
+ watchlocationchange[subject] = watchlocationchange[subject] || location[subject];
- IMDbID = IMDbID || Db.imdb || 'tt';
- TMDbID = TMDbID || Db.tmdb || 0;
- TVDbID = TVDbID || Db.tvdb || 0;
+ if(watchlocationchange[subject] != location[subject]) {
+ let from = watchlocationchange[subject],
+ to = location[subject],
+ properties = { from, to },
+ sign = code => (code + '').replace(/\s+/g, '');
- title = title || Db.title;
- year = +(year || Db.year || 0);
+ watchlocationchange[subject] = location[subject];
- data.splice(index, 1, { type, title, year, image, button, IMDbID, TMDbID, TVDbID });
- }
+ for(let index = 0, length = locationchangecallbacks.length, callback, called; length > 0 && index < length; index++) {
+ callback = locationchangecallbacks[index];
+ called = locationchangecallbacks.called[sign(callback)];
- if(!data.length)
- return terminal.error(PARSING_ERROR);
- else
- squabblePlex(data, button);
- } else {
- if(!data.title || !data.type)
- return terminal.error(PARSING_ERROR);
+ let event = new Event('locationchange', { bubbles: true });
- let { image, type, title, year, IMDbID, TMDbID, TVDbID } = data;
- let Db = await getIDs(data);
+ if(!called && callback && typeof callback == 'function') {
+ locationchangecallbacks.called[sign(callback)] = true;
+ window.addEventListener('beforeunload', event => {
+ event.preventDefault(false);
- IMDbID = IMDbID || Db.imdb || 'tt';
- TMDbID = TMDbID || Db.tmdb || 0;
- TVDbID = TVDbID || Db.tvdb || 0;
+ callback({ event, ...properties });
+ });
- title = title || Db.title;
- year = +(year || Db.year || 0);
+ callback({ event, ...properties });
- findPlexMedia({ type, title, year, image, button, IMDbID, TMDbID, TVDbID });
+ open(to, '_self');
+ } else {
+ return /* The eventlistener was already called */;
}
- return true;
-
- default:
-// terminal.warn(`Unknown event [${ request.type }]`);
- return false;
+ }
}
-});
-
-/* Listen for Window events - from iframes, etc. */
-top.addEventListener('message', request => {
- try {
- request = request.data;
-
- switch(request.type) {
- case 'SEND_VIDEO_LINK':
- let options = { ...findPlexMedia.OPTIONS, href: request.href, remote: request.from };
+}
+watchlocationchange.locationchangecallbacks = watchlocationchange.locationchangecallbacks || [];
+watchlocationchange.locationchangecallbacks.called = watchlocationchange.locationchangecallbacks.called || {};
- modifyPlexButton(options.button, 'downloader', 'Download', options);
- return true;
+if(!('onlocationchange' in window))
+ Object.defineProperty(window, 'onlocationchange', {
+ set: callback => (typeof callback == 'function'? watchlocationchange.locationchangecallbacks.push(callback): null),
+ get: () => watchlocationchange.locationchangecallbacks
+ });
- default:
- // terminal.warn(`Unknown event [${ request.type }]`);
- return false;
- }
- } catch(error) {
- new Notification('error', `Unable to use downloader: ${ String(error) }`);
- throw error
- }
-});
+watchlocationchange.onlocationchangeinterval = watchlocationchange.onlocationchangeinterval || setInterval(() => watchlocationchange('href'), 1);
+// at least 1s is needed to properly fire the event ._.
-String.prototype.toCaps = function toCaps(all) {
+String.prototype.toCaps = String.prototype.toCaps || function toCaps(all) {
/** Titling Caplitalization
* Articles: a, an, & the
* Conjunctions: and, but, for, nor, or, so, & yet
* Prepositions: across, after, although, at, because, before, between, by, during, from, if, in, into, of, on, to, through, under, with, & without
*/
let array = this.toLowerCase(),
- 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,
+ 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+[dclxvi]*|d+[clxvi]*|c+[lxvi]*|l+[xvi]*|x+[vi]*|v+i*|i+))\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)\./gi; // Titles (Most Common?)
+ 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?)
+ low_exceptions = /'([\w]+)/gi; // Apostrphe cases
array = array.split(/\s+/);
@@ -1704,10 +2134,11 @@ 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, $$, $_) => $0[0].toUpperCase() + $0.slice(1, $0.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;
};
@@ -1732,9 +2163,9 @@ String.prototype.toCaps = function toCaps(all) {
2
3
*/
- parent.queryBy = function queryBy(selectors, container = parent) {
+ parent.queryBy = parent.queryBy || function queryBy(selectors, container = parent) {
// Helpers
- let copy = array => [].slice.call(array),
+ let copy = array => [...array],
query = (SELECTORS, CONTAINER = container) => CONTAINER.querySelectorAll(SELECTORS);
// Get rid of enclosing syntaxes: [...] and (...)
@@ -1765,24 +2196,26 @@ String.prototype.toCaps = function toCaps(all) {
selector = selector
.replace(/\:nth-parent\((\d+)\)/g, ($0, $1, $$, $_) => (generations -= +$1, ''))
.replace(/(\:{1,2}parent\b|<\s*(\*|\s*(,|$)))/g, ($0, $$, $_) => (--generations, ''))
- .replace(/<([^<,]+)?/g, ($0, $1, $$, $_) => (ancestor = $1, --generations, ''));
+ .replace(/<([^<,]+)?/g, ($0, $1, $$, $_) => (ancestor = $1, --generations, ''))
+ .replace(/^\s+|\s+$/g, '');
let elements = query(selector),
parents = [], parent;
for(; generations < 0; generations++)
elements.forEach( element => {
- let P = element,
- E = C => [].slice.call(query(ancestor, C)),
- F;
+ let P = element, Q = P.parentElement, R = (Q? Q.parentElement: {}),
+ E = C => [...query(ancestor, C)],
+ F, G;
- for(let I = 0, L = -generations; ancestor && !!P && I < L; I++)
- P = !!~E(P.parentElement).indexOf(P)? P: P.parentElement;
+ for(let I = 0, L = -generations; ancestor && !!R && !!Q && !!P && I < L; I++)
+ parent = !!~E(R).indexOf(Q)? Q: G;
- parent = ancestor? !~E(P.parentElement).indexOf(P)? null: P: P.parentElement;
+ for(let I = 0, L = -generations; !!Q && !!P && I < L; I++)
+ parent = Q = (P = Q).parentElement;
- if(!~parents.indexOf(parent))
- parents.push(parent);
+ if(!~parents.indexOf(parent))
+ parents.push(parent);
});
media.push(parents.length? parents: elements);
}
@@ -1811,7 +2244,11 @@ String.prototype.toCaps = function toCaps(all) {
child: {
value: index => media[index - 1],
...properties
- }
+ },
+ empty: {
+ value: !media.length,
+ ...properties
+ },
});
return media;
@@ -1820,8 +2257,8 @@ String.prototype.toCaps = function toCaps(all) {
/** Adopted from
* LICENSE: MIT (2018)
*/
- parent.furnish = function furnish(name, attributes = {}, ...children) {
- let u = v => v && v.length, R = RegExp;
+ parent.furnish = parent.furnish || function furnish(TAGNAME, ATTRIBUTES = {}, ...CHILDREN) {
+ let u = v => v && v.length, R = RegExp, name = TAGNAME, attributes = ATTRIBUTES, children = CHILDREN;
if( !u(name) )
throw TypeError(`TAGNAME cannot be ${ (name === '')? 'empty': name }`);
@@ -1842,7 +2279,7 @@ 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);
@@ -1851,9 +2288,23 @@ String.prototype.toCaps = function toCaps(all) {
attributes.classList = attributes.classList.join(' ');
Object.entries(attributes).forEach(
- ([name, value]) => (/^(on|(?:inner|outer)(?:HTML|Text)|textContent|class(?:List|Name)$|value)/.test(name))?
+ ([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)
+ element.setAttribute(name, value)
);
children
|