This notebooks is hosted on codeberg.

It depends on some packages and files which are not part of this repo:

Install these dependencies by

pip install mastodonpy networkx nxv
In [1]:
import time
print(time.ctime())
# https://github.com/cknoll/ipydex (optional)
%load_ext ipydex.displaytools 
Fri Jan 14 17:08:44 2022
In [2]:
from mastodon import Mastodon  # https://mastodonpy.readthedocs.io/en/stable/index.html#
import networkx as nx  # https://networkx.org/documentation/stable/reference/drawing.html
import nxv  # https://github.com/twosigma/nxv
from matplotlib import pyplot as plt
try:
    import cachewrapper as cw # https://codeberg.org/cknoll/cachewrapper (optional)
except ImportError:
    cw = None
from IPython.display import SVG, display, HTML


masto_url = "https://social.tchncs.de"
cache_path = "cache.pcl"


# mc means "mastodon-client"
mc = Mastodon(
    access_token = 'pymastoclient_usercred.secret',
    api_base_url = masto_url,
)

# this is optional (to save api-calls)
if cw:
    mc = cw.CacheWrapper(mc)
    mc.load_cache(cache_path)

# id taken from url
root_id = "107611511992734130"
res = mc.status_context(root_id) ##:i
mc.save_cache(cache_path)

ancestors = res["ancestors"] ##:i
descendants = res["descendants"] ##:i
info(res) := "<class 'mastodon.Mastodon.AttribAccessDict'> with length: 2"
---
info(ancestors) := "<class 'list'> with length: 0"
---
info(descendants) := "<class 'list'> with length: 80"
---
In [3]:
# instantiate the default color_cycler from matlotlib
color_cycler = plt.rcParamsDefault['axes.prop_cycle']()
# next(color_cycler) -> {'color': '#2ca02c'} etc
In [4]:
author_color_map = {}
In [5]:
def get_author_color(data):
    aid = data["account"]["id"]
    color = author_color_map.get(aid)
    if color:
        return color
    else:
        # get new color from cycler, save it, return it
        color = next(color_cycler)["color"]
        author_color_map[aid] = color
        return color
    
In [6]:
class Node(object):
    def __init__(self, data: dict):
        self.id = str(data["id"])
        self.label = self.id
        self.data = data
        
        
        author = data["account"]["display_name"]
        N = 10
        if len(author) > N:
            author = f"{author[:N-2]}…"
        # label-trick: generate strings like r'{K12345}'
        # these can be substituted later -> two keys for linebreak
        self.repr_str = f"{author}:\n{{K{self.id}}}\n{{X{self.id}}}"
        
        self.style_dict = {
            "color": get_author_color(data),
            "fillcolor": get_author_color(data)+"20", # add low alpha-value for transparency
            "style": "filled",
            "shape": "circle",
            "fixedsize": True,
            "width": 0.7,
            "fontsize": 8,
            "penwidth": 2,

        }

    def __repr__(self):
        
        return self.repr_str
In [7]:
G = nx.DiGraph()

root_node = Node(data=mc.status(root_id))
id_map = {root_id: root_node}

G.add_node(root_node)
parent = root_node

for obj_data in descendants:
    node = Node(obj_data)
    id_map[node.id] = node
    
    parent = id_map[str(node.data["in_reply_to_id"])]
    G.add_node(node, color="red", shape="pentagon")
    G.add_edge(parent, node)
    
    
In [8]:
# see https://nxv.readthedocs.io/en/latest/reference.html#styling
style = nxv.Style(
    graph={"rankdir": "TB"},
    node=lambda u, d: u.style_dict,
        # u is a node and d is its attribute dict (which is ignored here)
    edge=lambda u, v, d: {"style": "solid", "arrowType": "normal", "label": ""},
)



svg_data = nxv.render(G, style, format="svg")

entity_links = []

for node in G.nodes.keys():
    date = node.data["created_at"]
    str1 = f'<a href="{node.data["url"]}">{date.strftime("%Y-%m-%d")}</a>'
    str2 = f'<a href="{node.data["url"]}">{date.strftime("%H:%M:%S")}</a>'
    entity_links.append((f"K{node.id}", str1))
    entity_links.append((f"X{node.id}", str2))

# insert links to wiki data urls
svg_data = svg_data.decode("utf8").format(**dict(entity_links)).encode("utf8")

svg_fname = f"toot-analysis-{root_id}.svg"

with open(svg_fname, "wb") as svgfile:
    svgfile.write(svg_data)

#display(SVG(svg_data))

