- // ==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
- // ==/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 = '<a href="javascript:return false;"><i class="fa fa-refresh"></i></a><br>'
- 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 += `<i class="fa fa-photo"></i> ${nPics} <i class="fa fa-video-camera"></i> ${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 = '<i class="fa fa-file-image-o"></i> Loading...';
- loadPhotoAlbums(SERVER_UUID).then(a => {
- a.forEach(x => {photoAlbums[x.title] = x});
- pAlbumStatus.innerHTML = (a.length+' <i class="fa fa-file-image-o"></i><br><input id="pasearch" placeholder="Search a photo album">');
- 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 = '<i class="fa fa-file-video-o"></i> Loading...';
- loadVideoAlbums(SERVER_UUID).then(a => {
- a.forEach(x => {videoAlbums[x.title] = x});
- vAlbumStatus.innerHTML = (a.length+' <i class="fa fa-file-video-o"></i><br><input id="vasearch" placeholder="Search a video album">');
- 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 = '<button><i class="fa fa-external-link"></i></button>';
- link.target = '_blank';
- link.onclick = function(e) {
- e.stopPropagation();
- };
- }
- else if (link2){
- const a = document.createElement('a');
- a.innerHTML = '<button><i class="fa fa-external-link"></i></button>';
- 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 = '<button style="font-size:0.8em;"><i class="fa fa-film"></i></button>';
- 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 = '<i class="fa fa-external-link"></i>';
- 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 = '<i class="fa fa-clipboard"></i>';
- pathBtn.style.fontSize = '0.8em';
- btnContainer.appendChild(pathBtn);
- pathBtn.addEventListener('click', function(){
- GM_setClipboard(info.path);
- });
- }
- footer.innerHTML = ('ALBUM: ' + info.meta['upnp:album'] + '<br>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 = '<button style="font-size:0.8em;"><i class="fa fa-photo"></i></button>';
- 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 = '<i class="fa fa-external-link"></i>';
- 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 = '<i class="fa fa-clipboard"></i>';
- pathBtn.style.fontSize = '0.8em';
- btnContainer.appendChild(pathBtn);
- pathBtn.addEventListener('click', function(){
- GM_setClipboard(info.path);
- });
- }
- footer.innerHTML = ('ALBUM: ' + info.meta['upnp:album'] + '<br>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';
- };
- /**/
- }
-
- }
- /**/
- })();