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!');

41
app/bag.plist Normal file
View File

@@ -0,0 +1,41 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>auth-type</key>
<string>js</string>
<key>enabled</key>
<string>YES</string>
<key>menu-title</key>
<string>JellyCAT</string>
<key>merchant</key>
<string>com.jellycat.appletv</string>
<key>sandbox-mode</key>
<string>NO</string>
<key>menu-icon-url</key>
<dict>
<key>720</key>
<string>http://jcathost.dns/assets/img/icons/jcat@720.png</string>
<key>1080</key>
<string>http://jcathost.dns/assets/img/icons/jcat@1080.png</string>
</dict>
<key>menu-icon-url-version</key>
<string>7</string>
<key>app-dictionary</key>
<dict>
<key>adam-id</key>
<integer>700636371</integer>
<key>app-version</key>
<integer>732333</integer>
<key>bundle-identifier</key>
<string>com.jellycat.appletv</string>
<key>bundle-version</key>
<string>1.0</string>
</dict>
<key>javascript-url</key>
<string>./js/application.js</string>
</dict>
</plist>

56
app/js/jclog.js Normal file
View File

@@ -0,0 +1,56 @@
// /\_/|
// { ' ' } JellyCAT
// \____\
overrideConsoleLog();
console.log("Successfully overwritten console log, sending logs to JCATHOST now.")
// ***************************************************
// 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 = "http://jcathost.dns/log"; // insecure for now
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));
}

33
app/xml/home.xml Normal file
View File

@@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<atv>
<head>
<script src="http://jcathost.dns/js/jclog.js"/>
</head>
<body>
<optionDialog id="com.sample.error-dialog">
<header>
<simpleHeader accessibilityLabel="Dialog with Options">
<title>JellyCAT Debug/Dev Tools</title>
</simpleHeader>
</header>
<description>Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.</description>
<menu>
<initialSelection>
<row>1</row>
</initialSelection>
<sections>
<menuSection>
<items>
<oneLineMenuItem id="list_0" accessibilityLabel="Option 1" onSelect="console.log('log information sent');">
<label>Send Test log</label>
</oneLineMenuItem>
<oneLineMenuItem id="list_0" accessibilityLabel="Option 2" onSelect="console.log('Option 2 selected'); atv.unloadPage();">
<label>Unload Page</label>
</oneLineMenuItem>
</items>
</menuSection>
</sections>
</menu>
</optionDialog>
</body>
</atv>