/*jslint indent: 4, maxerr: 50, browser: true, devel: true, sub: false, fragment: false, nomen: true, plusplus: true, bitwise: true, regexp: true, newcap: true */ // ==UserScript== // Do not modify and re-release this script! // If you would like to add support for other sites, please tell me and I'll put it in the includes. // @id youtube-me-again // @name YouTube Me Again! // @namespace hateradio))) // @author hateradio // @version 7.2 // @description ytma! automatically converts YouTube(TM), Vimeo, Vine, Soundcloud, WebM, and MP4 links into real embedded videos. // @homepage https://greasyfork.org/en/scripts/1023-youtube-me-again // @icon https://www.dropbox.com/s/b85qmq0bsim407s/ytma32.png?dl=1 // @icon64 https://www.dropbox.com/s/5zw3al38yf39wxb/ytma64.png?dl=1 // @screenshot https://www.dropbox.com/s/syy9916b1prygl9/ytmascreen5.png?dl=1 // @include https://vine.co/v/*/embed/simple // @match https://vine.co/v/*/embed/simple // @include http*://*youtube-nocookie.com/embed/* // @match *://*.youtube-nocookie.com/embed/* // @include http*://*youtube.com/embed/* // @match *://*.youtube.com/embed/* // @include https://gfycat.com/iframe/* // @match https://gfycat.com/iframe/* // @include http*://*.neogaf.com/forum/showthread.php* // @include http*://*.neogaf.com/forum/showpost.php?p* // @include http*://*.neogaf.com/forum/newreply.php* // @include http*://*.neogaf.com/forum/editpost.php* // @include http*://*.neogaf.com/forum/private.php* // @match *://*.neogaf.com/forum/showthread.php* // @match *://*.neogaf.com/forum/showpost.php?p* // @match *://*.neogaf.com/forum/newreply.php* // @match *://*.neogaf.com/forum/editpost.php* // @match *://*.neogaf.com/forum/private.php* // @include http*://*what.cd/forums.php?*viewthread* // @include http*://*what.cd/torrents.php?* // @include http*://*what.cd/user.php?* // @match *://*.what.cd/forums.php?*viewthread* // @match *://*.what.cd/torrents.php?* // @match *://*.what.cd/user.php?* // @updated 03 Jul 2017 // @grant GM_xmlhttpRequest // @grant unsafeWindow // @run-at document-end // @downloadURL none // ==/UserScript== /* ## Updates #### 7.2 * New: CSS rule to make videos fit within smaller windows * New: GitHub repository and update links * New: Streamable favicon * Fix: Vimeo favicon #### 7.1 * HTTPS links for Vimeo and Gfycat * Fix: Safari bug #### 7 * New: NeoGAF HTTPS Support * New: Streamable.com added * New: Soundcloud playlist support * Improved time parser * Upon scrolling, cached descriptions are shown * Code reorganization makes adding new media sites easier ### 6 * New: Imgur GIFV (WEBM/MP4) support * New: Button to remove cache (descriptions/thumbnail links/etc) * New: SoundCloud playlist support * Default video quality is now 720p/HD * Soundcloud now uses HTML5 player * Players that open on scroll will no longer trigger the opening of players higher on the page * Adds HTML5, Gfycat, Imgur icons on links * Improved Soundcloud and GfyCat URL matchers * Restructured code base to simplify creation of media controls * Restructured CSS * Patched back Gfycat iFrame setting for Safari (it is incompatible with new settings) * Updates YouTube data API * Removes: * Object tag for YouTube for Flash (Deprecated) * "Batch" loading of descriptions (Only manual and scroll methods are supported) // #Updates Whitelist these sites on NoScript/NotScript/etc. ------------------------------------------------ * neogaf.com * youtube.com * youtube-nocookie.com * googlevideo.com (HTML5 player sends videos from this domain) * googleapis.com (YT video descriptions) * vimeo.com * vimeocdn.com * soundcloud.com * sndcdn.com * vineco.com * vine.com * vine.co * gfycat.com * github.io Whitelist these on Ghostery --------------------------- * SoundCloud (Widgets, Audio / Music Player) */ (function () { 'use strict'; var $$, strg, update; if (!Function.prototype.bind) { Function.prototype.bind = function (self) { var args = [].slice.call(arguments, 1), fn = this; return function () { return fn.apply(self, args.concat([].slice.call(arguments))); }; }; } function isNumber(n) { return !isNaN(parseFloat(n)) && isFinite(n); } function inject(func) { var script = document.createElement('script'); script.type = 'text/javascript'; script.textContent = '(' + func + ')();'; document.body.appendChild(script); document.body.removeChild(script); } function removeSearch(uri, keepHash) { // removes search query from a uri var s = uri.indexOf('?'), h = uri.indexOf('#'), hash = ''; if (s > -1) { if (keepHash && h > -1) { hash = uri.substr(h); } uri = uri.substr(0, s) + hash; } return uri; } // D O M Handle $$ = { s: function (selector, cb) { var s = document.querySelectorAll(selector), i = -1; while (++i < s.length) { if (cb(s[i], i, s) === false) { break; } } }, o: function (object, cb) { var i; for (i in object) { if (object.hasOwnProperty(i)) { if (cb(i, object[i], object) === false) { break; } } } }, a: function (e) { var i = 1, j = arguments.length, f = document.createDocumentFragment(); for (i; i < j; i++) { if (arguments[i]) { f.appendChild(arguments[i]); } } e.appendChild(f); return e; }, e: function (t, o, e, p) { var c = document.createElement(t); $$.o(o, function (k, v) { var b = k.charAt(0); switch (b) { case '_': c.dataset[k.substring(1)] = v; break; case '$': c.setAttribute(k.substring(1), v); break; default: c[k] = v; } }); if (e && p) { c.appendChild(e); } else if (e) { e.appendChild(c); } return c; }, x: function (selector) { return this.ary(document.querySelectorAll(selector)); }, ary: function (ary) { return Array.from ? Array.from(ary) : Array.prototype.slice.call(ary); }, top: document.head || document.body, css: function (t) { if (!this.style) { this.style = document.createElement('style'); this.style.type = 'text/css'; this.top.appendChild(this.style); } this.style.appendChild(document.createTextNode(t + '\n')); }, js: function (t) { var j = document.createElement('script'); j.type = 'text/javascript'; j[/^https?\:\/\//i.test(t) ? 'src' : 'textContent'] = t; this.top.appendChild(j); } }; // S T O R A G E HANDLE strg = { MAX: 5012, on: false, test: function () { try { var a, b = localStorage, c = Math.random().toString(16).substr(2, 8); b.setItem(c, c); a = b.getItem(c); return a === c ? !b.removeItem(c) : false; } catch (e) { return false; } }, read: function (key) { try { return JSON.parse(localStorage.getItem(key)); } catch (e) { console.error(e.lineNumber + ':' + e.message); return undefined; } }, save: function (key, val) { return this.on ? !localStorage.setItem(key, JSON.stringify(val)) : false; }, wipe: function (key) { return this.on ? !localStorage.removeItem(key) : false; }, zero: function (o) { var k; for (k in o) { if (o.hasOwnProperty(k)) { return false; } } return true; }, grab: function (key, def) { var s = strg.read(key); return strg.zero(s) ? def : s; }, size: function () { var length = 0, key; try { for (key in window.localStorage) { if (window.localStorage.hasOwnProperty(key)) { length += window.localStorage[key].length; } } } catch (e) {} return 3 + ((length * 16) / (8 * 1024)); }, full: function () { try { var date = +(new Date()); localStorage.setItem(date, date); localStorage.removeItem(date); return false; } catch (e) { if (e.name === 'QuotaExceededError' || e.name === 'NS_ERROR_DOM_QUOTA_REACHED') { return true; } } }, init: function () { this.on = this.test(); } }; strg.init(); // U P D A T E HANDLE update = { name: 'ytma!', version: 7200, key: 'ujs_YTMA_UPDT_HR', callback: 'ytmaupdater', page: 'https://greasyfork.org/scripts/1023-youtube-me-again', urij: 'https://hateradio.github.io/ytma/update.json', interval: 5, day: (new Date()).getTime(), time: function () { return new Date(this.day + (1000 * 60 * 60 * 24 * this.interval)).getTime(); }, notification: function (j) { if (this.version < j.version) { strg.save(this.key, { date: this.time(), version: j.version, page: j.page }); this.link(); } }, link: function () { this.csstxt(); var a = document.createElement('a'), b = strg.read(this.key); a.href = b.page || '#'; a.id = 'userscriptupdater2'; a.title = 'Update now.'; a.target = '_blank'; a.textContent = 'An update for ' + this.name + ' is available.'; a.addEventListener('click', function () { this.style.display = 'none'; }, false); document.body.appendChild(a); }, xhr: function () { var x = new XMLHttpRequest(); x.addEventListener('load', function () { update.notification(JSON.parse(this.responseText)); }, false); x.open('get', update.urij, true); x.send(); }, check: function (opt) { if (!strg.on) { return; } if (window.chrome && window.chrome.extension) { return; } var stored = strg.read(this.key), page; if (opt || !stored || stored.date < this.day) { page = (stored && stored.page) || '#'; strg.save(this.key, {date: this.time(), version: this.version, page: page}); this.xhr(); } else if (this.version < stored.version) { this.link(); } }, csstxt: function () { if (!this.pop) { this.pop = true; $$.css('#userscriptupdater2,#userscriptupdater2:visited{box-shadow:1px 1px 6px #7776;border-bottom:3px solid #d65e55;cursor:pointer;color:#555;font-family:sans-serif;font-size:12px;font-weight:700;text-align:justify;position:fixed;z-index:999999;right:10px;top:10px;background:#ebebeb url(data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxOTguODQ4NTMgMTk5LjM4MzA3Ij48ZyB0cmFuc2Zvcm09InRyYW5zbGF0ZSgtNC4yNzYgLTE2LjM2NykiPjxjaXJjbGUgY3g9IjEwNC4zMjEiIGN5PSIxMTYuMzI3IiByPSI5OC4yNzQiIGZpbGw9IiNkNjVlNTUiLz48cGF0aCBmaWxsPSIjZTljZTAyIiBzdHJva2U9IiNlOWM4MDIiIHN0cm9rZS13aWR0aD0iMTYuNyIgZD0iTTE2Ni40NSAxNTcuMzEySDQxLjg5bDMxLjE0LTUzLjkzNSAzMS4xNC01My45MzUgMzEuMTM3IDUzLjkzNXoiIHN0cm9rZS1saW5lam9pbj0icm91bmQiLz48dGV4dCB4PSI4NS42NDMiIHk9IjE1MS44NjYiIGZpbGw9IiNkNjVlNTUiIHN0cm9rZS13aWR0aD0iMS40NzciIHN0eWxlPSJsaW5lLWhlaWdodDoxLjI1Oy1pbmtzY2FwZS1mb250LXNwZWNpZmljYXRpb246J0Jvb2sgQW50aXF1YSciIGZvbnQtd2VpZ2h0PSI0MDAiIGZvbnQtc2l6ZT0iNTkuMDg4IiBmb250LWZhbWlseT0iQm9vayBBbnRpcXVhIiBsZXR0ZXItc3BhY2luZz0iMCIgd29yZC1zcGFjaW5nPSIwIj48dHNwYW4geD0iODUuNjQzIiB5PSIxNTEuODY2IiBzdHlsZT0iLWlua3NjYXBlLWZvbnQtc3BlY2lmaWNhdGlvbjonQm9vayBBbnRpcXVhJyIgZm9udC13ZWlnaHQ9IjcwMCIgZm9udC1zaXplPSIxMjYuMDU0Ij4hPC90c3Bhbj48L3RleHQ+PC9nPjwvc3ZnPg==) no-repeat 10px center;background-size:40px;padding:0 20px 0 60px;height:55px;line-height:55px}#userscriptupdater2:hover,#userscriptupdater2:visited:hover{color:#b33a3a !important;border-color:#ce4b30}'); } } }; update.check(); /** Y T M A CLASS * Bare YTMA class, filled through _new() or _reactivate() */ function YTMA() {} YTMA.events = { clicks: function (e) { // YTMA global click dispatcher var t = e.target; if (t) { // console.log('YTMA.clicks'); if (t.tagName === 'VAR' && t.hasAttribute('data-ytmuid')) { // trigger the ui console.log('show', t.dataset.ytmuid); YTMA.UI.createFromTrigger(t).showPlayer(); } else if (t.hasAttribute('data-ytmdescription')) { console.log('load', t.dataset.ytmid); YTMA.external.events.manualLoad(e); } } }, thumb: { start: function (e) { var el = e.target; el.dataset.thumb = el.dataset.thumb > 0 ? (el.dataset.thumb % 3) + 1 : 2; el.style.backgroundImage = ['url(https://i3.ytimg.com/vi/', el.dataset.ytmid, '/', el.dataset.thumb, '.jpg)'].join(''); el.dataset.timeout = window.setTimeout(YTMA.events.thumb.start.bind(this, e), 800); }, stop: function (e) { window.clearTimeout(e.target.dataset.timeout); } } }; YTMA.num = 0; YTMA.addToSet = function (ytma) { YTMA.set[ytma.data.uid] = ytma; }; YTMA.create = function (link) { return YTMA.grabIdAndSite(link, function (data, err) { if (err) { console.error(link.href, err); return {}; } var y = new YTMA()._new(data.id, data.site, link); YTMA.addToSet(y); y.setup(); return y; }); }; YTMA.grabIdAndSite = function (link, cb) { var uri = link.href, id, site, match; try { site = YTMA.reg.siteByTest[YTMA.reg.siteExpressions.test(uri) ? RegExp.lastMatch : '']; // console.log(site); if (site === 'html5') { // || site === 'html5-audio' id = uri.slice(-15); } else if (site === 'soundcloud') { if (!YTMA.reg.extra.soundcloud.playlist.test(uri)) { link.href = uri = YTMA.reg.fix.soundcloud(uri); } match = YTMA.DB.sites.soundcloud.matcher.exec(uri); id = YTMA.escapeId(match[1]); if (match && YTMA.reg.extra.soundcloud.tracks.test(uri)) { id = id.slice(-50); } } else { id = uri.match(YTMA.DB.sites[site].matcher)[1]; } console.log(id, site, match); if (id && YTMA.DB.sites[site]) { return cb({id: id, site: site}, null); } throw TypeError('Invalid ID/Site: ' + id + ' @ ' + site); } catch (e) { return cb(null, e); } }; YTMA.escapeId = function (id) { return (id += '').replace(/(?:\W)/g, '_'); }; YTMA.route = { host: document.location.host.replace('www.', ''), control: { $: { patchSafari: function () { delete YTMA.DB.sites.gfycat.videoTag; delete YTMA.DB.sources.gfycat; }, checkStorage: function () { if (strg.full() === true) { console.log('YTMA ERROR: Storage is full!'); try { localStorage.removeItem(YTMA.external.version); strg.on = strg.test(); } catch (e) { console.error(e); } } }, runOnce: function () { if (!document.body.hasAttribute('ytma-enabled')) { document.body.setAttribute('ytma-enabled', true); this.checkStorage(); if (!YTMA.DB.extension) { update.check(); } YTMA.css(); YTMA.user.init(); YTMA.DB.postInit(); document.body.addEventListener('click', YTMA.events.clicks, false); } } }, go: function (host) { if (/(?:googlevideo|youtube-nocookie\.com|youtube\.com\.?)/i.test(host)) { this.sites.youtube(); } else if (this.sites[host]) { this.sites[host](); } else { this.sites.$generic(); } }, sites: { $generic: function () { YTMA.route.control.$.runOnce(); if (YTMA.DB.browser.safari) { // safari patch YTMA.route.control.$.patchSafari(); } if (YTMA.selector.processor() > 0) { YTMA.user.fn.loadPreferences(); } }, 'gfycat.com': function () { var v = document.querySelector('video'); v.controls = true; $$.css('body,html {overflow:hidden;height:100%;width:100%} video {display:table;height:100%;margin:0 auto;}'); document.body.appendChild(v); }, 'vine.co': function () { // console.log('vine.co'); window.addEventListener('resize', function () { $$.s('[style]', function (e) { e.removeAttribute('style'); }); }); }, youtube: function () { // lets force some quality parity console.log('now inside youtube . . .', document.location); if (/(?:vq=(\w+))/.test(document.location.search)) { document.body.setAttribute('gm-player-quality', RegExp.lastParen); } if (/(?:volume=(\d+))/.test(document.location.search)) { document.body.setAttribute('gm-player-volume', RegExp.lastParen); } inject(function () { var max = 10, count = 1, intr = window.setInterval(function () { console.log('inside says: ', count, !!window.yt); if (window.yt && window.player) { var p = window.yt.player.getPlayerByElement(window.player); if (document.body.hasAttribute('gm-player-quality')) { console.log('inside says: setting quality to ', document.body.getAttribute('gm-player-quality')); p.setPlaybackQuality(document.body.getAttribute('gm-player-quality')); } if (document.body.hasAttribute('gm-player-volume')) { console.log('inside says: setting volume to ', document.body.getAttribute('gm-player-volume')); p.setVolume(document.body.getAttribute('gm-player-volume')); } window.clearInterval(intr); } else { console.log(count); count += 1; if (count > max) { window.clearInterval(intr); } } }, 500); }); } } }, load: function () { this.control.go(this.host); } }; YTMA.main = function () { YTMA.reg.siteExpressions = YTMA.DB.views.getAllSiteRegExps(); // console.log(YTMA.reg.siteExpressions); YTMA.route.load(); }; YTMA.set = {}; YTMA.collect = function (id) { var i, a = []; for (i in YTMA.set) { if (YTMA.set.hasOwnProperty(i) && YTMA.set[i].data.id === id) { a.push(YTMA.set[i]); } } return a; }; YTMA.reg = { siteExpressions: null, time: /(?:t\=(?:(\d+)m)?(\d+))/, ios: /(?:\b(?:ipod|iphone|ipad))\b/i, extra: { soundcloud: { playlist: /(?:soundcloud\.com\/.+\/sets\/)/, tracks: /(?:soundcloud\.com\/.+\/tracks\/)/ } }, siteByTest: { youtu: 'youtube', vimeo: 'vimeo', vine: 'vine', gfycat: 'gfycat', imgur: 'imgur', '.webm': 'html5', '.mp4': 'html5', // '.mp3': 'html5-audio', '.gifv': 'html5', soundcloud: 'soundcloud', 'streamable.com': 'streamable' }, fix: { soundcloud: function (uri) { var match = YTMA.DB.sites.soundcloud.matcher.exec(uri), id; if (match) { id = match[1].split('/', 2).join('/'); uri = removeSearch('https://soundcloud.com/' + id, true); } return uri; } } }; YTMA.selector = { // to build the selector parentBlacklist: ['.smallfont', '.colhead_dark', '.spoiler', 'pre'], chrome37Blacklist: 'a[href*="pomf.se/"]', ignore: function () { var i, j, ignore = [], all = YTMA.DB.views.getAllSiteSelectors().split(','), blacklist = this.parentBlacklist; for (i = 0; i < blacklist.length; i++) { for (j = 0; j < all.length; j++) { ignore.push(blacklist[i] + ' ' + all[j]); } } //console.log(ignore); return ignore.join(','); }, links: function () { var links; $$.x(YTMA.selector.ignore()).map(function (el) { el.setAttribute('ytmaignore', true); }); links = $$.x(YTMA.DB.views.getAllSiteSelectors()).filter(function (el) { var r = !el.hasAttribute('ytmaprocessed') && !el.hasAttribute('ytmaignore'); el.setAttribute('ytmaprocessed', true); return r; }); return links; }, processor: function () { var links = this.links(); if (links.length > 0) { if (window.chrome && (/(?:Chrome\/(\d+))/.exec(window.navigator.appVersion) && RegExp.lastParen < 38)) { $$.s(YTMA.selector.chrome37Blacklist, function (a) { if (/(?:\.webm)/i.test(a.href)) { a.dataset.ytmscroll = false; } }); } links.forEach(YTMA.create); } return links.length; } }; /** * User Preferences * size: Small (240p), Medium (360p), Large (480p), XL (720p) * ratio: 1 4:3, 2 16:9 * quality: 240, 360, 480, 720, 1080 * focus: 0/1; Will attempt to set the window's focus near the video * autoShow: 0/1; Will automatically display HTML5 videos, which currently lack descriptions and thumbnails * desc: (Descriptions) 0 None; 1 Yes on scroll; 2 Yes all at once * yt_nocookie: 0/1; Will disable/enable youtube-nocookie.com * yt_volume: positive number; youtube volume * yt_annotation: 0/1; youtube annotations */ YTMA.user = { KEY: 'ytmasetts', $form: null, init: function () { this.load(); if (strg.on) { this.fn.makeForm(); this.mark(); } }, valid: { focus: [0, 1], desc: [0, 1, 2], ratio: [1, 2], size: [240, 360, 480, 720], quality: [240, 360, 480, 720, 1080], autoShow: [0, 1], yt_nocookie: [0, 1], yt_annotation: [0, 1], // hide | show yt_volume: 100 // todo? (function () { var a = new Array(101); for (i = 0; i <= 100; i++) { a[i] = i; } return a; }()) }, mapping: { // map values to some other values used by an external API, for example yt_annotation: [3, 1] // 3 = hide | 1 = show }, validate: function (property, n) { n = +n; if (property === 'yt_volume') { return n >= 0 && n <= 100 ? (+n) : YTMA.user.defaults()[property]; } return YTMA.user.valid[property].indexOf(n) > -1 ? n : YTMA.user.defaults()[property]; }, defaults: function () { return { focus : 0, desc : 1, ratio : 2, size : 360, quality : 720, autoShow : 1, yt_nocookie : 0, yt_annotation : 1, yt_volume : 100 }; }, load: function () { var s = strg.grab(YTMA.user.KEY, {}); YTMA.user.preferences = { size : YTMA.user.validate('size', s.size), ratio : YTMA.user.validate('ratio', s.ratio), desc : YTMA.user.validate('desc', s.desc), focus : YTMA.user.validate('focus', s.focus), quality : YTMA.user.validate('quality', s.quality), autoShow : YTMA.user.validate('autoShow', s.autoShow), yt_nocookie : YTMA.user.validate('yt_nocookie', s.yt_nocookie), yt_annotation : YTMA.user.validate('yt_annotation', s.yt_annotation), yt_volume : YTMA.user.validate('yt_volume', s.yt_volume) }; $$.o(YTMA.user.mapping, function (key, val) { if (!val.hasOwnProperty('indexOf')) { YTMA.user.preferences[key] = val[YTMA.user.valid[key].indexOf(YTMA.user.preferences[key])]; } }); console.log('loaded: ', YTMA.user.preferences); }, mark: function () { var a = {}; a.ytma__focus = !!YTMA.user.preferences.focus; a.ytma__autoShow = !!YTMA.user.preferences.autoShow; a.ytma__yt_nocookie = !!YTMA.user.preferences.yt_nocookie; a.ytma__yt_annotation = !!YTMA.user.preferences.yt_annotation; a.ytma__yt_volume = YTMA.user.preferences.yt_volume; a['ytma__ratio' + YTMA.user.preferences.ratio] = true; a['ytma__size' + YTMA.user.preferences.size] = true; a['ytma__desc' + YTMA.user.preferences.desc] = true; a['ytma__quality' + YTMA.user.preferences.quality] = !!YTMA.user.preferences.quality; // console.log('marking', a); $$.o(a, function (id, val) { try { var el = document.getElementById(id); el.checked = val; el.value = val; } catch (e) { // console.log(id, e); } }); }, events: { save: function (e) { var o = {}; if (e && /(?:INPUT|LABEL)/i.test(e.target.nodeName)) { // console.log(YTMA.user.$form.querySelectorAll('[data-key]')); // [data-key]:checked $$.ary(YTMA.user.$form.querySelectorAll('[data-key]')).forEach(function (e) { var key; key = e.dataset.key; if (e.type === 'checkbox') { o[key] = +e.checked; } else if (e.type === 'radio') { if (e.checked) { if (e.hasAttribute('data-num')) { o[key] = +e.dataset.num; } } } else { o[key] = +e.value; } }); if (strg.save(YTMA.user.KEY, o)) { YTMA.user.load(); } else { YTMA.user.error.classList.remove('ytm_none'); } } }, reset: function () { YTMA.user.preferences = YTMA.user.defaults(); YTMA.user.mark(); strg.wipe(YTMA.user.KEY); YTMA.user.error.classList.add('ytm_none'); }, clear: function () { try { localStorage.removeItem(YTMA.external.version); YTMA.user.events.reset(); console.log('removed all YTMA cache'); } catch (e) { console.error(e); } }, formToggle: function (e) { if (!e || (e && e.target && !/(?:INPUT|LABEL)/i.test(e.target.nodeName))) { YTMA.user.$form.classList.toggle('ytm_none'); } }, formToggleKeyboard: function (e) { // press CTRL+SHIFT+Y (META+SHIFT+Y) to display settings form if ((e.ctrlKey || e.metaKey) && e.shiftKey && String.fromCharCode(e.which).toLowerCase() === 'y') { e.preventDefault(); YTMA.user.events.formToggle(); } } }, fn: { $scroller: null, $once: false, loadPreferences: function () { YTMA.user.fn.onScrollLoadDescriptions(YTMA.user.preferences.desc === 1); this.loadPreferencesOnce(); }, loadPreferencesOnce: function () { if (this.$once) { return; } this.$once = true; if (YTMA.user.preferences.autoShow === 1) { YTMA.user.fn.onScrollViewMedia(); } }, showMedia: function () { console.log('showMedia'); return new YTMA.Scroll('a.ytm_scroll:not([data-ytmscroll="false"])', function (link) { if (YTMA.Scroll.visibleAll(link, 50)) { $$.s('var[data-ytmsid="' + link.dataset.ytmsid + '"]:not([data-ytmscroll="false"])', function (trigger) { var ui = YTMA.UI.createFromTrigger(trigger); ui.showOnScroll(link); }); } }); }, toggleMedia: function () { return new YTMA.Scroll('div.ytm_panel_switcher', function (div) { var v = div.querySelector('video'), paused = v && (v.paused || v.ended), ui = YTMA.set[div.dataset.ytmuid].getUI(); if (paused && !YTMA.Scroll.visibleAll(div, 0)) { return ui.play.switchStandby(); } if (ui.play.isStandby() && YTMA.Scroll.visibleAll(div, 200)) { return ui.play.switchOn(); } // todo ascertain embedded player properties // f = div.querySelector('iframe, object'); // if (f && !YTMA.Scroll.visibleAll(div, 200)) { // y.hidePlayer(); // } }); }, onScrollViewMedia: function () { this.showMedia(); this.toggleMedia(); }, onScrollLoadDescriptions: function (ajax) { if (YTMA.user.fn.$scroller) { YTMA.user.fn.$scroller.stop(); } YTMA.user.fn.$scroller = new YTMA.Scroll('span.ytm_manual > a.ytm_title:not(.ytm_error)', function (a) { if (YTMA.Scroll.visibleAll(a, 200)) { if (ajax) { YTMA.ajax.loadFromDataset(a.dataset); } else { YTMA.ajax.loadFromCacheDataset(a.dataset); } // console.log('doc', document.querySelectorAll(YTMA.user.fn.$scroller.selector).length, a.dataset.id); } if (document.querySelectorAll(YTMA.user.fn.$scroller.selector).length === 0) { YTMA.user.fn.$scroller.stop(); } }); }, makeForm: function () { var e, f = [ '
' ].join(''); YTMA.user.$form = $$.e('div', {className: 'ytm_fix_center ytm_none ytm_box', innerHTML: f}, document.body); YTMA.user.error = document.getElementById('ytm_settings_error'); e = YTMA.Scroll.debounce(YTMA.user.events.save, 500); YTMA.user.$form.addEventListener('submit', function (evt) { evt.preventDefault(); }, false); YTMA.user.$form.addEventListener('keyup', e, false); YTMA.user.$form.addEventListener('click', e, false); YTMA.user.$form.addEventListener('dblclick', YTMA.user.events.formToggle, false); document.getElementById('ytmaclose').addEventListener('click', YTMA.user.events.formToggle, false); document.getElementById('ytmareset').addEventListener('click', YTMA.user.events.reset, false); document.getElementById('ytmaclear').addEventListener('click', YTMA.user.events.clear, false); document.body.addEventListener('keydown', YTMA.user.events.formToggleKeyboard, false); } } }; YTMA.css = function () { var playerCss = YTMA.Player.css.generator(), loadingIcon = 'data:image/gif;base64,R0lGODlhDgAKAJEAAP///+BKV////wAAACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgACACwAAAAADgAKAAACHFSOeQYI71p6MtAJz41162yBH+do5Ih1kKG0QgEAIfkEBQoAAgAsAAABAA0ACAAAAhSUYGEoerkgdIzKGlu2ET/9ceJmFAAh+QQFCgACACwAAAEADQAIAAACFJRhcbmiglx78SXKYK6za+NxHyYVACH5BAUKAAIALAAAAQANAAgAAAIWVCSAl+hqEGRTLhtbdvTqnlUf9nhTAQAh+QQFCgACACwAAAEADQAIAAACFZRiYCh6uaCRzNXYsKVT+5eBW3gJBQAh+QQJCgACACwAAAAADgAKAAACGpSPaWGwfZhwQtIK8VTUvuxpm9Yp4XlmpiIUADs='; // Roboto font-o // $$.e('link', { // rel: 'stylesheet', // $type: 'text/css', // href: 'https://fonts.googleapis.com/css?family=Roboto' // }, document.body); // console.log(playerCss); $$.css(playerCss); // images // todo update(site, size, padding) $$.css([ '.ytm_loading{background:url(', loadingIcon, ') 0 3px no-repeat;}', '.ytm_link{background:url(', YTMA.DB.sites.youtube.favicon, ') 0 center no-repeat !important;margin-left:4px;padding-left:20px!important;}', '.ytm_link.ytm_link_vimeo{background-image:url(', YTMA.DB.sites.vimeo.favicon, ') !important;background-size:12px 12px !important;padding-left:18px!important}', '.ytm_link.ytm_link_vine{background-image:url(', YTMA.DB.sites.vine.favicon, ') !important;background-size:10px 10px!important;padding-left:16px!important}', '.ytm_link.ytm_link_soundcloud{background-image:url(', YTMA.DB.sites.soundcloud.favicon, ')!important;padding-left:17px!important}', '.ytm_link.ytm_link_html5{background-image:url(', YTMA.DB.sites.html5.favicon, ') !important;padding-left:16px!important}', '.ytm_link.ytm_link_gfycat{background-image:url(', YTMA.DB.sites.gfycat.favicon, ') !important;background-size:12px 12px !important;padding-left:16px!important;}', '.ytm_link.ytm_link_imgur{background-image:url(', YTMA.DB.sites.imgur.favicon, ') !important;background-size:12px 12px !important;padding-left:16px!important}', '.ytm_link.ytm_link_streamable{background-image:url(', YTMA.DB.sites.streamable.favicon, ') !important; background-size: 12px 12px !important;padding-left: 14px !important;}' ].join('')); // todo // if (window.NO_YTMA_CSS) { return; } $$.css('.ytm_none,.ytm_link br{display:none!important}.ytm_box{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}.ytm_block{display:block;position:relative;clear:both;text-align:left;border:0;margin:0;padding:0;overflow:hidden}.ytm_normalize{font-weight:400!important;font-style:normal!important;line-height:1.2!important}.ytm_sans{font-family:Arial,Helvetica,sans-serif!important}.ytm_spacer{overflow:auto;margin:0 0 6px;padding:4px}.ytm_spacer.ytm_site_slim{display:inline}.ytm_clear:after{content:"";display:table;clear:both}.ytm_center{text-align:center}.ytm_link b,.ytm_link strong{font-weight:400!important}.ytm_link u{text-decoration:none!important}.ytm_link i,.ytm_link em{font-style:normal!important}.ytm_trigger{width:118px;height:66px;background-color:#262626!important;cursor:pointer;background-position:-1px -12px;float:left;box-shadow:2px 2px rgba(0,0,0,.3);background-size:auto 90px!important;color:#fff;text-shadow:#333 0 0 2px;font-size:13px}.ytm_trigger:hover{box-shadow:2px 2px #9eae9e;opacity:.95}.ytm_trigger var{z-index:2;height:100%;width:100%;position:absolute;left:0;top:0;text-align:right}.ytm_label{display:block;padding:3px 6px;line-height:1.2;font-style:normal}.ytm_init{height:22px;background:rgba(11,11,11,.62);padding:4px 25px 6px 6px}.ytm_site_vine .ytm_trigger{background-color:#90ee90!important;background-size:120px auto!important}.ytm_site_slim .ytm_trigger{background:#e34c26!important;height:auto;box-shadow:0 0 2px #ffdb9d inset,2px 2px rgba(0,0,0,.3);margin:0 3px 0 0;width:auto;transition:all .3s ease-in-out 0s}.ytm_site_slim .ytm_trigger:hover{opacity:.8}.ytm_site_slim .ytm_label{text-shadow:0 0 1px #f06529}.ytm_site_slim .ytm_init{background:transparent}.ytm_bd{float:left;max-width:500px;margin:2px 10px;font-size:90%}.ytm_title{font-weight:700}.ytm_error{color:#cc2f24;font-style:italic}.ytm_loading{font-style:italic;padding:1px 1.5em}.ytm_descr{word-wrap:break-word;max-height:48px;overflow:auto;padding-right:20px}.ytm_descr[data-full]{cursor:pointer}.ytm_descr_open{resize:both;white-space:pre-line}.ytm_descr_open[style]{max-height:none}.ytm_projector{margin-bottom:4px}ul.ytm_control{overflow:hidden;margin:0!important;padding:3px 0 1px;list-style-position:outside!important}.ytm_control li{display:inline;margin:0!important;padding:0!important}.ytm_control li>ul{display:inline-block;margin:0;padding:0 1px 0 0}.ytm_control li ul li{-webkit-user-select:none;-moz-user-select:none;-o-user-select:none;user-select:none;list-style-type:none;cursor:pointer;float:left;color:#858585;border:1px solid #1d1d1d;border-bottom:1px solid #000;border-top:1px solid #292929;box-shadow:0 0 1px #555;height:14px;font-size:12px!important;line-height:12px!important;background:#222;background:linear-gradient(#2d2c2c,#222);margin:0!important;padding:5px 9px 3px!important}.ytm_control li ul li:first-child{border-radius:2px 0 0 2px}.ytm_control li ul li:last-child{border-left:0!important;border-radius:0 2px 2px 0;margin:0 2px 0 0!important}.ytm_control li ul li:first-child:last-child,.ytm_li_setting{border-radius:2px}.ytm_control li ul li:hover{color:#ccc;text-shadow:1px 1px 0 #333;background:#181818}.ytm_control li ul li[id]{color:#ddd;text-shadow:0 0 2px #444}.ytm_panel_size{background:#000;max-width:100%;}.ytm_panel_switcher[data-standby="true"]{background:#111}.ytm_panel_switcher[data-standby="true"]:after{cursor:cell;color:#0e0e0e;content:"ytma!";display:block;font-size:85px;font-style:italic;font-weight:700;left:50%;position:absolute;text-shadow:2px 1px #181818,-1px -1px #0a0a0a;top:50%;transform:translate(-50%,-50%)}.ytm_site_soundcloud .ytm_panel_size.ytm_soundcloud-playlist{height:334px!important}.ytm_fix_center{background:rgba(51,51,51,.41);height:100%;left:0;position:fixed;top:0;width:100%;z-index:99998}#ytm_settings{z-index:99999;max-width:500px;max-height:85%;overflow:auto;background:#fbfbfb;border:1px solid #bbb;color:#444;box-shadow:0 0 5px rgba(0,0,0,.2),0 0 3px rgba(239,239,239,.1) inset;margin:4% auto;padding:4px 8px 0}#ytm_settings p{margin:5px 0;padding:0}#ytm_settings fieldset{vertical-align:top;border-radius:3px;border:1px solid #ccc;margin:0 0 5px}#ytm_settings fieldset span{display:inline-block;min-width:5em}#ytm_settings input{vertical-align:baseline!important;margin:3px 5px!important}#ytm_settingst{font-size:110%;border-bottom:1px solid #d00;margin:3px 0 9px;padding:0 3px 3px}#ytm_settings label{cursor:pointer}#ytm_settings small{font-size:90%}#ytm_opts button{cursor:pointer;margin:10px 5px 8px 2px;padding:3px;border:1px solid #adadad;border-radius:2px;background:#eee;font-size:90%}#ytm_opts button:hover{background:#ddd}'); // $$.css('.ytm_site_youtube .ytm_sans { font-family: \'Roboto\'; }'); }; YTMA.ajax = { load: function (site, id, uri) { console.log('YTMA.ajax.load:', site, id, uri); uri = YTMA.DB.sites[site].ajax.replace('%key', id).replace('%uri', uri); if (YTMA.DB.sites[site].ajaxExtension) { return this.gmxhr(uri, site, id); } console.log('ajax.site?', YTMA.DB.sites[site].ajax.replace('%key', id).replace('%uri', uri)); if (YTMA.DB.sites[site].ajax) { console.log('preping uri'); return this.xhr(uri, site, id); } return null; }, loadFromDataset: function (dataset) { if (!this.loadFromCacheDataset(dataset)) { return this.load(dataset.ytmsite, dataset.ytmid, dataset.ytmuri); } }, loadFromCacheDataset: function (dataset) { var cache = YTMA.external.dataFromStorage(dataset.ytmsite, dataset.ytmid); console.log('YTMA.ajax.cache:', dataset.ytmsite, dataset.ytmid); console.log('@cache:', cache); if (cache) { YTMA.external.populate(cache); } return cache; }, gmxhr: function (uri, site, id) { try { // console.log('gmxhr starting!'); GM_xmlhttpRequest({ method: 'GET', url: uri, onload: function (response) { // console.log(response); YTMA.external.parse(response.responseText, site, id); }, onerror: function () { console.log('GM Cannot XHR'); YTMA.ajax.failure.call({id: id}); } }); YTMA.ajax.preProcess(id); } catch (e) { if (YTMA.DB.extension) { console.log('attempting cs xhr'); this.xhr(uri, site, id); } else { console.log('No applicable CORS request available.'); this.failure.call({id: id}); } } }, xhr: function (uri, site, id) { var x = new XMLHttpRequest(); console.log('xhr', uri, id, site); YTMA.ajax.preProcess(id); x.onreadystatechange = function () { if (this.readyState === this.DONE) { // console.log(this.readyState, this.status); if (this.status === 200) { YTMA.external.parse(this.responseText, site, id); } else if (this.status === 403) { YTMA.external.populate({site: site, id: id, title: 'Error 403', desc: ''}); YTMA.external.save({site: site, id: id, title: 'Error 403', desc: ''}); } else { // if (this.status >= 400 || this.status === 0) { YTMA.ajax.failure.call({id: id}); } } }; try { console.log('sending'); x.open('get', uri, true); x.send(); } catch (e) { console.error('Cannot send xhr', uri); YTMA.ajax.failure.call({id: id}); console.error(e); } }, failure: function () { $$.s('.ytm_bd._' + YTMA.escapeId(this.id), function (el) { var a = el.querySelector('a'); a.dataset.tries = a.dataset.tries ? parseFloat(a.dataset.tries) + 1 : 1; a.textContent = 'Error, unable to load data. ' + (a.dataset.tries > 0 ? ('(' + a.dataset.tries + ')') : '[Retry]'); a.className = 'ytm_error ytm_title'; }); }, preProcess: function (id) { $$.s('.ytm_manual._' + YTMA.escapeId(id) + ' a', function (el) { el.classList.add('ytm_loading'); el.textContent = 'Loading data . . .'; el.title = 'Retry loading data.'; }); } }; /** E X T E R N A L Apparatus * Data from external sites */ YTMA.external = { version: 'ytma.4.1.dat', parse: function (response, site, id) { if (this.parsers[site]) { response = YTMA.DB.sites[site].rawResponse ? response : JSON.parse(response); this.populate(this.helper.cutDescription(this.parsers[site](response, id))); } }, parsers: { soundcloud: function (j, id) { return { site: 'soundcloud', id: id, //unescape(j.html).match(/tracks\/(\d+)/)[1], title: j.title, desc: j.description, th: removeSearch(j.thumbnail_url) }; }, vimeo: function (j) { j = j[0]; return { site: 'vimeo', id: j.id, title: j.title + ' ' + YTMA.external.time.fromSeconds(j.duration), desc: j.description.replace(/