git ssb

0+

Dominic / ssb-irc



Tree: ca83e81efcab58179c5f51dcb99d20ee51c645b8

Files: ca83e81efcab58179c5f51dcb99d20ee51c645b8 / ssb.js

5338 bytesRaw
1var pull = require('pull-stream')
2var ref = require('ssb-ref')
3var hash = require('ssb-keys/util').hash
4var markdown = require('ssb-markdown')
5
6function isString (s) {
7 return 'string' == typeof s
8}
9
10function 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
23function 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
44exports.init = init
45
46function 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
59exports.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
80function 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
87function getChannel(state, name) {
88 name = name.replace(/^#/,'')
89 return state.channels[name] === true ? name : state.channels[name]
90}
91
92function 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
97exports.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
119exports.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
137exports.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
148exports.tests = [
149 exports.isChannelPost,
150 exports.isChannelMention,
151 exports.isUserMention,
152 exports.isUserFollow
153]
154
155exports.match = function (state, msg) {
156 return exports.tests.reduce(function (found, test) {
157 return found.concat(test(state, msg) || [])
158 }, [])
159
160}
161
162exports.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
175exports.link = function (id, config) {
176 return ((config && config.irc && config.irc.domain) || "http://viewer.scuttlebot.io") + '/' + encodeURIComponent(id)
177}
178
179if(!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