2019-07-28 20:49:01 +00:00
|
|
|
'use strict'
|
2019-08-15 01:10:22 +00:00
|
|
|
|
2019-11-13 04:24:19 +00:00
|
|
|
const debug = require('debug')('oasis:model-post')
|
2019-06-27 05:25:13 +00:00
|
|
|
const lodash = require('lodash')
|
2019-11-13 04:24:19 +00:00
|
|
|
const parallelMap = require('pull-paramap')
|
2019-06-28 20:55:05 +00:00
|
|
|
const prettyMs = require('pretty-ms')
|
2019-11-13 04:24:19 +00:00
|
|
|
const pull = require('pull-stream')
|
|
|
|
const { isRoot, isReply } = require('ssb-thread-schema')
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-11-13 04:24:19 +00:00
|
|
|
// HACK: https://github.com/ssbc/ssb-thread-schema/issues/4
|
|
|
|
const isNestedReply = require('ssb-thread-schema/post/nested-reply/validator')
|
2019-10-01 00:46:04 +00:00
|
|
|
|
2019-06-27 05:25:13 +00:00
|
|
|
const configure = require('./lib/configure')
|
2019-11-13 04:24:19 +00:00
|
|
|
const cooler = require('./lib/cooler')
|
2019-06-27 05:25:13 +00:00
|
|
|
const markdown = require('./lib/markdown')
|
|
|
|
|
2019-10-02 22:41:43 +00:00
|
|
|
const maxMessages = 128
|
|
|
|
|
2019-10-31 22:25:31 +00:00
|
|
|
const getMessages = async ({ myFeedId, customOptions, ssb, query, filter }) => {
|
2019-09-25 18:46:43 +00:00
|
|
|
const options = configure({ query, index: 'DTA' }, customOptions)
|
|
|
|
|
|
|
|
const source = await cooler.read(
|
|
|
|
ssb.backlinks.read, options
|
|
|
|
)
|
|
|
|
|
|
|
|
return new Promise((resolve, reject) => {
|
|
|
|
pull(
|
|
|
|
source,
|
|
|
|
pull.filter((msg) =>
|
|
|
|
typeof msg.value.content !== 'string' &&
|
|
|
|
msg.value.content.type === 'post' &&
|
2019-10-31 22:25:31 +00:00
|
|
|
(filter == null || filter(msg) === true)
|
2019-09-25 18:46:43 +00:00
|
|
|
),
|
2019-10-02 22:41:43 +00:00
|
|
|
pull.take(maxMessages),
|
2019-09-25 18:46:43 +00:00
|
|
|
pull.collect((err, collectedMessages) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err)
|
|
|
|
} else {
|
|
|
|
resolve(transform(ssb, collectedMessages, myFeedId))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
const transform = (ssb, messages, myFeedId) =>
|
|
|
|
Promise.all(messages.map(async (msg) => {
|
|
|
|
debug('transforming %s', msg.key)
|
2019-06-28 20:55:05 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
if (msg == null) {
|
|
|
|
return null
|
|
|
|
}
|
2019-06-28 20:55:05 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
lodash.set(msg, 'value.meta.md.block', () =>
|
|
|
|
markdown(msg.value.content.text, msg.value.content.mentions)
|
|
|
|
)
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
const filterQuery = {
|
|
|
|
$filter: {
|
|
|
|
dest: msg.key
|
|
|
|
}
|
2019-06-27 05:25:13 +00:00
|
|
|
}
|
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
const referenceStream = await cooler.read(ssb.backlinks.read, {
|
|
|
|
query: [filterQuery],
|
|
|
|
index: 'DTA', // use asserted timestamps
|
|
|
|
private: true,
|
|
|
|
meta: true
|
|
|
|
})
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
const rawVotes = await new Promise((resolve, reject) => {
|
|
|
|
pull(
|
|
|
|
referenceStream,
|
|
|
|
pull.filter((ref) => typeof ref.value.content !== 'string' &&
|
|
|
|
ref.value.content.type === 'vote' &&
|
|
|
|
ref.value.content.vote &&
|
|
|
|
typeof ref.value.content.vote.value === 'number' &&
|
|
|
|
ref.value.content.vote.value >= 0 &&
|
|
|
|
ref.value.content.vote.link === msg.key),
|
|
|
|
pull.collect((err, collectedMessages) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err)
|
|
|
|
} else {
|
|
|
|
resolve(collectedMessages)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
})
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
// { @key: 1, @key2: 0, @key3: 1 }
|
|
|
|
//
|
|
|
|
// only one vote per person!
|
|
|
|
const reducedVotes = rawVotes.reduce((acc, vote) => {
|
|
|
|
acc[vote.value.author] = vote.value.content.vote.value
|
|
|
|
return acc
|
|
|
|
}, {})
|
|
|
|
|
|
|
|
// gets *only* the people who voted 1
|
|
|
|
// [ @key, @key, @key ]
|
|
|
|
const voters = Object
|
|
|
|
.entries(reducedVotes)
|
|
|
|
.filter(([key, value]) => value === 1)
|
|
|
|
.map(([key]) => key)
|
|
|
|
|
|
|
|
const pendingName = cooler.get(
|
|
|
|
ssb.about.socialValue, {
|
|
|
|
key: 'name',
|
|
|
|
dest: msg.value.author
|
|
|
|
}
|
|
|
|
)
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
const pendingAvatarMsg = cooler.get(
|
|
|
|
ssb.about.socialValue, {
|
|
|
|
key: 'image',
|
|
|
|
dest: msg.value.author
|
|
|
|
}
|
|
|
|
)
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
const pending = [pendingName, pendingAvatarMsg]
|
|
|
|
const [name, avatarMsg] = await Promise.all(pending)
|
2019-07-03 18:03:10 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
const nullImage = `&${'0'.repeat(43)}=.sha256`
|
|
|
|
const avatarId = avatarMsg != null && typeof avatarMsg.link === 'string'
|
|
|
|
? avatarMsg.link || nullImage
|
|
|
|
: avatarMsg || nullImage
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
const avatarUrl = `/image/32/${encodeURIComponent(avatarId)}`
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
const ts = new Date(msg.value.timestamp)
|
|
|
|
let isoTs
|
2019-09-25 18:47:41 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
try {
|
|
|
|
isoTs = ts.toISOString()
|
|
|
|
} catch (e) {
|
|
|
|
// Just in case it's an invalid date. :(
|
|
|
|
debug(e)
|
|
|
|
const receivedTs = new Date(msg.timestamp)
|
|
|
|
isoTs = receivedTs.toISOString()
|
|
|
|
}
|
2019-09-25 18:47:41 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
lodash.set(msg, 'value.meta.timestamp.received.iso8601', isoTs)
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
const ago = Date.now() - Number(ts)
|
|
|
|
const prettyAgo = prettyMs(ago, { compact: true })
|
|
|
|
lodash.set(msg, 'value.meta.timestamp.received.since', prettyAgo)
|
|
|
|
lodash.set(msg, 'value.meta.author.name', name)
|
|
|
|
lodash.set(msg, 'value.meta.author.avatar', {
|
|
|
|
id: avatarId,
|
|
|
|
url: avatarUrl
|
|
|
|
})
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
lodash.set(msg, 'value.meta.votes', voters)
|
|
|
|
lodash.set(msg, 'value.meta.voted', voters.includes(myFeedId))
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-09-26 00:19:55 +00:00
|
|
|
return msg
|
|
|
|
}))
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-09-27 00:19:18 +00:00
|
|
|
const post = {
|
2019-06-27 05:25:13 +00:00
|
|
|
fromFeed: async (feedId, customOptions = {}) => {
|
|
|
|
const ssb = await cooler.connect()
|
|
|
|
|
2019-07-03 17:53:11 +00:00
|
|
|
const whoami = await cooler.get(ssb.whoami)
|
|
|
|
const myFeedId = whoami.id
|
|
|
|
|
|
|
|
const options = configure({ id: feedId }, customOptions)
|
2019-06-27 05:25:13 +00:00
|
|
|
const source = await cooler.read(
|
|
|
|
ssb.createUserStream,
|
|
|
|
options
|
|
|
|
)
|
|
|
|
|
|
|
|
const messages = await new Promise((resolve, reject) => {
|
|
|
|
pull(
|
|
|
|
source,
|
2019-08-15 01:10:22 +00:00
|
|
|
pull.filter((msg) => typeof msg.value.content !== 'string' &&
|
|
|
|
msg.value.content.type === 'post'),
|
2019-10-02 22:41:43 +00:00
|
|
|
pull.take(maxMessages),
|
2019-08-15 01:10:22 +00:00
|
|
|
pull.collect((err, collectedMessages) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err)
|
|
|
|
} else {
|
|
|
|
resolve(transform(ssb, collectedMessages, myFeedId))
|
|
|
|
}
|
2019-07-26 17:06:47 +00:00
|
|
|
})
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
return messages
|
|
|
|
},
|
|
|
|
mentionsMe: async (customOptions = {}) => {
|
|
|
|
const ssb = await cooler.connect()
|
|
|
|
|
|
|
|
const whoami = await cooler.get(ssb.whoami)
|
|
|
|
const myFeedId = whoami.id
|
|
|
|
|
|
|
|
const query = [{
|
|
|
|
$filter: {
|
|
|
|
dest: myFeedId
|
|
|
|
}
|
|
|
|
}]
|
|
|
|
|
2019-10-31 22:25:31 +00:00
|
|
|
const messages = await getMessages({
|
|
|
|
myFeedId,
|
|
|
|
customOptions,
|
|
|
|
ssb,
|
|
|
|
query,
|
|
|
|
filter: (msg) => msg.value.author !== myFeedId
|
|
|
|
})
|
2019-06-27 05:25:13 +00:00
|
|
|
|
|
|
|
return messages
|
|
|
|
},
|
|
|
|
fromHashtag: async (hashtag, customOptions = {}) => {
|
|
|
|
const ssb = await cooler.connect()
|
2019-07-03 17:53:11 +00:00
|
|
|
|
|
|
|
const whoami = await cooler.get(ssb.whoami)
|
|
|
|
const myFeedId = whoami.id
|
|
|
|
|
2019-07-26 16:48:41 +00:00
|
|
|
const query = [{
|
2019-06-27 05:25:13 +00:00
|
|
|
$filter: {
|
2019-08-15 01:10:22 +00:00
|
|
|
dest: `#${hashtag}`
|
2019-06-27 05:25:13 +00:00
|
|
|
}
|
2019-07-26 16:48:41 +00:00
|
|
|
}]
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-09-25 18:46:43 +00:00
|
|
|
const messages = await getMessages({ myFeedId, customOptions, ssb, query })
|
2019-06-27 05:25:13 +00:00
|
|
|
|
|
|
|
return messages
|
|
|
|
},
|
2019-10-01 00:37:30 +00:00
|
|
|
likes: async (customOptions = {}) => {
|
|
|
|
const ssb = await cooler.connect()
|
|
|
|
|
|
|
|
const whoami = await cooler.get(ssb.whoami)
|
|
|
|
const myFeedId = whoami.id
|
|
|
|
|
|
|
|
const query = {
|
|
|
|
$filter: {
|
|
|
|
value: {
|
|
|
|
author: myFeedId, // for some reason this `author` isn't being respected
|
|
|
|
content: {
|
|
|
|
type: 'vote'
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
const options = configure({
|
|
|
|
query,
|
|
|
|
index: 'DTA',
|
|
|
|
reverse: true
|
|
|
|
}, customOptions)
|
|
|
|
|
|
|
|
const source = await cooler.read(
|
|
|
|
ssb.query.read, options
|
|
|
|
)
|
|
|
|
|
|
|
|
const messages = await new Promise((resolve, reject) => {
|
|
|
|
pull(
|
|
|
|
source,
|
|
|
|
pull.filter((msg) => {
|
|
|
|
return typeof msg.value.content === 'object' &&
|
|
|
|
msg.value.author === myFeedId &&
|
|
|
|
typeof msg.value.content.vote === 'object' &&
|
|
|
|
typeof msg.value.content.vote.link === 'string'
|
|
|
|
}),
|
2019-10-02 22:41:43 +00:00
|
|
|
pull.take(maxMessages),
|
2019-10-01 00:46:04 +00:00
|
|
|
parallelMap(async (val, cb) => {
|
2019-10-01 00:37:30 +00:00
|
|
|
const msg = await post.get(val.value.content.vote.link)
|
|
|
|
cb(null, msg)
|
|
|
|
}),
|
|
|
|
pull.collect((err, collectedMessages) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err)
|
|
|
|
} else {
|
|
|
|
resolve(collectedMessages)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
return messages
|
|
|
|
},
|
2019-06-27 05:25:13 +00:00
|
|
|
latest: async (customOptions = {}) => {
|
|
|
|
const ssb = await cooler.connect()
|
2019-07-03 17:53:11 +00:00
|
|
|
|
|
|
|
const whoami = await cooler.get(ssb.whoami)
|
|
|
|
const myFeedId = whoami.id
|
|
|
|
|
2019-06-27 05:25:13 +00:00
|
|
|
const options = configure({
|
|
|
|
type: 'post',
|
2019-10-02 22:41:43 +00:00
|
|
|
limit: maxMessages
|
2019-06-27 05:25:13 +00:00
|
|
|
}, customOptions)
|
|
|
|
|
|
|
|
const source = await cooler.read(
|
|
|
|
ssb.messagesByType,
|
|
|
|
options
|
|
|
|
)
|
|
|
|
|
|
|
|
const messages = await new Promise((resolve, reject) => {
|
|
|
|
pull(
|
|
|
|
source,
|
2019-09-27 03:22:33 +00:00
|
|
|
pull.filter((message) => // avoid private messages (!)
|
|
|
|
typeof message.value.content !== 'string'
|
|
|
|
),
|
2019-08-15 01:10:22 +00:00
|
|
|
pull.collect((err, collectedMessages) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err)
|
|
|
|
} else {
|
|
|
|
resolve(transform(ssb, collectedMessages, myFeedId))
|
|
|
|
}
|
2019-06-27 05:25:13 +00:00
|
|
|
})
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
return messages
|
|
|
|
},
|
|
|
|
fromThread: async (msgId, customOptions) => {
|
2019-06-29 21:14:09 +00:00
|
|
|
debug('thread: %s', msgId)
|
2019-06-27 05:25:13 +00:00
|
|
|
const ssb = await cooler.connect()
|
2019-07-03 17:53:11 +00:00
|
|
|
|
|
|
|
const whoami = await cooler.get(ssb.whoami)
|
|
|
|
const myFeedId = whoami.id
|
|
|
|
|
2019-06-27 05:25:13 +00:00
|
|
|
const options = configure({ id: msgId }, customOptions)
|
|
|
|
const rawMsg = await cooler.get(ssb.get, options)
|
2019-06-29 21:14:09 +00:00
|
|
|
debug('got raw message')
|
2019-06-27 05:25:13 +00:00
|
|
|
|
|
|
|
const parents = []
|
|
|
|
|
2019-07-26 16:58:28 +00:00
|
|
|
const getRootAncestor = (msg) => new Promise((resolve, reject) => {
|
2019-08-07 14:47:21 +00:00
|
|
|
if (msg.key == null) {
|
|
|
|
debug('something is very wrong, we used `{ meta: true }`')
|
2019-08-15 01:10:22 +00:00
|
|
|
resolve(parents)
|
|
|
|
} else {
|
|
|
|
debug('getting root ancestor of %s', msg.key)
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-08-15 01:10:22 +00:00
|
|
|
if (typeof msg.value.content === 'string') {
|
|
|
|
debug('private message')
|
|
|
|
// Private message we can't decrypt, stop looking for parents.
|
|
|
|
resolve(parents)
|
2019-06-29 21:14:09 +00:00
|
|
|
}
|
2019-08-15 01:10:22 +00:00
|
|
|
|
2019-09-28 22:53:23 +00:00
|
|
|
if (msg.value.content.type !== 'post') {
|
2019-11-13 04:24:19 +00:00
|
|
|
debug('not a post')
|
2019-09-28 22:53:23 +00:00
|
|
|
resolve(msg)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (isNestedReply(msg)) {
|
2019-08-15 01:10:22 +00:00
|
|
|
debug('fork, get the parent')
|
|
|
|
try {
|
|
|
|
// It's a message reply, get the parent!
|
|
|
|
cooler.get(ssb.get, {
|
|
|
|
id: msg.value.content.fork,
|
|
|
|
meta: true,
|
|
|
|
private: true
|
|
|
|
}).then((fork) => {
|
|
|
|
resolve(getRootAncestor(fork))
|
|
|
|
}).catch(reject)
|
|
|
|
} catch (e) {
|
|
|
|
debug(e)
|
|
|
|
resolve(msg)
|
|
|
|
}
|
2019-09-28 22:53:23 +00:00
|
|
|
} else if (isReply(msg)) {
|
2019-08-15 01:10:22 +00:00
|
|
|
debug('thread reply: %s', msg.value.content.root)
|
|
|
|
try {
|
|
|
|
// It's a thread reply, get the parent!
|
|
|
|
cooler.get(ssb.get, {
|
|
|
|
id: msg.value.content.root,
|
|
|
|
meta: true,
|
|
|
|
private: true
|
|
|
|
}).then((root) => {
|
|
|
|
resolve(getRootAncestor(root))
|
|
|
|
}).catch(reject)
|
|
|
|
} catch (e) {
|
|
|
|
debug(e)
|
|
|
|
resolve(msg)
|
|
|
|
}
|
2019-09-28 22:53:23 +00:00
|
|
|
} else if (isRoot(msg)) {
|
2019-08-15 01:10:22 +00:00
|
|
|
debug('got root ancestor')
|
2019-06-29 21:14:09 +00:00
|
|
|
resolve(msg)
|
2019-09-28 22:53:23 +00:00
|
|
|
} else {
|
|
|
|
// type !== "post", probably
|
|
|
|
// this should show up as JSON
|
2019-11-13 04:24:19 +00:00
|
|
|
debug('got mysterious root ancestor that fails all known schemas')
|
|
|
|
debug('%O', msg)
|
2019-09-28 22:53:23 +00:00
|
|
|
resolve(msg)
|
2019-06-29 21:14:09 +00:00
|
|
|
}
|
2019-06-27 05:25:13 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
|
2019-07-26 16:58:28 +00:00
|
|
|
const getReplies = (key) => new Promise((resolve, reject) => {
|
2019-08-15 01:10:22 +00:00
|
|
|
const filterQuery = {
|
2019-06-28 20:55:05 +00:00
|
|
|
$filter: {
|
|
|
|
dest: key
|
|
|
|
}
|
2019-06-27 05:25:13 +00:00
|
|
|
}
|
|
|
|
|
2019-07-26 16:58:28 +00:00
|
|
|
cooler.read(ssb.backlinks.read, {
|
2019-06-28 20:55:05 +00:00
|
|
|
query: [filterQuery],
|
|
|
|
index: 'DTA' // use asserted timestamps
|
2019-08-15 01:10:22 +00:00
|
|
|
}).then((referenceStream) => {
|
2019-07-26 16:58:28 +00:00
|
|
|
pull(
|
|
|
|
referenceStream,
|
2019-08-15 01:10:22 +00:00
|
|
|
pull.filter((msg) => {
|
2019-07-26 16:58:28 +00:00
|
|
|
const isPost = lodash.get(msg, 'value.content.type') === 'post'
|
|
|
|
if (isPost === false) {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
const root = lodash.get(msg, 'value.content.root')
|
|
|
|
const fork = lodash.get(msg, 'value.content.fork')
|
|
|
|
|
|
|
|
if (root !== key && fork !== key) {
|
|
|
|
// mention
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
if (fork === key) {
|
|
|
|
// not a reply to this post
|
|
|
|
// it's a reply *to a reply* of this post
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
|
|
|
return true
|
|
|
|
}),
|
|
|
|
pull.collect((err, messages) => {
|
2019-08-15 01:10:22 +00:00
|
|
|
if (err) {
|
|
|
|
reject(err)
|
|
|
|
} else {
|
|
|
|
resolve(messages || undefined)
|
|
|
|
}
|
2019-07-26 16:58:28 +00:00
|
|
|
})
|
|
|
|
)
|
|
|
|
}).catch(reject)
|
2019-06-28 20:55:05 +00:00
|
|
|
})
|
|
|
|
|
|
|
|
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/flat
|
2019-08-15 01:10:22 +00:00
|
|
|
const flattenDeep = (arr1) => arr1.reduce(
|
2019-09-26 00:19:55 +00:00
|
|
|
(acc, val) => (Array.isArray(val)
|
|
|
|
? acc.concat(flattenDeep(val))
|
|
|
|
: acc.concat(val)
|
|
|
|
),
|
|
|
|
[]
|
2019-08-15 01:10:22 +00:00
|
|
|
)
|
2019-06-28 20:55:05 +00:00
|
|
|
|
2019-07-26 16:58:28 +00:00
|
|
|
const getDeepReplies = (key) => new Promise((resolve, reject) => {
|
2019-06-28 20:55:05 +00:00
|
|
|
const oneDeeper = async (replyKey, depth) => {
|
|
|
|
const replies = await getReplies(replyKey)
|
2019-08-15 01:10:22 +00:00
|
|
|
debug('replies', replies.map((m) => m.key))
|
2019-06-28 20:55:05 +00:00
|
|
|
|
|
|
|
debug('found %s replies for %s', replies.length, replyKey)
|
|
|
|
|
|
|
|
if (replies.length === 0) {
|
|
|
|
return replies
|
|
|
|
}
|
2019-08-15 01:10:22 +00:00
|
|
|
return Promise.all(replies.map(async (reply) => {
|
|
|
|
const deeperReplies = await oneDeeper(reply.key, depth + 1)
|
|
|
|
lodash.set(reply, 'value.meta.thread.depth', depth)
|
2019-09-27 18:40:00 +00:00
|
|
|
lodash.set(reply, 'value.meta.thread.reply', true)
|
2019-08-15 01:10:22 +00:00
|
|
|
return [reply, deeperReplies]
|
|
|
|
}))
|
2019-06-28 20:55:05 +00:00
|
|
|
}
|
2019-09-27 18:40:00 +00:00
|
|
|
oneDeeper(key, 0).then((nested) => {
|
2019-07-26 16:58:28 +00:00
|
|
|
const nestedReplies = [...nested]
|
|
|
|
const deepReplies = flattenDeep(nestedReplies)
|
|
|
|
resolve(deepReplies)
|
|
|
|
}).catch(reject)
|
2019-06-28 20:55:05 +00:00
|
|
|
})
|
|
|
|
|
2019-06-29 21:14:09 +00:00
|
|
|
debug('about to get root ancestor')
|
2019-06-28 20:55:05 +00:00
|
|
|
const rootAncestor = await getRootAncestor(rawMsg)
|
2019-06-29 21:14:09 +00:00
|
|
|
debug('got root ancestors')
|
2019-06-28 20:55:05 +00:00
|
|
|
const deepReplies = await getDeepReplies(rootAncestor.key)
|
2019-06-29 21:14:09 +00:00
|
|
|
debug('got deep replies')
|
2019-06-28 20:55:05 +00:00
|
|
|
|
2019-08-15 01:10:22 +00:00
|
|
|
const allMessages = [rootAncestor, ...deepReplies].map((message) => {
|
2019-06-29 19:06:45 +00:00
|
|
|
const isThreadTarget = message.key === msgId
|
|
|
|
lodash.set(message, 'value.meta.thread.target', isThreadTarget)
|
|
|
|
return message
|
|
|
|
})
|
2019-06-27 05:25:13 +00:00
|
|
|
|
2019-07-03 17:53:11 +00:00
|
|
|
const transformed = await transform(ssb, allMessages, myFeedId)
|
2019-06-28 20:55:05 +00:00
|
|
|
return transformed
|
2019-08-07 14:47:21 +00:00
|
|
|
},
|
|
|
|
get: async (msgId, customOptions) => {
|
|
|
|
debug('get: %s', msgId)
|
|
|
|
const ssb = await cooler.connect()
|
|
|
|
|
|
|
|
const whoami = await cooler.get(ssb.whoami)
|
|
|
|
const myFeedId = whoami.id
|
|
|
|
|
|
|
|
const options = configure({ id: msgId }, customOptions)
|
|
|
|
const rawMsg = await cooler.get(ssb.get, options)
|
|
|
|
debug('got raw message')
|
|
|
|
|
|
|
|
const transformed = await transform(ssb, [rawMsg], myFeedId)
|
|
|
|
debug('transformed: %O', transformed)
|
|
|
|
return transformed[0]
|
|
|
|
},
|
|
|
|
publish: async (options) => {
|
|
|
|
const ssb = await cooler.connect()
|
|
|
|
const body = { type: 'post', ...options }
|
|
|
|
|
2019-08-14 00:45:14 +00:00
|
|
|
debug('Published: %O', body)
|
2019-08-07 14:47:21 +00:00
|
|
|
return cooler.get(ssb.publish, body)
|
2019-09-27 00:19:18 +00:00
|
|
|
},
|
|
|
|
reply: async ({ parent, message }) => {
|
2019-09-28 22:53:23 +00:00
|
|
|
message.root = parent.key
|
|
|
|
message.fork = lodash.get(parent, 'value.content.root')
|
|
|
|
message.branch = await post.branch({ root: parent.key })
|
|
|
|
message.type = 'post' // redundant but used for validation
|
|
|
|
|
|
|
|
if (isNestedReply(message) !== true) {
|
|
|
|
const messageString = JSON.stringify(message, null, 2)
|
|
|
|
throw new Error(`message should be valid reply: ${messageString}`)
|
|
|
|
}
|
2019-09-27 00:19:18 +00:00
|
|
|
|
|
|
|
return post.publish(message)
|
|
|
|
},
|
|
|
|
replyAll: async ({ parent, message }) => {
|
2019-09-28 22:53:23 +00:00
|
|
|
const fork = parent.key
|
|
|
|
const root = lodash.get(parent, 'value.content.root', parent.key)
|
|
|
|
message.root = fork || root
|
2019-09-27 06:36:47 +00:00
|
|
|
message.branch = await post.branch({ root: parent.key })
|
2019-09-28 22:53:23 +00:00
|
|
|
message.type = 'post' // redundant but used for validation
|
|
|
|
|
|
|
|
if (isReply(message) !== true) {
|
|
|
|
const messageString = JSON.stringify(message, null, 2)
|
|
|
|
throw new Error(`message should be valid replyAll: ${messageString}`)
|
|
|
|
}
|
2019-09-27 00:19:18 +00:00
|
|
|
|
|
|
|
return post.publish(message)
|
2019-09-27 06:36:47 +00:00
|
|
|
},
|
|
|
|
branch: async ({ root }) => {
|
|
|
|
const ssb = await cooler.connect()
|
|
|
|
const keys = await cooler.get(ssb.tangle.branch, root)
|
|
|
|
|
|
|
|
return Promise.all(keys
|
|
|
|
.map((key) => post.get(key))
|
|
|
|
.filter((message) => lodash.get(message, 'value.content.type') === 'post')
|
|
|
|
)
|
2019-10-03 19:39:22 +00:00
|
|
|
},
|
|
|
|
inbox: async (customOptions = {}) => {
|
|
|
|
const ssb = await cooler.connect()
|
|
|
|
|
|
|
|
const whoami = await cooler.get(ssb.whoami)
|
|
|
|
const myFeedId = whoami.id
|
|
|
|
|
|
|
|
const options = configure({
|
2019-11-13 04:24:19 +00:00
|
|
|
type: 'post'
|
2019-10-03 19:39:22 +00:00
|
|
|
}, customOptions)
|
|
|
|
|
|
|
|
const source = await cooler.read(
|
|
|
|
ssb.messagesByType,
|
|
|
|
options
|
|
|
|
)
|
|
|
|
|
|
|
|
const messages = await new Promise((resolve, reject) => {
|
|
|
|
pull(
|
|
|
|
source,
|
|
|
|
pull.filter((message) => // avoid private messages (!)
|
|
|
|
typeof message.value.content !== 'string' &&
|
|
|
|
lodash.get(message, 'value.meta.private')
|
|
|
|
),
|
|
|
|
pull.unique((message) => {
|
|
|
|
const { root } = message.value.content
|
|
|
|
if (root == null) {
|
|
|
|
return message.key
|
|
|
|
} else {
|
|
|
|
return root
|
|
|
|
}
|
|
|
|
}),
|
|
|
|
pull.take(maxMessages),
|
|
|
|
pull.collect((err, collectedMessages) => {
|
|
|
|
if (err) {
|
|
|
|
reject(err)
|
|
|
|
} else {
|
|
|
|
resolve(transform(ssb, collectedMessages, myFeedId))
|
|
|
|
}
|
|
|
|
})
|
|
|
|
)
|
|
|
|
})
|
|
|
|
|
|
|
|
return messages
|
2019-06-27 05:25:13 +00:00
|
|
|
}
|
|
|
|
}
|
2019-09-27 00:19:18 +00:00
|
|
|
|
|
|
|
module.exports = post
|