Files: 387dcd7ac040579bb840df3e94bfd3bf9ab361b2 / ssb-server-ticktack.js
6461 bytesRaw
1 | const FlumeView = require('flumeview-level') |
2 | const get = require('lodash/get') |
3 | const pull = require('pull-stream') |
4 | const defer = require('pull-defer') |
5 | const isBlog = require('scuttle-blog/isBlog') |
6 | const { isMsg: isMsgRef } = require('ssb-ref') |
7 | |
8 | const getType = (msg) => get(msg, 'value.content.type') |
9 | const getAuthor = (msg) => get(msg, 'value.author') |
10 | const getCommentRoot = (msg) => get(msg, 'value.content.root') |
11 | const getLikeRoot = (msg) => get(msg, 'value.content.vote.link') |
12 | const getShareRoot = (msg) => get(msg, 'value.content.share.link') |
13 | const getTimestamp = (msg) => get(msg, 'value.timestamp') |
14 | |
15 | const FLUME_VIEW_VERSION = 1 |
16 | const MIN_LENGTH_FOR_BLOG_POST = 2500 // note this defn duplicated in blog.sync.isBlog |
17 | |
18 | module.exports = { |
19 | name: 'ticktack', |
20 | version: 1, |
21 | manifest: { |
22 | get: 'async', |
23 | read: 'source', |
24 | readBlogs: 'source', |
25 | getBlogs: 'async', |
26 | readComments: 'source', |
27 | readAllComments: 'source', |
28 | readAllLikes: 'source', |
29 | readAllShares: 'source', |
30 | readLikes: 'source' |
31 | }, |
32 | init: (server, config) => { |
33 | // NOTE - this could and should be refactored to use e.g. ssb-query |
34 | // because ssb-query I think can provide much of this functionality |
35 | // and then I can provide better stepping tools for pull-streams, like pull-next-query |
36 | console.log('> initialising ticktack plugin') |
37 | const myKey = server.keys.id |
38 | |
39 | const view = server._flumeUse( |
40 | 'ticktack', |
41 | FlumeView(FLUME_VIEW_VERSION, map) |
42 | ) |
43 | |
44 | return { |
45 | get: view.get, |
46 | read: view.read, |
47 | readBlogs, |
48 | getBlogs, |
49 | readComments, |
50 | readAllComments, |
51 | readAllLikes, |
52 | readAllShares, |
53 | readLikes |
54 | // readShares |
55 | } |
56 | |
57 | function map (msg, seq) { |
58 | var root |
59 | |
60 | switch (getType(msg)) { |
61 | case 'blog': |
62 | if (isBlog(msg) && isMyMsg(msg)) return [['B', msg.key, getTimestamp(msg)]] |
63 | else return [] |
64 | |
65 | case 'vote': |
66 | root = getLikeRoot(msg) |
67 | // TODO figure out how to only store likes I care about |
68 | if (root) return [['L', root, getTimestamp(msg)]] |
69 | else return [] |
70 | |
71 | // Note this catches: |
72 | // - all likes, on all things D: |
73 | // - likes AND unlikes |
74 | |
75 | case 'post': |
76 | root = getCommentRoot(msg) |
77 | // TODO figure out how to only store comments I care about |
78 | if (!root && isMyMsg(msg) && isPlog(msg)) return [['B', msg.key, getTimestamp(msg)]] |
79 | else if (root) return [['C', root, getTimestamp(msg)]] |
80 | else return [] |
81 | |
82 | // Note this catches: |
83 | // - all comments, on all things D: |
84 | |
85 | default: |
86 | return [] |
87 | } |
88 | } |
89 | |
90 | function readBlogs (options = {}) { |
91 | const query = Object.assign({}, { |
92 | gte: ['B', null, null], |
93 | // null is the 'minimum' structure in bytewise ordering |
94 | lte: ['B', undefined, undefined], |
95 | reverse: true, |
96 | values: true, |
97 | keys: false, |
98 | seqs: false |
99 | }, options) |
100 | |
101 | return view.read(query) |
102 | } |
103 | |
104 | function getBlogs (options, cb) { |
105 | if (typeof options === 'function') { |
106 | cb = options |
107 | options = {} |
108 | } |
109 | |
110 | pull( |
111 | readBlogs(options), |
112 | pull.collect(cb) |
113 | ) |
114 | } |
115 | |
116 | function readComments (blog, options = {}) { |
117 | var key = getBlogKey(blog) |
118 | |
119 | const query = Object.assign({}, { |
120 | gt: ['C', key, null], |
121 | lt: ['C', key, undefined], |
122 | // undefined is the 'maximum' structure in bytewise ordering https://www.npmjs.com/package/bytewise#order-of-supported-structures |
123 | reverse: true, |
124 | values: true, |
125 | keys: false, |
126 | seqs: false |
127 | }, options) |
128 | |
129 | return view.read(query) |
130 | } |
131 | |
132 | function readLikes (blog, options = {}) { |
133 | var key = getBlogKey(blog) |
134 | |
135 | const query = Object.assign({}, { |
136 | gt: ['L', key, null], |
137 | lt: ['L', key, undefined], |
138 | reverse: true, |
139 | values: true, |
140 | keys: false, |
141 | seqs: false |
142 | }, options) |
143 | |
144 | return view.read(query) |
145 | } |
146 | |
147 | function readAllComments (opts = {}) { |
148 | return readAllSource({ |
149 | type: 'post', |
150 | makeFilter: blogIds => msg => { |
151 | if (getAuthor(msg) === myKey) return false // exclude my posts |
152 | if (getCommentRoot(msg) === undefined) return false // want only 'comments' (reply posts) |
153 | // NOTE - this one will get nested replies too |
154 | |
155 | return blogIds.includes(getCommentRoot(msg)) // is about one of my blogs |
156 | }, |
157 | opts |
158 | }) |
159 | } |
160 | |
161 | function readAllLikes (opts = {}) { |
162 | return readAllSource({ |
163 | type: 'vote', |
164 | makeFilter: blogIds => msg => { |
165 | if (getAuthor(msg) === myKey) return false // exclude my likes |
166 | |
167 | return blogIds.includes(getLikeRoot(msg)) // is about one of my blogs |
168 | }, |
169 | opts |
170 | }) |
171 | } |
172 | |
173 | function readAllShares (opts = {}) { |
174 | return readAllSource({ |
175 | type: 'share', |
176 | makeFilter: (blogIds) => msg => { |
177 | if (getAuthor(msg) === myKey) return false // exclude my shares |
178 | |
179 | return blogIds.includes(getShareRoot(msg)) // is about one of my blogs |
180 | }, |
181 | opts |
182 | }) |
183 | } |
184 | |
185 | function readAllSource ({ type, makeFilter, opts = {} }) { |
186 | var source = defer.source() |
187 | |
188 | getBlogs({ keys: true, values: false }, (err, data) => { |
189 | if (err) throw err |
190 | |
191 | const blogIds = data.map(d => d[1]) |
192 | |
193 | opts.type = type |
194 | var limit = opts.limit |
195 | delete opts.limit |
196 | // have to remove limit from the query otherwise Next stalls out if it doesn't get a new result |
197 | |
198 | const _source = pull( |
199 | server.messagesByType(opts), // TODO - check/ note why I didn't use e.g. readComments |
200 | pull.filter(makeFilter(blogIds)), |
201 | limit ? pull.take(limit) : true |
202 | ) |
203 | |
204 | source.resolve(_source) |
205 | }) |
206 | |
207 | return source |
208 | } |
209 | |
210 | function isMyMsg (msg) { |
211 | return getAuthor(msg) === myKey |
212 | } |
213 | } |
214 | } |
215 | |
216 | function getBlogKey (blog) { |
217 | if (isMsgRef(blog)) return blog |
218 | // else if (isMsgRef(blog.key) && isBlog(blog)) return blog.key |
219 | else if (isMsgRef(blog.key) && (isBlog(blog) || isPlog(blog))) return blog.key |
220 | } |
221 | |
222 | // a Plog is a Blog shaped Post! |
223 | function isPlog (msg) { |
224 | // if (get(msg, 'value.content.text', '').length >= MIN_LENGTH_FOR_BLOG_POST) console.log(get(msg, 'value.content.text', '').length) |
225 | return get(msg, 'value.content.text', '').length >= MIN_LENGTH_FOR_BLOG_POST |
226 | } |
227 |
Built with git-ssb-web