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