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

+
+

Loose Searching

+
+ Allows the plugin to search for non-English titles using pattern matching (as a last resort).
+ Higer sensitivity means more strict searches. +
+
+ +
+ + +
+ +
+ + +

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+/);