├── .github └── stale.yml ├── .gitignore ├── .npmrc ├── README.md ├── package.json └── ssb-simple-whois.js /.github/stale.yml: -------------------------------------------------------------------------------- 1 | _extends: .github 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SSB Simple Whois 2 | 3 | Simple program to lookup petname mappings in ssb 4 | 5 | ``` 6 | $ git clone https://github.com/ssbc/ssb-simple-whois.git 7 | $ cd ssb-simple-whois 8 | $ npm install 9 | 10 | $ ./ssb-simple-whois.js 11 | paul *** @hxGxqPrplLjRG2vtjQL87abX4QKqeLgCwQpS730nNwE=.ed25519 12 | Dominic ** @BIbVppzlrNiRJogxDYz3glUS7G4s4D4NiXiPEAEzxdE=.ed25519 13 | bob * @HSZ7V+Hrm0mbqNGkINtN1CL8VEsY1CDMBu5yPCHg5zI=.ed25519 14 | bob * @PgeunKGJm05DZ0WWoRtGvH37gXMbDnVuse9HhaUT6RI=.ed25519 15 | 16 | $ ./ssb-simple-whois.js paul 17 | paul *** @hxGxqPrplLjRG2vtjQL87abX4QKqeLgCwQpS730nNwE=.ed25519 18 | 19 | $ ./ssb-simple-whois.js bob 20 | bob * @HSZ7V+Hrm0mbqNGkINtN1CL8VEsY1CDMBu5yPCHg5zI=.ed25519 21 | bob * @PgeunKGJm05DZ0WWoRtGvH37gXMbDnVuse9HhaUT6RI=.ed25519 22 | ``` 23 | 24 | The stars indicate the amount of trust in the assignment. 25 | - Three stars is full trust, because it's the name chosen by the local user. 26 | - Two stars is partial trust, because it's the name chosen by someone the local user follows. 27 | - One star is little trust, because it's the name chosen by an unfollowed user. 28 | 29 | There are no universal rules for petnames in SSB. 30 | There is no single registry or authority, so you can choose your own policies and algorithms. 31 | 32 | ## How it works 33 | 34 | Users publish a `type: about` message, which has the following schema: 35 | 36 | ```js 37 | { 38 | type: 'about', 39 | about: FeedLink, 40 | name: String 41 | } 42 | ``` 43 | 44 | This program uses a very simple set of rules for computing the petname map. 45 | Only self-assigned names are used. 46 | The trust ranking is described above. 47 | 48 | The petname map is a "materialized view," in the [Kappa Architecture](http://www.kappa-architecture.com/) semantic. 49 | It is created by streaming `type: about` messages, in the order received, into a view-processing function. 50 | The output is then produced from the map. 51 | 52 | The streaming code: 53 | 54 | ```js 55 | // fetch... 56 | var done = multicb({ pluck: 1, spread: true }) 57 | sbot.whoami(done()) // ...local users id 58 | sbot.friends.all('follow', done()) // ...computed follow-graph 59 | done(function (err, whoami, follows) { 60 | if (err) throw err 61 | 62 | // store in globals 63 | _selfId = whoami.id 64 | _follows = follows 65 | 66 | pull( 67 | // fetch `type: about` messages, in order received 68 | sbot.messagesByType('about'), 69 | 70 | // process each message 71 | pull.drain(processAboutMsg, function (err) { 72 | if (err) throw err 73 | 74 | // ... render ... 75 | }) 76 | ) 77 | }) 78 | ``` 79 | 80 | The processing function: 81 | 82 | ```js 83 | // `type: about` message processor 84 | // - expected schema: { type: 'about', name: String, about: FeedLink } 85 | function processAboutMsg (msg) { 86 | var c = msg.value.content 87 | 88 | // sanity check 89 | if (!nonEmptyStr(c.name)) 90 | return 91 | 92 | // only process self-assignments 93 | var target = mlib.link(c.about, 'feed') 94 | if (!target || target.link !== msg.value.author) 95 | return 96 | 97 | // remove any past assignments by this user 98 | for (var k in _names) 99 | _names[k] = _names[k].filter(function (entry) { return entry.id !== target.link }) 100 | 101 | // store the new assignment 102 | var name = makeNameSafe(c.name) 103 | _names[name] = _names[name] || [] 104 | _names[name].push({ 105 | id: target.link, 106 | name: name, 107 | trust: rateTrust(msg) 108 | }) 109 | } 110 | 111 | // trust-policy 112 | function rateTrust (msg) { 113 | // is local user: high trust 114 | if (msg.value.author === _selfId) 115 | return 3 116 | // followed by local user: medium trust 117 | if (_follows[_selfId][msg.value.author]) 118 | return 2 119 | // otherwise: low trust 120 | return 1 121 | } 122 | ``` 123 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ssb-simple-whois", 3 | "version": "1.0.0", 4 | "description": "Simple program to lookup petname mappings in ssb", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "repository": { 10 | "type": "git", 11 | "url": "git+https://github.com/ssbc/ssb-simple-whois.git" 12 | }, 13 | "keywords": [ 14 | "ssb", 15 | "whois" 16 | ], 17 | "author": "Paul Frazee ", 18 | "license": "MIT", 19 | "bugs": { 20 | "url": "https://github.com/ssbc/ssb-simple-whois/issues" 21 | }, 22 | "homepage": "https://github.com/ssbc/ssb-simple-whois#readme", 23 | "dependencies": { 24 | "minimist": "^1.2.0", 25 | "multicb": "^1.2.0", 26 | "pull-stream": "^2.28.4", 27 | "ssb-client": "^2.0.0", 28 | "ssb-msgs": "^5.0.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /ssb-simple-whois.js: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env node 2 | 3 | var pull = require('pull-stream') 4 | var mlib = require('ssb-msgs') 5 | var multicb = require('multicb') 6 | var argv = require('minimist')(process.argv.slice(2)) 7 | 8 | var _selfId = null 9 | var _follows = null 10 | var _names = {} 11 | 12 | require('ssb-client')(function (err, sbot) { 13 | if (err) throw err 14 | 15 | // fetch... 16 | var done = multicb({ pluck: 1, spread: true }) 17 | sbot.whoami(done()) // ...local users id 18 | sbot.friends.all('follow', done()) // ...computed follow-graph 19 | done(function (err, whoami, follows) { 20 | if (err) throw err 21 | 22 | // store in globals 23 | _selfId = whoami.id 24 | _follows = follows 25 | 26 | pull( 27 | // fetch `type: about` messages, in order received 28 | sbot.messagesByType('about'), 29 | 30 | // process each message 31 | pull.drain(processAboutMsg, function (err) { 32 | if (err) throw err 33 | 34 | // render requested name or all names 35 | var nameArg = argv._[0] 36 | if (!nameArg) 37 | renderNames() 38 | else 39 | renderName(_names[nameArg] || []) 40 | 41 | sbot.close() 42 | }) 43 | ) 44 | }) 45 | }) 46 | 47 | // `type: about` message processor 48 | // - expected schema: { type: 'about', name: String, about: FeedLink } 49 | function processAboutMsg (msg) { 50 | var c = msg.value.content 51 | 52 | // sanity check 53 | if (!nonEmptyStr(c.name)) 54 | return 55 | 56 | // only process self-assignments 57 | var target = mlib.link(c.about, 'feed') 58 | if (!target || target.link !== msg.value.author) 59 | return 60 | 61 | // remove any past assignments by this user 62 | for (var k in _names) 63 | _names[k] = _names[k].filter(function (entry) { return entry.id !== target.link }) 64 | 65 | // store the new assignment 66 | var name = makeNameSafe(c.name) 67 | _names[name] = _names[name] || [] 68 | _names[name].push({ 69 | id: target.link, 70 | name: name, 71 | trust: rateTrust(msg) 72 | }) 73 | } 74 | 75 | // trust-policy 76 | function rateTrust (msg) { 77 | // is local user: high trust 78 | if (msg.value.author === _selfId) 79 | return 3 80 | // followed by local user: medium trust 81 | if (_follows[_selfId][msg.value.author]) 82 | return 2 83 | // otherwise: low trust 84 | return 1 85 | } 86 | 87 | function renderNames () { 88 | // determine the longest name 89 | var width = 0 90 | for (var k in _names) 91 | width = (k.length > width) ? k.length : width 92 | 93 | // render all 94 | for (var k in _names) 95 | renderName(_names[k], width) 96 | } 97 | 98 | function renderName (list, width) { 99 | list.forEach(function (entry) { 100 | console.log(padLeft(entry.name, width, ' '), toStars(entry.trust), entry.id) 101 | }) 102 | } 103 | 104 | function padLeft (str, width, pad) { 105 | if (!width) 106 | return str 107 | return Array(width + 1 - str.length).join(pad) + str 108 | } 109 | 110 | function toStars (v) { 111 | return ({ 1: '* ', 2: '** ', 3: '***' })[v] 112 | } 113 | 114 | function nonEmptyStr (str) { 115 | return (typeof str === 'string' && !!(''+str).trim()) 116 | } 117 | 118 | // allow A-z0-9._-, dont allow a trailing . 119 | var badNameCharsRegex = /[^A-z0-9\._-]/g 120 | function makeNameSafe (str) { 121 | str = str.replace(badNameCharsRegex, '_') 122 | if (str.charAt(str.length - 1) == '.') 123 | str = str.slice(0, -1) + '_' 124 | return str 125 | } 126 | --------------------------------------------------------------------------------