├── util.js ├── README.md ├── simple-screen.js ├── index.js ├── package.json ├── input.js ├── commands.js ├── interface.js └── .gitignore /util.js: -------------------------------------------------------------------------------- 1 | function logResult (err, result) { 2 | if (err) { console.error('hyperdb failed with', err) } 3 | if (arguments.length >= 2) { console.log(result) } 4 | } 5 | 6 | module.exports = {log: logResult} 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # cabal 2 | p2p forum software 3 | 4 | cabal is a place where mesh conspirators can talk about p2p topics in a p2p way 5 | 6 | see [cabal-club](https://github.com/cabal-club) for active development 7 | 8 | ## usage 9 | Start a new instance: 10 | ``` 11 | node index.js --db --nick 12 | ``` 13 | 14 | Connect to an existing instance: 15 | ``` 16 | node index.js --key --nick 17 | ``` 18 | -------------------------------------------------------------------------------- /simple-screen.js: -------------------------------------------------------------------------------- 1 | var strftime = require('strftime') 2 | function Screen () { 3 | if (!(this instanceof Screen)) return new Screen() 4 | } 5 | 6 | // use to write user messages 7 | Screen.prototype.writeMessage = function (msg) { 8 | console.log(`${formatTime(msg.value.time)} <${msg.value.author}> ${msg.value.text}`) 9 | } 10 | 11 | // use to write anything else to the screen, e.g. info messages or emotes 12 | Screen.prototype.writeLine = function (line) { 13 | console.log(line) 14 | } 15 | 16 | function formatTime (t) { 17 | return strftime('%T', new Date(t)) 18 | } 19 | module.exports = Screen 20 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Cabal = require("../cabal-club/cabal-node/index.js") 2 | var cabalSwarm = require("../cabal-club/cabal-node/swarm.js") 3 | var frontend = require("./neat-screen.js") 4 | var minimist = require("minimist") 5 | 6 | var args = minimist(process.argv.slice(2)) 7 | 8 | if (!args.db) { 9 | if (args.key) { 10 | args.db = 'archives/' + args.key 11 | } else { 12 | console.error("error: need --db flag!\nexamples:\n\tnode index.js --db \n\tnode index.js --db my.db") 13 | return 14 | } 15 | } 16 | 17 | cabal = Cabal(args.db, args.key, {username: args.nick || "anonymous"}) 18 | 19 | cabal.db.on("ready", function() { 20 | frontend(cabal) 21 | var swarm = cabalSwarm(cabal) 22 | }) 23 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cabal", 3 | "version": "1.0.0", 4 | "description": "p2p forum software", 5 | "main": "index.js", 6 | "dependencies": { 7 | "concat-stream": "^1.6.2", 8 | "dat-swarm-defaults": "^1.0.1", 9 | "discovery-swarm": "^5.1.1", 10 | "hyperdb": "git+https://github.com/mafintosh/hyperdb.git", 11 | "minimist": "^1.2.0", 12 | "neat-log": "^2.4.0", 13 | "strftime": "^0.10.0", 14 | "through2": "^2.0.3" 15 | }, 16 | "devDependencies": {}, 17 | "scripts": { 18 | "test": "echo \"Error: no test specified\" && exit 1" 19 | }, 20 | "repository": { 21 | "type": "git", 22 | "url": "git+https://github.com/new-computers/cabal.git" 23 | }, 24 | "keywords": [ 25 | "hyperdb", 26 | "dat", 27 | "cblgh", 28 | "p2p" 29 | ], 30 | "author": "cblgh", 31 | "license": "ISC", 32 | "bugs": { 33 | "url": "https://github.com/new-computers/cabal/issues" 34 | }, 35 | "homepage": "https://github.com/new-computers/cabal#readme" 36 | } 37 | -------------------------------------------------------------------------------- /input.js: -------------------------------------------------------------------------------- 1 | var readline = require("readline") 2 | var through = require("through2") 3 | var Interface = require("./interface.js") 4 | var Screen = require("./neat-screen.js") 5 | var util = require("./util.js") 6 | 7 | function readInput(db, nick) { 8 | var rl = readline.createInterface({ 9 | input: process.stdin, 10 | output: process.stdout, 11 | terminal: false 12 | }) 13 | 14 | var currentChannel = "default" 15 | 16 | var interface = Interface(db) 17 | var screen = Screen(inputHandler) 18 | var watcher 19 | 20 | loadChannel(currentChannel) 21 | 22 | function inputHandler(state, bus) { 23 | state.messages = [] 24 | bus = bus 25 | state.channel = "default" 26 | bus.emit("render") 27 | console.log(screen) 28 | } 29 | 30 | function monitor(channel) { 31 | // if we monitor a new channel, destroy the old watcher first 32 | if (watcher) watcher.destroy() 33 | watcher = db.watch(channel, () => { 34 | interface.getMessages(channel, 25).then((msg) => { 35 | msg.map((m) => { 36 | screen.writeMessage.bind(screen)(m) 37 | }) 38 | }) 39 | }) 40 | } 41 | 42 | var pattern = (/\.(\w*)\s*(.*)/) 43 | rl.on("line", function(line) { 44 | }) 45 | 46 | function writeMessage(msg) { 47 | var channel = `channels/${currentChannel}` 48 | interface.writeMessage(channel, nick, msg) 49 | } 50 | 51 | function loadChannel(name) { 52 | var channel = `channels/${name}` 53 | monitor(channel) 54 | interface.getMessages(channel, 25).then((msg) => { 55 | msg.map((m) => { 56 | screen.writeMessage.bind(screen)(m) 57 | }) 58 | }) 59 | } 60 | } 61 | 62 | module.exports = readInput 63 | -------------------------------------------------------------------------------- /commands.js: -------------------------------------------------------------------------------- 1 | var util = require("./util.js") 2 | var through = require("through2") 3 | 4 | function Commander(view, cabal) { 5 | if (!(this instanceof Commander)) return new Commander(view, cabal) 6 | var self = this 7 | this.cabal = cabal 8 | this.channel = "default" 9 | this.view = view 10 | this.pattern = (/\/(\w*)\s*(.*)/) 11 | } 12 | 13 | Commander.prototype.process = function(line) { 14 | var self = this 15 | var match = self.pattern.exec(line) 16 | var cmd = match && match[1] || "" 17 | var arg = match && match[2] || "" 18 | arg = arg.trim() 19 | 20 | if (cmd === "put") { 21 | var [key, value] = arg.split("=") 22 | self.cabal.db.put(key, value, util.log) 23 | } else if (cmd === "list") { 24 | self.cabal.getChannels((err, channels) => { 25 | channels.map(self.view.writeLine.bind(self.view)) 26 | }) 27 | } else if (cmd === "chat") { 28 | if (arg !== '') { 29 | cabal.message(channel, arg) 30 | } 31 | } else if (cmd === "nick") { 32 | if (arg == '') return 33 | self.cabal.username = arg 34 | } else if (cmd === "get") { 35 | self.cabal.db.get(arg, console.log) 36 | } else if (cmd === "all") { 37 | var stream = cabal.db.createHistoryStream() 38 | stream.pipe(through.obj(function(chunk, enc, next) { 39 | console.log(chunk) 40 | next() 41 | })) 42 | } else if (cmd === "change") { 43 | if (arg == '') arg = 'what' 44 | self.channel = arg 45 | self.view.changeChannel(arg) 46 | } else if (cmd === "auth") { self.cabal.db.authorize(Buffer.from(arg, "hex"), util.log) 47 | } else if (cmd === "local") { console.log("local key is\n\t", self.cabal.db.local.key.toString("hex")) 48 | } else if (cmd === "db") { console.log("db key is\n\t", self.cabal.db.key.toString("hex")) 49 | } else if (cmd === "registered") { self.cabal.db.authorized(Buffer.from(arg, "hex"), util.log) 50 | } else { 51 | line = line.trim() 52 | if (line !== '') { 53 | cabal.message(self.channel, line) 54 | } 55 | } 56 | } 57 | 58 | function loadChannel(name) { 59 | var channel = `channels/${name}` 60 | // monitor(channel) 61 | interface.getMessages(channel, 25, (messages) => { 62 | messages.map((m) => { 63 | self.view.writeMessage.bind(self.view)(m) 64 | }) 65 | }) 66 | } 67 | 68 | module.exports = Commander 69 | -------------------------------------------------------------------------------- /interface.js: -------------------------------------------------------------------------------- 1 | var util = require('./util.js') 2 | var through = require('through2') 3 | var concat = require('concat-stream') 4 | function Interface (db) { 5 | if (!(this instanceof Interface)) return new Interface(db) 6 | this.db = db 7 | } 8 | 9 | Interface.prototype.writeMessage = function (channel, nick, msg) { 10 | var self = this 11 | self.db.get(`${channel}/latest`, (err, node) => { 12 | node = node || {value: 0} 13 | var latest = parseInt(node.value) 14 | var newLatest = latest + 1 15 | self.db.put(`${channel}/${newLatest}`, {text: msg, author: nick, time: Date.now()}, (err, node) => { 16 | self.db.put(`${channel}/latest`, newLatest) 17 | }) 18 | }) 19 | } 20 | 21 | var channelPattern = /channels\/(.*)\/.*/ 22 | Interface.prototype.getChannels = function () { 23 | var self = this 24 | return new Promise((resolve, reject) => { 25 | var stream = self.db.createReadStream('channels') 26 | var concatStream = concat((data) => { 27 | var channels = {} 28 | data.forEach((d) => { 29 | var match = channelPattern.exec(d) 30 | if (match && match[1]) { 31 | channels[match[1]] = true 32 | } 33 | }) 34 | resolve(Object.keys(channels)) 35 | }) 36 | 37 | stream 38 | .pipe(through.obj(function (chunk, enc, next) { 39 | if (chunk.key.slice(-6) !== 'latest') this.push([chunk.key]) 40 | next() 41 | })) 42 | .pipe(concatStream) 43 | }) 44 | } 45 | 46 | Interface.prototype.test = function () { 47 | var self = this 48 | var stream = self.db.createReadStream('data') 49 | stream 50 | .pipe(through.obj(function (chunk, enc, next) { 51 | this.push(chunk.value) 52 | next() 53 | })) 54 | .pipe(process.stdout) 55 | 56 | self.db.list('data', (arr) => { 57 | console.log('DB.LIST') 58 | console.log(arr) 59 | }) 60 | } 61 | 62 | Interface.prototype.getMessages = function (channel, max) { 63 | var self = this 64 | return new Promise((resolve, reject) => { 65 | self.db.get(`${channel}/latest`, (err, node) => { 66 | if (!node) return 67 | var latest = node.value 68 | var messagePromises = [] 69 | for (var i = 0; i < max; i++) { 70 | if (latest - i < 1) break 71 | var promise = getMessage(latest - i, channel) 72 | messagePromises.push(promise) 73 | } 74 | 75 | function getMessage (time, channel) { 76 | return new Promise((resolve, reject) => { 77 | self.db.get(`${channel}/${time}`, (err, node) => { 78 | if (err) reject(err) 79 | resolve(node) 80 | }) 81 | }) 82 | } 83 | 84 | messagePromises.reverse() 85 | Promise.all(messagePromises).then((messages) => { 86 | resolve(messages) 87 | }) 88 | }) 89 | }) 90 | } 91 | module.exports = Interface 92 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | #~top ignores~ 2 | node_modules/ 3 | *.css 4 | *.vim 5 | *bundle*.js 6 | /html/*.html 7 | *.swo 8 | config.conf 9 | config.js 10 | *.txt 11 | *.pdf 12 | archives 13 | 14 | ################# 15 | ## Eclipse 16 | ################# 17 | *.pydevproject 18 | .project 19 | .metadata 20 | bin/ 21 | tmp/ 22 | *.tmp 23 | *.bak 24 | *.swp 25 | *~.nib 26 | local.properties 27 | .classpath 28 | .settings/ 29 | .loadpath 30 | 31 | # External tool builders 32 | .externalToolBuilders/ 33 | 34 | # Locally stored "Eclipse launch configurations" 35 | *.launch 36 | 37 | # CDT-specific 38 | .cproject 39 | 40 | # PDT-specific 41 | .buildpath 42 | 43 | 44 | ################# 45 | ## Visual Studio 46 | ################# 47 | 48 | ## Ignore Visual Studio temporary files, build results, and 49 | ## files generated by popular Visual Studio add-ons. 50 | 51 | # User-specific files 52 | *.suo 53 | *.user 54 | *.sln.docstates 55 | 56 | # Build results 57 | 58 | [Dd]ebug/ 59 | [Rr]elease/ 60 | x64/ 61 | build/ 62 | [Bb]in/ 63 | [Oo]bj/ 64 | 65 | # MSTest test Results 66 | [Tt]est[Rr]esult*/ 67 | [Bb]uild[Ll]og.* 68 | 69 | *_i.c 70 | *_p.c 71 | *.ilk 72 | *.meta 73 | *.obj 74 | *.pch 75 | *.pdb 76 | *.pgc 77 | *.pgd 78 | *.rsp 79 | *.sbr 80 | *.tlb 81 | *.tli 82 | *.tlh 83 | *.tmp 84 | *.tmp_proj 85 | *.log 86 | *.vspscc 87 | *.vssscc 88 | .builds 89 | *.pidb 90 | *.log 91 | *.scc 92 | 93 | # Visual C++ cache files 94 | ipch/ 95 | *.aps 96 | *.ncb 97 | *.opensdf 98 | *.sdf 99 | *.cachefile 100 | 101 | # Visual Studio profiler 102 | *.psess 103 | *.vsp 104 | *.vspx 105 | 106 | # Guidance Automation Toolkit 107 | *.gpState 108 | 109 | # ReSharper is a .NET coding add-in 110 | _ReSharper*/ 111 | *.[Rr]e[Ss]harper 112 | 113 | # TeamCity is a build add-in 114 | _TeamCity* 115 | 116 | # DotCover is a Code Coverage Tool 117 | *.dotCover 118 | 119 | # NCrunch 120 | *.ncrunch* 121 | .*crunch*.local.xml 122 | 123 | # Installshield output folder 124 | [Ee]xpress/ 125 | 126 | # DocProject is a documentation generator add-in 127 | DocProject/buildhelp/ 128 | DocProject/Help/*.HxT 129 | DocProject/Help/*.HxC 130 | DocProject/Help/*.hhc 131 | DocProject/Help/*.hhk 132 | DocProject/Help/*.hhp 133 | DocProject/Help/Html2 134 | DocProject/Help/html 135 | 136 | # Click-Once directory 137 | publish/ 138 | 139 | # Publish Web Output 140 | *.Publish.xml 141 | *.pubxml 142 | 143 | # NuGet Packages Directory 144 | ## TODO: If you have NuGet Package Restore enabled, uncomment the next line 145 | #packages/ 146 | 147 | # Windows Azure Build Output 148 | csx 149 | *.build.csdef 150 | 151 | # Windows Store app package directory 152 | AppPackages/ 153 | 154 | # Others 155 | sql/ 156 | *.Cache 157 | ClientBin/ 158 | [Ss]tyle[Cc]op.* 159 | ~$* 160 | *~ 161 | *.dbmdl 162 | *.[Pp]ublish.xml 163 | *.pfx 164 | *.publishsettings 165 | 166 | # RIA/Silverlight projects 167 | Generated_Code/ 168 | 169 | # Backup & report files from converting an old project file to a newer 170 | # Visual Studio version. Backup files are not needed, because we have git ;-) 171 | _UpgradeReport_Files/ 172 | Backup*/ 173 | UpgradeLog*.XML 174 | UpgradeLog*.htm 175 | 176 | # SQL Server files 177 | App_Data/*.mdf 178 | App_Data/*.ldf 179 | 180 | ############# 181 | ## Windows detritus 182 | ############# 183 | 184 | # Windows image file caches 185 | Thumbs.db 186 | ehthumbs.db 187 | 188 | # Folder config file 189 | Desktop.ini 190 | 191 | # Recycle Bin used on file shares 192 | $RECYCLE.BIN/ 193 | 194 | # Mac crap 195 | .DS_Store 196 | 197 | 198 | ############# 199 | ## Python 200 | ############# 201 | 202 | *.py[co] 203 | 204 | # Packages 205 | *.egg 206 | *.egg-info 207 | dist/ 208 | build/ 209 | eggs/ 210 | parts/ 211 | var/ 212 | sdist/ 213 | develop-eggs/ 214 | .installed.cfg 215 | 216 | # Installer logs 217 | pip-log.txt 218 | 219 | # Unit test / coverage reports 220 | .coverage 221 | .tox 222 | 223 | #Translations 224 | *.mo 225 | 226 | #Mr Developer 227 | .mr.developer.cfg 228 | --------------------------------------------------------------------------------