// ==UserScript== // @name Twonky Enhancer // @version v20230809.1524 // @description Fix Twonky public Web UI // @author ltlwinston // @match http*://*/* // @grant GM_addElement // @grant GM_setClipboard // @require https://cdnjs.cloudflare.com/ajax/libs/awesomplete/1.1.5/awesomplete.min.js // @namespace https://greasyfork.org/users/754595 // @downloadURL https://update.greasyfork.icu/scripts/424441/Twonky%20Enhancer.user.js // @updateURL https://update.greasyfork.icu/scripts/424441/Twonky%20Enhancer.meta.js // ==/UserScript== GM_addElement('link',{ rel: "stylesheet", href: "//cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.css" }); GM_addElement('link',{ rel: "stylesheet", href: "//cdnjs.cloudflare.com/ajax/libs/awesomplete/1.1.5/awesomplete.min.css" }); const interesting_words = [ 'sex', 'intim', 'sess', 'osé', 'porc', 'porn', 'intim', 'naught', 'xxx', 'privat', 'whatsapp', 'signal', 'telegram', 'sent', 'bitch', 'cunt', 'puttan', 'hot', 'blowjob', 'pussy', 'figa', 'tette', 'culo', 'anal', 'pomp', 'bocchin', 'personal' ]; (async function () { 'use strict'; const USE_CACHE = true; if (document.title.match(/(twonky|pv connect|mediaserver)/i)) { if(document.body.innerText.indexOf('Access is restricted to MediaServer configuration!')>=0) { window.location.href = '/webbrowse'; return; } async function loadServerStatus() { const status = {}; await fetch('/rpc/info_status').then(r => r.text()).then(s => s.split(/[\t\n ]/).forEach(i => { const [k,v] = i.split('|'); status[k] = isNaN(v) ? v : parseInt(v); })); return status; } async function loadPhotoAlbums(SERVER_UUID) { const albumUrl = '/nmc/rss/server/RB' + SERVER_UUID + ',0/IB' + SERVER_UUID + ',_MCQyJDI0,,1,0,_Um9vdA==,0,,0,0,_UGhvdG9z,_MCQz,?start=0&count=30000&fmt=json'; const albumResult = await fetch(albumUrl).then(x=>x.json()); if (!albumResult || !albumResult.item) { throw 'ERR: Cannot load photo albums'; } return albumResult.item.map(x=>({title: x.title, bookmark: x.bookmark})); } async function loadVideoAlbums(SERVER_UUID) { const albumUrl = '/nmc/rss/server/RB' + SERVER_UUID + ',0/IB' + SERVER_UUID + ',_MCQzJDM1,,1,0,_Um9vdA==,0,,0,0,_VmlkZW9z,_MCQz,?start=0&count=30000&fmt=json'; const albumResult = await fetch(albumUrl).then(x=>x.json()); if (!albumResult || !albumResult.item) { throw 'ERR: Cannot load video albums'; } return albumResult.item.map(x=>({title: x.title, bookmark: x.bookmark})); } async function getPath(bookmark) { return fetch('/nmc/rpc/get_item_path?server='+encodeURIComponent(bookmark)).then(x => x.text()); } if (typeof unsafeWindow['statusData'] == 'undefined') { unsafeWindow['statusData'] = {'language': 'en'}; } if (!('language' in unsafeWindow['statusData'])) { unsafeWindow['statusData']['language'] = 'en'; initPage(); } const statusElem = document.createElement('div'); statusElem.id = 'te_status'; statusElem.style.position = 'fixed'; statusElem.style.color = 'black'; statusElem.style.top = '1em'; statusElem.style.left = '1em'; statusElem.innerHTML = '
' document.body.appendChild(statusElem); const status = await loadServerStatus(); let SERVER_UUID = ''; let photoAlbums = {}; let videoAlbums = {}; if (status) { if (('videos' in status) && ('pictures' in status)) { let nPics = status.pictures; let nVids = status.videos; statusElem.innerHTML += ` ${nPics} ${nVids}`; SERVER_UUID = status.server_udn; if (SERVER_UUID) { const pAlbumStatus = document.createElement('div'); const vAlbumStatus = document.createElement('div'); statusElem.appendChild(pAlbumStatus); statusElem.appendChild(vAlbumStatus); pAlbumStatus.innerHTML = ' Loading...'; loadPhotoAlbums(SERVER_UUID).then(a => { a.forEach(x => {photoAlbums[x.title] = x}); pAlbumStatus.innerHTML = (a.length+'
'); const pasearch = document.querySelector('#pasearch'); pasearch.addEventListener('blur', function(e){this.value = ''}); pasearch.addEventListener('awesomplete-select', function(e){ openPhotoAlbum(SERVER_UUID, e.text.value); //window.open(window.location.pathname + "#"+window.location.origin+"/nmc/rss/server/RB" + status.server_udn + ",0/IB" + e.text.value + '?start=0&count=30', '_blank'); e.preventDefault(); }); new Awesomplete(pasearch, {list: a, data: i => ({label:i.title, value:i.bookmark})}); }).catch(e => { pAlbumStatus.innerText = (e); }); vAlbumStatus.innerHTML = ' Loading...'; loadVideoAlbums(SERVER_UUID).then(a => { a.forEach(x => {videoAlbums[x.title] = x}); vAlbumStatus.innerHTML = (a.length+'
'); const vasearch = document.querySelector('#vasearch'); vasearch.addEventListener('blur', function(e){this.value = ''}); vasearch.addEventListener('awesomplete-select', function(e){ window.open(window.location.pathname + "#"+window.location.origin+"/nmc/rss/server/RB" + status.server_udn + ",0/IB" + e.text.value + '?start=0&count=30', '_blank'); e.preventDefault(); }); new Awesomplete(vasearch, {list: a, data: i => ({label:i.title, value:i.bookmark})}); }).catch(e => { vAlbumStatus.innerText = (e); }); } else { const pAlbumStatus = document.createElement('div'); pAlbumStatus.innerText = 'Album search not available.'; statusElem.appendChild(pAlbumStatus); } } } function fixUrl(url) { if (!url || typeof url !== 'string') { return ""; } const re = /((127\.\d+\.\d+\.\d+)|(10\.\d+\.\d+\.\d+)|(172\.1[6-9]\.\d+\.\d+)|(172\.2[0-9]\.\d+\.\d+)|(172\.3[0-1]\.\d+\.\d+)|(192\.168\.\d+\.\d+))(:\d+)?/g; return url.replace(re,window.location.host); } unsafeWindow.fixLoadedPage = function fixLoadedPage() { document.querySelectorAll('img').forEach(function(img){ if (img.src) { img.src = fixUrl(img.src); } }); document.querySelectorAll('a').forEach(function(a){ if (a.href) { a.href = fixUrl(a.href); } }); } function hijackXHR() { var rawOpen = XMLHttpRequest.prototype.open; XMLHttpRequest.prototype.open = function(method, url, async, user, password) { if (!this._hooked) { this._hooked = true; this._url = url; setupHook(this); } rawOpen.apply(this, [method, url, async, user, password]); } function setupHook(xhr) { function get() { delete xhr.responseText; var ret = xhr.responseText; try { if (USE_CACHE && xhr._url && xhr._url.match(/start=/)) { var index = parseInt(xhr._url.match(/start=(\d+)/)[1]); var json = JSON.parse(ret); if (json && json.item && json.item.length) { json.item.forEach((i,k) => { if (i && i.meta && i.meta.id) { var id1 = 'fTh' + i.meta.id; var id2 = 'fThBB' + (index + k); cachePut(id1, i); cachePut(id2, i); } }); } } } catch (ex) {} setup(); return fixUrl(ret); } function set(str) { // Should be unused console.log('set responseText: %s', str); } function setup() { Object.defineProperty(xhr, 'responseText', { get, set, configurable: true }); } setup(); } } const CACHE = unsafeWindow.CACHE = {}; function cachePut(k,v) { CACHE[k] = v; } function cacheGet(k, defaultValue='') { return k in CACHE ? CACHE[k] : defaultValue; } function getFilename(url) { if (!url) return ''; var match = url.match(/[^/]+$/); if (!match.length) return false; return match[0].replace(/\?.*$/,''); } function addShortcuts() { document.body.addEventListener('keyup', function (e) { var currentPage = document.querySelector('#browsePages span'); if (!currentPage) return; switch(e.keyCode) { // Left case 37: currentPage.previousElementSibling && currentPage.previousElementSibling.click(); console.log('prev'); break; // Right case 39: currentPage.nextElementSibling && currentPage.nextElementSibling.click(); console.log('next'); break; } }); } function watchOnNewNodes(baseElementSelector, newNodeSelector, callback) { const observer = new MutationObserver(function(mutationsList, observer) { for(const mutation of mutationsList) { if (mutation.type === 'childList') { mutation.addedNodes.forEach(function(n){ if (!n || !n.querySelectorAll) return; n.querySelectorAll(newNodeSelector).forEach(node => { if(node) callback(node); }) }); } } }); let targetNode = baseElementSelector; if (typeof baseElementSelector === 'string') { targetNode = document.querySelector(baseElementSelector); } if (!targetNode) { return; } const config = { attributes: false, childList: true, subtree: true }; observer.observe(targetNode, config); } function watchOnEvent(baseElementSelector, eventName, selector, callback) { watchOnNewNodes(baseElementSelector, selector, function(node){ node.addEventListener(eventName, callback); }); } function createPhotoAlbumUrl(SERVER_UUID, bookmark) { return window.location.pathname + "#"+window.location.origin+"/nmc/rss/server/RB" + SERVER_UUID + ",0/IB" + bookmark + '?start=0&count=30'; } function openPhotoAlbum(SERVER_UUID, bookmark) { window.open(createPhotoAlbumUrl(SERVER_UUID, bookmark), '_blank'); } fixLoadedPage(); hijackXHR(); addShortcuts(); watchOnNewNodes('#wrapper', '.byFolderContainer', function(n){ const link = n.querySelector('.myLibraryBeamContainerNmcLocalDevice'); const link2 = n.querySelector('.beam-button'); const title = n.querySelector('.titleContainer'); if (link && title) { const href = '/#' + (title.onclick+'').match(/http[^']+/)[0]; link.href = href; link.title = 'Open album'; link.style.height = 'auto'; link.style.marginTop = '7px'; link.style.background = 'none'; link.style.backgroundImage = 'none'; link.innerHTML = ''; link.target = '_blank'; link.onclick = function(e) { e.stopPropagation(); }; } else if (link2){ const a = document.createElement('a'); a.innerHTML = ''; a.target = '_blank'; a.href = '/webbrowse#' + (n.onclick+'').match(/http[^']+/)[0]; link2.parentElement.appendChild(a); link2.parentElement.removeChild(link2); } }); if (USE_CACHE) { /**/ const footer = document.createElement('div'); footer.id = 'info_footer'; footer.style.color = 'black'; footer.style.padding = '1em'; footer.style.display = 'none'; footer.style.position = 'fixed'; footer.style.background = 'grey'; document.body.appendChild(footer); watchOnEvent('#wrapper', 'mouseleave', '.photoThumbnail', async function (e) { footer.innerHTML = ''; footer.style.display = 'none'; let pathBtn = document.querySelector('#' + this.id.replace(/\$/g,'\\$') + 'pathbtn'); }); watchOnEvent('#wrapper', 'mouseleave', '.myLibraryMediaIconVideo img', async function (e) { footer.innerHTML = ''; footer.style.display = 'none'; let pathBtn = document.querySelector('#' + this.id.replace(/\$/g,'\\$') + 'pathbtn'); }); watchOnEvent('#wrapper', 'mouseenter', '.myLibraryMediaIconVideo img', async function (e) { var info = cacheGet(this.id); if (info) { let btnContainer = document.querySelector('#' + this.id.replace(/\$/g,'\\$') + 'btncontainer'); if (!btnContainer) { btnContainer = document.createElement('div'); btnContainer.id = this.id + 'btncontainer'; btnContainer.style.position = 'absolute'; btnContainer.style.bottom = '0px'; let container = this.parentElement.parentElement; container.appendChild(btnContainer); } let aVid = document.querySelector('#' + this.id.replace(/\$/g,'\\$') + 'avid'); if (this.src && !aVid) { const url = fixUrl(info.meta.res[0].value); this.parentElement.href = url; this.parentElement.onclick = function(){}; aVid = document.createElement('a'); aVid.id = this.id + 'avid'; aVid.href = url; aVid.target = '_blank'; aVid.title = 'Open video in new tab'; aVid.innerHTML = ''; btnContainer.appendChild(aVid); } let toAlbumBtn = document.querySelector('#' + this.id.replace(/\$/g,'\\$') + 'toalbum'); if (!toAlbumBtn && (info.meta['upnp:album'] in photoAlbums)) { const a = document.createElement('a'); toAlbumBtn = document.createElement('button'); toAlbumBtn.id = this.id + 'toalbum'; toAlbumBtn.title = 'Open photo album'; toAlbumBtn.innerHTML = ''; toAlbumBtn.style.fontSize = '0.8em'; btnContainer.appendChild(a); a.target = '_blank'; a.href = createPhotoAlbumUrl(status.server_udn, photoAlbums[info.meta['upnp:album']].bookmark); a.appendChild(toAlbumBtn); } if (!info.path) { info.path = await getPath(info.bookmark); } let pathBtn = document.querySelector('#' + this.id.replace(/\$/g,'\\$') + 'pathbtn'); if (!pathBtn) { pathBtn = document.createElement('button'); pathBtn.id = this.id + 'pathbtn'; pathBtn.title = 'Click to copy file path'; pathBtn.innerHTML = ''; pathBtn.style.fontSize = '0.8em'; btnContainer.appendChild(pathBtn); pathBtn.addEventListener('click', function(){ GM_setClipboard(info.path); }); } footer.innerHTML = ('ALBUM: ' + info.meta['upnp:album'] + '
PATH: ' + info.path); footer.style.display = 'block'; } }); watchOnEvent('#wrapper', 'mouseenter', '.photoThumbnail', async function (e) { var info = cacheGet(this.id); if (info) { let btnContainer = document.querySelector('#' + this.id.replace(/\$/g,'\\$') + 'btncontainer'); if (!btnContainer) { btnContainer = document.createElement('div'); btnContainer.id = this.id + 'btncontainer'; btnContainer.style.position = 'absolute'; btnContainer.style.bottom = '0px'; let container = this.parentElement.parentElement; container.appendChild(btnContainer); } let aImg = document.querySelector('#' + this.id.replace(/\$/g,'\\$') + 'aimg'); if (this.src && !aImg) { aImg = document.createElement('a'); aImg.id = this.id + 'aimg'; aImg.href = this.src.replace(/\?.*/,''); aImg.target = '_blank'; aImg.title = 'Open image in new tab'; aImg.innerHTML = ''; btnContainer.appendChild(aImg); } let toAlbumBtn = document.querySelector('#' + this.id.replace(/\$/g,'\\$') + 'toalbum'); if (!toAlbumBtn && (info.meta['upnp:album'] in photoAlbums)) { const a = document.createElement('a'); toAlbumBtn = document.createElement('button'); toAlbumBtn.id = this.id + 'toalbum'; toAlbumBtn.title = 'Open photo album'; toAlbumBtn.innerHTML = ''; toAlbumBtn.style.fontSize = '0.8em'; btnContainer.appendChild(a); a.target = '_blank'; a.href = createPhotoAlbumUrl(status.server_udn, photoAlbums[info.meta['upnp:album']].bookmark); a.appendChild(toAlbumBtn); } if (!info.path) { info.path = await getPath(info.bookmark); } let pathBtn = document.querySelector('#' + this.id.replace(/\$/g,'\\$') + 'pathbtn'); if (!pathBtn) { pathBtn = document.createElement('button'); pathBtn.id = this.id + 'pathbtn'; pathBtn.title = 'Click to copy file path'; pathBtn.innerHTML = ''; pathBtn.style.fontSize = '0.8em'; btnContainer.appendChild(pathBtn); pathBtn.addEventListener('click', function(){ GM_setClipboard(info.path); }); } footer.innerHTML = ('ALBUM: ' + info.meta['upnp:album'] + '
PATH: ' + info.path); footer.style.display = 'block'; } }); window.onmousemove = function (e) { footer.style.top = (e.clientY + 20) + 'px'; footer.style.left = (e.clientX + 20) + 'px'; }; /**/ } } /**/ })();