display(HTML(f"<a href='{svg_fname}'>{svg_fname}</a>"))
In [9]:
# display(HTML(f"<img src='{svg_fname}'>")) # this is prone to unexpected behavior due to caching
display(SVG(svg_data))
# click in the gray area below `In [..]` to disable/enable scrolling
G node0000 CarK: 2022-01-12 21:02:42 node0001 BUNDjuge…: 2022-01-12 21:38:06 node0000->node0001 node0066 zem: 2022-01-12 21:50:39 node0000->node0066 node0072 Max πŸ‡ͺπŸ‡ΊπŸ––: 2022-01-12 22:49:35 node0000->node0072 node0073 Carl: 2022-01-12 23:37:24 node0000->node0073 node0002 CarK: 2022-01-12 21:47:59 node0001->node0002 node0003 CarK: 2022-01-12 21:52:15 node0002->node0003 node0018 Laufi: 2022-01-12 22:10:32 node0002->node0018 node0004 CarK: 2022-01-12 21:54:34 node0003->node0004 node0007 Laufi: 2022-01-12 22:13:56 node0003->node0007 node0005 CarK: 2022-01-12 22:02:10 node0004->node0005 node0006 : 2022-01-13 00:12:10 node0004->node0006 node0008 CarK: 2022-01-12 22:20:46 node0007->node0008 node0009 Laufi: 2022-01-12 22:26:23 node0008->node0009 node0010 CarK: 2022-01-12 22:33:25 node0009->node0010 node0011 CarK: 2022-01-12 22:39:33 node0010->node0011 node0017 Laufi: 2022-01-12 22:40:27 node0010->node0017 node0012 Laufi: 2022-01-12 22:41:37 node0011->node0012 node0013 CarK: 2022-01-12 22:49:42 node0012->node0013 node0014 Laufi: 2022-01-12 22:51:03 node0013->node0014 node0015 Laufi: 2022-01-12 22:52:02 node0014->node0015 node0016 CarK: 2022-01-12 22:57:59 node0015->node0016 node0019 CarK: 2022-01-12 22:16:10 node0018->node0019 node0020 Laufi: 2022-01-12 22:20:08 node0019->node0020 node0023 Birgit πŸ€: 2022-01-12 23:24:55 node0019->node0023 node0021 CarK: 2022-01-12 22:26:32 node0020->node0021 node0022 Laufi: 2022-01-12 22:28:28 node0021->node0022 node0024 Laufi: 2022-01-13 09:29:02 node0023->node0024 node0060 Laufi: 2022-01-13 09:52:52 node0023->node0060 node0025 Laufi: 2022-01-13 09:31:03 node0024->node0025 node0026 Laufi: 2022-01-13 09:34:32 node0025->node0026 node0041 Laufi: 2022-01-13 09:36:50 node0025->node0041 node0058 Birgit πŸ€: 2022-01-13 09:59:46 node0025->node0058 node0027 Birgit πŸ€: 2022-01-13 10:11:34 node0026->node0027 node0028 Laufi: 2022-01-13 10:15:45 node0027->node0028 node0029 Birgit πŸ€: 2022-01-13 11:41:05 node0028->node0029 node0030 Laufi: 2022-01-13 11:43:36 node0029->node0030 node0031 Birgit πŸ€: 2022-01-13 11:51:51 node0030->node0031 node0032 Laufi: 2022-01-13 11:59:43 node0031->node0032 node0033 Birgit πŸ€: 2022-01-13 12:08:56 node0032->node0033 node0034 Laufi: 2022-01-13 13:19:35 node0033->node0034 node0035 Birgit πŸ€: 2022-01-13 14:30:25 node0034->node0035 node0036 Laufi: 2022-01-13 14:55:57 node0035->node0036 node0037 Laufi: 2022-01-13 15:00:34 node0036->node0037 node0040 Birgit πŸ€: 2022-01-13 15:28:13 node0036->node0040 node0038 Birgit πŸ€: 2022-01-13 15:30:45 node0037->node0038 node0039 Laufi: 2022-01-13 16:39:49 node0038->node0039 node0042 Laufi: 2022-01-13 09:38:27 node0041->node0042 node0056 Birgit πŸ€: 2022-01-13 10:34:50 node0041->node0056 node0043 Laufi: 2022-01-13 09:40:21 node0042->node0043 node0054 Birgit πŸ€: 2022-01-13 10:38:40 node0042->node0054 node0044 Laufi: 2022-01-13 09:44:48 node0043->node0044 node0053 Birgit πŸ€: 2022-01-13 10:43:31 node0043->node0053 node0045 Laufi: 2022-01-13 09:46:32 node0044->node0045 node0050 Birgit πŸ€: 2022-01-13 10:47:09 node0044->node0050 node0046 Birgit πŸ€: 2022-01-13 10:49:13 node0045->node0046 node0047 Laufi: 2022-01-13 11:33:08 node0046->node0047 node0048 Birgit πŸ€: 2022-01-13 12:37:41 node0047->node0048 node0049 Laufi: 2022-01-13 13:16:07 node0048->node0049 node0051 CarK: 2022-01-13 11:02:15 node0050->node0051 node0052 Laufi: 2022-01-13 11:31:10 node0051->node0052 node0055 Laufi: 2022-01-13 11:33:49 node0054->node0055 node0057 Laufi: 2022-01-13 11:37:31 node0056->node0057 node0059 Laufi: 2022-01-13 10:11:41 node0058->node0059 node0061 Laufi: 2022-01-13 09:54:11 node0060->node0061 node0062 Birgit πŸ€: 2022-01-13 10:03:31 node0060->node0062 node0063 Laufi: 2022-01-13 10:13:58 node0062->node0063 node0064 Birgit πŸ€: 2022-01-13 10:57:18 node0063->node0064 node0065 Laufi: 2022-01-13 11:41:32 node0064->node0065 node0067 zem: 2022-01-12 22:02:42 node0066->node0067 node0068 CarK: 2022-01-12 22:10:18 node0066->node0068 node0069 zem: 2022-01-13 00:11:20 node0068->node0069 node0070 zem: 2022-01-13 10:18:13 node0069->node0070 node0071 zem: 2022-01-13 11:05:07 node0070->node0071 node0074 Laufi: 2022-01-13 09:55:10 node0073->node0074 node0077 Max πŸ‡ͺπŸ‡ΊπŸ––: 2022-01-13 09:57:53 node0073->node0077 node0075 CarK: 2022-01-13 09:59:03 node0074->node0075 node0076 Laufi: 2022-01-13 10:16:11 node0075->node0076 node0078 Carl: 2022-01-13 11:41:58 node0077->node0078 node0079 Carl: 2022-01-13 11:45:54 node0077->node0079 node0080 Carl: 2022-01-13 11:47:11 node0079->node0080