A cute feature of Project Moon Hermit is its debug mode. If you pass a -d
to the moonhermit
binary invocation, it will output some information about all the muxrpc calls its making.
To make the video above, I just changed the shebang/hashpling line at the top to include -d
.
Now, you can see every muxrpc call.
This script could be patched to be an interactive script prompting the user if they want to unfollow each pub. Or it could simply unfollow all of them (crude, I know) and hope for the best.
There is no other tool to do quick SSB scripts. The only comparable alternative is firing NodeJS, but that will require some boilerplate code to load keys, start a client connected to the running server, etc... moon hermit is a binary requiring no boilerplate. Lua is an easier language to learn than JS.
If you want to help Project Moon Hermit development, please join my ko-fi.
PS: Some of the Lua scripts included in the repository use third-party Lua libraries that can be installed using luarocks.
PPS: At the moment Moon Hermit relies on some local Lua files. I'll find a way to embed them in the binary at some point in the future, right now you need to keep them next to the binary.
Find all pubs you follow
Today I decided to tackle a task that is quite difficult to do in most SSB clients. Figuring out which pubs you're following.
During the development of this script, I found some bugs in moonhermit.c which have been fixed and pushed to the repo. Basically moonhermit had a hardcoded request id, which meant that multiple requests on the same script were getting borked. Now it is a random number.
First, a small video of the running script:
Neat, right? 😃
The script needs to do a ton of muxrpc calls.
- Figure out the name of the feed you've choosen (either you or some feed passed as an argument to the script).
- Find out all pub announcement messages in the feed.
- Compute a relationship map by fetching all contact messages for the feed.
- Find which feeds the chosen feed follows that have sent pub announcement messages.
- Get the name for each of those pub feeds.
Remember that project moon hermit has a extremely strong bottleneck because it does the JSON encoding and decoding on the Lua side using pure Lua string functions. It is really slow (I should have used a C JSON library).
Also, remember that project moon hermit doesn't stream. The code may look like pull-streams, but that is for developer convenience, it actually fetches all the data before passing it through the pipeline. It means that something like ssb:createUserStream()
will actually fetch and encode all the database before sending it further through the script.
It takes about 20 seconds for it to go through my feed and find all information it needs. That doesn't mean that all feeds that it identifies as pubs are actual pubs, apparently some people have send pub announcement messages from their own feeds...
Anyway, here is the script, it is a bit convoluted but not too much.
#!./moonhermit
--[[
This script does the following:
* Chooses a feed by either inspecting the first argument passed to it
or by finding out who the running user is.
* Gets the name for that feed.
* Finds all follows for that feed.
* Gets all pub annoucements
* Finds intersections between follows and pub announcements.
* Gets the name for all the feeds in the intersection.
Lots of muxrpc calls...
]]
require "pl"
--[[ Figure out if the user passed a feed or if we need to check the running
user feed is
]]
local key = arg[1]
if key == nil then
print "Finding who you are..."
local whoami, err = ssb:whoami()
if err then
print("error:" .. err)
exit(1)
end
key = whoami.id
else
print(string.format("Finding pubs for feed: %s", key))
end
--[[
Helper functions to compute relationship table and pub announcement set.
]]
function justContacts(m)
return lookup(m, "value", "content", "type") == "contact"
end
function justAbout(m)
local c1 = lookup(m, "value", "content", "type") == "about"
local c2 = lookup(m, "value", "author") == lookup(m, "value", "content", "about")
return c1 and c2
end
function getName(feed)
local s, err = ssb:createUserStream({
id = feed
})
if err then
return nil, err
end
local a = stream(s).filter(justAbout).reverse().toarray()
local name = lookup(a[1], "value", "content", "name")
if name == nil then
-- first message was not setting a name, look further...
for i, v in pairs(a) do
if lookup(v, "value", "content", "name") ~= nil then
name = lookup(v, "value", "content", "name")
break
end
end
end
return name, nil, a
end
local contacts = {}
function addToMap(m)
local user = m.value.author
local target = m.value.content.contact
local action = m.value.content.following and 1 or -1
contacts[target] = (contacts[target] or 0) + action
end
local keyname = getName(key)
--[[ Get all pub messages, compute relationship graph ]]
print "Finding all pub announcement messages..."
local pubMsgs, err2 = ssb:messagesByType({
type = "pub"
})
print("Looking up " .. keyname .. " history...")
local msgs, err1 = ssb:createHistoryStream({
id = key
})
if (not err1) and (not err2) then
-- compute contacts table
stream(msgs).filter(justContacts).foreach(addToMap)
-- rework pubMsgs table into a Set
local allPubs = Set{}
for i, v in pairs(pubMsgs) do
local k = lookup(v, "value", "content", "address", "key")
--local k = lookup(v, "value", "author")
allPubs = allPubs+Set{k}
end
-- output some cool information
local contactMsgs = stream(msgs).filter(justContacts).toarray()
print(string.format("\n%d contact msgs", #contactMsgs ))
print(string.format("%d pub msgs", #pubMsgs ))
print(string.format("%d unique pub ids\n", #allPubs ))
-- find pubs in contacts table
for feed, v in pairs(contacts) do
if v == 1 then
if allPubs[feed] then
local name, _ = getName(feed)
if name == nil then
name = "unknown name (maybe out of hops)"
end
print(string.format("%s follow pub %s known as %s", keyname, feed, name))
end
end
end
else
print(err1)
print(err2)
end
Show whole feed