forked from cory/tildefriends
sandboxos => tildefriends
git-svn-id: https://www.unprompted.com/svn/projects/tildefriends/trunk@3157 ed5197a5-7fde-0310-b194-c3ffbd925b24
This commit is contained in:
57
packages/cory/about/about.js
Normal file
57
packages/cory/about/about.js
Normal file
@ -0,0 +1,57 @@
|
||||
"use strict";
|
||||
|
||||
var kMessages = [
|
||||
[
|
||||
" _ _ _ ",
|
||||
" / \\ | |__ ___ _ _| |_ ",
|
||||
" / _ \\ | '_ \\ / _ \\| | | | __|",
|
||||
" / ___ \\| |_) | (_) | |_| | |_ ",
|
||||
"/_/ \\_\\_.__/ \\___/ \\__,_|\\__|",
|
||||
"",
|
||||
"Tilde Friends: Webapps that anyone can download, modify, run, and share.",
|
||||
"",
|
||||
"You are looking at a web site running on a JavaScript and C++ web server that uses Google V8 to let visitors author webapps.",
|
||||
"",
|
||||
["Full source is here <",
|
||||
{href: "https://www.unprompted.com/projects/browser/sandboxos/trunk/"},
|
||||
">, but it is probably more fun and useful to poke around the ",
|
||||
{href: "/~cory/index", value: "existing webapps"},
|
||||
". A ",
|
||||
{href: "https://www.unprompted.com/projects/wiki/Projects/SandboxOS", value: "prebuilt Windows .zip"},
|
||||
" is available as well. ",
|
||||
],
|
||||
"",
|
||||
[
|
||||
"Use the links at the top of the page to explore existing apps. When you are ready, click edit and start making your own. See the ",
|
||||
{href: "/~cory/documentation", value: "documentation"},
|
||||
" for more information.",
|
||||
],
|
||||
],
|
||||
];
|
||||
var gIndex = 0;
|
||||
|
||||
function printNextMessage() {
|
||||
if (gIndex < kMessages.length) {
|
||||
var block = kMessages[gIndex];
|
||||
for (var i = 0; i < block.length; i++) {
|
||||
terminal.print(block[i]);
|
||||
}
|
||||
terminal.print("");
|
||||
}
|
||||
if (gIndex < kMessages.length) {
|
||||
gIndex++;
|
||||
if (gIndex < kMessages.length) {
|
||||
terminal.print("(press enter to continue, \"exit\" to exit)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
core.register("onInput", function(input) {
|
||||
if (input == "exit") {
|
||||
exit();
|
||||
} else {
|
||||
printNextMessage();
|
||||
}
|
||||
});
|
||||
|
||||
printNextMessage();
|
137
packages/cory/administration/administration.js
Normal file
137
packages/cory/administration/administration.js
Normal file
@ -0,0 +1,137 @@
|
||||
"use strict";
|
||||
|
||||
//! {"permissions": ["administration"]}
|
||||
|
||||
terminal.print("Administration");
|
||||
if (core.user.credentials.permissions &&
|
||||
core.user.credentials.permissions.administration) {
|
||||
core.register("onInput", onInput);
|
||||
terminal.print("Welcome, administrator.");
|
||||
terminal.print("Usage:");
|
||||
let kCommands = [
|
||||
[
|
||||
"set",
|
||||
"List all global settings.",
|
||||
],
|
||||
[
|
||||
["set ", {class: "cyan", value: "key value"}],
|
||||
["Set global setting key to value."],
|
||||
],
|
||||
[
|
||||
"permission list",
|
||||
"List all permissions."
|
||||
],
|
||||
[
|
||||
["permission add ", {class: "cyan", value: "user action1 action2 ..."}],
|
||||
["Grant permission for ", {class: "cyan", value: "action1"}, ", ", {class: "cyan", value: "action2"}, ", ", {class: "cyan", value: "..."}, " to ", {class: "cyan", value: "user"}, "."],
|
||||
],
|
||||
[
|
||||
["permission remove ", {class: "cyan", value: "user action1 action2 ..."}],
|
||||
["Revoke permission for ", {class: "cyan", value: "action1"}, ", ", {class: "cyan", value: "action2"}, ", ", {class: "cyan", value: "..."}, " from ", {class: "cyan", value: "user"}, "."],
|
||||
],
|
||||
[
|
||||
"statistics", "List statistics."
|
||||
],
|
||||
];
|
||||
for (var i = 0; i < kCommands.length; i++) {
|
||||
terminal.print({class: "yellow", value: kCommands[i][0]});
|
||||
terminal.print({style: "display: block; margin-left: 2em", value: kCommands[i][1]});
|
||||
}
|
||||
} else {
|
||||
terminal.print("You are not an administrator.");
|
||||
}
|
||||
|
||||
var kSimpleSettings = [
|
||||
'httpPort',
|
||||
'httpsPort',
|
||||
'index',
|
||||
];
|
||||
|
||||
function printSettings(settings) {
|
||||
terminal.print("Current settings:");
|
||||
for (let i = 0; i < kSimpleSettings.length; i++) {
|
||||
terminal.print(" ", {class: "magenta", value: kSimpleSettings[i]}, " = ", {class: "yellow", value: settings[kSimpleSettings[i]]});
|
||||
}
|
||||
}
|
||||
|
||||
function printPermissions(settings) {
|
||||
terminal.print("Current permissions:");
|
||||
let permissions = settings.permissions || {};
|
||||
for (let entry in permissions) {
|
||||
terminal.print(" ", {class: "magenta", value: entry}, ": ", {class: "yellow", value: permissions[entry].join(" ")});
|
||||
}
|
||||
}
|
||||
|
||||
function onInput(input) {
|
||||
try {
|
||||
let match;
|
||||
if (input == "set") {
|
||||
administration.getGlobalSettings().then(printSettings);
|
||||
} else if (input == "statistics") {
|
||||
administration.getStatistics().then(function(s) {
|
||||
for (var i in s) {
|
||||
terminal.print(" ".repeat(16 - s[i].toString().length), s[i].toString(), " ", i);
|
||||
}
|
||||
});
|
||||
} else if (match = /^\s*set\s+(\w+)\s+(.*)/.exec(input)) {
|
||||
var key = match[1];
|
||||
var value = match[2];
|
||||
administration.getGlobalSettings().then(function(settings) {
|
||||
if (kSimpleSettings.indexOf(key) != -1) {
|
||||
settings[key] = value;
|
||||
administration.setGlobalSettings(settings).then(function() {
|
||||
administration.getGlobalSettings().then(printSettings);
|
||||
}).catch(function(error) {
|
||||
terminal.print("Error updating settings: " + JSON.stringify(error));
|
||||
});
|
||||
} else {
|
||||
terminal.print("Unknown setting: " + key);
|
||||
}
|
||||
});
|
||||
} else if (match = /^\s*permission\s+(\w+)(?:\s+(.*))?/.exec(input)) {
|
||||
var command = match[1];
|
||||
var remaining = (match[2] || "").split(/\s+/);
|
||||
if (command == "list") {
|
||||
administration.getGlobalSettings().then(printPermissions);
|
||||
} else if (command == "add") {
|
||||
var user = remaining[0];
|
||||
administration.getGlobalSettings().then(function(settings) {
|
||||
settings.permissions = settings.permissions || {};
|
||||
settings.permissions[user] = settings.permissions[user] || [];
|
||||
for (var i = 1; i < remaining.length; i++) {
|
||||
if (settings.permissions[user].indexOf(remaining[i]) == -1) {
|
||||
settings.permissions[user].push(remaining[i]);
|
||||
}
|
||||
}
|
||||
settings.permissions[user].sort();
|
||||
administration.setGlobalSettings(settings).then(function() {
|
||||
administration.getGlobalSettings().then(printPermissions);
|
||||
}).catch(function(error) {
|
||||
terminal.print("Error updating permissions: " + JSON.stringify(error));
|
||||
});
|
||||
});
|
||||
} else if (command == "remove") {
|
||||
var user = remaining[0];
|
||||
administration.getGlobalSettings().then(function(settings) {
|
||||
if (settings.permissions && settings.permissions[user]) {
|
||||
for (var i = 1; i < remaining.length; i++) {
|
||||
settings.permissions[user] = settings.permissions[user].filter(x => x != remaining[i]);
|
||||
}
|
||||
if (settings.permissions[user].length == 0) {
|
||||
delete settings.permissions[user];
|
||||
}
|
||||
}
|
||||
administration.setGlobalSettings(settings).then(function() {
|
||||
administration.getGlobalSettings().then(printPermissions);
|
||||
}).catch(function(error) {
|
||||
terminal.print("Error updating permissions: " + JSON.stringify(error));
|
||||
});
|
||||
});
|
||||
}
|
||||
} else if (typeof input == "string") {
|
||||
terminal.print("I didn't understand that.");
|
||||
}
|
||||
} catch (error) {
|
||||
terminal.print("error: " + error);
|
||||
}
|
||||
}
|
315
packages/cory/bbs/bbs.js
Normal file
315
packages/cory/bbs/bbs.js
Normal file
@ -0,0 +1,315 @@
|
||||
"use strict";
|
||||
var gOnInput = null;
|
||||
|
||||
var kMaxHistory = 20;
|
||||
var kShowHistory = 20;
|
||||
|
||||
var lastTimestamp = null;
|
||||
|
||||
if (imports.terminal) {
|
||||
core.register("onMessage", function(sender, message) {
|
||||
if (message.message && message.when) {
|
||||
printMessage(message, true);
|
||||
}
|
||||
});
|
||||
core.register("onSessionBegin", function(user) {
|
||||
if (user.packageName === core.user.packageName &&
|
||||
user.index !== core.user.index) {
|
||||
listUsers(user.name + " has joined the BBS. ");
|
||||
}
|
||||
});
|
||||
core.register("onSessionEnd", function(user) {
|
||||
if (user.packageName === core.user.packageName &&
|
||||
user.index !== core.user.index) {
|
||||
listUsers(user.name + " has left the BBS. ");
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// Chat service process.
|
||||
core.register("onMessage", function(sender, message) {
|
||||
if (message.message && message.when) {
|
||||
message.sender = sender;
|
||||
return database.get("board").catch(function() {
|
||||
return null;
|
||||
}).then(function(data) {
|
||||
try {
|
||||
data = JSON.parse(data);
|
||||
} catch(error) {
|
||||
data = [];
|
||||
}
|
||||
data.push(message);
|
||||
while (data.length > kMaxHistory) {
|
||||
data.shift();
|
||||
}
|
||||
return saveBoard(data);
|
||||
}).then(function() {
|
||||
return core.broadcast(message);
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function listUsers() {
|
||||
return core.getUsers(core.user.packageOwner, core.user.packageName).then(function(users) {
|
||||
terminal.select("users");
|
||||
terminal.clear();
|
||||
terminal.print("Users:");
|
||||
var counts = {};
|
||||
for (var i = 0; i < users.length; i++) {
|
||||
counts[users[i].name] = (counts[users[i].name] || 0) + 1;
|
||||
}
|
||||
var names = Object.keys(counts).sort();
|
||||
for (var i = 0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
var message = [];
|
||||
if (message.length > 1) {
|
||||
message.push(", ");
|
||||
}
|
||||
message.push({class: "orange", value: name});
|
||||
if (counts[name] > 1) {
|
||||
message.push({class: "base01", value: "(x" + counts[name] + ")"});
|
||||
}
|
||||
terminal.print(message);
|
||||
}
|
||||
terminal.select("terminal");
|
||||
});
|
||||
}
|
||||
|
||||
function saveBoard(data) {
|
||||
return database.set("board", JSON.stringify(data)).catch(function(error) {
|
||||
if (error.message.indexOf("MDB_MAP_FULL") != -1) {
|
||||
data.shift();
|
||||
return saveBoard(data);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
core.register("onInput", function(input) {
|
||||
if (gOnInput && typeof input == "string") {
|
||||
gOnInput(input);
|
||||
}
|
||||
});
|
||||
|
||||
function logo() {
|
||||
terminal.clear();
|
||||
terminal.print("");
|
||||
terminal.print("");
|
||||
terminal.print('Welcome to');
|
||||
terminal.print(' ______ _ ____ ____ _____');
|
||||
terminal.print(' / ____/___ _______ _( )_____ / __ )/ __ ) ___/');
|
||||
terminal.print(' / / / __ \\/ ___/ / / /// ___/ / __ / __ \\__ \\ ');
|
||||
terminal.print('/ /___/ /_/ / / / /_/ / (__ ) / /_/ / /_/ /__/ / ');
|
||||
terminal.print('\\____/\\____/_/ \\__, / /____/ /_____/_____/____/ ');
|
||||
terminal.print(' /____/ ');
|
||||
terminal.print(' yesterday\'s technology...today!');
|
||||
terminal.print("");
|
||||
}
|
||||
|
||||
function welcome() {
|
||||
logo();
|
||||
chat();
|
||||
}
|
||||
|
||||
function main() {
|
||||
terminal.clear();
|
||||
logo();
|
||||
terminal.print("");
|
||||
terminal.print("Main menu commands:");
|
||||
terminal.print(" ", {command: "chat"}, " chat message board");
|
||||
terminal.print(" ", {command: "guess"}, " guess the number game");
|
||||
terminal.print(" ", {command: "exit"}, " back to that sweet logo");
|
||||
gOnInput = function(input) {
|
||||
input = input.toLowerCase();
|
||||
if (input == "chat") {
|
||||
chat();
|
||||
} else if (input == "guess") {
|
||||
guess();
|
||||
} else if (input == "exit") {
|
||||
terminal.print("Goodbye.");
|
||||
exit(0);
|
||||
} else {
|
||||
terminal.print("I didn't understand that: " + input);
|
||||
main();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function formatMessage(message) {
|
||||
var result;
|
||||
if (typeof message == "string") {
|
||||
result = [];
|
||||
var regex = /(\w+:\/*\S+?)(?=(?:[\.!?])?(?:$|\s))/gi;
|
||||
var match;
|
||||
var lastIndex = 0;
|
||||
while ((match = regex.exec(message)) !== null) {
|
||||
result.push({class: "base1", value: message.substring(lastIndex, match.index)});
|
||||
result.push({href: match[0]});
|
||||
lastIndex = regex.lastIndex;
|
||||
}
|
||||
result.push({class: "base1", value: message.substring(lastIndex)});
|
||||
} else {
|
||||
result = message;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function niceTime(lastTime, thisTime) {
|
||||
if (!lastTime) {
|
||||
return thisTime;
|
||||
}
|
||||
let result = [];
|
||||
let lastParts = lastTime.split(" ");
|
||||
let thisParts = thisTime.split(" ");
|
||||
for (let i = 0; i < thisParts.length; i++) {
|
||||
if (thisParts[i] !== lastParts[i]) {
|
||||
result.push(thisParts[i]);
|
||||
}
|
||||
}
|
||||
return result.join(" ");
|
||||
}
|
||||
|
||||
function printMessage(message, notify) {
|
||||
terminal.print(
|
||||
{class: "base0", value: niceTime(lastTimestamp, message.when)},
|
||||
" ",
|
||||
{class: "base00", value: "<"},
|
||||
{class: "base3", value: (message.sender ? message.sender.name : "unknown")},
|
||||
{class: "base00", value: ">"},
|
||||
" ",
|
||||
formatMessage(message.message));
|
||||
lastTimestamp = message.when;
|
||||
if (notify) {
|
||||
return core.getUser().then(function(user) {
|
||||
if (message.message.indexOf("!") != -1) {
|
||||
return terminal.notify("SOMEONE IS SHOUTING!", {body: "<" + (message.sender ? message.sender.name : "unknown") + "> " + message.message});
|
||||
} else if (message.message.indexOf(user.name + ":") != -1) {
|
||||
return terminal.notify("Someone is talking at you.", {body: "<" + (message.sender ? message.sender.name : "unknown") + "> " + message.message});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function chat() {
|
||||
terminal.setEcho(false);
|
||||
terminal.print("");
|
||||
terminal.print("You are now in a chat. Anything you type will be broadcast to everyone else connected. To leave, say ", {command: "exit"}, ".");
|
||||
listUsers();
|
||||
database.get("board").catch(function() {
|
||||
return null;
|
||||
}).then(function(board) {
|
||||
try {
|
||||
board = JSON.parse(board);
|
||||
} catch (error) {
|
||||
board = [];
|
||||
}
|
||||
|
||||
for (let i = Math.max(0, board.length - kShowHistory); i < board.length; i++) {
|
||||
printMessage(board[i], false);
|
||||
}
|
||||
});
|
||||
gOnInput = function(input) {
|
||||
if (input == "exit") {
|
||||
terminal.setEcho(true);
|
||||
main();
|
||||
} else {
|
||||
core.getService("chat").then(function(chatService) {
|
||||
return chatService.postMessage({when: new Date().toString(), message: input});
|
||||
}).catch(function(error) {
|
||||
terminal.print("ERROR: " + JSON.stringify(error));
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function guess() {
|
||||
terminal.clear();
|
||||
var number = Math.round(Math.random() * 100);
|
||||
var guesses = 0;
|
||||
terminal.print("OK, I have a number in mind. What do you think it is? Use ", {command: "exit"}, " to stop.");
|
||||
gOnInput = function(input) {
|
||||
if (input == "exit") {
|
||||
main();
|
||||
} else {
|
||||
var guess = parseInt(input);
|
||||
guesses++;
|
||||
if (input != guess.toString()) {
|
||||
terminal.print("I'm not sure that's an integer. Please guess only integers.");
|
||||
} else {
|
||||
if (guess < number) {
|
||||
terminal.print("Too low.");
|
||||
} else if (guess > number) {
|
||||
terminal.print("Too high.");
|
||||
} else if (guess == number) {
|
||||
terminal.print("Wow, you got it in " + guesses + " guesses! It was " + number + ".");
|
||||
guessEnd(guesses);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
function guessEnd(guesses) {
|
||||
terminal.print("What's your name, for the high score table?");
|
||||
gOnInput = function(name) {
|
||||
var entry = {'guesses': guesses, 'name': name, 'when': new Date().toString()};
|
||||
database.get("guessHighScores").then(function(data) {
|
||||
data = JSON.parse(data);
|
||||
var index = data.length;
|
||||
for (var i in data) {
|
||||
if (guesses < data[i].guesses) {
|
||||
index = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
data.splice(index, 0, entry);
|
||||
printHighScores(data);
|
||||
database.set("guessHighScores", JSON.stringify(data));
|
||||
gOnInput = function() {
|
||||
main();
|
||||
};
|
||||
}).catch(function() {
|
||||
var data = [entry];
|
||||
printHighScores(data);
|
||||
database.set("guessHighScores", JSON.stringify(data));
|
||||
main();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
function printTable(data) {
|
||||
var widths = [];
|
||||
for (var i in data) {
|
||||
var row = data[i];
|
||||
for (var c in row) {
|
||||
widths[c] = Math.max(widths[c] || 0, row[c].length);
|
||||
}
|
||||
}
|
||||
|
||||
for (var i in data) {
|
||||
var row = data[i];
|
||||
var line = "";
|
||||
for (var c in row) {
|
||||
line += row[c];
|
||||
line += " ".repeat(widths[c] - row[c].length + 2);
|
||||
}
|
||||
terminal.print(line);
|
||||
}
|
||||
}
|
||||
|
||||
function printHighScores(data) {
|
||||
printTable([["Name", "Guesses", "Date"]].concat(data.map(function(entry) {
|
||||
return [entry.name, entry.guesses.toString(), entry.when];
|
||||
})));
|
||||
}
|
||||
|
||||
if (imports.terminal) {
|
||||
terminal.split([
|
||||
{type: "horizontal", children: [
|
||||
{name: "terminal", grow: 1},
|
||||
{name: "users", grow: 0},
|
||||
]},
|
||||
]);
|
||||
welcome();
|
||||
}
|
93
packages/cory/documentation/documentation.js
Normal file
93
packages/cory/documentation/documentation.js
Normal file
@ -0,0 +1,93 @@
|
||||
"use strict";
|
||||
|
||||
let kDocumentation = {
|
||||
"core.broadcast": ["message", "Broadcast a message to every other instance of the same app. Messages will be received through the \"onMessage\" event."],
|
||||
"core.getService": ["name", "Get a reference to a long-running service process identified by name. A process will be started if it is not already running. Useful for coordinating between client processes."],
|
||||
"core.getPackages": ["", "Get a list of all available applications."],
|
||||
"core.getUser": ["", "Gets information about the current user."],
|
||||
"core.getUsers": ["packageOwner, packageName", "Get a list of all online users, restricted to a package if specified."],
|
||||
"core.register": ["eventName, handlerFunction", "Register a callback function for the given event."],
|
||||
"database.get": ["key", "Retrieve the database value associated with the given key."],
|
||||
"database.set": ["key, value", "Sets the database value for the given key, overwriting any existing value."],
|
||||
"database.getAll": ["", "Retrieve a list of all key names."],
|
||||
"database.remove": ["key", "Remove the database entry for the given key."],
|
||||
"terminal.print": ["arguments...", `Print to the terminal. Multiple arguments and lists are all expanded. The following special values are supported:
|
||||
{href: "http://www..."} => Create a link to the href value. Text will be the href value or 'value' if specified.
|
||||
{iframe: "<html>...</html>", width: 640, height: 480} => Create an iframe with the given srcdoc.
|
||||
{style: "color: #f00", value: "Hello, world!"} => Create styled text.
|
||||
{command: "exit", value: "get out of here"} => Create a link that when clicked will act as if the user typed the given command.`],
|
||||
"terminal.clear": ["", "Remove all terminal output."],
|
||||
"terminal.readLine": ["", "Produces the next line of text from user input."],
|
||||
"terminal.setEcho": ["echo", "Controls whether the terminal will automatically echo user input (default=true)."],
|
||||
"terminal.setTitle": ["title", "Sets the browser window/tab title."],
|
||||
"terminal.setPrompt": ["prompt", "Sets the terminal prompt (default \">\")."],
|
||||
"terminal.setPassword": ["enabled", "Controls whether the terminal input box is set as a password input and obscures the entered text."],
|
||||
"terminal.setHash": ["hash", "Sets the URL #hash, typically so that the user can copy / bookmark it and return to a similar state."],
|
||||
"terminal.postMessageToIframe": ["name, message", "Sends the message to the iframe that was created with the given name using window.postMessage."],
|
||||
"terminal.notify": ["body, {title, icon}", ["Produces an ", {href: "https://developer.mozilla.org/en-US/docs/Web/API/notification", value: "HTML5 Notification"}, ". Arguments are the same as the Notification constructor."]],
|
||||
};
|
||||
|
||||
terminal.print("V8 Version ", version);
|
||||
terminal.print("");
|
||||
|
||||
heading("API Documentation");
|
||||
dumpDocumentation("imports", imports);
|
||||
|
||||
heading("Notes");
|
||||
terminal.print(`All API functions are invoked asynchronously. They
|
||||
immediately return a `,
|
||||
{href: "https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise", value: "Promise"},
|
||||
` object. If you want to do
|
||||
something with the result, you most likely want to call them
|
||||
like this:
|
||||
|
||||
database.get(key).then(function(value) {
|
||||
doSomethingWithTheResult(value);
|
||||
});`);
|
||||
terminal.print("");
|
||||
heading("Colors (CSS class names)");
|
||||
dumpColors();
|
||||
|
||||
function heading(text) {
|
||||
terminal.print({class: "green", value: "+" + "-".repeat(text.length + 2) + "+"});
|
||||
terminal.print({class: "green", value: "| " + text + " |"});
|
||||
terminal.print({class: "green", value: "+" + "-".repeat(text.length + 2) + "+"});
|
||||
}
|
||||
|
||||
function dumpDocumentation(prefix, object, depth) {
|
||||
if (typeof object == "function") {
|
||||
let documentation = kDocumentation[prefix.substring("imports.".length)] || ["", ""];
|
||||
terminal.print(
|
||||
{class: "yellow", value: prefix.substring("imports.".length)},
|
||||
"(",
|
||||
{class: "base0", value: documentation[0]},
|
||||
")");
|
||||
terminal.print({style: "display: block; margin-left: 2em", value: documentation[1]});
|
||||
terminal.print("");
|
||||
} else if (object && typeof object != "string") {
|
||||
for (let i in object) {
|
||||
dumpDocumentation(prefix + "." + i, object[i], (depth || 0) + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function dumpColors() {
|
||||
var kColors = [
|
||||
"base03",
|
||||
"base02",
|
||||
"base01",
|
||||
"base00",
|
||||
"base0",
|
||||
"base1",
|
||||
"base2",
|
||||
"base3",
|
||||
"yellow",
|
||||
"red",
|
||||
"magenta",
|
||||
"violet",
|
||||
"blue",
|
||||
"cyan",
|
||||
"green",
|
||||
];
|
||||
terminal.print({style: "background-color: #000", value: kColors.map(function(color) { return [" ", {class: color, value: color}, " "]; })});
|
||||
}
|
53
packages/cory/index/index.js
Normal file
53
packages/cory/index/index.js
Normal file
@ -0,0 +1,53 @@
|
||||
"use strict";
|
||||
|
||||
core.register("onSessionBegin", index);
|
||||
core.register("onSessionEnd", index);
|
||||
|
||||
function index() {
|
||||
Promise.all([core.getPackages(), core.getUsers()]).then(function(values) {
|
||||
let packages = values[0];
|
||||
let users = values[1];
|
||||
let usersByApp = {};
|
||||
for (let i in users) {
|
||||
let user = users[i];
|
||||
if (!usersByApp["/~" + user.packageOwner + "/" + user.packageName]) {
|
||||
usersByApp["/~" + user.packageOwner + "/" + user.packageName] = [];
|
||||
}
|
||||
usersByApp["/~" + user.packageOwner + "/" + user.packageName].push(user.name);
|
||||
}
|
||||
|
||||
terminal.clear();
|
||||
terminal.print("Available applications [active users]:");
|
||||
packages.sort(function(x, y) {
|
||||
return Math.sign(x.owner.localeCompare(y.owner)) * 10 + Math.sign(x.name.localeCompare(y.name)) * 1;
|
||||
}).forEach(function(app) {
|
||||
let users = usersByApp["/~" + app.owner + "/" + app.name];
|
||||
let message = [];
|
||||
if (users) {
|
||||
message.push(" [");
|
||||
let counts = {};
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
counts[users[i]] = (counts[users[i]] || 0) + 1;
|
||||
}
|
||||
let names = Object.keys(counts).sort();
|
||||
for (let i = 0; i < names.length; i++) {
|
||||
var name = names[i];
|
||||
if (message.length > 1) {
|
||||
message.push(", ");
|
||||
}
|
||||
message.push({class: "orange", value: name});
|
||||
if (counts[name] > 1) {
|
||||
message.push({class: "base01", value: "(x" + counts[name] + ")"});
|
||||
}
|
||||
}
|
||||
message.push("]");
|
||||
}
|
||||
terminal.print(
|
||||
"* ",
|
||||
{href: "/~" + app.owner + "/" + app.name},
|
||||
message);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
index();
|
124
packages/cory/mmoturtle/mmoturtle.js
Normal file
124
packages/cory/mmoturtle/mmoturtle.js
Normal file
@ -0,0 +1,124 @@
|
||||
"use strict";
|
||||
|
||||
// This script runs server-side, once for each client session.
|
||||
|
||||
if (imports.terminal) {
|
||||
terminal.setEcho(false);
|
||||
terminal.split([
|
||||
{name: "graphics", basis: "520px", shrink: "0", grow: "0"},
|
||||
{name: "text"},
|
||||
]);
|
||||
|
||||
// Request a callback every time the user hits enter at the terminal prompt.
|
||||
core.register("onInput", function(input) {
|
||||
// Ask a persistent service session to broadcast our message. We'll also get a copy back.
|
||||
return core.getService("turtle").then(function(service) {
|
||||
return service.postMessage(input);
|
||||
});
|
||||
});
|
||||
|
||||
// Request a callback for every message that is broadcast.
|
||||
core.register("onMessage", function(sender, message) {
|
||||
if (message.history) {
|
||||
for (var i = 0; i < message.history.length; i++) {
|
||||
// Pass the message on to the iframe in the client.
|
||||
terminal.postMessageToIframe("turtle", message.history[i]);
|
||||
}
|
||||
} else {
|
||||
// Pass the message on to the iframe in the client.
|
||||
terminal.postMessageToIframe("turtle", message);
|
||||
}
|
||||
});
|
||||
|
||||
core.register("onWindowMessage", function(data) {
|
||||
terminal.print(data.message);
|
||||
});
|
||||
|
||||
terminal.select("graphics");
|
||||
terminal.print("MMO Turtle Graphics using ", {href: "http://codeheartjs.com/turtle/"}, ".");
|
||||
|
||||
// Add an iframe to the terminal. This is how we sandbox code running on the client.
|
||||
var contents = `
|
||||
<script src="http://codeheartjs.com/turtle/turtle.min.js">-*- javascript -*-</script>
|
||||
<script>
|
||||
setScale(2);
|
||||
setWidth(3);
|
||||
|
||||
// Receive messages in the iframe and use them to draw.
|
||||
function onMessage(event) {
|
||||
var parts = event.data.split(" ");
|
||||
var command = parts.shift();
|
||||
if (command == "reset") {
|
||||
setPosition(0, 0);
|
||||
setHeading(0);
|
||||
clear(WHITE);
|
||||
_ch_startTimer(30);
|
||||
} else if (command == "home") {
|
||||
var wasDown = _turtle.penDown;
|
||||
pu();
|
||||
setPosition(0, 0);
|
||||
setHeading(0);
|
||||
if (wasDown) {
|
||||
pd();
|
||||
}
|
||||
_ch_startTimer(30);
|
||||
} else if (command == "clear") {
|
||||
clear(WHITE);
|
||||
_ch_startTimer(30);
|
||||
} else if (["fd", "bk", "rt", "lt", "pu", "pd"].indexOf(command) != -1) {
|
||||
window[command].apply(window, parts.map(parseInt));
|
||||
event.source.postMessage(event.data, event.origin);
|
||||
_ch_startTimer(30);
|
||||
} else {
|
||||
event.source.postMessage("Unrecognized command: " + command, event.origin);
|
||||
}
|
||||
}
|
||||
|
||||
// Register for messages in the iframe
|
||||
window.addEventListener('message', onMessage, false);
|
||||
</script>
|
||||
`
|
||||
terminal.print({iframe: contents, width: 640, height: 480, name: "turtle"});
|
||||
|
||||
terminal.select("text");
|
||||
terminal.print("Supported commands: ", ["fd <distance>", "bk <distance>", "rt <angle>", "lt <angle>", "pu", "pd", "home", "reset", "clear"].join(", "));
|
||||
|
||||
// Get the party started by asking for the history of commands (the turtle party).
|
||||
setTimeout(function() {
|
||||
core.getService("turtle").then(function(service) {
|
||||
return service.postMessage("sync");
|
||||
});
|
||||
}, 1000);
|
||||
} else {
|
||||
var gHistory = null;
|
||||
|
||||
function ensureHistoryLoaded() {
|
||||
if (!gHistory) {
|
||||
return database.get("history").then(function(data) {
|
||||
gHistory = JSON.parse(data);
|
||||
return gHistory;
|
||||
}).catch(function(error) {
|
||||
gHistory = [];
|
||||
return gHistory;
|
||||
});
|
||||
} else {
|
||||
return new Promise(function(resolve, reject) { resolve(gHistory); });
|
||||
}
|
||||
}
|
||||
|
||||
core.register("onMessage", function(sender, message) {
|
||||
return ensureHistoryLoaded().then(function(history) {
|
||||
if (message == "reset") {
|
||||
history.length = 0;
|
||||
database.set("history", JSON.stringify(history));
|
||||
return core.broadcast(message);
|
||||
} else if (message == "sync") {
|
||||
sender.postMessage({history: history});
|
||||
} else {
|
||||
history.push(message);
|
||||
database.set("history", JSON.stringify(history));
|
||||
return core.broadcast(message);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
74
packages/cory/smtp/smtp.js
Normal file
74
packages/cory/smtp/smtp.js
Normal file
@ -0,0 +1,74 @@
|
||||
"use strict";
|
||||
|
||||
//! {"permissions": ["network"]}
|
||||
|
||||
terminal.print("Hello, world!");
|
||||
|
||||
let kFrom = core.user.name + "@unprompted.com";
|
||||
let kTo = "test@unprompted.com";
|
||||
let kSubject = "Hello, world!";
|
||||
let kBody = "This is the body of the email."
|
||||
|
||||
let inBuffer = "";
|
||||
let sentFrom = false;
|
||||
let sentTo = false;
|
||||
let sentData = false;
|
||||
|
||||
function lineReceived(socket, line) {
|
||||
terminal.print("> ", line);
|
||||
let parts = line.split(" ", 1);
|
||||
terminal.print(JSON.stringify(parts));
|
||||
if (parts[0] == "220") {
|
||||
socket.write("HELO rowlf.unprompted.com\r\n");
|
||||
} else if (parts[0] == "250") {
|
||||
if (!sentFrom) {
|
||||
terminal.print("FROM");
|
||||
socket.write("MAIL FROM: " + kFrom + "\r\n");
|
||||
sentFrom = true;
|
||||
} else if (!sentTo) {
|
||||
terminal.print("TO");
|
||||
socket.write("RCPT TO: " + kTo + "\r\n");
|
||||
sentTo = true;
|
||||
} else if (!sentData) {
|
||||
terminal.print("DATA");
|
||||
socket.write("DATA\r\n");
|
||||
sentData = true;
|
||||
} else {
|
||||
terminal.print("QUIT");
|
||||
socket.write("QUIT\r\n");
|
||||
}
|
||||
} else if (parts[0] == "354") {
|
||||
terminal.print("MESSAGE");
|
||||
socket.write("Subject: " + kSubject + "\r\n\r\n" + kBody + "\r\n.\r\n");
|
||||
}
|
||||
}
|
||||
|
||||
function dataReceived(socket, data) {
|
||||
if (data === null) {
|
||||
return;
|
||||
}
|
||||
terminal.print(data);
|
||||
inBuffer += data;
|
||||
let again = true;
|
||||
while (again) {
|
||||
again = false;
|
||||
let end = inBuffer.indexOf("\n");
|
||||
if (end != -1) {
|
||||
again = true;
|
||||
let line = inBuffer.substring(0, end);
|
||||
inBuffer = inBuffer.substring(end + 1);
|
||||
lineReceived(socket, line);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
network.newConnection().then(function(socket) {
|
||||
socket.read(function(data) {
|
||||
try {
|
||||
dataReceived(socket, data);
|
||||
} catch (error) {
|
||||
terminal.print("ERROR: ", error.message);
|
||||
}
|
||||
});
|
||||
socket.connect("localhost", 25);
|
||||
});
|
204
packages/cory/todo/todo.js
Normal file
204
packages/cory/todo/todo.js
Normal file
@ -0,0 +1,204 @@
|
||||
"use strict";
|
||||
|
||||
var kUnchecked = "☐";
|
||||
var kChecked = "☑";
|
||||
|
||||
let activeList = null;
|
||||
let confirmRemove;
|
||||
|
||||
terminal.setPrompt("Add Item>");
|
||||
|
||||
core.register("onInput", function(command) {
|
||||
if (typeof command == "string" && command.substring(0, "action:".length) == "action:") {
|
||||
command = JSON.parse(command.substring("action:".length));
|
||||
if (confirmRemove && command.action != "reallyRemoveList" && command.action != "reallyRemove") {
|
||||
confirmRemove = false;
|
||||
}
|
||||
if (command.action == "set") {
|
||||
setItem(command.key, command.item, command.value).then(notifyChanged).then(redisplay);
|
||||
} else if (command.action == "remove") {
|
||||
confirmRemove = command;
|
||||
redisplay();
|
||||
} else if (command.action == "reallyRemove") {
|
||||
confirmRemove = false;
|
||||
removeItem(command.key, command.item).then(notifyChanged).then(redisplay);
|
||||
} else if (command.action == "editList") {
|
||||
activeList = command.key;
|
||||
terminal.setHash(activeList);
|
||||
redisplay();
|
||||
} else if (command.action == "lists") {
|
||||
activeList = null;
|
||||
redisplay();
|
||||
} else if (command.action == "removeList") {
|
||||
confirmRemove = true;
|
||||
redisplay();
|
||||
} else if (command.action == "reallyRemoveList") {
|
||||
confirmRemove = false;
|
||||
activeList = null;
|
||||
database.remove(command.key).then(notifyChanged).then(redisplay).catch(function(error) {
|
||||
terminal.print(JSON.stringify(error));
|
||||
terminal.print(command.key);
|
||||
});
|
||||
}
|
||||
} else if (typeof command == "string") {
|
||||
if (activeList) {
|
||||
addItem(activeList, command).then(notifyChanged).then(redisplay);
|
||||
} else {
|
||||
activeList = makePrivateKey(command);
|
||||
writeList(activeList, {name: command, items: []}).then(notifyChanged).then(redisplay);
|
||||
}
|
||||
} else if (command.hash) {
|
||||
activeList = command.hash;
|
||||
if (activeList.charAt(0) == "#") {
|
||||
activeList = activeList.substring(1);
|
||||
}
|
||||
redisplay();
|
||||
}
|
||||
});
|
||||
|
||||
core.register("onMessage", function(message) {
|
||||
return redisplay();
|
||||
});
|
||||
|
||||
function notifyChanged() {
|
||||
return core.broadcast({changed: true});
|
||||
}
|
||||
|
||||
function readList(key) {
|
||||
return database.get(key).catch(function(error) {
|
||||
return null;
|
||||
}).then(function(todo) {
|
||||
try {
|
||||
todo = JSON.parse(todo);
|
||||
} catch (error) {
|
||||
todo = {name: "TODO", items: []};
|
||||
}
|
||||
return todo;
|
||||
});
|
||||
}
|
||||
|
||||
function writeList(key, todo) {
|
||||
return database.set(key, JSON.stringify(todo));
|
||||
}
|
||||
|
||||
function addItem(key, name) {
|
||||
return readList(key).then(function(todo) {
|
||||
todo.items.push({name: name, value: false});
|
||||
return writeList(key, todo);
|
||||
});
|
||||
}
|
||||
|
||||
function setItem(key, name, value) {
|
||||
return readList(key).then(function(todo) {
|
||||
for (var i = 0; i < todo.items.length; i++) {
|
||||
if (todo.items[i].name == name) {
|
||||
todo.items[i].value = value;
|
||||
}
|
||||
}
|
||||
return writeList(key, todo);
|
||||
});
|
||||
}
|
||||
|
||||
function removeItem(key, name) {
|
||||
return readList(key).then(function(todo) {
|
||||
todo.items = todo.items.filter(function(item) {
|
||||
return item.name != name;
|
||||
});
|
||||
return writeList(key, todo);
|
||||
});
|
||||
}
|
||||
|
||||
function printList(name, key, items) {
|
||||
terminal.print(name,
|
||||
" - ",
|
||||
{command: "action:" + JSON.stringify({action: "lists"}), value: "back"},
|
||||
" - ",
|
||||
{command: "action:" + JSON.stringify({action: (confirmRemove === true ? "reallyRemoveList" : "removeList"), key: key}), value: (confirmRemove === true ? "confirm remove" : "remove")});
|
||||
terminal.print("=".repeat(name.length));
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
var isChecked = items[i].value;
|
||||
var style = ["", "text-decoration: line-through"];
|
||||
terminal.print(
|
||||
{command: "action:" + JSON.stringify({action: "set", key: key, item: items[i].name, value: !isChecked}), value: isChecked ? kChecked : kUnchecked},
|
||||
" ",
|
||||
{style: style[isChecked ? 1 : 0], value: items[i].name},
|
||||
" (",
|
||||
{command: "action:" + JSON.stringify({
|
||||
action: (confirmRemove && confirmRemove.item == items[i].name ? "reallyRemove" : "remove"),
|
||||
key: key,
|
||||
item: items[i].name}), value: (confirmRemove && confirmRemove.item == items[i].name ? "confirm remove" : "remove")},
|
||||
")");
|
||||
}
|
||||
}
|
||||
|
||||
function redisplay() {
|
||||
terminal.clear();
|
||||
terminal.setEcho(false);
|
||||
if (activeList) {
|
||||
readList(activeList).then(function(data) {
|
||||
printList(getName(activeList), activeList, data.items);
|
||||
}).catch(function(error) {
|
||||
terminal.print("error: " + error);
|
||||
});
|
||||
} else {
|
||||
printListOfLists();
|
||||
}
|
||||
}
|
||||
|
||||
function makeId() {
|
||||
var alphabet = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
|
||||
var result = "";
|
||||
for (var i = 0; i < 32; i++) {
|
||||
result += alphabet.charAt(Math.floor(Math.random() * alphabet.length));
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function makePublicKey(name) {
|
||||
return JSON.stringify({public: true, id: makeId(), name: name});
|
||||
}
|
||||
|
||||
function makePrivateKey(name) {
|
||||
return JSON.stringify({public: false, id: makeId(), name: name, user: core.user.name});
|
||||
}
|
||||
|
||||
function hasPermission(key) {
|
||||
let result = false;
|
||||
try {
|
||||
let data = JSON.parse(key);
|
||||
result = data.public || data.user == core.user.name;
|
||||
} catch (error) {
|
||||
result = true;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
function getName(key) {
|
||||
let name = "TODO";
|
||||
try {
|
||||
name = JSON.parse(key).name || name;
|
||||
} catch (error) {
|
||||
}
|
||||
return name;
|
||||
}
|
||||
|
||||
function getVisibleLists() {
|
||||
return database.getAll().then(function(data) {
|
||||
return data.filter(hasPermission);
|
||||
});
|
||||
}
|
||||
|
||||
function printListOfLists() {
|
||||
terminal.print("TODO Lists:");
|
||||
getVisibleLists().then(function(keys) {
|
||||
for (var i = 0; i < keys.length; i++) {
|
||||
let key = keys[i];
|
||||
terminal.print({
|
||||
command: "action:" + JSON.stringify({action: "editList", key: key}),
|
||||
value: getName(key),
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
redisplay();
|
41
packages/cory/turtle/turtle.js
Normal file
41
packages/cory/turtle/turtle.js
Normal file
@ -0,0 +1,41 @@
|
||||
"use strict";
|
||||
|
||||
// Start at bottom left facing up.
|
||||
// Height = 20. Width = 10.
|
||||
// 10 between.
|
||||
|
||||
var letters = {
|
||||
A: 'fd(20); rt(90); fd(10); rt(90); fd(10); rt(90); fd(10); pu(); bk(10); lt(90); pd(); fd(10); pu(); lt(90); fd(10); lt(90); pd();',
|
||||
D: 'fd(20); rt(90); fd(10); rt(70); fd(11); rt(40); fd(11); rt(70); fd(10); pu(); bk(20); rt(90); pd();',
|
||||
E: 'pu(); fd(20); rt(90); fd(10); lt(180); pd(); fd(10); lt(90); fd(10); lt(90); fd(8); pu(); rt(180); fd(8); lt(90); pd(); fd(10); lt(90); fd(10); pu(); fd(10); lt(90); pd()',
|
||||
H: 'fd(20); pu(); bk(10); pd(); rt(90); fd(10); lt(90); pu(); fd(10); rt(180); pd(); fd(20); pu(); lt(90); fd(10); lt(90); pd();',
|
||||
L: 'pu(); fd(20); rt(180); pd(); fd(20); lt(90); fd(10); pu(); fd(10); lt(90); pd();',
|
||||
O: 'fd(20); rt(90); fd(10); rt(90); fd(20); rt(90); fd(10); pu(); bk(20); rt(90); pd();',
|
||||
R: 'fd(20); rt(90); fd(10); rt(90); fd(10); rt(90); fd(10); pu(); bk(8); lt(90); pd(); fd(10); pu(); lt(90); fd(12); lt(90); pd();',
|
||||
W: 'pu(); fd(20); rt(180); pd(); fd(20); lt(90); fd(5); lt(90); fd(12); rt(180); pu(); fd(12); pd(); lt(90); fd(5); lt(90); fd(20); pu(); bk(20); rt(90); fd(10); lt(90); pd();',
|
||||
' ': 'pu(); rt(90); fd(20); lt(90); pd();',
|
||||
};
|
||||
|
||||
function render(text) {
|
||||
terminal.clear();
|
||||
terminal.print(text, " using ", {href: "http://codeheartjs.com/turtle/"}, ".");
|
||||
var contents = '<script src="http://codeheartjs.com/turtle/turtle.min.js">-*- javascript -*-</script><script>\n';
|
||||
contents += 'setScale(2); setWidth(5);\n';
|
||||
for (var i = 0; i < text.length; i++) {
|
||||
var c = text.charAt(i).toUpperCase();
|
||||
if (letters[c]) {
|
||||
contents += letters[c] + '\n';
|
||||
} else {
|
||||
contents += letters[' '] + '\n';
|
||||
}
|
||||
}
|
||||
contents += "ht();\n";
|
||||
contents += "window.addEventListener('message', function(event) { console.debug(event.data); }, false);\n";
|
||||
contents += "</script>\n";
|
||||
terminal.print({iframe: contents, width: 640, height: 480});
|
||||
terminal.print("Type text and the letters ", {style: "color: #ff0", value: Object.keys(letters).join("")}, " in it will be drawn.");
|
||||
}
|
||||
|
||||
render("Hello, world!");
|
||||
|
||||
core.register("onInput", render);
|
952
packages/cory/xmpp/xmpp.js
Normal file
952
packages/cory/xmpp/xmpp.js
Normal file
@ -0,0 +1,952 @@
|
||||
"use strict";
|
||||
|
||||
//! {"permissions": ["network"]}
|
||||
|
||||
// md5.js
|
||||
|
||||
/*
|
||||
* JavaScript MD5 1.0.1
|
||||
* https://github.com/blueimp/JavaScript-MD5
|
||||
*
|
||||
* Copyright 2011, Sebastian Tschan
|
||||
* https://blueimp.net
|
||||
*
|
||||
* Licensed under the MIT license:
|
||||
* http://www.opensource.org/licenses/MIT
|
||||
*
|
||||
* Based on
|
||||
* A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
|
||||
* Digest Algorithm, as defined in RFC 1321.
|
||||
* Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
|
||||
* Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
|
||||
* Distributed under the BSD License
|
||||
* See http://pajhome.org.uk/crypt/md5 for more info.
|
||||
*/
|
||||
|
||||
/*jslint bitwise: true */
|
||||
/*global unescape, define */
|
||||
|
||||
'use strict';
|
||||
|
||||
/*
|
||||
* Add integers, wrapping at 2^32. This uses 16-bit operations internally
|
||||
* to work around bugs in some JS interpreters.
|
||||
*/
|
||||
function safe_add(x, y) {
|
||||
var lsw = (x & 0xFFFF) + (y & 0xFFFF),
|
||||
msw = (x >> 16) + (y >> 16) + (lsw >> 16);
|
||||
return (msw << 16) | (lsw & 0xFFFF);
|
||||
}
|
||||
|
||||
/*
|
||||
* Bitwise rotate a 32-bit number to the left.
|
||||
*/
|
||||
function bit_rol(num, cnt) {
|
||||
return (num << cnt) | (num >>> (32 - cnt));
|
||||
}
|
||||
|
||||
/*
|
||||
* These functions implement the four basic operations the algorithm uses.
|
||||
*/
|
||||
function md5_cmn(q, a, b, x, s, t) {
|
||||
return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);
|
||||
}
|
||||
function md5_ff(a, b, c, d, x, s, t) {
|
||||
return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
|
||||
}
|
||||
function md5_gg(a, b, c, d, x, s, t) {
|
||||
return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
|
||||
}
|
||||
function md5_hh(a, b, c, d, x, s, t) {
|
||||
return md5_cmn(b ^ c ^ d, a, b, x, s, t);
|
||||
}
|
||||
function md5_ii(a, b, c, d, x, s, t) {
|
||||
return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the MD5 of an array of little-endian words, and a bit length.
|
||||
*/
|
||||
function binl_md5(x, len) {
|
||||
/* append padding */
|
||||
x[len >> 5] |= 0x80 << (len % 32);
|
||||
x[(((len + 64) >>> 9) << 4) + 14] = len;
|
||||
|
||||
var i, olda, oldb, oldc, oldd,
|
||||
a = 1732584193,
|
||||
b = -271733879,
|
||||
c = -1732584194,
|
||||
d = 271733878;
|
||||
|
||||
for (i = 0; i < x.length; i += 16) {
|
||||
olda = a;
|
||||
oldb = b;
|
||||
oldc = c;
|
||||
oldd = d;
|
||||
|
||||
a = md5_ff(a, b, c, d, x[i], 7, -680876936);
|
||||
d = md5_ff(d, a, b, c, x[i + 1], 12, -389564586);
|
||||
c = md5_ff(c, d, a, b, x[i + 2], 17, 606105819);
|
||||
b = md5_ff(b, c, d, a, x[i + 3], 22, -1044525330);
|
||||
a = md5_ff(a, b, c, d, x[i + 4], 7, -176418897);
|
||||
d = md5_ff(d, a, b, c, x[i + 5], 12, 1200080426);
|
||||
c = md5_ff(c, d, a, b, x[i + 6], 17, -1473231341);
|
||||
b = md5_ff(b, c, d, a, x[i + 7], 22, -45705983);
|
||||
a = md5_ff(a, b, c, d, x[i + 8], 7, 1770035416);
|
||||
d = md5_ff(d, a, b, c, x[i + 9], 12, -1958414417);
|
||||
c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
|
||||
b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
|
||||
a = md5_ff(a, b, c, d, x[i + 12], 7, 1804603682);
|
||||
d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
|
||||
c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
|
||||
b = md5_ff(b, c, d, a, x[i + 15], 22, 1236535329);
|
||||
|
||||
a = md5_gg(a, b, c, d, x[i + 1], 5, -165796510);
|
||||
d = md5_gg(d, a, b, c, x[i + 6], 9, -1069501632);
|
||||
c = md5_gg(c, d, a, b, x[i + 11], 14, 643717713);
|
||||
b = md5_gg(b, c, d, a, x[i], 20, -373897302);
|
||||
a = md5_gg(a, b, c, d, x[i + 5], 5, -701558691);
|
||||
d = md5_gg(d, a, b, c, x[i + 10], 9, 38016083);
|
||||
c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
|
||||
b = md5_gg(b, c, d, a, x[i + 4], 20, -405537848);
|
||||
a = md5_gg(a, b, c, d, x[i + 9], 5, 568446438);
|
||||
d = md5_gg(d, a, b, c, x[i + 14], 9, -1019803690);
|
||||
c = md5_gg(c, d, a, b, x[i + 3], 14, -187363961);
|
||||
b = md5_gg(b, c, d, a, x[i + 8], 20, 1163531501);
|
||||
a = md5_gg(a, b, c, d, x[i + 13], 5, -1444681467);
|
||||
d = md5_gg(d, a, b, c, x[i + 2], 9, -51403784);
|
||||
c = md5_gg(c, d, a, b, x[i + 7], 14, 1735328473);
|
||||
b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);
|
||||
|
||||
a = md5_hh(a, b, c, d, x[i + 5], 4, -378558);
|
||||
d = md5_hh(d, a, b, c, x[i + 8], 11, -2022574463);
|
||||
c = md5_hh(c, d, a, b, x[i + 11], 16, 1839030562);
|
||||
b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
|
||||
a = md5_hh(a, b, c, d, x[i + 1], 4, -1530992060);
|
||||
d = md5_hh(d, a, b, c, x[i + 4], 11, 1272893353);
|
||||
c = md5_hh(c, d, a, b, x[i + 7], 16, -155497632);
|
||||
b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
|
||||
a = md5_hh(a, b, c, d, x[i + 13], 4, 681279174);
|
||||
d = md5_hh(d, a, b, c, x[i], 11, -358537222);
|
||||
c = md5_hh(c, d, a, b, x[i + 3], 16, -722521979);
|
||||
b = md5_hh(b, c, d, a, x[i + 6], 23, 76029189);
|
||||
a = md5_hh(a, b, c, d, x[i + 9], 4, -640364487);
|
||||
d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
|
||||
c = md5_hh(c, d, a, b, x[i + 15], 16, 530742520);
|
||||
b = md5_hh(b, c, d, a, x[i + 2], 23, -995338651);
|
||||
|
||||
a = md5_ii(a, b, c, d, x[i], 6, -198630844);
|
||||
d = md5_ii(d, a, b, c, x[i + 7], 10, 1126891415);
|
||||
c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
|
||||
b = md5_ii(b, c, d, a, x[i + 5], 21, -57434055);
|
||||
a = md5_ii(a, b, c, d, x[i + 12], 6, 1700485571);
|
||||
d = md5_ii(d, a, b, c, x[i + 3], 10, -1894986606);
|
||||
c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
|
||||
b = md5_ii(b, c, d, a, x[i + 1], 21, -2054922799);
|
||||
a = md5_ii(a, b, c, d, x[i + 8], 6, 1873313359);
|
||||
d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
|
||||
c = md5_ii(c, d, a, b, x[i + 6], 15, -1560198380);
|
||||
b = md5_ii(b, c, d, a, x[i + 13], 21, 1309151649);
|
||||
a = md5_ii(a, b, c, d, x[i + 4], 6, -145523070);
|
||||
d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
|
||||
c = md5_ii(c, d, a, b, x[i + 2], 15, 718787259);
|
||||
b = md5_ii(b, c, d, a, x[i + 9], 21, -343485551);
|
||||
|
||||
a = safe_add(a, olda);
|
||||
b = safe_add(b, oldb);
|
||||
c = safe_add(c, oldc);
|
||||
d = safe_add(d, oldd);
|
||||
}
|
||||
return [a, b, c, d];
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert an array of little-endian words to a string
|
||||
*/
|
||||
function binl2rstr(input) {
|
||||
var i,
|
||||
output = '';
|
||||
for (i = 0; i < input.length * 32; i += 8) {
|
||||
output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a raw string to an array of little-endian words
|
||||
* Characters >255 have their high-byte silently ignored.
|
||||
*/
|
||||
function rstr2binl(input) {
|
||||
var i,
|
||||
output = [];
|
||||
output[(input.length >> 2) - 1] = undefined;
|
||||
for (i = 0; i < output.length; i += 1) {
|
||||
output[i] = 0;
|
||||
}
|
||||
for (i = 0; i < input.length * 8; i += 8) {
|
||||
output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the MD5 of a raw string
|
||||
*/
|
||||
function rstr_md5(s) {
|
||||
return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
|
||||
}
|
||||
|
||||
/*
|
||||
* Calculate the HMAC-MD5, of a key and some data (raw strings)
|
||||
*/
|
||||
function rstr_hmac_md5(key, data) {
|
||||
var i,
|
||||
bkey = rstr2binl(key),
|
||||
ipad = [],
|
||||
opad = [],
|
||||
hash;
|
||||
ipad[15] = opad[15] = undefined;
|
||||
if (bkey.length > 16) {
|
||||
bkey = binl_md5(bkey, key.length * 8);
|
||||
}
|
||||
for (i = 0; i < 16; i += 1) {
|
||||
ipad[i] = bkey[i] ^ 0x36363636;
|
||||
opad[i] = bkey[i] ^ 0x5C5C5C5C;
|
||||
}
|
||||
hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
|
||||
return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
|
||||
}
|
||||
|
||||
/*
|
||||
* Convert a raw string to a hex string
|
||||
*/
|
||||
function rstr2hex(input) {
|
||||
var hex_tab = '0123456789abcdef',
|
||||
output = '',
|
||||
x,
|
||||
i;
|
||||
for (i = 0; i < input.length; i += 1) {
|
||||
x = input.charCodeAt(i);
|
||||
output += hex_tab.charAt((x >>> 4) & 0x0F) +
|
||||
hex_tab.charAt(x & 0x0F);
|
||||
}
|
||||
return output;
|
||||
}
|
||||
|
||||
/*
|
||||
* Encode a string as utf-8
|
||||
*/
|
||||
function str2rstr_utf8(input) {
|
||||
return unescape(input);
|
||||
}
|
||||
|
||||
/*
|
||||
* Take string arguments and return either raw or hex encoded strings
|
||||
*/
|
||||
function raw_md5(s) {
|
||||
return rstr_md5(str2rstr_utf8(s));
|
||||
}
|
||||
function hex_md5(s) {
|
||||
return rstr2hex(raw_md5(s));
|
||||
}
|
||||
function raw_hmac_md5(k, d) {
|
||||
return rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d));
|
||||
}
|
||||
function hex_hmac_md5(k, d) {
|
||||
return rstr2hex(raw_hmac_md5(k, d));
|
||||
}
|
||||
|
||||
function md5(string, key, raw) {
|
||||
if (!key) {
|
||||
if (!raw) {
|
||||
return hex_md5(string);
|
||||
}
|
||||
return raw_md5(string);
|
||||
}
|
||||
if (!raw) {
|
||||
return hex_hmac_md5(key, string);
|
||||
}
|
||||
return raw_hmac_md5(key, string);
|
||||
}
|
||||
|
||||
// end md5.js
|
||||
|
||||
// base64.js
|
||||
/**
|
||||
*
|
||||
* Base64 encode / decode
|
||||
* http://www.webtoolkit.info/
|
||||
*
|
||||
**/
|
||||
|
||||
// private property
|
||||
var _keyStr = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
|
||||
var Base64 = {
|
||||
|
||||
// public method for encoding
|
||||
encode : function (input) {
|
||||
var output = "";
|
||||
var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
|
||||
var i = 0;
|
||||
|
||||
input = Base64._utf8_encode(input);
|
||||
|
||||
while (i < input.length) {
|
||||
|
||||
chr1 = input.charCodeAt(i++);
|
||||
chr2 = input.charCodeAt(i++);
|
||||
chr3 = input.charCodeAt(i++);
|
||||
|
||||
enc1 = chr1 >> 2;
|
||||
enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
|
||||
enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
|
||||
enc4 = chr3 & 63;
|
||||
|
||||
if (isNaN(chr2)) {
|
||||
enc3 = enc4 = 64;
|
||||
} else if (isNaN(chr3)) {
|
||||
enc4 = 64;
|
||||
}
|
||||
|
||||
output = output +
|
||||
_keyStr.charAt(enc1) + _keyStr.charAt(enc2) +
|
||||
_keyStr.charAt(enc3) + _keyStr.charAt(enc4);
|
||||
|
||||
}
|
||||
|
||||
return output;
|
||||
},
|
||||
|
||||
// public method for decoding
|
||||
decode : function (input) {
|
||||
var output = "";
|
||||
var chr1, chr2, chr3;
|
||||
var enc1, enc2, enc3, enc4;
|
||||
var i = 0;
|
||||
|
||||
input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
|
||||
|
||||
while (i < input.length) {
|
||||
|
||||
enc1 = _keyStr.indexOf(input.charAt(i++));
|
||||
enc2 = _keyStr.indexOf(input.charAt(i++));
|
||||
enc3 = _keyStr.indexOf(input.charAt(i++));
|
||||
enc4 = _keyStr.indexOf(input.charAt(i++));
|
||||
|
||||
chr1 = (enc1 << 2) | (enc2 >> 4);
|
||||
chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
|
||||
chr3 = ((enc3 & 3) << 6) | enc4;
|
||||
|
||||
output = output + String.fromCharCode(chr1);
|
||||
|
||||
if (enc3 != 64) {
|
||||
output = output + String.fromCharCode(chr2);
|
||||
}
|
||||
if (enc4 != 64) {
|
||||
output = output + String.fromCharCode(chr3);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
output = Base64._utf8_decode(output);
|
||||
|
||||
return output;
|
||||
|
||||
},
|
||||
|
||||
// private method for UTF-8 encoding
|
||||
_utf8_encode : function (string) {
|
||||
string = string.replace(/\r\n/g,"\n");
|
||||
var utftext = "";
|
||||
|
||||
for (var n = 0; n < string.length; n++) {
|
||||
|
||||
var c = string.charCodeAt(n);
|
||||
|
||||
if (c < 128) {
|
||||
utftext += String.fromCharCode(c);
|
||||
}
|
||||
else if((c > 127) && (c < 2048)) {
|
||||
utftext += String.fromCharCode((c >> 6) | 192);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
else {
|
||||
utftext += String.fromCharCode((c >> 12) | 224);
|
||||
utftext += String.fromCharCode(((c >> 6) & 63) | 128);
|
||||
utftext += String.fromCharCode((c & 63) | 128);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return utftext;
|
||||
},
|
||||
|
||||
// private method for UTF-8 decoding
|
||||
_utf8_decode : function (utftext) {
|
||||
var string = "";
|
||||
var i = 0;
|
||||
var c = 0;
|
||||
var c1 = 0;
|
||||
var c2 = 0;
|
||||
|
||||
while ( i < utftext.length ) {
|
||||
|
||||
c = utftext.charCodeAt(i);
|
||||
|
||||
if (c < 128) {
|
||||
string += String.fromCharCode(c);
|
||||
i++;
|
||||
}
|
||||
else if((c > 191) && (c < 224)) {
|
||||
c2 = utftext.charCodeAt(i+1);
|
||||
string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
|
||||
i += 2;
|
||||
}
|
||||
else {
|
||||
c2 = utftext.charCodeAt(i+1);
|
||||
c3 = utftext.charCodeAt(i+2);
|
||||
string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
|
||||
i += 3;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return string;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// end base64.js
|
||||
|
||||
function xmlEncode(text) {
|
||||
return text.replace(/([\&"'<>])/g, function(x, item) {
|
||||
return {'&': '&', '"': '"', '<': '<', '>': '>', "'": '''}[item];
|
||||
});
|
||||
}
|
||||
function xmlDecode(xml) {
|
||||
return xml.replace(/("|<|>|&|')/g, function(x, item) {
|
||||
return {'&': '&', '"': '"', '<': '<', '>': '>', ''': "'"}[item];
|
||||
});
|
||||
}
|
||||
|
||||
// xmpp.js
|
||||
function XmlStreamParser() {
|
||||
this.buffer = "";
|
||||
this._parsed = [];
|
||||
this.reset();
|
||||
return this;
|
||||
}
|
||||
|
||||
XmlStreamParser.kText = "text";
|
||||
XmlStreamParser.kElement = "element";
|
||||
XmlStreamParser.kEndElement = "endElement";
|
||||
XmlStreamParser.kAttributeName = "attributeName";
|
||||
XmlStreamParser.kAttributeValue = "attributeValue";
|
||||
|
||||
XmlStreamParser.prototype.reset = function() {
|
||||
this._state = XmlStreamParser.kText;
|
||||
this._attributes = {};
|
||||
this._attributeName = "";
|
||||
this._attributeValue = "";
|
||||
this._attributeEquals = false;
|
||||
this._attributeQuote = "";
|
||||
this._slash = false;
|
||||
this._value = "";
|
||||
this._decl = false;
|
||||
}
|
||||
|
||||
XmlStreamParser.prototype.parse = function(data) {
|
||||
this._parsed = [];
|
||||
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var c = data.charAt(i);
|
||||
this.parseCharacter(c);
|
||||
}
|
||||
|
||||
return this._parsed;
|
||||
}
|
||||
|
||||
XmlStreamParser.prototype.flush = function() {
|
||||
var node = {type: this._state};
|
||||
if (this._value) {
|
||||
node.value = xmlDecode(this._value);
|
||||
}
|
||||
if (this._attributes.length || this._state == XmlStreamParser.kElement) {
|
||||
node.attributes = this._attributes;
|
||||
}
|
||||
if (this._state != XmlStreamParser.kText || this._value) {
|
||||
this.emit(node);
|
||||
}
|
||||
this.reset();
|
||||
}
|
||||
|
||||
XmlStreamParser.prototype.parseCharacter = function(c) {
|
||||
switch (this._state) {
|
||||
case XmlStreamParser.kText:
|
||||
if (c == '<') {
|
||||
this.flush();
|
||||
this._state = XmlStreamParser.kElement;
|
||||
} else {
|
||||
this._value += c;
|
||||
}
|
||||
break;
|
||||
case XmlStreamParser.kElement:
|
||||
case XmlStreamParser.kEndElement:
|
||||
switch (c) {
|
||||
case '>':
|
||||
this.finishElement();
|
||||
break;
|
||||
case '/':
|
||||
if (!this._value) {
|
||||
this._state = XmlStreamParser.kEndElement;
|
||||
} else if (!this._slash) {
|
||||
this._slash = true;
|
||||
} else {
|
||||
this._value += c;
|
||||
}
|
||||
break;
|
||||
case '?':
|
||||
if (!this._value) {
|
||||
this._decl = true;
|
||||
} else {
|
||||
this._value += '?';
|
||||
}
|
||||
break;
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
this._state = XmlStreamParser.kAttributeName;
|
||||
break;
|
||||
default:
|
||||
if (this._slash) {
|
||||
this._slash = false;
|
||||
this._value += '/';
|
||||
}
|
||||
this._value += c;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case XmlStreamParser.kAttributeName:
|
||||
switch (c) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
if (this._attributeName) {
|
||||
this._state = XmlStreamParser.kAttributeValue;
|
||||
}
|
||||
break;
|
||||
case '/':
|
||||
if (!this._slash) {
|
||||
this._slash = true;
|
||||
} else {
|
||||
this._value += '/';
|
||||
}
|
||||
break;
|
||||
case '=':
|
||||
this._state = XmlStreamParser.kAttributeValue;
|
||||
break;
|
||||
case '>':
|
||||
if (this._attributeName) {
|
||||
this._attributes[this._attributeName] = null;
|
||||
}
|
||||
this._state = XmlStreamParser.kElement;
|
||||
this.finishElement();
|
||||
break;
|
||||
default:
|
||||
this._attributeName += c;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case XmlStreamParser.kAttributeValue:
|
||||
switch (c) {
|
||||
case ' ':
|
||||
case '\t':
|
||||
case '\r':
|
||||
case '\n':
|
||||
if (this._attributeValue) {
|
||||
this._state = XmlStreamParser.kAttributeName;
|
||||
}
|
||||
break;
|
||||
case '"':
|
||||
case "'":
|
||||
if (!this._attributeValue && !this._attributeQuote) {
|
||||
this._attributeQuote = c;
|
||||
} else if (this._attributeQuote == c) {
|
||||
this._attributes[this._attributeName] = this._attributeValue;
|
||||
this._attributeName = "";
|
||||
this._attributeValue = "";
|
||||
this._attributeQuote = "";
|
||||
this._state = XmlStreamParser.kAttributeName;
|
||||
} else {
|
||||
this._attributeValue += c;
|
||||
}
|
||||
break;
|
||||
case '>':
|
||||
this.finishElement();
|
||||
break;
|
||||
default:
|
||||
this._attributeValue += c;
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
XmlStreamParser.prototype.finishElement = function() {
|
||||
if (this._decl) {
|
||||
this.reset();
|
||||
} else {
|
||||
var value = this._value;
|
||||
var slash = this._slash;
|
||||
this.flush();
|
||||
if (slash) {
|
||||
this._state = XmlStreamParser.kEndElement;
|
||||
this._value = value;
|
||||
this.flush();
|
||||
}
|
||||
}
|
||||
this._state = XmlStreamParser.kText;
|
||||
}
|
||||
|
||||
XmlStreamParser.prototype.emit = function(node) {
|
||||
this._parsed.push(node);
|
||||
}
|
||||
|
||||
function XmlStanzaParser(depth) {
|
||||
this._depth = depth || 0;
|
||||
this._parsed = [];
|
||||
this._stack = [];
|
||||
this._stream = new XmlStreamParser();
|
||||
return this;
|
||||
}
|
||||
|
||||
XmlStanzaParser.prototype.reset = function() {
|
||||
this._parsed = [];
|
||||
this._stack = [];
|
||||
this._stream.reset();
|
||||
}
|
||||
|
||||
XmlStanzaParser.prototype.emit = function(stanza) {
|
||||
this._parsed.push(stanza);
|
||||
}
|
||||
|
||||
XmlStanzaParser.prototype.parse = function(data) {
|
||||
this._parsed = [];
|
||||
var nodes = this._stream.parse(data);
|
||||
for (var i = 0; i < nodes.length; i++) {
|
||||
this.parseNode(nodes[i]);
|
||||
}
|
||||
return this._parsed;
|
||||
}
|
||||
|
||||
XmlStanzaParser.prototype.parseNode = function(node) {
|
||||
switch (node.type) {
|
||||
case XmlStreamParser.kElement:
|
||||
this._stack.push({name: node.value, attributes: node.attributes, children: [], text: ""});
|
||||
break;
|
||||
case XmlStreamParser.kEndElement:
|
||||
if (this._stack.length == 1 + this._depth) {
|
||||
this.emit(this._stack.pop());
|
||||
} else {
|
||||
var last = this._stack.pop();
|
||||
this._stack[this._stack.length - 1].children.push(last);
|
||||
}
|
||||
break;
|
||||
case XmlStreamParser.kText:
|
||||
if (this._stack) {
|
||||
this._stack[this._stack.length - 1].text += node.value;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// end xmpp.js
|
||||
|
||||
var gFocus = false;
|
||||
var gUnread = 0;
|
||||
|
||||
function updateTitle() {
|
||||
if (gUnread) {
|
||||
terminal.setTitle("(" + gUnread.toString() + ") ~Friends XMPP");
|
||||
} else {
|
||||
terminal.setTitle("~Friends XMPP");
|
||||
}
|
||||
}
|
||||
|
||||
terminal.print("~Friends XMPP");
|
||||
updateTitle();
|
||||
terminal.setEcho(false);
|
||||
terminal.setPrompt("Username:");
|
||||
terminal.readLine().then(function(userName) {
|
||||
terminal.setPassword(true);
|
||||
terminal.setPrompt("Password:");
|
||||
terminal.readLine().then(function(password) {
|
||||
terminal.setPrompt(">");
|
||||
terminal.setPassword(false);
|
||||
network.newConnection().then(function(socket) {
|
||||
connect(socket, userName, password);
|
||||
}).catch(function(error) {
|
||||
terminal.print(error);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function niceTime(lastTime, thisTime) {
|
||||
if (!lastTime) {
|
||||
return thisTime;
|
||||
}
|
||||
let result = [];
|
||||
let lastParts = lastTime.split(" ");
|
||||
let thisParts = thisTime.split(" ");
|
||||
for (let i = 0; i < thisParts.length; i++) {
|
||||
if (thisParts[i] !== lastParts[i]) {
|
||||
result.push(thisParts[i]);
|
||||
}
|
||||
}
|
||||
return result.join(" ");
|
||||
}
|
||||
|
||||
function formatMessage(message) {
|
||||
var result;
|
||||
if (typeof message == "string") {
|
||||
result = [];
|
||||
var regex = /(\w+:\/*\S+?)(?=(?:[\.!?])?(?:$|\s))/gi;
|
||||
var match;
|
||||
var lastIndex = 0;
|
||||
while ((match = regex.exec(message)) !== null) {
|
||||
result.push({class: "base1", value: message.substring(lastIndex, match.index)});
|
||||
result.push({href: match[0]});
|
||||
lastIndex = regex.lastIndex;
|
||||
}
|
||||
result.push({class: "base1", value: message.substring(lastIndex)});
|
||||
} else {
|
||||
result = message;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
var lastTimestamp = null;
|
||||
function printMessage(stanza) {
|
||||
var body;
|
||||
var delayed = false;
|
||||
var now = new Date().toString();
|
||||
for (var i in stanza.children) {
|
||||
if (stanza.children[i].name == "body") {
|
||||
body = stanza.children[i].text;
|
||||
}
|
||||
if (stanza.children[i].name == "delay") {
|
||||
delayed = true;
|
||||
now = new Date(stanza.children[i].attributes.stamp).toString();
|
||||
}
|
||||
}
|
||||
|
||||
var from = stanza.attributes.from || "unknown";
|
||||
if (from && from.indexOf('/') != -1) {
|
||||
from = from.split("/")[1];
|
||||
}
|
||||
|
||||
terminal.print(
|
||||
{class: "base0", value: niceTime(lastTimestamp, now)},
|
||||
" ",
|
||||
{class: "base00", value: "<"},
|
||||
{class: "base3", value: from},
|
||||
{class: "base00", value: ">"},
|
||||
" ",
|
||||
formatMessage(body));
|
||||
lastTimestamp = now;
|
||||
}
|
||||
|
||||
var gRecent = [];
|
||||
|
||||
core.register("focus", function() {
|
||||
gFocus = true;
|
||||
gUnread = 0;
|
||||
updateTitle();
|
||||
});
|
||||
|
||||
core.register("blur", function() {
|
||||
gFocus = false;
|
||||
});
|
||||
|
||||
var gPingCount = 0;
|
||||
|
||||
function schedulePing(socket) {
|
||||
setTimeout(function() {
|
||||
socket.write("<iq type='get' id='ping" + (++gPingCount) + "'><ping xmlns='urn:xmpp:ping'/></iq>");
|
||||
schedulePing(socket);
|
||||
}, 60000);
|
||||
}
|
||||
|
||||
terminal.split([
|
||||
{type: "horizontal", children: [
|
||||
{name: "terminal", grow: 1},
|
||||
{name: "users", grow: 0},
|
||||
]},
|
||||
]);
|
||||
terminal.select("terminal");
|
||||
|
||||
var gPresence = {};
|
||||
|
||||
function refreshUsers() {
|
||||
terminal.select("users");
|
||||
terminal.clear();
|
||||
for (var i in gPresence) {
|
||||
terminal.print(i);
|
||||
}
|
||||
terminal.select("terminal");
|
||||
}
|
||||
|
||||
function connect(socket, userName, password) {
|
||||
var kTrustedCertificate = "-----BEGIN CERTIFICATE-----\n" +
|
||||
"MIICqjCCAhOgAwIBAgIJAPEhMguftPdoMA0GCSqGSIb3DQEBCwUAMG4xCzAJBgNV\n" +
|
||||
"BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVzdGluMRswGQYDVQQK\n" +
|
||||
"DBJUcm91YmxlIEltcGFjdCBMTEMxITAfBgNVBAMMGGphYmJlci50cm91YmxlaW1w\n" +
|
||||
"YWN0LmNvbTAeFw0xNDEyMjYwMzU5NDRaFw0yNDEyMjMwMzU5NDRaMG4xCzAJBgNV\n" +
|
||||
"BAYTAlVTMQ4wDAYDVQQIDAVUZXhhczEPMA0GA1UEBwwGQXVzdGluMRswGQYDVQQK\n" +
|
||||
"DBJUcm91YmxlIEltcGFjdCBMTEMxITAfBgNVBAMMGGphYmJlci50cm91YmxlaW1w\n" +
|
||||
"YWN0LmNvbTCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEAueniASgCpF7mQFGt\n" +
|
||||
"TycOhMt9VMetFwwDkwVglvO+VKq8JWxWkJaCWm8YYacG6+zn4RlV3zVQhrAcReTU\n" +
|
||||
"pPQAe+28wJdqVt/HPyfcwJtLKUEL7Nk5N8mY2s6yyBVvMn9e7Yt/fnv7pOCpcmBi\n" +
|
||||
"kuLlwSGEfMnDskt8kH4coidP4w0CAwEAAaNQME4wHQYDVR0OBBYEFOztZhuuqXrN\n" +
|
||||
"yUnPo/9aoNNb/o2CMB8GA1UdIwQYMBaAFOztZhuuqXrNyUnPo/9aoNNb/o2CMAwG\n" +
|
||||
"A1UdEwQFMAMBAf8wDQYJKoZIhvcNAQELBQADgYEAgK/7yoGEHeG95i6E1A8ZBkeL\n" +
|
||||
"monKMys3RxnJciuFdBrUcvymsgOTrAGvatPXatNbHQ/eY8LnkKHtf0pCCs0B/xST\n" +
|
||||
"DTO3KdlNCXApMUieFPjVggRzikbmbPCvtTt2BzqQKzVqubf9eM+kbsD7Pkgycm5+\n" +
|
||||
"q46TZws0oz5lAvklIgo=\n" +
|
||||
"-----END CERTIFICATE-----";
|
||||
var resource = "tildefriend" + core.user.index;
|
||||
socket.connect("jabber.troubleimpact.com", 5222).then(function() {
|
||||
var parse = new XmlStanzaParser(1);
|
||||
socket.write("<?xml version='1.0'?>");
|
||||
socket.write("<stream:stream to='jabber.troubleimpact.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>");
|
||||
|
||||
var started = false;
|
||||
var authenticated = false;
|
||||
socket.onError(function(error) {
|
||||
terminal.print("SOCKET ERROR");
|
||||
terminal.print(JSON.stringify(error));
|
||||
terminal.print(error);
|
||||
terminal.print(error.message);
|
||||
});
|
||||
socket.read(function(data) {
|
||||
try {
|
||||
gRecent.push(data);
|
||||
while (gRecent.length > 10) {
|
||||
gRecent.shift();
|
||||
}
|
||||
if (data === undefined) {
|
||||
terminal.print(JSON.stringify(data));
|
||||
terminal.print("Disconnected.");
|
||||
terminal.print("Recent data:");
|
||||
for (let i = 0; i < gRecent.length; i++) {
|
||||
terminal.print(JSON.stringify(gRecent[i]));
|
||||
}
|
||||
return;
|
||||
}
|
||||
parse.parse(data).forEach(function(stanza) {
|
||||
if (stanza.name == "stream:features") {
|
||||
if (!started) {
|
||||
socket.write("<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>");
|
||||
} else if (!authenticated) {
|
||||
socket.write("<auth xmlns='urn:ietf:params:xml:ns:xmpp-sasl' mechanism='DIGEST-MD5'/>");
|
||||
} else {
|
||||
socket.write("<iq type='set' id='bind0'><bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'><resource>" + resource + "</resource></bind></iq>");
|
||||
}
|
||||
} else if (stanza.name == "proceed") {
|
||||
if (!started) {
|
||||
started = true;
|
||||
socket.addTrustedCertificate(kTrustedCertificate);
|
||||
socket.startTls().then(function() {
|
||||
parse.reset();
|
||||
socket.write("<stream:stream to='jabber.troubleimpact.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>");
|
||||
}).catch(function(e) {
|
||||
terminal.print("TLS FAILED: " + e);
|
||||
});
|
||||
}
|
||||
} else if (stanza.name == "success") {
|
||||
authenticated = true;
|
||||
socket.write("<?xml version='1.0'?>");
|
||||
socket.write("<stream:stream to='jabber.troubleimpact.com' xmlns='jabber:client' xmlns:stream='http://etherx.jabber.org/streams' version='1.0'>");
|
||||
parse.reset();
|
||||
} else if (stanza.name == "iq") {
|
||||
if (stanza.attributes.id == "bind0") {
|
||||
socket.write("<iq type='set' id='session0'><session xmlns='urn:ietf:params:xml:ns:xmpp-session'/></iq>");
|
||||
} else if (stanza.attributes.id == "session0") {
|
||||
socket.write("<presence to='chadhappyfuntime@conference.jabber.troubleimpact.com/" + userName + "'><priority>1</priority><x xmlns='http://jabber.org/protocol/muc'/></presence>");
|
||||
core.register("onInput", function(input) {
|
||||
socket.write("<message type='groupchat' to='chadhappyfuntime@conference.jabber.troubleimpact.com'><body>" + xmlEncode(input) + "</body></message>");
|
||||
});
|
||||
schedulePing(socket);
|
||||
} else if (stanza.attributes.id == "ping" + gPingCount) {
|
||||
// Ping response.
|
||||
} else {
|
||||
terminal.print(JSON.stringify(stanza));
|
||||
}
|
||||
} else if (stanza.name == "message") {
|
||||
printMessage(stanza);
|
||||
if (!gFocus) {
|
||||
++gUnread;
|
||||
updateTitle();
|
||||
}
|
||||
} else if (stanza.name == "challenge") {
|
||||
var challenge = Base64.decode(stanza.text);
|
||||
var parts = challenge.split(',');
|
||||
challenge = {};
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
var equals = parts[i].indexOf("=");
|
||||
if (equals != -1) {
|
||||
var key = parts[i].substring(0, equals);
|
||||
var value = parts[i].substring(equals + 1);
|
||||
if (value.length > 2 && value.charAt(0) == '"' && value.charAt(value.length - 1) == '"') {
|
||||
value = value.substring(1, value.length - 1);
|
||||
}
|
||||
challenge[key] = value;
|
||||
}
|
||||
}
|
||||
if (challenge.rspauth) {
|
||||
socket.write("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'/>");
|
||||
} else {
|
||||
var realm = "jabber.troubleimpact.com";
|
||||
var cnonce = Base64.encode(new Date().toString());
|
||||
var x = userName + ":" + realm + ":" + password;
|
||||
var y = raw_md5(x);
|
||||
var a1 = y + ":" + challenge.nonce + ":" + cnonce;
|
||||
var digestUri = "xmpp/" + realm;
|
||||
var a2 = "AUTHENTICATE:" + digestUri;
|
||||
var ha1 = md5(a1);
|
||||
var ha2 = md5(a2);
|
||||
var nc = "00000001";
|
||||
var kd = ha1 + ":" + challenge.nonce + ":" + nc + ":" + cnonce + ":" + challenge.qop + ":" + ha2;
|
||||
var response = md5(kd);
|
||||
var value = Base64.encode('username="' + userName + '",realm="' + realm + '",nonce="' + challenge.nonce + '",cnonce="' + cnonce + '",nc=' + nc + ',qop=' + challenge.qop + ',digest-uri="' + digestUri + '",response=' + response + ',charset=utf-8');
|
||||
socket.write("<response xmlns='urn:ietf:params:xml:ns:xmpp-sasl'>" + value + "</response>");
|
||||
}
|
||||
} else if (stanza.name == "presence") {
|
||||
var name = stanza.attributes.from.split('/', 2)[1];
|
||||
if (stanza.attributes.type == "unavailable") {
|
||||
terminal.print(name + " has left the room.");
|
||||
delete gPresence[name];
|
||||
} else {
|
||||
if (!gPresence[name]) {
|
||||
terminal.print(name + " has joined the room.");
|
||||
}
|
||||
gPresence[name] = stanza;
|
||||
}
|
||||
refreshUsers();
|
||||
} else {
|
||||
terminal.print(data);
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
terminal.print("ERROR: " + error);
|
||||
terminal.print("ERROR: " + JSON.stringify(error));
|
||||
terminal.print("ERROR: " + error.message);
|
||||
}
|
||||
});
|
||||
}).catch(function(e) {
|
||||
terminal.print("connect failed: ", e);
|
||||
});
|
||||
}
|
Reference in New Issue
Block a user