mirror of
https://github.com/SEPPDROID/JellyCAT.git
synced 2025-10-22 16:04:20 +00:00
Done for tonight, project is becoming to big for "ctrl+z version control"... Start using GIT
This commit is contained in:
582
app/application.js
Normal file
582
app/application.js
Normal file
@@ -0,0 +1,582 @@
|
||||
// /\_/|
|
||||
// { ' ' } JellyCAT
|
||||
// \____\
|
||||
|
||||
atv.config = {
|
||||
doesJavaScriptLoadRoot: true
|
||||
};
|
||||
|
||||
atv.onAppEntry = function () {
|
||||
fetchSettings(function() {
|
||||
overrideConsoleLog();
|
||||
console.log("Successfully overwritten console log, sending logs to JCATHOST now.")
|
||||
console.log("Received ATVCSETTINGS From JCATHOST.");
|
||||
console.log("Starting JellyCAT-JS on AppleTV...")
|
||||
jcatMain();
|
||||
});
|
||||
}
|
||||
|
||||
atv.onAppExit = function() {
|
||||
console.log('Exiting App!');
|
||||
};
|
||||
|
||||
// ***************************************************
|
||||
// JellyCAT Main | Main JS app function
|
||||
// Help
|
||||
|
||||
function jcatMain(){
|
||||
atvutils.loadURL("https://" + atv.jcathost.SigHost + "/xml/home.xml");
|
||||
}
|
||||
|
||||
// ***************************************************
|
||||
// JellyCAT Logger | Jclogger
|
||||
// Function to override console.log, console.error, and console.warn and send logs to the JellyCAT stHack server
|
||||
// We shall never send any sensitive information!!
|
||||
function overrideConsoleLog() {
|
||||
var originalConsoleLog = console.log;
|
||||
var originalConsoleError = console.error;
|
||||
var originalConsoleWarn = console.warn;
|
||||
|
||||
console.log = function () {
|
||||
// Call the original console.log
|
||||
originalConsoleLog.apply(console, arguments);
|
||||
|
||||
// Send the log to the server
|
||||
logToServer("LOG: " + JSON.stringify(arguments));
|
||||
};
|
||||
|
||||
console.error = function () {
|
||||
// Call the original console.error
|
||||
originalConsoleError.apply(console, arguments);
|
||||
|
||||
// Send the error to the server
|
||||
logToServer("ERROR: " + JSON.stringify(arguments));
|
||||
};
|
||||
|
||||
console.warn = function () {
|
||||
// Call the original console.warn
|
||||
originalConsoleWarn.apply(console, arguments);
|
||||
|
||||
// Send the warning to the server
|
||||
logToServer("WARNING: " + JSON.stringify(arguments));
|
||||
};
|
||||
}
|
||||
|
||||
// Function to log console information to the server
|
||||
function logToServer(logData) {
|
||||
var logEndpoint = "https://" + atv.jcathost.SigHost + "/log";
|
||||
|
||||
var logRequest = new XMLHttpRequest();
|
||||
logRequest.open("POST", logEndpoint, true);
|
||||
logRequest.setRequestHeader("Content-Type", "application/json");
|
||||
|
||||
var logPayload = {
|
||||
timestamp: new Date().toISOString(),
|
||||
logData: logData
|
||||
};
|
||||
|
||||
logRequest.send(JSON.stringify(logPayload));
|
||||
}
|
||||
|
||||
// ***************************************************
|
||||
// JellyCAT Host fetcher
|
||||
// Function to fetch information we need from the host server
|
||||
// Function to fetch JSON from the HTTP URL using XMLHttpRequest
|
||||
|
||||
function fetchSettings(callback) {
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', 'http://jcathost.dns/atvcsettings', true);
|
||||
|
||||
xhr.onreadystatechange = function () {
|
||||
if (xhr.readyState === 4) {
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
|
||||
// Store all properties in atv.jcathost
|
||||
atv.jcathost = {
|
||||
SigHost: data.sig_host,
|
||||
SigHostPort: data.sig_host_p,
|
||||
HostIP: data.host_ip,
|
||||
System: data.system,
|
||||
Version: data.version,
|
||||
HelloMessage: data.hello,
|
||||
// Add other properties as needed
|
||||
};
|
||||
|
||||
// Execute the callback after setting atv.jcathost
|
||||
callback();
|
||||
} catch (jsonError) {
|
||||
console.error('Error parsing JSON:', jsonError);
|
||||
}
|
||||
} else {
|
||||
console.error('Error fetching settings. Status:', xhr.status);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
// ***************************************************
|
||||
// ATVUtils - a JavaScript helper library for Apple TV
|
||||
// Copied & Edited in full from:
|
||||
// https://kortv.com/appletv/js/application.js
|
||||
// https://github.com/wahlmanj/sample-aTV/blob/master/js/application.js
|
||||
|
||||
var atvutils = ATVUtils = {
|
||||
makeRequest: function(url, method, headers, body, callback) {
|
||||
if ( !url ) {
|
||||
throw "loadURL requires a url argument";
|
||||
}
|
||||
|
||||
var method = method || "GET",
|
||||
headers = headers || {},
|
||||
body = body || "";
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.onreadystatechange = function() {
|
||||
try {
|
||||
if (xhr.readyState == 4 ) {
|
||||
if ( xhr.status == 200) {
|
||||
callback(xhr.responseXML);
|
||||
} else {
|
||||
console.log("makeRequest received HTTP status " + xhr.status + " for " + url);
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error('makeRequest caught exception while processing request for ' + url + '. Aborting. Exception: ' + e);
|
||||
xhr.abort();
|
||||
callback(null);
|
||||
}
|
||||
}
|
||||
xhr.open(method, url, true);
|
||||
|
||||
for(var key in headers) {
|
||||
xhr.setRequestHeader(key, headers[key]);
|
||||
}
|
||||
|
||||
xhr.send();
|
||||
return xhr;
|
||||
},
|
||||
|
||||
makeErrorDocument: function(message, description) {
|
||||
if ( !message ) {
|
||||
message = "";
|
||||
}
|
||||
if ( !description ) {
|
||||
description = "";
|
||||
}
|
||||
|
||||
var errorXML = '<?xml version="1.0" encoding="UTF-8"?> \
|
||||
<atv> \
|
||||
<body> \
|
||||
<dialog id="com.sample.error-dialog"> \
|
||||
<title><![CDATA[' + message + ']]></title> \
|
||||
<description><![CDATA[' + description + ']]></description> \
|
||||
</dialog> \
|
||||
</body> \
|
||||
</atv>';
|
||||
|
||||
return atv.parseXML(errorXML);
|
||||
},
|
||||
|
||||
siteUnavailableError: function() {
|
||||
// TODO: localize
|
||||
return this.makeErrorDocument("JellyCAT is currently unavailable. Try again later.", "Check JCHOST for log information.");
|
||||
},
|
||||
|
||||
loadError: function(message, description) {
|
||||
atv.loadXML(this.makeErrorDocument(message, description));
|
||||
},
|
||||
|
||||
loadAndSwapError: function(message, description) {
|
||||
atv.loadAndSwapXML(this.makeErrorDocument(message, description));
|
||||
},
|
||||
|
||||
loadURLInternal: function(url, method, headers, body, loader) {
|
||||
var me = this,
|
||||
xhr,
|
||||
proxy = new atv.ProxyDocument;
|
||||
|
||||
proxy.show();
|
||||
|
||||
proxy.onCancel = function() {
|
||||
if ( xhr ) {
|
||||
xhr.abort();
|
||||
}
|
||||
};
|
||||
|
||||
xhr = me.makeRequest(url, method, headers, body, function(xml) {
|
||||
try {
|
||||
loader(proxy, xml);
|
||||
} catch(e) {
|
||||
console.error("Caught exception in for " + url + ". " + e);
|
||||
loader(me.siteUnavailableError());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
loadURL: function( options ) { //url, method, headers, body, processXML) {
|
||||
var me = this;
|
||||
if( typeof( options ) === "string" ) {
|
||||
var url = options;
|
||||
} else {
|
||||
var url = options.url,
|
||||
method = options.method || null,
|
||||
headers = options.headers || null,
|
||||
body = options.body || null,
|
||||
processXML = options.processXML || null;
|
||||
}
|
||||
|
||||
this.loadURLInternal(url, method, headers, body, function(proxy, xml) {
|
||||
if(typeof(processXML) == "function") processXML.call(this, xml);
|
||||
try {
|
||||
proxy.loadXML(xml, function(success) {
|
||||
if ( !success ) {
|
||||
console.log("loadURL failed to load " + url);
|
||||
proxy.loadXML(me.siteUnavailableError());
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("loadURL caught exception while loading " + url + ". " + e);
|
||||
proxy.loadXML(me.siteUnavailableError());
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
// loadAndSwapURL can only be called from page-level JavaScript of the page that wants to be swapped out.
|
||||
loadAndSwapURL: function( options ) { //url, method, headers, body, processXML) {
|
||||
var me = this;
|
||||
if( typeof( options ) === "string" ) {
|
||||
var url = options;
|
||||
} else {
|
||||
var url = options.url,
|
||||
method = options.method || null,
|
||||
headers = options.headers || null,
|
||||
body = options.body || null,
|
||||
processXML = options.processXML || null;
|
||||
}
|
||||
|
||||
this.loadURLInternal(url, method, headers, body, function(proxy, xml) {
|
||||
if(typeof(processXML) == "function") processXML.call(this, xml);
|
||||
try {
|
||||
proxy.loadXML(xml, function(success) {
|
||||
if ( success ) {
|
||||
atv.unloadPage();
|
||||
} else {
|
||||
console.log("loadAndSwapURL failed to load " + url);
|
||||
proxy.loadXML(me.siteUnavailableError(), function(success) {
|
||||
if ( success ) {
|
||||
atv.unloadPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
console.error("loadAndSwapURL caught exception while loading " + url + ". " + e);
|
||||
proxy.loadXML(me.siteUnavailableError(), function(success) {
|
||||
if ( success ) {
|
||||
atv.unloadPage();
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Used to manage setting and retrieving data from local storage
|
||||
*/
|
||||
data: function(key, value) {
|
||||
if(key && value) {
|
||||
try {
|
||||
atv.localStorage.setItem(key, value);
|
||||
return value;
|
||||
} catch(error) {
|
||||
console.error('Failed to store data element: '+ error);
|
||||
}
|
||||
|
||||
} else if(key) {
|
||||
try {
|
||||
return atv.localStorage.getItem(key);
|
||||
} catch(error) {
|
||||
console.error('Failed to retrieve data element: '+ error);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
|
||||
deleteData: function(key) {
|
||||
try {
|
||||
atv.localStorage.removeItem(key);
|
||||
} catch(error) {
|
||||
console.error('Failed to remove data element: '+ error);
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* @params options.name - string node name
|
||||
* @params options.text - string textContent
|
||||
* @params options.attrs - array of attribute to set {"name": string, "value": string, bool}
|
||||
* @params options.children = array of childNodes same values as options
|
||||
* @params doc - document to attach the node to
|
||||
* returns node
|
||||
*/
|
||||
createNode: function(options, doc) {
|
||||
var doc = doc || document;
|
||||
options = options || {};
|
||||
|
||||
if(options.name && options.name != '') {
|
||||
var newElement = doc.makeElementNamed(options.name);
|
||||
|
||||
if(options.text) newElement.textContent = options.text;
|
||||
|
||||
if(options.attrs) {
|
||||
options.attrs.forEach(function(e, i, a) {
|
||||
newElement.setAttribute(e.name, e.value);
|
||||
}, this);
|
||||
}
|
||||
|
||||
if(options.children) {
|
||||
options.children.forEach(function(e,i,a) {
|
||||
newElement.appendChild( this.createNode( e, doc ) );
|
||||
}, this)
|
||||
}
|
||||
|
||||
return newElement;
|
||||
}
|
||||
},
|
||||
|
||||
validEmailAddress: function( email ) {
|
||||
var emailRegex = /^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,4}$/i,
|
||||
isValid = email.search( emailRegex );
|
||||
return ( isValid > -1 );
|
||||
},
|
||||
|
||||
softwareVersionIsAtLeast: function( version ) {
|
||||
var deviceVersion = atv.device.softwareVersion.split('.'),
|
||||
requestedVersion = version.split('.');
|
||||
|
||||
// We need to pad the device version length with "0" to account for 5.0 vs 5.0.1
|
||||
if( deviceVersion.length < requestedVersion.length ) {
|
||||
var difference = requestedVersion.length - deviceVersion.length,
|
||||
dvl = deviceVersion.length;
|
||||
|
||||
for( var i = 0; i < difference; i++ ) {
|
||||
deviceVersion[dvl + i] = "0";
|
||||
};
|
||||
};
|
||||
|
||||
// compare the same index from each array.
|
||||
for( var c = 0; c < deviceVersion.length; c++ ) {
|
||||
var dv = deviceVersion[c],
|
||||
rv = requestedVersion[c] || "0";
|
||||
|
||||
if( parseInt( dv ) > parseInt( rv ) ) {
|
||||
return true;
|
||||
} else if( parseInt( dv ) < parseInt( rv ) ) {
|
||||
return false;
|
||||
};
|
||||
};
|
||||
|
||||
// If we make it this far the two arrays are identical, so we're true
|
||||
return true;
|
||||
},
|
||||
|
||||
shuffleArray: function( arr ) {
|
||||
var tmp, current, top = arr.length;
|
||||
|
||||
if(top) {
|
||||
while(--top) {
|
||||
current = Math.floor(Math.random() * (top + 1));
|
||||
tmp = arr[current];
|
||||
arr[current] = arr[top];
|
||||
arr[top] = tmp;
|
||||
};
|
||||
};
|
||||
|
||||
return arr;
|
||||
},
|
||||
|
||||
loadTextEntry: function( textEntryOptions ) {
|
||||
var textView = new atv.TextEntry;
|
||||
|
||||
textView.type = textEntryOptions.type || "emailAddress";
|
||||
textView.title = textEntryOptions.title || "";
|
||||
textView.image = textEntryOptions.image || null;
|
||||
textView.instructions = textEntryOptions.instructions || "";
|
||||
textView.label = textEntryOptions.label || "";
|
||||
textView.footnote = textEntryOptions.footnote || "";
|
||||
textView.defaultValue = textEntryOptions.defaultValue || null;
|
||||
textView.defaultToAppleID = textEntryOptions.defaultToAppleID || false;
|
||||
textView.onSubmit = textEntryOptions.onSubmit,
|
||||
textView.onCancel = textEntryOptions.onCancel,
|
||||
|
||||
textView.show();
|
||||
},
|
||||
|
||||
log: function ( message , level ) {
|
||||
var debugLevel = atv.sessionStorage.getItem( "DEBUG_LEVEL" ),
|
||||
level = level || 0;
|
||||
|
||||
if( level <= debugLevel ) {
|
||||
console.log( message );
|
||||
}
|
||||
},
|
||||
|
||||
accessibilitySafeString: function ( string ) {
|
||||
var string = unescape( string );
|
||||
|
||||
string = string
|
||||
.replace( /&/g, 'and' )
|
||||
.replace( /&/g, 'and' )
|
||||
.replace( /</g, 'less than' )
|
||||
.replace( /\</g, 'less than' )
|
||||
.replace( />/g, 'greater than' )
|
||||
.replace( /\>/g, 'greater than' );
|
||||
|
||||
return string;
|
||||
}
|
||||
};
|
||||
|
||||
// Extend atv.ProxyDocument to load errors from a message and description.
|
||||
if( atv.ProxyDocument ) {
|
||||
atv.ProxyDocument.prototype.loadError = function(message, description) {
|
||||
var doc = atvutils.makeErrorDocument(message, description);
|
||||
this.loadXML(doc);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// atv.Document extensions
|
||||
if( atv.Document ) {
|
||||
atv.Document.prototype.getElementById = function(id) {
|
||||
var elements = this.evaluateXPath("//*[@id='" + id + "']", this);
|
||||
if ( elements && elements.length > 0 ) {
|
||||
return elements[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// atv.Element extensions
|
||||
if( atv.Element ) {
|
||||
atv.Element.prototype.getElementsByTagName = function(tagName) {
|
||||
return this.ownerDocument.evaluateXPath("descendant::" + tagName, this);
|
||||
}
|
||||
|
||||
atv.Element.prototype.getElementByTagName = function(tagName) {
|
||||
var elements = this.getElementsByTagName(tagName);
|
||||
if ( elements && elements.length > 0 ) {
|
||||
return elements[0];
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
// Simple Array Sorting methods
|
||||
Array.prototype.sortAsc = function() {
|
||||
this.sort(function( a, b ){
|
||||
return a - b;
|
||||
});
|
||||
};
|
||||
|
||||
Array.prototype.sortDesc = function() {
|
||||
this.sort(function( a, b ){
|
||||
return b - a;
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
// Date methods and properties
|
||||
Date.lproj = {
|
||||
"DAYS": {
|
||||
"en": {
|
||||
"full": ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||
"abbrv": ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
||||
},
|
||||
"en_GB": {
|
||||
"full": ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'],
|
||||
"abbrv": ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']
|
||||
}
|
||||
},
|
||||
"MONTHS": {
|
||||
"en": {
|
||||
"full": ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||
"abbrv": ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
},
|
||||
"en_GB": {
|
||||
"full": ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'],
|
||||
"abbrv": ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Date.prototype.getLocaleMonthName = function( type ) {
|
||||
var language = atv.device.language,
|
||||
type = ( type === true ) ? "abbrv" : "full",
|
||||
MONTHS = Date.lproj.MONTHS[ language ] || Date.lproj.MONTHS[ "en" ];
|
||||
|
||||
return MONTHS[ type ][ this.getMonth() ];
|
||||
};
|
||||
|
||||
Date.prototype.getLocaleDayName = function( type ) {
|
||||
var language = atv.device.language,
|
||||
type = ( type === true ) ? "abbrv" : "full",
|
||||
DAYS = Date.lproj.DAYS[ language ] || Date.lproj.DAYS[ "en" ];
|
||||
|
||||
return DAYS[ type ][ this.getDay() ];
|
||||
};
|
||||
|
||||
Date.prototype.nextDay = function( days ) {
|
||||
var oneDay = 86400000,
|
||||
days = days || 1;
|
||||
this.setTime( new Date( this.valueOf() + ( oneDay * days ) ) );
|
||||
};
|
||||
|
||||
Date.prototype.prevDay = function( days ) {
|
||||
var oneDay = 86400000,
|
||||
days = days || 1;
|
||||
this.setTime( new Date( this.valueOf() - ( oneDay * days ) ) );
|
||||
};
|
||||
|
||||
|
||||
// String Trim methods
|
||||
String.prototype.trim = function ( ch )
|
||||
{
|
||||
var ch = ch || '\\s',
|
||||
s = new RegExp( '^['+ch+']+|['+ch+']+$','g');
|
||||
return this.replace(s,'');
|
||||
};
|
||||
|
||||
String.prototype.trimLeft = function ( ch )
|
||||
{
|
||||
var ch = ch || '\\s',
|
||||
s = new RegExp( '^['+ch+']+','g');
|
||||
return this.replace(s,'');
|
||||
};
|
||||
|
||||
String.prototype.trimRight = function ( ch )
|
||||
{
|
||||
var ch = ch || '\\s',
|
||||
s = new RegExp( '['+ch+']+$','g');
|
||||
return this.replace(s,'');
|
||||
};
|
||||
|
||||
String.prototype.xmlEncode = function()
|
||||
{
|
||||
var string = unescape( this );
|
||||
|
||||
string = string
|
||||
.replace( /&/g, '&' )
|
||||
.replace( /\</g, '<' )
|
||||
.replace( /\>/g, '>' );
|
||||
|
||||
return string;
|
||||
};
|
||||
|
||||
console.log('EOF!');
|
||||
|
Reference in New Issue
Block a user