Done for tonight, project is becoming to big for "ctrl+z version control"... Start using GIT

This commit is contained in:
2024-02-05 00:23:38 +01:00
commit 1be97536ef
14 changed files with 1407 additions and 0 deletions

582
app/application.js Normal file
View 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( /&amp;/g, 'and' )
.replace( /&/g, 'and' )
.replace( /&lt;/g, 'less than' )
.replace( /\</g, 'less than' )
.replace( /&gt;/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, '&amp;' )
.replace( /\</g, '&lt;' )
.replace( /\>/g, '&gt;' );
return string;
};
console.log('EOF!');