Access graph directly to process friendships way faster and easier
This commit is contained in:
parent
850147ea2f
commit
8211a8cb78
|
@ -0,0 +1,145 @@
|
|||
// Monkeypatched to add getGraph
|
||||
|
||||
"use strict";
|
||||
var LayeredGraph = require("layered-graph");
|
||||
var pull = require("pull-stream");
|
||||
var isFeed = require("ssb-ref").isFeed;
|
||||
// friends plugin
|
||||
// methods to analyze the social graph
|
||||
// maintains a 'follow' and 'flag' graph
|
||||
|
||||
exports.name = "friends";
|
||||
exports.version = "1.0.0";
|
||||
exports.manifest = {
|
||||
hopStream: "source",
|
||||
onEdge: "sync",
|
||||
isFollowing: "async",
|
||||
isBlocking: "async",
|
||||
getGraph: "async",
|
||||
hops: "async",
|
||||
help: "sync",
|
||||
// createLayer: 'sync', // not exposed over RPC as returns a function
|
||||
get: "async", // legacy
|
||||
createFriendStream: "source", // legacy
|
||||
stream: "source", // legacy
|
||||
};
|
||||
|
||||
//mdm.manifest(apidoc)
|
||||
|
||||
exports.init = function (sbot, config) {
|
||||
var max =
|
||||
(config.friends && config.friends.hops) ||
|
||||
(config.replicate && config.replicate.hops) ||
|
||||
3;
|
||||
var layered = LayeredGraph({ max: max, start: sbot.id });
|
||||
|
||||
function getGraph(cb) {
|
||||
layered.onReady(function () {
|
||||
var g = layered.getGraph();
|
||||
cb(null, g);
|
||||
});
|
||||
}
|
||||
|
||||
function isFollowing(opts, cb) {
|
||||
layered.onReady(function () {
|
||||
var g = layered.getGraph();
|
||||
cb(null, g[opts.source] ? g[opts.source][opts.dest] >= 0 : false);
|
||||
});
|
||||
}
|
||||
|
||||
function isBlocking(opts, cb) {
|
||||
layered.onReady(function () {
|
||||
var g = layered.getGraph();
|
||||
cb(null, Math.round(g[opts.source] && g[opts.source][opts.dest]) == -1);
|
||||
});
|
||||
}
|
||||
|
||||
//opinion: do not authorize peers blocked by this node.
|
||||
sbot.auth.hook(function (fn, args) {
|
||||
var self = this;
|
||||
isBlocking({ source: sbot.id, dest: args[0] }, function (err, blocked) {
|
||||
if (blocked) args[1](new Error("client is blocked"));
|
||||
else fn.apply(self, args);
|
||||
});
|
||||
});
|
||||
|
||||
if (!sbot.replicate)
|
||||
throw new Error("ssb-friends expects a replicate plugin to be available");
|
||||
|
||||
// opinion: replicate with everyone within max hops (max passed to layered above ^)
|
||||
pull(
|
||||
layered.hopStream({ live: true, old: true }),
|
||||
pull.drain(function (data) {
|
||||
if (data.sync) return;
|
||||
for (var k in data) {
|
||||
sbot.replicate.request(k, data[k] >= 0);
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
require("ssb-friends/contacts")(sbot, layered.createLayer, config);
|
||||
|
||||
var legacy = require("ssb-friends/legacy")(layered);
|
||||
|
||||
//opinion: pass the blocks to replicate.block
|
||||
setImmediate(function () {
|
||||
var block =
|
||||
(sbot.replicate && sbot.replicate.block) || (sbot.ebt && sbot.ebt.block);
|
||||
if (block) {
|
||||
function handleBlockUnlock(from, to, value) {
|
||||
if (value === false) block(from, to, true);
|
||||
else block(from, to, false);
|
||||
}
|
||||
pull(
|
||||
legacy.stream({ live: true }),
|
||||
pull.drain(function (contacts) {
|
||||
if (!contacts) return;
|
||||
|
||||
if (isFeed(contacts.from) && isFeed(contacts.to)) {
|
||||
// live data
|
||||
handleBlockUnlock(contacts.from, contacts.to, contacts.value);
|
||||
} else {
|
||||
// initial data
|
||||
for (var from in contacts) {
|
||||
var relations = contacts[from];
|
||||
for (var to in relations)
|
||||
handleBlockUnlock(from, to, relations[to]);
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
hopStream: layered.hopStream,
|
||||
onEdge: layered.onEdge,
|
||||
isFollowing: isFollowing,
|
||||
isBlocking: isBlocking,
|
||||
getGraph: getGraph,
|
||||
|
||||
// expose createLayer, so that other plugins may express relationships
|
||||
createLayer: layered.createLayer,
|
||||
|
||||
// legacy, debugging
|
||||
hops: function (opts, cb) {
|
||||
layered.onReady(function () {
|
||||
if (isFunction(opts)) (cb = opts), (opts = {});
|
||||
cb(null, layered.getHops(opts));
|
||||
});
|
||||
},
|
||||
help: function () {
|
||||
return require("ssb-friends/help");
|
||||
},
|
||||
// legacy
|
||||
get: legacy.get,
|
||||
createFriendStream: legacy.createFriendStream,
|
||||
stream: legacy.stream,
|
||||
};
|
||||
};
|
||||
|
||||
// helpers
|
||||
|
||||
function isFunction(f) {
|
||||
return "function" === typeof f;
|
||||
}
|
|
@ -207,94 +207,41 @@ const searchPeople = async (ssbServer, search) => {
|
|||
const getFriends = async (ssbServer, profile) => {
|
||||
debugFriends("Fetching");
|
||||
|
||||
let contacts = await promisePull(
|
||||
// @ts-ignore
|
||||
cat([
|
||||
ssbServer.query.read({
|
||||
reverse: true,
|
||||
query: [
|
||||
{
|
||||
$filter: {
|
||||
value: {
|
||||
author: profile.id,
|
||||
content: {
|
||||
type: "contact",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
limit: 100,
|
||||
}),
|
||||
ssbServer.query.read({
|
||||
reverse: true,
|
||||
query: [
|
||||
{
|
||||
$filter: {
|
||||
value: {
|
||||
content: {
|
||||
type: "contact",
|
||||
contact: profile.id,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
limit: 100,
|
||||
}),
|
||||
])
|
||||
).then(mapValues);
|
||||
let graph = await ssbServer.friends.getGraph();
|
||||
|
||||
let network = {};
|
||||
let requestRejections = [];
|
||||
for (let contact of contacts.reverse()) {
|
||||
if (contact.content.following) {
|
||||
network[contact.author] = network[contact.author] || {};
|
||||
network[contact.author][contact.content.contact] = true;
|
||||
} else {
|
||||
// contact.content.blocking or contact.content.flagged or !contact.content.following
|
||||
if (contact.author == profile.id && contact.content.following === false) {
|
||||
requestRejections.push(contact.content.contact);
|
||||
}
|
||||
|
||||
if (network[contact.author])
|
||||
delete network[contact.author][contact.content.contact];
|
||||
let connections = {};
|
||||
for (let key in graph) {
|
||||
let isFollowing = graph[profile.id][key] > 0;
|
||||
let isFollowingBack = graph[key] && graph[key][profile.id] > 0;
|
||||
if (isFollowing && isFollowingBack) {
|
||||
connections[key] = "friends";
|
||||
} else if (isFollowing && !isFollowingBack) {
|
||||
connections[key] = "requestsSent";
|
||||
} else if (!isFollowing && isFollowingBack) {
|
||||
if (graph[profile.id][key] === undefined)
|
||||
connections[key] = "requestsReceived";
|
||||
}
|
||||
}
|
||||
|
||||
let friends = [];
|
||||
let requestsSent = [];
|
||||
let requestsReceived = [];
|
||||
|
||||
const unique = (x) => Array.from(new Set(x));
|
||||
const allIds = unique(
|
||||
Object.keys(network).concat(Object.keys(network[profile.id]))
|
||||
);
|
||||
const profilesList = await Promise.all(
|
||||
allIds.map((id) => getProfile(ssbServer, id))
|
||||
Object.keys(connections).map((id) => getProfile(ssbServer, id))
|
||||
);
|
||||
const profilesHash = profilesList.reduce((hash, profile) => {
|
||||
hash[profile.id] = profile;
|
||||
return hash;
|
||||
}, {});
|
||||
|
||||
for (let key of allIds) {
|
||||
if (key == profile.id) continue;
|
||||
|
||||
let isFollowing = network[profile.id][key];
|
||||
let isFollowingBack = network[key] && network[key][profile.id];
|
||||
if (isFollowing && isFollowingBack) {
|
||||
friends.push(profilesHash[key]);
|
||||
} else if (isFollowing && !isFollowingBack) {
|
||||
requestsSent.push(profilesHash[key]);
|
||||
} else if (!isFollowing && isFollowingBack) {
|
||||
if (!requestRejections.includes(key))
|
||||
requestsReceived.push(profilesHash[key]);
|
||||
}
|
||||
let result = {
|
||||
friends: [],
|
||||
requestsSent: [],
|
||||
requestsReceived: [],
|
||||
};
|
||||
for (let key in connections) {
|
||||
result[connections[key]].push(profilesHash[key]);
|
||||
}
|
||||
|
||||
debugFriends("Done");
|
||||
return { friends, requestsSent, requestsReceived };
|
||||
return result;
|
||||
};
|
||||
|
||||
const getFriendshipStatus = async (ssbServer, source, dest) => {
|
||||
|
|
|
@ -23,7 +23,7 @@ Server.use(require("ssb-master"))
|
|||
.use(require("ssb-about"))
|
||||
.use(require("ssb-contacts"))
|
||||
.use(require("ssb-invite"))
|
||||
.use(require("ssb-friends"))
|
||||
.use(require("./monkeypatch/ssb-friends"))
|
||||
.use(require("ssb-query"))
|
||||
.use(require("ssb-device-address"))
|
||||
.use(require("./monkeypatch/ssb-identities"))
|
||||
|
|
Loading…
Reference in New Issue