diff --git a/README.md b/README.md
index e52c63e9..49a97a4b 100644
--- a/README.md
+++ b/README.md
@@ -16,6 +16,8 @@ Tilde Friends is [routinely](https://www.unprompted.com/projects/build/tildefrie
scons uv=path/to/libuv v8=path/to/v8
```
+Note for Raspberry Pi: http://www.mccarroll.net/blog/v8_pi2/index.html
+
## Running
Running the built tildefriends executable will start a web server. This is a good starting point: .
diff --git a/packages/cory/blink/blink.js b/packages/cory/blink/blink.js
new file mode 100644
index 00000000..a4241d45
--- /dev/null
+++ b/packages/cory/blink/blink.js
@@ -0,0 +1,34 @@
+//! {"require": ["libiframe"]}
+
+// Server-side blink! Woo!
+
+require("libiframe").iframe({
+ source: `
+ document.body.style.color = '#fff';
+ document.body.style.backgroundColor = '#000';
+ var canvas = document.createElement("canvas");
+ document.body.append(canvas);
+ canvas.width = 640;
+ canvas.height = 480;
+ var context = canvas.getContext("2d");
+ rpc.export({
+ setFillStyle: function(style) { context.fillStyle = style; },
+ fillRect: context.fillRect.bind(context),
+ fillText: context.fillText.bind(context),
+ });`,
+ style: `
+ border: 0;
+`}).then(draw).catch(terminal.print);
+
+var blink = true;
+
+function draw(iframe) {
+ iframe.setFillStyle('#222');
+ iframe.fillRect(0, 0, 640, 480);
+ if (blink) {
+ iframe.setFillStyle('#fff');
+ iframe.fillText("Hello, world!", 50, 50);
+ }
+ blink = !blink;
+ setTimeout(function() { draw(iframe); }, 500);
+}
\ No newline at end of file
diff --git a/packages/cory/chat/chat.js b/packages/cory/chat/chat.js
index 59e2eb77..ace8ae05 100644
--- a/packages/cory/chat/chat.js
+++ b/packages/cory/chat/chat.js
@@ -286,9 +286,9 @@ function updateUsers() {
terminal.cork();
terminal.split([
{type: "horizontal", children: [
- {name: "windows", basis: "2in", grow: "0", shrink: "0"},
+ {name: "windows", basis: "1in", grow: "0", shrink: "0"},
{name: "terminal", grow: "1"},
- {name: "users", basis: "2in", grow: "0", shrink: "0"},
+ {name: "users", basis: "1in", grow: "0", shrink: "0"},
]},
]);
updateTitle();
@@ -444,7 +444,7 @@ function printMessage(message) {
{class: "base3", value: from},
" ",
formatMessage(message.message));
- } else {
+ } else if (message.message) {
terminal.print(
{class: "base0", value: niceTime(lastTimestamp, now)},
" ",
@@ -453,6 +453,11 @@ function printMessage(message) {
{class: "base00", value: ">"},
" ",
formatMessage(message.message));
+ } else {
+ terminal.print(
+ {class: "base0", value: niceTime(lastTimestamp, now)},
+ " ",
+ {class: "base3", value: JSON.stringify(message)});
}
lastTimestamp = now;
}
diff --git a/packages/cory/chattest/chattest.js b/packages/cory/chattest/chattest.js
new file mode 100644
index 00000000..339d8704
--- /dev/null
+++ b/packages/cory/chattest/chattest.js
@@ -0,0 +1,76 @@
+"use strict";
+
+//! {
+//! "chat": {
+//! "version": 1,
+//! "settings": [
+//! {"name": "nickname", "type": "text"},
+//! {"name": "password", "type": "password"}
+//! ]
+//! },
+//! "category": "libraries"
+//! }
+
+let gServices = {};
+
+class ChatService {
+ constructor(options) {
+ let self = this;
+ self._name = options.name;
+ self._callback = options.callback;
+ setTimeout(function() {
+ self._callback({
+ action: "message",
+ from: "service",
+ to: self._name,
+ message: "Hello, world!",
+ conversation: null,
+ });
+ }, 1000);
+ }
+
+ sendMessage(target, message) {
+ let self = this;
+ setTimeout(function() {
+ self._callback({
+ action: "message",
+ from: target,
+ to: self._name,
+ message: "I saw you say: " + message,
+ conversation: target,
+ });
+ }, 500);
+ return self._callback({
+ action: "message",
+ from: self._name,
+ to: target,
+ message: message,
+ conversation: target,
+ });
+ }
+}
+
+core.register("onMessage", function(sender, message) {
+ print(message);
+ let service = gServices[message.name];
+ if (!service) {
+ service = new ChatService(message);
+ gServices[message.name] = service;
+ } else {
+ service._callback = message.callback || service._callback;
+ }
+
+ setTimeout(function() {
+ service._callback({
+ action: "message",
+ from: "service",
+ to: service._name,
+ message: "I got your message.",
+ conversation: null,
+ });
+ }, 500);
+
+ return {
+ sendMessage: service.sendMessage.bind(service),
+ };
+});
\ No newline at end of file
diff --git a/packages/cory/contest/contest.js b/packages/cory/contest/contest.js
new file mode 100644
index 00000000..98e2a160
--- /dev/null
+++ b/packages/cory/contest/contest.js
@@ -0,0 +1,198 @@
+"use strict";
+
+//! {"require": ["ui"]}
+
+terminal.setEcho(false);
+terminal.setTitle("Programming Contest Test");
+
+let kProblem = {
+ description: "Write a function foo that returns a number.",
+ tests: [
+ [1, 1],
+ [2, 2],
+ [4, 4],
+ ],
+ default: `// Problem 1
+// ${core.user.name}
+// ${new Date()}
+
+function foo() {
+ print("hi cory");
+ return 0;
+}
+
+foo();`,
+};
+
+function back() {
+ terminal.split([{name: "terminal"}]);
+ if (gEditEvent) {
+ gEditEvent.back();
+ }
+}
+
+function runScript(script, input) {
+ var results = {};
+ try {
+ var output = [];
+ function print() {
+ for (var i in arguments) {
+ output.push(arguments[i].toString());
+ }
+ output.push("\n");
+ }
+ results.results = eval(script || "");
+ results.output = output.join("");
+ } catch (error) {
+ results.error = error;
+ }
+ return results;
+}
+
+function sendResults(document) {
+ var results = runScript(document);
+ var message = `${kProblem.description}
+
+Return value:
+${results.results}
+
+Output:
+${results.output}
+
+`;
+
+ if (results.error) {
+ message += `Error:
+${results.error}`;
+ }
+
+ terminal.postMessageToIframe("iframe", { results: message });
+}
+
+function loadScript() {
+ return database.get(core.user.name).then(function(script) {
+ return script || kProblem.default;
+ }).catch(function(error) {
+ return kProblem.default;
+ });
+}
+
+core.register("onWindowMessage", function(event) {
+ if (event.message.ready) {
+ loadScript().then(function(script) {
+ terminal.postMessageToIframe("iframe", {contents: script});
+ sendResults(script);
+ });
+ } else if (event.message.contents) {
+ database.set(core.user.name, event.message.contents).then(function() {
+ sendResults(event.message.contents);
+ });
+ }
+});
+
+//if (!core.user.credentials.permissions || !core.user.credentials.permissions.authenticated) {
+// terminal.print("Please authenticate.");
+//} else {
+ showEditor();
+//}
+
+function showEditor() {
+ terminal.split([{name: "terminal", type: "vertical"}]);
+ terminal.clear();
+ terminal.print({iframe: `
+
+
+
+
+
+
+
+
+
+
+
+ `, name: "iframe", style: "flex: 1 1 auto; border: 0; width: 100%"});
+}
\ No newline at end of file
diff --git a/packages/cory/db/db.js b/packages/cory/db/db.js
new file mode 100644
index 00000000..732b983b
--- /dev/null
+++ b/packages/cory/db/db.js
@@ -0,0 +1,269 @@
+//! {"category": "libraries"}
+
+class DatabaseList {
+ constructor(name) {
+ this._name = name;
+ }
+
+ _getList() {
+ let self = this;
+ return database.get(this._name).then(function(node) {
+ return JSON.parse(node);
+ }).catch(function(error) {
+ return self._emptyNode();
+ });
+ }
+
+ _randomKey() {
+ let kAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+ let kBytes = 32;
+ let result = "";
+ for (let i = 0; i < kBytes; i++) {
+ result += kAlphabet.charAt(Math.floor(Math.random() * kAlphabet.length));
+ }
+ return this._name + "_" + result;
+ }
+
+ _emptyNode() {
+ return {next: null, previous: null};
+ }
+
+ _getNode() {
+ var self = this;
+ var key = self._randomKey();
+ return database.get(key).then(function(found) {
+ if (!found) {
+ return key;
+ } else {
+ return self._getNode();
+ }
+ });
+ }
+
+ _getRelative(node, next, count) {
+ }
+
+ _insertEnd(item, after) {
+ let self = this;
+ return this._getList().then(function(listNode) {
+ let readPromises = [self._getNode()];
+ readPromises.push(listNode.previous ? database.get(listNode.previous).then(JSON.parse) : null);
+ readPromises.push(listNode.next ? database.get(listNode.next).then(JSON.parse) : null);
+ return Promise.all(readPromises).then(function(results) {
+ let newNodeName = results[0];
+ let previousNode = results[1];
+ let nextNode = results[2];
+ let newNode = self._emptyNode();
+ let oldPrevious = listNode.previous;
+ let oldNext = listNode.next;
+ newNode.value = item;
+ newNode.previous = listNode.previous || newNodeName;
+ newNode.next = listNode.next || newNodeName;
+ if (after) {
+ listNode.previous = newNodeName;
+ listNode.next = listNode.next || newNodeName;
+ } else {
+ listNode.next = newNodeName;
+ listNode.previous = listNode.previous || newNodeName;
+ }
+ let writePromises = [
+ database.set(self._name, JSON.stringify(listNode)),
+ database.set(newNodeName, JSON.stringify(newNode)),
+ ];
+ if (oldPrevious && oldPrevious == oldNext) {
+ previousNode.next = newNodeName;
+ previousNode.previous = newNodeName;
+ writePromises.push(database.set(oldPrevious, JSON.stringify(previousNode)));
+ } else {
+ if (previousNode) {
+ previousNode.next = newNodeName;
+ writePromises.push(database.set(oldPrevious, JSON.stringify(previousNode)));
+ }
+ if (nextNode) {
+ nextNode.previous = newNodeName;
+ writePromises.push(database.set(oldNext, JSON.stringify(nextNode)));
+ }
+ }
+ return Promise.all(writePromises);
+ });
+ });
+ }
+
+ _removeEnd(after) {
+ let self = this;
+ return this._getList().then(function(listNode) {
+ if (listNode.next) {
+ if (listNode.next == listNode.previous) {
+ return database.get(listNode.next).then(JSON.parse).then(function(removedNode) {
+ let removedName = listNode.next;
+ listNode.next = null;
+ listNode.previous = null;
+ return Promise.all([
+ database.remove(removedName),
+ database.set(self._name, JSON.stringify(listNode)),
+ ]).then(function() {
+ return removedNode.value;
+ });
+ });
+ } else {
+ let name1 = listNode.previous;
+ let name2 = listNode.next;
+ return Promise.all([
+ database.get(listNode.previous),
+ database.get(listNode.next),
+ ]).then(function(nodes) {
+ var node1 = JSON.parse(nodes[0]);
+ var node2 = JSON.parse(nodes[1]);
+ if (after) {
+ let name0 = node1.previous;
+ return database.get(name0).then(JSON.parse).then(function(node0) {
+ node0.next = name2;
+ node2.previous = name0;
+ listNode.previous = name0;
+ return Promise.all([
+ database.set(name0, JSON.stringify(node0)),
+ database.remove(name1),
+ database.set(name2, JSON.stringify(node2)),
+ database.set(self._name, JSON.stringify(listNode)),
+ ])
+ }).then(function() {
+ return node1.value;
+ });
+ } else {
+ let name3 = node2.next;
+ return database.get(name3).then(JSON.parse).then(function(node3) {
+ node1.next = name3;
+ node3.previous = name1;
+ listNode.next = name3;
+ terminal.print(name1, " ", name2, " ", name3, " ", self._name);
+ terminal.print(JSON.stringify(listNode));
+ return Promise.all([
+ database.set(name1, JSON.stringify(node1)),
+ database.remove(name2),
+ database.set(name3, JSON.stringify(node3)),
+ database.set(self._name, JSON.stringify(listNode)),
+ ])
+ }).then(function() {
+ return node2.value;
+ });
+ }
+ });
+ }
+ }
+ });
+ }
+
+ push(item) {
+ return this._insertEnd(item, true);
+ }
+
+ pop() {
+ return this._removeEnd(true);
+ }
+
+ shift() {
+ return this._removeEnd(false);
+ }
+
+ unshift(item) {
+ return this._insertEnd(item, false);
+ }
+
+ _sliceInternal(head, count, next, end, result) {
+ let self = this;
+ if (head[next] == end && count > 0) {
+ result.push(head.value);
+ return new Promise(function(resolve, reject) { resolve(result); });
+ } else {
+ if (count > 0) {
+ return database.get(head[next]).then(JSON.parse).then(function(retrieved) {
+ return self._sliceInternal(retrieved, count - 1, next, end, result).then(function(result) {
+ result.push(head.value);
+ return result;
+ });
+ });
+ } else {
+ return new Promise(function(resolve, reject) { resolve(result); });
+ }
+ }
+ }
+
+ get(count) {
+ let self = this;
+ return self._getList().then(function(listNode) {
+ if (listNode.next) {
+ return database.get(count > 0 ? listNode.next : listNode.previous).then(JSON.parse).then(function(head) {
+ if (head) {
+ return self._sliceInternal(
+ head,
+ Math.abs(count),
+ count > 0 ? "next" : "previous",
+ count > 0 ? listNode.next : listNode.previous,
+ []).then(function(result) {
+ result.reverse();
+ return result;
+ });
+ } else {
+ return null;
+ }
+ });
+ }
+ });
+ }
+}
+
+function wipeDatabase() {
+ let promises = [];
+ return database.getAll().then(function(list) {
+ for (let i = 0; i < list.length; i++) {
+ promises.push(database.remove(list[i]));
+ }
+ });
+ return Promise.all(promises);
+}
+
+function dumpDatabase() {
+ return database.getAll().then(function(list) {
+ let promises = [];
+ for (let i = 0; i < list.length; i++) {
+ promises.push(database.get(list[i]));
+ }
+ return Promise.all(promises).then(function(values) {
+ for (let i = 0; i < list.length; i++) {
+ terminal.print(list[i], " ", values[i]);
+ }
+ });
+ }).catch(function(error) {
+ terminal.print(error);
+ });
+}
+
+if (imports.terminal) {
+ x = new DatabaseList("list");
+ core.register("onInput", function(input) {
+ if (input == "clear") {
+ wipeDatabase().then(function() {
+ terminal.print("Database is now empty.");
+ });
+ } else if (input.substring(0, "push ".length) == "push ") {
+ x.push(input.substring("push ".length)).then(dumpDatabase).catch(terminal.print);
+ } else if (input.substring(0, "unshift ".length) == "unshift ") {
+ x.unshift(input.substring("unshift ".length)).then(dumpDatabase).catch(terminal.print);
+ } else if (input == "pop") {
+ x.pop().then(function(out) {
+ terminal.print("POPPED: ", out);
+ }).then(dumpDatabase).catch(terminal.print);
+ } else if (input == "shift") {
+ x.shift().then(function(out) {
+ terminal.print("SHIFTED: ", out);
+ }).then(dumpDatabase).catch(terminal.print);
+ } else if (input.substring(0, "get ".length) == "get ") {
+ let parts = input.split(" ");
+ x.get(parseInt(parts[1])).then(function(result) {
+ terminal.print(JSON.stringify(result))
+ }).catch(terminal.print);
+ } else {
+ dumpDatabase();
+ }
+ });
+}
\ No newline at end of file
diff --git a/packages/cory/emojipush/emojipush.js b/packages/cory/emojipush/emojipush.js
new file mode 100644
index 00000000..8161699f
--- /dev/null
+++ b/packages/cory/emojipush/emojipush.js
@@ -0,0 +1,201 @@
+"use strict";
+
+class Client {
+ async start() {
+ let self = this;
+ terminal.setTitle("Emoji Push");
+ terminal.setSendKeyEvents(true);
+ self.send({action: "ready"});
+
+ core.register("key", function(event) {
+ if (event.type == "keydown") {
+ switch (event.keyCode) {
+ case 37:
+ self.requestMove(-1, 0);
+ break;
+ case 38:
+ self.requestMove(0, -1);
+ break;
+ case 39:
+ self.requestMove(1, 0);
+ break;
+ case 40:
+ self.requestMove(0, 1);
+ break;
+ }
+ }
+ });
+
+ core.register("onMessage", function(sender, message) {
+ if (message.action == "display") {
+ terminal.cork();
+ terminal.clear();
+ terminal.print(message.board);
+ terminal.uncork();
+ }
+ });
+ }
+
+ async send(message) {
+ (await core.getService("server")).postMessage(message);
+ }
+
+ async requestMove(dx, dy) {
+ await this.send({action: "move", delta: [dx, dy]});
+ }
+}
+
+class Server {
+ constructor() {
+ this.rows = 17;
+ this.columns = 17;
+ this.players = {};
+ this.objects = [];
+
+ for (let i = 2; i < this.rows - 1; i += 2) {
+ for (let j = 2; j < this.columns - 1; j += 2) {
+ let block = new Entity();
+ block.position = [j, i];
+ block.symbol = '#';
+ this.objects.push(block);
+ }
+ }
+ }
+
+ async start() {
+ let self = this;
+ core.register("onMessage", function(sender, message) {
+ if (!self.players[sender.index]) {
+ let player = new Entity();
+ player.symbol = '@';
+ player.position = self.findNewPlayerPosition();
+ self.players[sender.index] = player;
+ self.objects.push(player);
+ }
+ switch (message.action) {
+ case "move":
+ self.move(sender.index, message.delta);
+ self.redisplay();
+ break;
+ case "ready":
+ self.redisplay();
+ break;
+ }
+ });
+ core.register("onSessionEnd", function(session) {
+ let player = self.players[session.index];
+ if (player) {
+ delete self.players[session.index];
+ self.objects.splice(self.objects.indexOf(player), 1);
+ self.redisplay();
+ }
+ });
+ }
+
+ occupied(position) {
+ let board = this.getBoard();
+
+ if (position[0] < 0 || position[1] < 0 || position[1] >= board.length || position[0] >= board[position[1]].length) {
+ return true;
+ }
+
+ if (board[position[1]].charAt(position[0]) != ' ') {
+ return true;
+ }
+
+ if (this.objects.some(x => position[0] == x.position[0] && position[1] == x.position[1])) {
+ return true;
+ }
+ }
+
+ move(index, delta) {
+ let newPosition = [
+ this.players[index].position[0] + delta[0],
+ this.players[index].position[1] + delta[1],
+ ];
+ if (!this.occupied(newPosition)) {
+ this.players[index].position = newPosition;
+ }
+ }
+
+ findNewPlayerPosition() {
+ let best = null;
+ let bestDistance = -1;
+ let players = Object.values(this.players);
+
+ for (let i = 0; i < this.rows; i++) {
+ for (let j = 0; j < this.columns; j++) {
+ if (!this.occupied([j, i])) {
+ let distance = Math.min.apply(null, players.map(x => Math.sqrt(
+ (x.position[0] - j) * (x.position[0] - j)
+ + (x.position[1] - i) * (x.position[1] - i))));
+ if (distance > bestDistance) {
+ best = [j, i];
+ bestDistance = distance;
+ }
+ }
+ }
+ }
+
+ return best;
+ }
+
+ redisplay() {
+ let board;
+ try {
+ board = this.getBoardString();
+ } catch (error) {
+ board = error;
+ }
+ core.broadcast({action: "display", board: board});
+ }
+
+ getBoard() {
+ let board = [];
+ for (let i = 0; i < this.rows; i++) {
+ let row = "";
+ for (let j = 0; j < this.columns; j++) {
+ if (i == 0 && j == 0) {
+ row += '┌';
+ } else if (i == 0 && j == this.columns - 1) {
+ row += '┐';
+ } else if (i == this.rows - 1 && j == 0) {
+ row += '└';
+ } else if (i == this.rows - 1 && j == this.columns - 1) {
+ row += '┘';
+ } else if (i == 0 || i == this.rows - 1) {
+ row += '─';
+ } else if (j == 0 || j == this.columns - 1) {
+ row += '│';
+ } else {
+ row += ' ';
+ }
+ }
+ board.push(row);
+ }
+ for (let object of this.objects) {
+ board[object.position[1]] = board[object.position[1]].slice(0, object.position[0]) + object.symbol + board[object.position[1]].slice(object.position[0] + 1);
+ }
+ return board;
+ }
+
+ getBoardString() {
+ let board = this.getBoard();
+ let colors = board.map(x => x.split("").map(y => '#fff'));
+ return board.map((r, row) => r.split("").map((v, column) => ({style: "color: " + colors[row][column], value: v})).concat(["\n"]));
+ }
+}
+
+class Entity {
+ constructor() {
+ this.position = [0, 0];
+ this.symbol = '?';
+ this.color = '#fff';
+ }
+}
+
+if (imports.terminal) {
+ new Client().start().catch(terminal.print);
+} else {
+ new Server().start().catch(print);
+}
diff --git a/packages/cory/guess/guess.js b/packages/cory/guess/guess.js
new file mode 100644
index 00000000..f752bc00
--- /dev/null
+++ b/packages/cory/guess/guess.js
@@ -0,0 +1,31 @@
+"use strict";
+
+//! {"category": "sample"}
+
+async function main() {
+ terminal.print("Hi. What's your name?");
+ let name = await terminal.readLine();
+ terminal.print("Hello, " + name + ".");
+
+ let number = Math.floor(Math.random() * 100);
+ let guesses = 0;
+ while (true) {
+ terminal.print("Guess the number.");
+ try {
+ let guess = parseInt(await terminal.readLine());
+ guesses++;
+ if (guess < number) {
+ terminal.print("Too low.");
+ } else if (guess > number) {
+ terminal.print("Too high.");
+ } else {
+ terminal.print("You got it in " + guesses.toString() + " guesses! It was " + number.toString() + ". Good job, " + name + ".");
+ break;
+ }
+ } catch (error) {
+ terminal.print(error);
+ }
+ }
+}
+
+main().catch(terminal.print);
\ No newline at end of file
diff --git a/packages/cory/images/images.js b/packages/cory/images/images.js
new file mode 100644
index 00000000..eebdb78d
--- /dev/null
+++ b/packages/cory/images/images.js
@@ -0,0 +1,3 @@
+core.register("fileDrop", function(event) {
+ terminal.print({image: event.file});
+});
\ No newline at end of file
diff --git a/packages/cory/invite/invite.js b/packages/cory/invite/invite.js
new file mode 100644
index 00000000..9be4b1d9
--- /dev/null
+++ b/packages/cory/invite/invite.js
@@ -0,0 +1,260 @@
+"use strict";
+
+terminal.setEcho(false);
+terminal.setPrompt("🍔");
+
+terminal.split([
+ {
+ type: "horizontal",
+ children: [
+ {
+ name: "form",
+ },
+ {
+ type: "vertical",
+ basis: "240px",
+ grow: "0",
+ shrink: "0",
+ children: [
+ {
+ name: "image",
+ basis: "8em",
+ shrink: "0",
+ },
+ {
+ name: "fizzbuzz",
+ grow: "1",
+ },
+ ],
+ },
+ ],
+ },
+]);
+
+database.getAll().then(function(data) {
+ for (var i = 0; i < data.length; i++) {
+ database.remove(data[i]);
+ }
+});
+
+let kBurger = ` ......
+/'.'.'.\\
+&%@%@%@&
+(######)
+\\,,,,,,/`;
+
+terminal.select("image");
+terminal.print(kBurger);
+terminal.select("form");
+
+function countUp() {
+ var current = 0;
+ function countUpNext() {
+ terminal.select("fizzbuzz");
+ ++current;
+ var result = "";
+ if (current % 3 == 0) {
+ result += "Fizz";
+ }
+ if (current % 5 == 0) {
+ result += "Buzz";
+ }
+ if (current % 150 == 0) {
+ result += "Grumble";
+ }
+ if (result == "") {
+ result = current.toString();
+ }
+ terminal.print(result);
+ terminal.select("form");
+ setTimeout(countUpNext, 1000);
+ };
+ setTimeout(countUpNext, 1000);
+}
+countUp();
+
+var user = core.user.name;
+
+let kIntroduction = [
+ {style: "font-size: xx-large; font-family: sans", value: "Summer of Cory"},
+ "Hi, it is warm outside again, and as is tradition, I want you to join me "+
+ "after work for some burgers on my deck. There are some quick questions "+
+ "below to gauge interest and availability. Please answer as best you can "+
+ "by clicking the options below, and I will get back to you with a plan.",
+ [],
+];
+
+let kBottom = [
+ {
+ iframe: `
+
+
+
+
+`,
+ style: "border: 0; width: 640px; height: 240px; margin: 0; padding: 0",
+ },
+];
+
+let kQuestions = [
+ {text: "Do you want to be a part of the Summer of Cory?", options: ["yes", "no"]},
+ {text: "Tuesday after work works best for me. Does that work for you?", options: ["yes", "no"]},
+ {text: "How often would you expect to join us?", options: ["weekly", "fortnightly", "monthly", "impossible to predict"]},
+
+ //{text: "Pick numbers", options: ["1", "2", "3"], allowMultiple: true},
+ //{text: "Pick a letter", options: ["a", "b", "c", "d"]},
+ //{text: "Pick whatever", options: ["1", "2", "3", "a", "b", "c", "d"], writeIn: true},
+];
+
+let choices = [];
+let writeIn = [];
+let writingIn = undefined;
+
+function getOptions(index) {
+ return kQuestions[index].options.concat(writeIn[index] || []);
+}
+
+function isChosen(question, choice) {
+ let selected = false;
+ if (kQuestions[question].allowMultiple) {
+ selected = choices[question] && choices[question].indexOf(choice) != -1;
+ } else {
+ selected = choices[question] == choice;
+ }
+ return selected;
+}
+
+function readData() {
+ return database.getAll().then(function(keys) {
+ var promises = [];
+ for (var i = 0; i < keys.length; i++) {
+ promises.push(database.get(keys[i]));
+ }
+ return Promise.all(promises);
+ }).then(function(allData) {
+ writeIn = [];
+ for (var i = 0; i < allData.length; i++) {
+ if (allData[i]) {
+ var entry = JSON.parse(allData[i]);
+ if (entry && entry.writeIn) {
+ for (var j = 0; j < entry.writeIn.length; j++) {
+ if (writeIn.indexOf(entry.writeIn[j]) == -1) {
+ writeIn.push(entry.writeIn[j]);
+ }
+ }
+ }
+ }
+ }
+ return database.get("data_" + user);
+ }).then(function(data) {
+ if (data) {
+ choices = JSON.parse(data).choices;
+ }
+ }).catch(function(error) {
+ terminal.select("image");
+ terminal.print(error);
+ terminal.select("form");
+ });
+}
+
+function writeData() {
+ return database.set("data_" + user, JSON.stringify({choices: choices, writeIn: writeIn})).catch(function(error) {
+ terminal.select("image");
+ terminal.print(error);
+ terminal.select("form");
+ });
+}
+
+function render() {
+ return readData().then(function() {
+ terminal.cork();
+ try {
+ terminal.clear();
+ for (let line in kIntroduction) {
+ terminal.print(kIntroduction[line]);
+ }
+ let allowFill = writingIn === undefined;
+ for (let i in kQuestions) {
+ let availableOptions = getOptions(i);
+ let options = [];
+ for (let j in availableOptions) {
+ options.push(" ");
+
+ let value = (isChosen(i, availableOptions[j]) ? "☒" : "☐") + availableOptions[j];
+ if (allowFill) {
+ options.push({
+ command: JSON.stringify({action: "toggle", question: i, option: availableOptions[j]}),
+ value: value,
+ });
+ } else {
+ options.push(value);
+ }
+ }
+ if (kQuestions[i].writeIn) {
+ options.push(" or ");
+ if (writingIn !== undefined) {
+ options.push("write in another option below")
+ options.push(" ");
+ options.push({command: JSON.stringify({action: "endWriteIn"}), value: "cancel"});
+ } else {
+ options.push({command: JSON.stringify({action: "beginWriteIn", question: i}), value: "write in another option"});
+ }
+ }
+ terminal.print(kQuestions[i].text, ":", options);
+ terminal.print();
+ }
+ } finally {
+ for (let line in kBottom) {
+ terminal.print(kBottom[line]);
+ }
+ terminal.uncork();
+ }
+ });
+}
+
+core.register("onInput", function(input) {
+ if (writingIn !== undefined && input != "cancel") {
+ readData().then(function() {
+ if (!writeIn[writingIn]) {
+ writeIn[writingIn] = [];
+ }
+ if (input && writeIn[writingIn].indexOf(input) == -1) {
+ writeIn[writingIn].push(input);
+ }
+ writingIn = undefined;
+ terminal.setPrompt("> ");
+ return writeData();
+ }).then(render);
+ } else {
+ let command = JSON.parse(input);
+ if (command.action == "toggle") {
+ readData().then(function() {
+ if (kQuestions[command.question].allowMultiple) {
+ if (choices[command.question] && choices[command.question].indexOf(command.option) != -1) {
+ choices[command.question].splice(choices[command.question].indexOf(command.option), 1);
+ } else {
+ if (!choices[command.question]) {
+ choices[command.question] = [];
+ }
+ choices[command.question].push(command.option);
+ }
+ } else {
+ choices[command.question] = command.option;
+ }
+ return writeData();
+ }).then(render);
+ } else if (command.action == "beginWriteIn") {
+ writingIn = command.question;
+ terminal.setPrompt('Enter new option for "' + kQuestions[command.question].text + '": ');
+ render();
+ } else if (command.action == "endWriteIn") {
+ writingIn = undefined;
+ terminal.setPrompt("> ");
+ render();
+ }
+ }
+});
+
+render().catch(function(error) {
+ terminal.print(error);
+});
\ No newline at end of file
diff --git a/packages/cory/invite2/invite2.js b/packages/cory/invite2/invite2.js
new file mode 100644
index 00000000..6221e0cb
--- /dev/null
+++ b/packages/cory/invite2/invite2.js
@@ -0,0 +1,179 @@
+"use strict";
+
+//! {"require": ["smtp"], "permissions": ["network"]}
+
+let administrator = core.user.name == core.user.packageOwner;
+
+function lameHash(value) {
+ let result = 12945;
+ for (let i = 0; i < value.length; i++) {
+ result += value.charCodeAt(i);
+ result *= 31;
+ result &= 0x7fffffff;
+ }
+ return result;
+}
+
+terminal.setEcho(false);
+if (administrator) {
+ terminal.print({style: "font-size: x-large", value: "Edit", command: "command:" + JSON.stringify("command")});
+}
+
+let gInvite = null;
+
+function display() {
+ return database.get("invite").then(function(data) {
+ if (data) {
+ let invite = JSON.parse(data);
+ let promises = [];
+ for (let i in invite.emails) {
+ promises.push(database.get("rsvp." + lameHash(invite.emails[i])));
+ }
+ return Promise.all(promises).then(function(rsvps) {
+ for (let i in rsvps) {
+ if (rsvps[i]) {
+ rsvps[i] = JSON.parse(rsvps[i]);
+ }
+ }
+ terminal.print({style: "font-size: xx-large", value: invite.title});
+ terminal.print(invite.message);
+ for (let i in invite.food) {
+ let line = [invite.food[i], "?", " ", "+", " ", "-"];
+ // hi
+ terminal.print({style: "font-size: x-large", value: line});
+ }
+ gInvite = invite;
+ });
+ }
+ }).catch(function(error) {
+ terminal.print(error);
+ });
+}
+
+core.register("hashChange", function(event) {
+ let hash = event.hash;
+ if (hash && hash.charAt(0) == '#') {
+ hash = hash.substring(1);
+ }
+ if (hash) {
+ let invite = gInvite || {};
+ for (let i in invite.emails) {
+ let email = invite.emails[i];
+ if (lameHash(email).toString() == hash) {
+ terminal.print("hash match!", email);
+ }
+ }
+ }
+})
+
+core.register("submit", function(event) {
+ let invite = event.value;
+ invite.emails = invite.emails.split(",");
+ invite.food = invite.food.split(",");
+ for (let i in invite.emails) {
+ let url = "https://www.tildefriends.net/~cory/invite2#" + lameHash(invite.emails[i]).toString();
+ terminal.print({href: url});
+ }
+ database.set("invite", JSON.stringify(invite));
+});
+
+display();
+
+core.register("onInput", function(input) {
+ if (input.substring(0, "command:".length) == "command:") {
+ command = JSON.parse(input.substring("command:".length));
+ if (command == "create") {
+ let invite = gInvite || {};
+ terminal.print("Title:", {input: "text", name: "title", style: "width: 512px", value: invite.title});
+ terminal.print("Message:", {input: "text", name: "message", style: "width: 512px", value: invite.message});
+ terminal.print("Email Addresses:", {input: "text", name: "emails", style: "width: 512px", value: invite.emails.join(",")});
+ terminal.print("Food:", {input: "text", name: "food", style: "width: 512px", value: invite.food.join(",")});
+ terminal.print({input: "submit", name: "Create", value: "Create"});
+ }
+ }
+});
+
+/*
+ require("smtp").sendMail({
+ from: core.user.name + "@unprompted.com",
+ to: "test1@unprompted.com",
+ subject: input,
+ body: input,
+ }).then(function() {
+ terminal.print("sent");
+ }).catch(function(error) {
+ terminal.print("error: ", error);
+ });
+*/
+
+let kEmojis = [
+ "🍇 Grapes",
+ "🍈 Melon",
+ "🍉 Watermelon",
+ "🍊 Tangerine",
+ "🍋 Lemon",
+ "🍌 Banana",
+ "🍍 Pineapple",
+ "🍎 Red Apple",
+ "🍏 Green Apple",
+ "🍐 Pear",
+ "🍑 Peach",
+ "🍒 Cherries",
+ "🍓 Strawberry",
+ "🍅 Tomato",
+ "🍆 Aubergine",
+ "🌽 Ear of Maize",
+ "🌶 Hot Pepper",
+ "🍄 Mushroom",
+ "🌰 Chestnut",
+ "🍞 Bread",
+ "🧀 Cheese Wedge",
+ "🍖 Meat on Bone",
+ "🍗 Poultry Leg",
+ "🍔 Hamburger",
+ "🍟 French Fries",
+ "🍕 Slice of Pizza",
+ "🌭 Hot Dog",
+ "🌮 Taco",
+ "🌯 Burrito",
+ "🍳 Cooking",
+ "🍲 Pot of Food",
+ "🍿 Popcorn",
+ "🍱 Bento Box",
+ "🍘 Rice Cracker",
+ "🍙 Rice Ball",
+ "🍚 Cooked Rice",
+ "🍛 Curry and Rice",
+ "🍜 Steaming Bowl",
+ "🍝 Spaghetti",
+ "🍠 Roasted Sweet Potato",
+ "🍢 Oden",
+ "🍣 Sushi",
+ "🍤 Fried Shrimp",
+ "🍥 Fish Cake With Swirl Design",
+ "🍡 Dango",
+ "🍦 Soft Ice Cream",
+ "🍧 Shaved Ice",
+ "🍨 Ice Cream",
+ "🍩 Doughnut",
+ "🍪 Cookie",
+ "🎂 Birthday Cake",
+ "🍰 Shortcake",
+ "🍫 Chocolate Bar",
+ "🍬 Candy",
+ "🍭 Lollipop",
+ "🍮 Custard",
+ "🍯 Honey Pot",
+ "🍼 Baby Bottle",
+ "☕ Hot Beverage",
+ "🍵 Teacup Without Handle",
+ "🍶 Sake Bottle and Cup",
+ "🍾 Bottle With Popping Cork",
+ "🍷 Wine Glass",
+ "🍸 Cocktail Glass",
+ "🍹 Tropical Drink",
+ "🍺 Beer Mug",
+ "🍻 Clinking Beer Mugs",
+ "🍽 Fork and Knife With Plate",
+ "🍴 Fork and Knife",
+];
\ No newline at end of file
diff --git a/packages/cory/ldjam34/ldjam34.js b/packages/cory/ldjam34/ldjam34.js
new file mode 100644
index 00000000..e6aa037f
--- /dev/null
+++ b/packages/cory/ldjam34/ldjam34.js
@@ -0,0 +1,936 @@
+imports.terminal.print({iframe: `
+
+
+
+ become a game developer in 60 seconds
+
+
+
+
+
+
+
+
+`,
+width: 720,
+height: 512,
+});
diff --git a/packages/cory/libhttp/libhttp.js b/packages/cory/libhttp/libhttp.js
index 12afec2d..3da4747b 100644
--- a/packages/cory/libhttp/libhttp.js
+++ b/packages/cory/libhttp/libhttp.js
@@ -23,6 +23,7 @@ function parseUrl(url) {
function parseResponse(data) {
var firstLine;
var headers = {};
+ var headerArray = [];
while (true) {
var endLine = data.indexOf("\r\n");
@@ -33,11 +34,14 @@ function parseResponse(data) {
break;
} else {
var colon = line.indexOf(":");
- headers[line.substring(0, colon).toLowerCase()] = line.substring(colon + 1).trim();
+ var key = line.substring(0, colon);
+ var value = line.substring(colon + 1).trim();
+ headers[key.toLowerCase()] = value;
+ headerArray.push([key, value]);
}
data = data.substring(endLine + 2);
}
- return {body: data, headers: headers};
+ return {body: data, headers: headers, headerArray: headerArray};
}
function get(url) {
diff --git a/packages/cory/libiframe/libiframe.js b/packages/cory/libiframe/libiframe.js
new file mode 100644
index 00000000..db4781e8
--- /dev/null
+++ b/packages/cory/libiframe/libiframe.js
@@ -0,0 +1,137 @@
+"use strict";
+
+//! {"category": "libraries"}
+
+const kRpcSource = `
+class RPC {
+ constructor() {
+ this._functions = [];
+ this.exports = null;
+ this.sendCallback = null;
+ }
+
+ export(functions) {
+ return this._send(["export", functions]);
+ }
+
+ _call(index) {
+ return this._send(["call", index, Array.prototype.slice.call(arguments, 1)]);
+ }
+
+ _send(message) {
+ var serialized = this._serialize(message);
+ if (this.sendCallback) {
+ return this.sendCallback(serialized);
+ }
+ return serialized;
+ }
+
+ deliver(message) {
+ var deserialized = this._deserialize(message);
+ if (deserialized[0] == "export") {
+ this.exports = deserialized[1];
+ } else if (deserialized[0] == "call") {
+ var f = this._functions[deserialized[1]];
+ f.apply(null, deserialized[2]);
+ }
+ }
+
+ _deserialize(data) {
+ var result = null;
+ if (data[0] == "array") {
+ result = new Array(data[1].length);
+ for (var i = 0; i < data[1].length; i++) {
+ result[i] = this._deserialize(data[1][i]);
+ }
+ } else if (data[0] == "object") {
+ result = {};
+ for (var i in data[1]) {
+ result[i] = this._deserialize(data[1][i]);
+ }
+ } else if (data[0] == "number" || data[0] == "boolean" || data[0] == "string") {
+ result = data[1];
+ } else if (data[0] == "function") {
+ result = this._call.bind(this, data[1]);
+ }
+ return result;
+ }
+
+ _serialize(data) {
+ var result = null;
+ if (Array.isArray(data)) {
+ result = ["array", data.map(x => this._serialize(x, this.functions))];
+ } else if (typeof data == "function") {
+ var index = this._functions.indexOf(data);
+ if (index == -1) {
+ index = this._functions.length;
+ this._functions.push(data);
+ }
+ result = ["function", index];
+ } else if (typeof data == "object") {
+ var fields = {};
+ while (data) {
+ var own = Object.getOwnPropertyNames(data);
+ for (var i = 0; i < own.length; i++) {
+ var name = own[i];
+ if (name != "constructor") {
+ var descriptor = Object.getOwnPropertyDescriptor(data, name);
+ if (descriptor&& typeof descriptor.value == "function") {
+ fields[name] = this._serialize(descriptor.value, this.functions);
+ }
+ }
+ }
+ if (data.__proto__ == Object.prototype) {
+ break;
+ }
+ data = Object.getPrototypeOf(data);
+ }
+ result = ["object", fields];
+ } else {
+ result = [typeof data, data];
+ }
+ return result;
+ }
+}`
+
+exports.iframe = function(options) {
+ return new Promise(function(resolve, reject) {
+ var iframeName = options.name || "iframe";
+ var RPC = eval(kRpcSource);
+ var rpc = new RPC();
+ rpc.sendCallback = terminal.postMessageToIframe.bind(null, iframeName);
+
+ core.register("onWindowMessage", function(message) {
+ if (message.message == "ready") {
+ resolve(rpc.exports);
+ } else {
+ rpc.deliver(message.message);
+ }
+ });
+
+ terminal.split([{name: "terminal", type: "vertical"}]);
+ terminal.print({
+ iframe: `
+
+
+
+ `,
+ name: iframeName,
+ style: "flex: 1 1; " + options.style,
+ width: null,
+ height: null,
+ });
+ });
+}
\ No newline at end of file
diff --git a/packages/cory/libirc/libirc.js b/packages/cory/libirc/libirc.js
index 2b8ca2a0..8ce5a715 100644
--- a/packages/cory/libirc/libirc.js
+++ b/packages/cory/libirc/libirc.js
@@ -98,6 +98,8 @@ class IrcService {
let person = prefix.split('!')[0];
let conversation = parts[1];
this._service.notifyPresenceChanged(conversation, person, "unavailable");
+ } else if (parts[0] == "PONG") {
+ // this._service.notifyMessageReceived("", {from: prefix, message: "RTT: " + (new Date().getTime() - parseInt(parts[2]))});
} else if (parts[0] == "QUIT") {
let person = prefix.split('!')[0];
let conversations = this._service.getConversations();
@@ -164,9 +166,15 @@ class IrcService {
self._service.notifyStateChanged("connected");
self._send("USER " + options.user + " 0 * :" + options.realName);
self._send("NICK " + options.nick);
+ self._sendPing();
}).catch(self._service.reportError);
}
+ _sendPing() {
+ this._send("PING :" + new Date().getTime());
+ setTimeout(this._sendPing.bind(this), 3000);
+ }
+
sendMessage(target, text) {
if (!target) {
this._socket.write(text + "\r\n");
diff --git a/packages/cory/libxmpp/libxmpp.js b/packages/cory/libxmpp/libxmpp.js
index f86bac4c..8bc69e67 100644
--- a/packages/cory/libxmpp/libxmpp.js
+++ b/packages/cory/libxmpp/libxmpp.js
@@ -771,11 +771,11 @@ class XmppService {
} else if (stanza.attributes.id == "session0") {
self._socket.write("1");
self._schedulePing();
- //self._conversations["chadhappyfuntime@conference.jabber.troubleimpact.com"] = {participants: [], history: []};
} else if (stanza.children.length && stanza.children[0].name == "ping") {
// Ping response.
} else {
self._service.notifyMessageReceived(null, {unknown: stanza});
+ self._socket.write(``);
}
} else if (stanza.name == "message") {
let message = self._convertMessage(stanza);
diff --git a/packages/cory/meetup/meetup.js b/packages/cory/meetup/meetup.js
new file mode 100644
index 00000000..fa365c71
--- /dev/null
+++ b/packages/cory/meetup/meetup.js
@@ -0,0 +1,128 @@
+//! {"permissions": ["network"], "require": ["libencoding", "libhttp"]}
+
+let libhttp = require("libhttp");
+
+async function getEvents() {
+ let now = new Date();
+ let cacheVersion = "1";
+ let useCache = await database.get("cacheVersion") == cacheVersion;
+ let events = [];
+ let url = "https://api.meetup.com/The-Most-Informal-Running-Club-Ever-TMIRCE-Upstate/events?&sign=true&photo-host=public&status=past";
+ let done = false;
+ while (!done) {
+ let response;
+ if (useCache) {
+ response = await database.get(url);
+ if (response) {
+ response = JSON.parse(response);
+ }
+ }
+ if (!response) {
+ response = await libhttp.get(url);
+ await database.set(url, JSON.stringify(response));
+ }
+ let nextLink;
+ let theseEvents = JSON.parse(response.body);
+ for (let j in theseEvents) {
+ if (new Date(theseEvents[j].time) < now) {
+ events.push(theseEvents[j]);
+ } else {
+ done = true;
+ break;
+ }
+ }
+ for (let j in response.headerArray) {
+ let link = response.headerArray[j];
+ if (link[0] == "Link" && link[1].split("; ")[1] == 'rel="next"') {
+ link = link[1].split("; ")[0];
+ link = link.substring(1, link.length - 1);
+ nextLink = link;
+ }
+ }
+ if (nextLink) {
+ url = nextLink;
+ } else {
+ break;
+ }
+ }
+ await database.set("cacheVersion", cacheVersion);
+ return events;
+}
+
+async function trackWorkouts(events) {
+ for (let i in events) {
+ let event = events[i];
+ if (event && event.venue && event.venue.name
+ && new Date(event.time).getDay() == 1) {
+ let match = /follow (?:my|our) (.*?) workout/.exec(event.description);
+ let workout;
+ if (match) {
+ workout = match[1];
+ } else if (/snowshoe/.exec(event.description)
+ || /No run/.exec(event.description)) {
+ // nothing
+ } else {
+ terminal.print(event.description);
+ }
+ if (workout) {
+ terminal.print(workout);
+ }
+ }
+ }
+}
+
+async function locationByDay(day, events) {
+ for (let i in events) {
+ let event = events[i];
+ if (event && event.venue && event.venue.name
+ && new Date(event.time).getDay() == day) {
+ terminal.print(event.venue.name);
+ }
+ }
+}
+
+async function redisplay(action, actions, events) {
+ terminal.clear();
+ if (actions[action]) {
+ terminal.setHash(action);
+ terminal.print({style: "font-size: xx-large", value: action});
+ }
+ for (let i in actions) {
+ terminal.print({command: i});
+ }
+ if (actions[action]) {
+ await actions[action](events);
+ }
+}
+
+async function main() {
+ terminal.print("loading events...");
+ let events = await getEvents();
+ terminal.print("loaded ", events.length.toString(), " events");
+
+ let actions = {
+ "Sunday Locations": locationByDay.bind(null, 0),
+ "Monday Track Workouts": trackWorkouts,
+ "Tuesday Locations": locationByDay.bind(null, 2),
+ "Wednesday Locations": locationByDay.bind(null, 3),
+ "Thursday Locations": locationByDay.bind(null, 4),
+ "Friday Locations": locationByDay.bind(null, 5),
+ "Saturday Locations": locationByDay.bind(null, 6),
+ }
+
+ core.register("hashChange", function(event) {
+ if (event.hash && event.hash[0] == '#') {
+ terminal.clear();
+ let hash = event.hash.substring(1);
+ redisplay(hash, actions, events);
+ }
+ });
+
+ let action = null;
+ while (true) {
+ redisplay(action, actions, events);
+ action = await terminal.readLine();
+ }
+}
+
+main().catch(terminal.print);
\ No newline at end of file
diff --git a/packages/cory/messages/messages.js b/packages/cory/messages/messages.js
new file mode 100644
index 00000000..9031d636
--- /dev/null
+++ b/packages/cory/messages/messages.js
@@ -0,0 +1,15 @@
+if (imports.terminal) {
+ core.register("onMessage", function(sender, message) {
+ terminal.print(JSON.stringify(core.user.index), " ", message);
+ });
+
+ core.register("onInput", function(input) {
+ core.getService("global").then(function(service) {
+ service.postMessage(input);
+ });
+ });
+} else {
+ core.register("onMessage", function(sender, message) {
+ core.broadcast(message);
+ });
+}
\ No newline at end of file
diff --git a/packages/cory/reminder/reminder.js b/packages/cory/reminder/reminder.js
new file mode 100644
index 00000000..2d7421bc
--- /dev/null
+++ b/packages/cory/reminder/reminder.js
@@ -0,0 +1,30 @@
+"use strict";
+
+//! {"category": "tests"}
+
+
+terminal.print("Time to buy a new life jacket.");
+/*
+terminal.print("hi");
+terminal.print("here is some svg:");
+terminal.print({svg: {
+ attributes: {
+ width: 32,
+ height: 32,
+ },
+ children: [
+ {
+ name: "circle",
+ attributes: {
+ cx: 16,
+ cy: 16,
+ r: 15,
+ stroke: "green",
+ "stroke-width": "4",
+ fill: "yellow",
+ },
+ },
+ ],
+}});
+
+terminal.print("end of svg");*/
\ No newline at end of file
diff --git a/packages/cory/splits/splits.js b/packages/cory/splits/splits.js
new file mode 100644
index 00000000..4e5cd586
--- /dev/null
+++ b/packages/cory/splits/splits.js
@@ -0,0 +1,33 @@
+"use strict";
+
+//! {"category": "tests"}
+
+terminal.print("Hello, world!");
+terminal.split([
+ {name: "h1", grow: 4},
+ {name: "h2", grow: 1},
+ {type: "vertical", children: [
+ {name: "h3.v1"},
+ {name: "h3.v2"},
+ {type: "horizontal", children: [
+ {name: "h3.v3.h1"},
+ {name: "h3.v3.h2"},
+ {name: "h3.v3.h3"},
+ ]},
+ {name: "h3.v4"},
+ ]},
+ {name: "h4"}
+]);
+
+function multiprint(text) {
+ var terminals = ["h1", "h2", "h3.v1", "h3.v2", "h3.v3.h1", "h3.v3.h2", "h3.v3.h3", "h3.v4", "h4"];
+ for (var i = 0; i < terminals.length; i++) {
+ terminal.select(terminals[i]);
+ terminal.print({style: "color: red", value: terminals[i]}, " ", text);
+ }
+}
+
+multiprint("hello");
+core.register("onInput", function(input) {
+ multiprint(input);
+});
\ No newline at end of file
diff --git a/packages/cory/tanks/tanks.js b/packages/cory/tanks/tanks.js
new file mode 100644
index 00000000..6a612e0d
--- /dev/null
+++ b/packages/cory/tanks/tanks.js
@@ -0,0 +1,208 @@
+"use strict";
+
+//! {"require": ["libiframe"]}
+
+let isClient = imports.terminal != null;
+if (isClient) {
+ require("libiframe").iframe({
+ source: `
+ document.documentElement.setAttribute("style", "width: 100%; height: 100%; margin: 0; padding: 0");
+ document.body.setAttribute("style", "display: flex; flex-direction: column; height: 100%; width: 100%; margin: 0; padding: 0");
+ document.body.style.color = '#fff';
+ document.body.style.backgroundColor = '#000';
+
+ var canvas = document.createElement("canvas");
+ canvas.setAttribute("style", "flex: 1 1; image-rendering: pixelated");
+ canvas.width = 128;
+ canvas.height = 128;
+ document.body.append(canvas);
+
+ var buttonBar = document.createElement("div");
+ buttonBar.setAttribute("style", "flex: 0 1 1in; display: flex; flex-direction: row");
+ var buttons = ["<", "^", "v", ">", "Fire!"];
+ for (var i = 0; i < buttons.length; i++) {
+ var button = document.createElement("input");
+ button.setAttribute("type", "button");
+ button.setAttribute("style", "flex: 1 1");
+ button.value = buttons[i];
+ button.onclick = buttonClicked.bind(null, buttons[i]);
+ buttonBar.append(button);
+ }
+ document.body.append(buttonBar);
+
+ var buttonCallback;
+ function buttonClicked(button) {
+ if (buttonCallback) {
+ buttonCallback(button);
+ }
+ }
+
+ var kExportNames = [
+ "beginPath",
+ "fill",
+ "save",
+ "restore",
+ "scale",
+ "rotate",
+ "translate",
+ "transform",
+ "arc",
+ "moveTo",
+ "lineTo",
+ "fillRect",
+ "fillText",
+ "stroke",
+ ];
+ var context = canvas.getContext("2d");
+ var exports = {};
+ for (var i = 0; i < kExportNames.length; i++) {
+ exports[kExportNames[i]] = context[kExportNames[i]].bind(context);
+ }
+ exports.setButtonCallback = function(callback) { buttonCallback = callback; };
+ exports.setFillStyle = function(style) { context.fillStyle = style; };
+ exports.setStrokeStyle = function(style) { context.strokeStyle = style; };
+ exports.setLineWidth = function(width) { context.lineWidth = width; };
+ rpc.export(exports);
+ `,
+ style: `
+ border: 0;
+ `}).then(initialize).catch(terminal.print);
+
+ function initialize(iframe) {
+ iframe.setButtonCallback(click);
+ draw(iframe);
+ core.register("onMessage", onMessage.bind(null, iframe));
+ core.getService("server").then(function(server) {
+ server.postMessage({ready: true});
+ });
+ }
+
+ async function click(button) {
+ (await core.getService("server")).postMessage({button: button});
+ }
+
+ function draw(iframe) {
+ iframe.setFillStyle('#222');
+ iframe.fillRect(0, 0, 640, 480);
+ }
+
+ function drawTank(iframe, x, y, color, angle) {
+ iframe.save();
+ iframe.setLineWidth(1);
+ iframe.setFillStyle(color);
+ iframe.setStrokeStyle(color);
+ iframe.translate(x, y);
+ iframe.beginPath();
+ iframe.arc(0, 0, 4, Math.PI, 2 * Math.PI);
+ iframe.fill();
+ iframe.beginPath();
+ var radians = Math.PI + angle * Math.PI / 180.0;
+ iframe.moveTo(Math.cos(radians) * 2, Math.sin(radians) * 2 - 0.5);
+ iframe.lineTo(Math.cos(radians) * 10, Math.sin(radians) * 10 - 0.5);
+ iframe.stroke();
+ iframe.restore();
+ }
+
+ function onMessage(iframe, sender, message) {
+ iframe.setFillStyle('#222');
+ iframe.fillRect(0, 0, 640, 480);
+
+ for (let i = 0; i < message.users.length; i++) {
+ var tank = message.users[i];
+ drawTank(iframe, tank.position.x, tank.position.y, tank.user == core.user.index ? '#f00' : '#0f0', tank.angle);
+ }
+ iframe.setFillStyle('#0f0');
+ for (let i = 0; i < message.terrain.length; i++) {
+ let h = message.terrain[i];
+ iframe.fillRect(i, 128 - h, i + 1, h);
+ }
+ }
+} else {
+ let users = {};
+ let terrain = new Array(128);
+ let updating = false;
+
+ function startUpdating() {
+ if (!updating) {
+ setTimeout(100, update);
+ updating = true;
+ }
+ }
+
+ function update() {
+ updating = false;
+ for (let u of Object.values(users)) {
+ user.position.y++;
+ updating = true;
+ }
+ broadcastUsers();
+ if (updating) {
+ setTimeout(100, update);
+ }
+ }
+
+ function initializeTerrain() {
+ for (let i = 0; i < terrain.length; i++) {
+ terrain[i] = 32;
+ }
+ }
+
+ function broadcastUsers() {
+ core.broadcast({
+ users: Object.keys(users).map(x => ({user: x, position: users[x].position, angle: users[x].angle})),
+ terrain: terrain,
+ });
+ }
+
+ function placeNewUser() {
+ let positions = Object.keys(users).map(x => users[x].position.x);
+ var best = 128 / 2;
+ var bestDistance = -1;
+ if (positions.length) {
+ for (var i = 10; i < 118; i++) {
+ var thisDistance = Math.min.apply(null, positions.map(x => Math.abs(i - x)));
+ if (thisDistance > bestDistance) {
+ bestDistance = thisDistance;
+ best = i;
+ }
+ }
+ }
+ return {x: best, y: 10, d: bestDistance};
+ }
+
+ initializeTerrain();
+
+ core.register("onMessage", function(sender, message) {
+ if (message.ready) {
+ users[sender.index] = {
+ user: sender,
+ angle: 0,
+ velocity: 100,
+ position: placeNewUser(),
+ };
+ broadcastUsers();
+ startUpdating();
+ } else if (message.button) {
+ switch (message.button) {
+ case "<":
+ users[sender.index].angle = Math.round(Math.max(users[sender.index].angle - 5.0, 0.0));
+ break;
+ case ">":
+ users[sender.index].angle = Math.round(Math.min(users[sender.index].angle + 5.0, 180.0));
+ break;
+ case "^":
+ users[sender.index].velocity++;
+ break;
+ case "v":
+ users[sender.index].velocity--;
+ break;
+ }
+ broadcastUsers();
+ }
+ });
+
+ core.register("onSessionEnd", function(user) {
+ delete users[user.index];
+ broadcastUsers();
+ });
+}
\ No newline at end of file
diff --git a/packages/cory/test/test.js b/packages/cory/test/test.js
new file mode 100644
index 00000000..a1061331
--- /dev/null
+++ b/packages/cory/test/test.js
@@ -0,0 +1,13 @@
+async function main() {
+ terminal.print("Hello, world!");
+
+ terminal.print("Please enter your name:");
+ var name = await terminal.readLine();
+ terminal.print("Hello, " + name + ".");
+
+ for (var i = 0; i < 5; i++) {
+ terminal.print(i.toString());
+ }
+}
+
+main().catch(terminal.print);
\ No newline at end of file
diff --git a/packages/cory/tmirce/tmirce.js b/packages/cory/tmirce/tmirce.js
new file mode 100644
index 00000000..7ac6d01e
--- /dev/null
+++ b/packages/cory/tmirce/tmirce.js
@@ -0,0 +1,187 @@
+"use strict";
+
+//! {"permissions": ["network"], "require": ["libencoding", "libhttp"]}
+
+let libhttp = require("libhttp");
+
+async function getEvents() {
+ let now = new Date();
+ let cacheVersion = "1-" + now.toDateString();
+ if (await database.get("cacheVersion") != cacheVersion) {
+ terminal.print("Refreshing database...");
+ let keys = await database.getAll();
+ await Promise.all(keys.map(x => database.remove(x)));
+ await database.set("cacheVersion", cacheVersion);
+ }
+ let events = [];
+ let url = "https://api.meetup.com/The-Most-Informal-Running-Club-Ever-TMIRCE-Upstate/events?&sign=true&photo-host=public&status=past";
+ let done = false;
+ while (!done) {
+ let response;
+ response = await database.get(url);
+ if (response) {
+ response = JSON.parse(response);
+ }
+ if (!response) {
+ response = await libhttp.get(url);
+ await database.set(url, JSON.stringify(response));
+ }
+ let nextLink;
+ let theseEvents = JSON.parse(response.body);
+ for (let j in theseEvents) {
+ if (new Date(theseEvents[j].time) < now) {
+ events.push(theseEvents[j]);
+ } else {
+ done = true;
+ break;
+ }
+ }
+ for (let j in response.headerArray) {
+ let link = response.headerArray[j];
+ if (link[0] == "Link" && link[1].split("; ")[1] == 'rel="next"') {
+ link = link[1].split("; ")[0];
+ link = link.substring(1, link.length - 1);
+ nextLink = link;
+ }
+ }
+ if (nextLink) {
+ url = nextLink;
+ } else {
+ break;
+ }
+ }
+ return events;
+}
+
+async function trackWorkouts(events) {
+ let results = [];
+ for (let i in events) {
+ let event = events[i];
+ if (event && event.venue && event.venue.name
+ && new Date(event.time).getDay() == 1) {
+ let match = /follow (?:my|our) (.*?) workout/.exec(event.description);
+ let workout;
+ if (match) {
+ workout = match[1];
+ } else if (/snowshoe/.exec(event.description)
+ || /No run/.exec(event.description)) {
+ // nothing
+ } else {
+ results.push(event.description);
+ }
+ if (workout) {
+ results.push(workout);
+ }
+ }
+ }
+ return results;
+}
+
+async function locationByDay(day, events) {
+ let results = [];
+ for (let i in events) {
+ let event = events[i];
+ if (event && event.venue && event.venue.name
+ && new Date(event.time).getDay() == day) {
+ results.push(event.venue.name);
+ }
+ }
+ return results;
+}
+
+async function redisplay(action, filter, actions, filters, events) {
+ //terminal.cork();
+ terminal.clear();
+ if (actions[action]) {
+ terminal.setHash(action);
+ terminal.print({style: "font-size: xx-large", value: action});
+ }
+ terminal.print("What:");
+ for (let i in actions) {
+ terminal.print(action == i ? "-> " : " * ", {command: i});
+ }
+ terminal.print("How:");
+ for (let i in filters) {
+ terminal.print(filter == i ? "-> " : " * ", {command: i});
+ }
+ terminal.print("--");
+ if (actions[action]) {
+ filters[filter](await actions[action](events));
+ }
+ //terminal.uncork();
+}
+
+function rawFilter(items) {
+ for (let i in items) {
+ terminal.print(items[i]);
+ }
+}
+
+function histogramFilter(items) {
+ let table = {};
+ for (let i in items) {
+ if (!table[items[i]]) {
+ table[items[i]] = 0;
+ }
+ table[items[i]] += 1;
+ }
+
+ let results = [];
+ for (let i in table) {
+ results.push([table[i], i]);
+ }
+ results.sort((x, y) => y[0] - x[0]);
+
+ for (let i in results) {
+ terminal.print(results[i][0].toString(), " ", results[i][1]);
+ }
+}
+
+async function main() {
+ terminal.print("loading events...");
+ let events = await getEvents();
+ terminal.print("loaded ", events.length.toString(), " events");
+
+ let actions = {
+ "Sunday Locations": locationByDay.bind(null, 0),
+ "Monday Track Workouts": trackWorkouts,
+ "Tuesday Locations": locationByDay.bind(null, 2),
+ "Wednesday Locations": locationByDay.bind(null, 3),
+ "Thursday Locations": locationByDay.bind(null, 4),
+ "Friday Locations": locationByDay.bind(null, 5),
+ "Saturday Locations": locationByDay.bind(null, 6),
+ }
+
+ let filters = {
+ "Chronological": rawFilter,
+ "Histogram": histogramFilter,
+ };
+
+ let action = null;
+ let filter = "Chronological";
+
+ core.register("hashChange", function(event) {
+ if (event.hash && event.hash[0] == '#') {
+ terminal.clear();
+ let hash = event.hash.substring(1);
+ if (filters[hash]) {
+ filter = hash;
+ } else if (actions[hash]) {
+ action = hash;
+ }
+ redisplay(action, filter, actions, filters, events);
+ }
+ });
+
+ while (true) {
+ redisplay(action, filter, actions, filters, events);
+ let command = await terminal.readLine();
+ if (filters[command]) {
+ filter = command;
+ } else if (actions[command]) {
+ action = command;
+ }
+ }
+}
+
+main().catch(terminal.print);
\ No newline at end of file
diff --git a/packages/cory/whatnext/whatnext.js b/packages/cory/whatnext/whatnext.js
new file mode 100644
index 00000000..a339ae9b
--- /dev/null
+++ b/packages/cory/whatnext/whatnext.js
@@ -0,0 +1,143 @@
+"use strict";
+
+//! {"require": ["ui"]}
+
+terminal.setEcho(false);
+terminal.setTitle("What Next?");
+
+let gEditEvent = null;
+
+function back() {
+ terminal.split([{name: "terminal"}]);
+ if (gEditEvent) {
+ gEditEvent.back();
+ }
+}
+
+core.register("onWindowMessage", function(event) {
+ if (event.message.ready) {
+ terminal.postMessageToIframe("iframe", {title: gEditEvent.name, contents: gEditEvent.value});
+ } else if (event.message.index) {
+ back();
+ } else {
+ gEditEvent.save(event.message.title, event.message.contents).then(back);
+ }
+});
+
+function editPage(event) {
+ gEditEvent = event;
+ terminal.split([{name: "terminal", type: "vertical"}]);
+ terminal.clear();
+ terminal.print({iframe: `
+
+
+
+
+
+
+
+
+
+
+
+ `, name: "iframe", style: "flex: 1 1 auto; border: 0; width: 100%"});
+}
+
+require("ui").fileList({
+ title: "What Next?",
+ edit: editPage,
+});
\ No newline at end of file
diff --git a/packages/cory/wiki/wiki.js b/packages/cory/wiki/wiki.js
index 14dd88a8..4ef695b8 100644
--- a/packages/cory/wiki/wiki.js
+++ b/packages/cory/wiki/wiki.js
@@ -32,6 +32,7 @@ function editPage(event) {
+
+
+
+
+
+`, width: 640, height: 390, style: "padding: 0; margin: 0; border: 0"});
+
+terminal.print("hi");
+core.register("onWindowMessage", function(event) {
+ terminal.print("message", JSON.stringify(event.message));
+});
\ No newline at end of file
diff --git a/src/Tls.cpp b/src/Tls.cpp
index e98a092b..fa2625a3 100644
--- a/src/Tls.cpp
+++ b/src/Tls.cpp
@@ -67,11 +67,23 @@ TlsSession* TlsContext_openssl::createSession() {
return new TlsSession_openssl(this);
}
+#include
+
TlsContext_openssl::TlsContext_openssl() {
SSL_library_init();
SSL_load_error_strings();
- _context = SSL_CTX_new(SSLv23_method());
+ const SSL_METHOD* method = SSLv23_method();
+ if (!method)
+ {
+ std::cerr << "SSLv23_method returned NULL\n";
+ }
+
+ _context = SSL_CTX_new(method);
+ if (!_context)
+ {
+ std::cerr << "SSL_CTX_new returned NULL\n";
+ }
SSL_CTX_set_default_verify_paths(_context);
}
diff --git a/tools/letsencrypt.sh b/tools/letsencrypt.sh
index 2b212da7..b9905f20 100755
--- a/tools/letsencrypt.sh
+++ b/tools/letsencrypt.sh
@@ -22,3 +22,4 @@ certbot renew \
--work-dir data/global/letsencrypt/work \
--cert-path data/global/httpd/certificate.pem \
--key-path data/global/httpd/privatekey.pem \
+ $*