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 + + + + + +
share link, updated on test
+ + +`, +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 \ + $*