diff --git a/change.log b/change.log
index 69561d7..a8a45b6 100644
--- a/change.log
+++ b/change.log
@@ -229,9 +229,19 @@
- variables, most notably `options.watcherURL` would persist after being removed
- Updated the manifest's description
-## v3.1.0.0 / unreleased
+## v3.1.0.0 / SpaceK33z@11a23e7f565981bbbcedc7037c840fbcbfeed2f6
- **Added Netflix support**
- Made changes to the `String..toCaps` method
- Made searches better via `/utils.js`
- replaced some non-latin characters (i.e. the curly apostrophe) with an acceptable counter-part (a "normal" apostrophe)
+
+## v3.1.1.0 / unreleased
+
+- **Added the "Use Loose" (RegExp) option (enabled by default)**
+ - this allows the plugin to use a RegExp of the title to find possible matches
+- Removed some unimportant code (debugging)
+- Fixed file naming for the "Save As..." feature
+- Fixed searching error in `/utils.js`
+ - The strict matching for searches was incorrectly implemented
+- Added more GUI elements
diff --git a/src.crx b/src.crx
index 3513dd9..f3c8c55 100644
Binary files a/src.crx and b/src.crx differ
diff --git a/src.zip b/src.zip
index dae4b3f..f3fb0f4 100644
Binary files a/src.zip and b/src.zip differ
diff --git a/src/background.js b/src/background.js
index 142e986..35b4250 100644
--- a/src/background.js
+++ b/src/background.js
@@ -43,7 +43,7 @@ function generateHeaders(auth) {
// Object{MovieOrShowID, MovieOrShowTitle, MovieOrShowType, MovieOrShowIDProvider, MovieOrShowYear, LinkURL, FileType} => undefined
function changeStatus({ id, tt, ty, pv, yr, ur = '', ft = '' }) {
- let tl = tt.replace(/\-/g, ' ').replace(/[\s\:]{2,}/g, ' - '),
+ let tl = tt.replace(/\-/g, ' ').replace(/[\s\:]{2,}/g, ' - ').replace(/[^\w\s\-\']+/g, ''),
// File friendly title
st = tt.replace(/[\-\s]+/g, '-').replace(/[^\w\-]+/g, ''),
// Search friendly title
@@ -420,6 +420,8 @@ chrome.contextMenus.onClicked.addListener((item) => {
tl = external.T,
yr = external.Y,
tt = external.S,
+ lt = external.F,
+ ft = external.Z,
p = (s, r = '+') => s.replace(/-/g, r);
switch(db) {
@@ -455,7 +457,7 @@ chrome.contextMenus.onClicked.addListener((item) => {
else
chrome.downloads.download({
url,
- filename: `${ external.F } (${ external.Y }).${ external.Z }`,
+ filename: `${ lt } (${ yr }).${ ft }`,
saveAs: true
});
});
@@ -517,7 +519,7 @@ parentItem = chrome.contextMenus.create({
saveItem = chrome.contextMenus.create({
id: 'W2P-DL',
- title: 'Ready'
+ title: 'Nothing to Save'
});
// Standard search engines
diff --git a/src/manifest.json b/src/manifest.json
index 38cdde8..d685195 100644
--- a/src/manifest.json
+++ b/src/manifest.json
@@ -6,7 +6,7 @@
"homepage_url": "https://github.com/Ephellon/web-to-plex/",
"manifest_version": 2,
- "version": "3.1.0.0",
+ "version": "3.1.1.0",
// Firefox Support =>
// "applications": {
// "gecko": {
diff --git a/src/options/index.html b/src/options/index.html
index 2d8777f..a188ec0 100644
--- a/src/options/index.html
+++ b/src/options/index.html
@@ -43,7 +43,7 @@
margin-bottom: 5px;
}
- input, select {
+ input[type="text"], input[type="password"], select {
width: 30vw !important;
line-height: 1.5em !important;
transition: background 0.2s;
@@ -195,6 +195,121 @@
color: #999;
}
+ /* 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);
+ }
+
+ .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 label {
+ display: block;
+ width: 34px;
+ height: 20px;
+ cursor: pointer;
+ position: absolute;
+ top: 3px;
+ left: 3px;
+ z-index: 1;
+ background: #cc7b19;
+ 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;
+ }
+
+ input[type="range"] {
+ appearance: none;
+ -webkit-appearance: none;
+
+ background: #0004;
+ outline: none;
+
+ height: 5px!important;
+ width: 83%!important;
+ }
+
+ input[type="range"] + output {
+ display: inline-block;
+ position: relative;
+ width: 7%!important;
+ color: #cc7b19;
+ line-height: 20px;
+ text-align: center;
+ border-radius: 3px;
+ background: #000;
+ padding: 5px 10px;
+ margin-left: 8px;
+ vertical-align: sub;
+ }
+
+ input[type="range"] + output:after {
+ position: absolute;
+ top: 8px;
+ left: -7px;
+ width: 0;
+ height: 0;
+ border-top: 7px solid transparent;
+ border-right: 7px solid #000;
+ border-bottom: 7px solid transparent;
+ content: '';
+ }
+
+ input[type="range"]::-webkit-slider-thumb {
+ appearance: none;
+ -webkit-appearance: none;
+
+ background: #cc7b19;
+ border: 1px solid #cc7b19;
+ border-radius: 100%;
+ cursor: pointer;
+
+ height: 32px;
+ width: 32px;
+ }
+
+ input[type="range"]::-moz-range-thumb {
+ background: #cc7b19;
+ border: 1px solid #cc7b19;
+ border-radius: 100%;
+ cursor: pointer;
+
+ height: 32px;
+ width: 32px;
+ }
+
#version {
color: #fff;
@@ -518,7 +633,26 @@
Other Settings
- API Keys
+ Advance Settings
+
+ Search Options
+
Extra API Keys
diff --git a/src/options/index.js b/src/options/index.js
index ad7a0d7..3296166 100644
--- a/src/options/index.js
+++ b/src/options/index.js
@@ -57,7 +57,9 @@ const storage = (chrome.storage.sync || chrome.storage.local),
'sonarrStoragePath',
'sonarrQualityProfileId',
'OMDbAPI',
- 'TMDbAPI'
+ 'TMDbAPI',
+ 'UseLoose',
+ 'UseLooseScore'
];
let PlexServers = [],
@@ -196,7 +198,9 @@ function getOptionValues() {
`[data-option="${ option }"]`
);
- if(element)
+ if(element.type == 'checkbox')
+ options[option] = element.checked;
+ else if(element)
options[option] = element.value;
});
@@ -444,10 +448,6 @@ function saveOptions() {
storage.set({ ClientID });
}
- options.watcherURLRoot = null;
- options['c2x1ZytyaW90KzIwMTg'] = null;
- options['slug+riot+2018'] = null;
-
options.plexURL = options.plexURLRoot = (options.plexURL || "")
.replace(/([^\\\/])$/, endingSlash)
.replace(/^(?!^https?:\/\/)(.+)/, 'http://$1');
@@ -470,8 +470,6 @@ function saveOptions() {
options.sonarrStoragePath = options.sonarrStoragePath
.replace(/([^\\\/])$/, endingSlash);
- terminal.log(options);
-
for(let index = 0, array = 'plex watcher radarr sonarr couchpotato'.split(' '), item = save('URLs', array); index < array.length; index++)
save(`${ item = array[index] }.url`, options[`${ item }URLRoot`]);
@@ -516,6 +514,7 @@ function saveOptions() {
terminal.error('Error with saving', chrome.runtime.lastError.message);
chrome.storage.local.set(data, showOptionsSaved);
} else {
+ terminal.log('Saved Options:', options);
showOptionsSaved();
}
});
@@ -530,18 +529,25 @@ function restoreOptions() {
if(!el) return;
- el.value = items[option] || '';
+ if(el.type == 'checkbox')
+ el.checked = (items[option] + '') == 'true';
+ else
+ el.value = items[option] || '';
if(el.value !== '' && !el.disabled) {
- if(/password$/i.test(option))
+ if(el.type == 'checkbox')
+ el.setAttribute('save', el.checked);
+ else if(el.type == 'range')
+ el.setAttribute('save', el.value),
+ el.oninput({ target: el });
+ else if(/password$/i.test(option))
el.setAttribute('type', el.type = 'password');
else
el.placeholder = `Last save: ${ el.value }`,
el.title = `Double-click to restore value ("${ el.value }")`,
- el.setAttribute('save', el.value);
+ el.setAttribute('save', el.value),
+ el.ondblclick = event => el.value = el.getAttribute('save');
}
-
- el.ondblclick = event => el.value = el.getAttribute('save');
});
if(items.plexToken)
@@ -589,3 +595,10 @@ document
document
.querySelector('#version')
.innerHTML = `Version ${ chrome.manifest.version }`;
+document
+ .querySelectorAll('[type="range"]')
+ .forEach((element, index, array) => {
+ element.nextElementSibling.value = element.value + '%';
+
+ element.oninput = (event, self) => (self = event.target).nextElementSibling.value = self.value + '%';
+ });
diff --git a/src/utils.js b/src/utils.js
index c4b5764..db12695 100644
--- a/src/utils.js
+++ b/src/utils.js
@@ -189,7 +189,7 @@ async function getIDs({ title, year, type, IMDbID, TMDbID, TVDbID, APIType, APII
.replace(/[\u2019\u201b]/g, "'")
.replace(/[\u201c\u201d]/g, '"')
.replace(/[^\u0000-\u00ff]+/g, '');
- year = year? (year + '').replace(/\D+/g, ''): load(title) || year;
+ year = year? (year + '').replace(/\D+/g, ''): load(`${title}.${rqut}`) || year;
function plus(string) { return string.replace(/\s+/g, '+') }
@@ -237,14 +237,36 @@ async function getIDs({ title, year, type, IMDbID, TMDbID, TVDbID, APIType, APII
terminal.log('Search results', { title, year, url, json });
- if('results' in 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(/\W+/g, '');
+ c = (s = "") => t(s).replace(/\&/g, 'and').replace(/\W+/g, ''),
+ 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+/g, '|']
+ ];
+
+ for(let i = 0; i < r.length; i++) {
+ if(/^([\(\|\)]+)?$/.test(s)) return "";
+
+ s = s.replace(r[i][0], r[i][1]);
+ }
+
+ return c(s);
+ },
+ R = (s = "", S = "", n = !0) => {
+ let score = 100 * ((S.match(RegExp(`\\b(${k(s)})\\b`, 'i')) || [null]).length / (S.split(' ').length || 1)),
+ passing = config.UseLooseScore | 0;
+
+ return (S != '' && score >= passing) || (n? R(S, s, !n): n);
+ },
+ en = /^en(glish)?$/i;
// Find an exact match: Title (Year) | #IMDbID
let index, found, $data, lastscore;
@@ -252,13 +274,19 @@ async function getIDs({ title, year, type, IMDbID, TMDbID, TVDbID, APIType, APII
$data = json[index];
//api.tvmaze.com/
- if('externals' in $data)
- found = (IMDbID == $data.externals.imdb || (t($data.name) === t(title) && year == $data.premiered.slice(0, 4)))?
+ if(('externals' in $data || 'show' in $data) && $data.premiered)
+ found = (IMDbID == ($data = $data.show || $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)
+ 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;
+
for(let 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);
@@ -268,15 +296,17 @@ async function getIDs({ title, year, type, IMDbID, TMDbID, TVDbID, APIType, APII
return f? o: f;
})($data);
//api.themoviedb.org/ \remote
- else if(('original_name' in $data || 'original_title' in $data) && 'release_date' in $data)
- found = (TMDbID == $data.id || (t($data.original_name) === t(title) || t($data.original_title) === t(title) || t($data.name) === t(title)) && year == ($data || b).release_date.slice(0, 4))?
+ else if(('original_name' in $data || 'original_title' in $data) && $data.release_date)
+ found = (TMDbID == $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
+ 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);
}
// Find a close match: Title
@@ -284,20 +314,26 @@ async function getIDs({ title, year, type, IMDbID, TMDbID, TVDbID, APIType, APII
$data = json[index];
//api.tvmaze.com/
- if('externals' in $data)
+ if('externals' in $data || 'show' in $data)
found =
// ignore language barriers
- (c($data.name) == c(title))?
+ (c(($data = $data.show || $data).name) == c(title))?
$data:
// trust the api matching
- ($data.score >= lastscore)?
- (lastscore = $data.score, $data):
+ ($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)
+ 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));
@@ -307,15 +343,68 @@ async function getIDs({ title, year, type, IMDbID, TMDbID, TVDbID, APIType, APII
return f? o: f;
})($data);
//api.themoviedb.org/ \remote
- else if('original_name' in $data || 'original_title' in $data)
- found = (c($data.original_name) == c(title) || c($data.original_title) == c(title) || c($data.name) == c(title))?
+ 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(/english/i.test($data.language))
+ else if(en.test($data.language))
found = (c($data.title) == c(title))?
$data:
found;
+
+// terminal.log(`Title Matching: ${ !!found }`, !!found? found: null);
+ }
+
+ // Find an OK match: 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 && index < json.length && (!found || lastscore > 0); index++) {
+ $data = json[index];
+
+ //api.tvmaze.com/
+ if('externals' in $data || 'show' in $data)
+ found =
+ // ignore language barriers
+ (R(($data = $data.show || $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);
}
json = found;
@@ -329,7 +418,7 @@ async function getIDs({ title, year, type, IMDbID, TMDbID, TVDbID, APIType, APII
let ei = 'tt-';
//api.tvmaze.com/
- if('externals' in json)
+ if('externals' in (json = json.show || json))
data = {
imdb: IMDbID || json.externals.imdb || ei,
tmdb: TMDbID || json.externals.themoviedb | 0,
@@ -381,7 +470,7 @@ async function getIDs({ title, year, type, IMDbID, TMDbID, TVDbID, APIType, APII
save(savename, data); // e.g. "Coco (0)" on Netflix before correction / no repeat searches
save(savename = `${title} (${year}).${rqut}`, data); // e.g. "Coco (2017)" on Netflix after correction / no repeat searches
- save(title, year);
+ save(`${title}.${rqut}`, year);
terminal.log(`Saved as "${ savename }"`, data);
@@ -857,7 +946,7 @@ String.prototype.toCaps = String.prototype.toCaps || function toCaps(all) {
*/
let array = this.toLowerCase(),
titles = /(?!^|an?|the)\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,
- exceptions = /([\:\|\.\!\?\"\(]\s*[a-z]|\b[^aeiou\d\W]+\b)/gi;
+ exceptions = /([\:\|\.\!\?\"\(]\s*[a-z]|(?![\'\-\+])\b[^aeiouy\d\W]+\b)/gi;
array = array.split(/\s+/);