Files: ca83e81efcab58179c5f51dcb99d20ee51c645b8 / ssb.js
5338 bytesRaw
1 | var pull = require('pull-stream') |
2 | var ref = require('ssb-ref') |
3 | var hash = require('ssb-keys/util').hash |
4 | var markdown = require('ssb-markdown') |
5 | |
6 | function isString (s) { |
7 | return 'string' == typeof s |
8 | } |
9 | |
10 | function update (state, msg) { |
11 | if(msg.content.type === 'channel') { |
12 | if(msg.content.subscribed) |
13 | state.channels[msg.content.channel] = msg.content.irc || msg.content.channel |
14 | else |
15 | delete state.channels[msg.content.channel] |
16 | } |
17 | else if(msg.content.type === 'about' && ref.isFeed(msg.content.about) && (msg.content.name || msg.content.irc != null)) { |
18 | state.users[msg.content.about] = msg.content.irc == null ? msg.content.name : msg.content.irc |
19 | } |
20 | return state |
21 | } |
22 | |
23 | function init(sbot, id, cb) { |
24 | var state = {users: {}, channels: {}}, int, called |
25 | |
26 | //hack since createUserStream doesn't supply sync: correctly. |
27 | pull( |
28 | sbot.createUserStream({id: id, limit: 1, reverse: true}), |
29 | pull.collect(function (err, ary) { |
30 | pull( |
31 | sbot.createHistoryStream({id: id || sbot.id, live: true}), |
32 | pull.drain(function (msg) { |
33 | state = update(state, msg.value) |
34 | if(msg.timestamp === ary[0].timestamp) |
35 | cb && cb(null, state) |
36 | }) |
37 | ) |
38 | }) |
39 | ) |
40 | |
41 | return state |
42 | } |
43 | |
44 | exports.init = init |
45 | |
46 | function toKey(fn) { |
47 | return function (state, msg) { |
48 | var key |
49 | if(msg.key) { |
50 | key = msg.key |
51 | msg = msg.value |
52 | } |
53 | else |
54 | key = '%'+hash(JSON.stringify(msg, null, 2)) |
55 | return fn(state, msg, key) |
56 | } |
57 | } |
58 | |
59 | exports.isChannelPost = toKey(function isChannelPost (state, msg, key) { |
60 | if(state.channels[msg.content.channel] && !msg.content.root) { |
61 | var text = msg.content.text |
62 | if(isString(text) && text) { |
63 | text = text.split('\n') |
64 | if(text.length > 1) |
65 | text = text[0] |
66 | else |
67 | text = text[0] |
68 | |
69 | return [{ |
70 | author: msg.author, |
71 | target: msg.content.channel, |
72 | text: text.length > 100 ? text.substring(0, 97)+'...' : text, |
73 | id: key, |
74 | type: 'channel' |
75 | }] |
76 | } |
77 | } |
78 | }) |
79 | |
80 | function uniq (a) { |
81 | var b = [] |
82 | for(var i = 0; i < a.length; i++) |
83 | if(!~b.indexOf(a[i])) b.push(a[i]) |
84 | return b |
85 | } |
86 | |
87 | function getChannel(state, name) { |
88 | name = name.replace(/^#/,'') |
89 | return state.channels[name] === true ? name : state.channels[name] |
90 | } |
91 | |
92 | function surrounding(text, match, length) { |
93 | var i = Math.max(text.indexOf(match)-100, 0) |
94 | return (i > 0 ? '...' : '') + text.substring(i, i+100) + (i + 100 < text.length ? '...' : '') |
95 | } |
96 | |
97 | exports.isChannelMention = toKey(function (state, msg, key) { |
98 | var text = msg.content.text |
99 | if(isString(text) && text) { |
100 | text = markdown.inline(text) |
101 | if(!/(#\w+)/.test(text)) return [] |
102 | |
103 | var m = uniq(text.match(/(#\w+)/g)) |
104 | return m.filter(function (name) { |
105 | return getChannel(state, name) |
106 | }).map(function (name) { |
107 | return { |
108 | author: msg.author, |
109 | target: getChannel(state, name), |
110 | text: surrounding(text, name, 100), |
111 | id: key, |
112 | type: 'channel' |
113 | } |
114 | }) |
115 | } |
116 | return [] |
117 | }) |
118 | |
119 | exports.isUserMention = toKey(function (state, msg, key) { |
120 | var text = msg.content.text |
121 | if(isString(text) && text && Array.isArray(msg.content.mentions)) { |
122 | text = markdown.inline(text) |
123 | return msg.content.mentions.filter(function (mention) { |
124 | return state.users[mention.link] |
125 | }).map(function (mention) { |
126 | return { |
127 | author: msg.author, |
128 | target: state.users[mention.link], |
129 | text: surrounding(markdown.inline(text), '@'+mention.name, 100), |
130 | id: key, |
131 | type: 'user' |
132 | } |
133 | }) |
134 | } |
135 | }) |
136 | |
137 | exports.isUserFollow = toKey(function (state, msg, key) { |
138 | if(msg.content.type == 'contact' && msg.content.following && state.users[msg.content.contact]) |
139 | return { |
140 | author: msg.author, |
141 | target: state.users[msg.content.contact], |
142 | text: 'followed you', //this will make sense since the notification will be a direct message |
143 | id: key, |
144 | type: 'user' |
145 | } |
146 | }) |
147 | |
148 | exports.tests = [ |
149 | exports.isChannelPost, |
150 | exports.isChannelMention, |
151 | exports.isUserMention, |
152 | exports.isUserFollow |
153 | ] |
154 | |
155 | exports.match = function (state, msg) { |
156 | return exports.tests.reduce(function (found, test) { |
157 | return found.concat(test(state, msg) || []) |
158 | }, []) |
159 | |
160 | } |
161 | |
162 | exports.render = function (note, link) { |
163 | //personal mentions are just cluttered with the target and action |
164 | //since it's only shown to one person it's implied. |
165 | if(note.target[0] !== '#') |
166 | return note.author + ':' + note.text + (link ? (' ' + link) : '') |
167 | |
168 | return ( |
169 | [note.author, note.action, note.target].join(' ') + ': ' + |
170 | note.text + ' ' + |
171 | (link || '') |
172 | ) |
173 | } |
174 | |
175 | exports.link = function (id, config) { |
176 | return ((config && config.irc && config.irc.domain) || "http://viewer.scuttlebot.io") + '/' + encodeURIComponent(id) |
177 | } |
178 | |
179 | if(!module.parent) { |
180 | //this is just for testing... |
181 | require('ssb-client')(function (err, sbot) { |
182 | if(err) throw err |
183 | var state = init(sbot, process.argv[2] || sbot.id, function (err, state) { |
184 | |
185 | //XXX: properly persist state with a flumeview? |
186 | console.log(state) |
187 | pull( |
188 | sbot.createLogStream({live: true, old: false}), |
189 | pull.drain(function (msg) { |
190 | if(msg.sync) return |
191 | var a = exports.match(state, msg) |
192 | if(a.length) { |
193 | console.log(a) |
194 | } |
195 | }) |
196 | ) |
197 | }) |
198 | }) |
199 | } |
200 | |
201 | |
202 | |
203 |
Built with git-ssb-web