Skip to content

Commit 438aaf1

Browse files
committed
V0.1.0
1 parent 9e3cebf commit 438aaf1

22 files changed

+1728
-0
lines changed

modules/BlueskyGephi/README.md

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# Bluesky Gephi
2+
3+
This plugin allow you to visualize and explore the network of users in bluesky via the atprotocol.
4+
5+
# Quick start
6+
- Get a bluesky account
7+
- Generate a password https://bsky.app/settings/app-passwords
8+
- Install the plugin in Gephi
9+
- Open Gephi
10+
- Put handle and password information
11+
- Search for yourself
12+
- Graph of your connection should appears.
13+
14+
# Docs
15+
16+
Keep in mind current atproto access point from bluesky is quite permissive and might change in the future.
17+
18+
## Fetch from user
19+
You can fetch network from user from multiple way :
20+
- Put one or multiple (separated by line return) handles or dids inside the plugin textarea and click on "Go!"
21+
- You can right click on a node and select contextual menu item related to the plugin
22+
- *Bluesky Fetch default data* , will fetch network based on the current configuration on the plugin panel
23+
- *Fetch followers only data*, will fetch only the followers of the node
24+
- *Fetch follows only data*, will fetch only the follows of the node

modules/BlueskyGephi/pom.xml

+103
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
3+
<modelVersion>4.0.0</modelVersion>
4+
<parent>
5+
<artifactId>gephi-plugin-parent</artifactId>
6+
<groupId>org.gephi</groupId>
7+
<version>0.10.0</version>
8+
</parent>
9+
10+
<groupId>fr.totetmatt</groupId>
11+
<artifactId>bluesky-gephi</artifactId>
12+
<version>0.1.0</version>
13+
<packaging>nbm</packaging>
14+
15+
<name>Bluesky Gephi</name>
16+
17+
<dependencies>
18+
<dependency>
19+
<groupId>com.fasterxml.jackson.core</groupId>
20+
<artifactId>jackson-databind</artifactId>
21+
<version>2.13.4.1</version>
22+
</dependency>
23+
<dependency>
24+
<groupId>org.netbeans.api</groupId>
25+
<artifactId>org-openide-awt</artifactId>
26+
</dependency>
27+
<dependency>
28+
<groupId>${project.parent.groupId}</groupId>
29+
<artifactId>visualization-api</artifactId>
30+
</dependency>
31+
<dependency>
32+
<groupId>${project.parent.groupId}</groupId>
33+
<artifactId>datalab-api</artifactId>
34+
</dependency>
35+
<dependency>
36+
<groupId>org.netbeans.api</groupId>
37+
<artifactId>org-openide-windows</artifactId>
38+
</dependency>
39+
<dependency>
40+
<groupId>org.netbeans.api</groupId>
41+
<artifactId>org-netbeans-modules-settings</artifactId>
42+
</dependency>
43+
<dependency>
44+
<groupId>org.netbeans.api</groupId>
45+
<artifactId>org-openide-util-lookup</artifactId>
46+
</dependency>
47+
<dependency>
48+
<groupId>org.netbeans.api</groupId>
49+
<artifactId>org-openide-util</artifactId>
50+
</dependency>
51+
<dependency>
52+
<groupId>org.gephi</groupId>
53+
<artifactId>graph-api</artifactId>
54+
</dependency>
55+
<dependency>
56+
<groupId>org.gephi</groupId>
57+
<artifactId>project-api</artifactId>
58+
</dependency>
59+
<dependency>
60+
<groupId>org.gephi</groupId>
61+
<artifactId>desktop-project</artifactId>
62+
63+
</dependency>
64+
<dependency>
65+
<groupId>org.gephi</groupId>
66+
<artifactId>utils-longtask</artifactId>
67+
</dependency>
68+
</dependencies>
69+
70+
<build>
71+
<plugins>
72+
<plugin>
73+
<groupId>org.apache.netbeans.utilities</groupId>
74+
<artifactId>nbm-maven-plugin</artifactId>
75+
<configuration>
76+
<verifyRuntime>skip</verifyRuntime>
77+
<licenseName>Apache 2.0</licenseName>
78+
<author>totetmatt</author>
79+
<authorEmail>[email protected]</authorEmail>
80+
<authorUrl>https://totetmatt.fr</authorUrl>
81+
<sourceCodeUrl>https://github.com/totetmatt/gephi-plugins.git</sourceCodeUrl>
82+
<publicPackages>
83+
<!-- Insert public packages -->
84+
</publicPackages>
85+
</configuration>
86+
</plugin>
87+
</plugins>
88+
</build>
89+
90+
<!-- Snapshot Repositories (only needed if developing against a SNAPSHOT version) -->
91+
<repositories>
92+
<repository>
93+
<id>oss-sonatype</id>
94+
<name>oss-sonatype</name>
95+
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
96+
<snapshots>
97+
<enabled>true</enabled>
98+
</snapshots>
99+
</repository>
100+
</repositories>
101+
</project>
102+
103+
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,215 @@
1+
package fr.totetmatt.blueskygephi;
2+
3+
import fr.totetmatt.blueskygephi.atproto.AtClient;
4+
import fr.totetmatt.blueskygephi.atproto.response.AppBskyGraphGetFollowers;
5+
import fr.totetmatt.blueskygephi.atproto.response.AppBskyGraphGetFollows;
6+
import fr.totetmatt.blueskygephi.atproto.response.common.Identity;
7+
import java.awt.Color;
8+
import java.util.HashSet;
9+
import java.util.List;
10+
import java.util.Set;
11+
import java.util.logging.Logger;
12+
import java.util.prefs.Preferences;
13+
import org.gephi.graph.api.Edge;
14+
import org.gephi.graph.api.GraphController;
15+
import org.gephi.graph.api.GraphModel;
16+
import org.gephi.graph.api.Node;
17+
import org.gephi.project.api.Project;
18+
import org.gephi.project.api.ProjectController;
19+
import org.gephi.utils.progress.Progress;
20+
import org.gephi.utils.progress.ProgressTicket;
21+
import org.gephi.utils.progress.ProgressTicketProvider;
22+
import org.openide.util.Lookup;
23+
import org.openide.util.NbPreferences;
24+
import org.openide.util.lookup.ServiceProvider;
25+
26+
/**
27+
*
28+
* @author totetmatt
29+
*/
30+
@ServiceProvider(service = BlueskyGephi.class)
31+
public class BlueskyGephi {
32+
33+
protected static final Logger logger = Logger.getLogger(BlueskyGephi.class.getName());
34+
private final static String NBPREF_BSKY_HANDLE = "bsky.handle";
35+
private final static String NBPREF_BSKY_PASSWORD = "bsky.password";
36+
private final static String NBPREF_QUERY = "query";
37+
private final static String NBPREF_QUERY_ISFOLLOWERSACTIVE = "query.isFollowersActive";
38+
private final static String NBPREF_QUERY_ISFOLLOWSACTIVE = "query.isFollowsActive";
39+
private final static String NBPREF_QUERY_ISDEEPSEARCH = "query.isDeepSearch";
40+
41+
private final Preferences nbPref = NbPreferences.forModule(BlueskyGephi.class);
42+
// If ATProto get released and decentralized, this will change to adapt to other instances
43+
final private AtClient client = new AtClient("bsky.social");
44+
final private GraphModel graphModel;
45+
46+
public BlueskyGephi() {
47+
initProjectAndWorkspace();
48+
graphModel = Lookup.getDefault().lookup(GraphController.class).getGraphModel();
49+
}
50+
51+
private void initProjectAndWorkspace() {
52+
ProjectController projectController = Lookup.getDefault().lookup(ProjectController.class);
53+
Project currentProject = projectController.getCurrentProject();
54+
if (currentProject == null) {
55+
projectController.newProject();
56+
}
57+
}
58+
59+
public boolean connect(String handle, String password) {
60+
nbPref.put(NBPREF_BSKY_HANDLE, handle);
61+
nbPref.put(NBPREF_BSKY_PASSWORD, password);
62+
63+
return client.comAtprotoServerCreateSession(handle, password);
64+
65+
}
66+
67+
public String getHandle() {
68+
return nbPref.get(NBPREF_BSKY_HANDLE, "");
69+
}
70+
71+
public String getPassword() {
72+
return nbPref.get(NBPREF_BSKY_PASSWORD, "");
73+
}
74+
75+
public void setQuery(String query) {
76+
nbPref.put(NBPREF_QUERY, query);
77+
}
78+
79+
public String getQuery() {
80+
return nbPref.get(NBPREF_QUERY, "");
81+
}
82+
83+
public void setIsFollowersActive(boolean isFollowersActive) {
84+
nbPref.putBoolean(NBPREF_QUERY_ISFOLLOWERSACTIVE, isFollowersActive);
85+
}
86+
87+
public boolean getIsFollowersActive() {
88+
return nbPref.getBoolean(NBPREF_QUERY_ISFOLLOWERSACTIVE, true);
89+
}
90+
91+
public void setIsFollowsActive(boolean isFollowsActive) {
92+
nbPref.putBoolean(NBPREF_QUERY_ISFOLLOWSACTIVE, isFollowsActive);
93+
}
94+
95+
public boolean getIsFollowsActive() {
96+
return nbPref.getBoolean(NBPREF_QUERY_ISFOLLOWSACTIVE, true);
97+
}
98+
99+
public void setIsDeepSearch(boolean setIsDeepSearch) {
100+
nbPref.putBoolean(NBPREF_QUERY_ISDEEPSEARCH, setIsDeepSearch);
101+
}
102+
103+
public boolean getIsDeepSearch() {
104+
return nbPref.getBoolean(NBPREF_QUERY_ISDEEPSEARCH, true);
105+
}
106+
107+
private Node createNode(Identity i) {
108+
109+
Node node = graphModel.getGraph().getNode(i.getDid());
110+
if (node == null) {
111+
node = graphModel.factory().newNode(i.getDid());
112+
node.setLabel(i.getHandle());
113+
node.setSize(10);
114+
node.setColor(Color.GRAY);
115+
node.setX((float) ((0.01 + Math.random()) * 1000) - 500);
116+
node.setY((float) ((0.01 + Math.random()) * 1000) - 500);
117+
graphModel.getGraph().addNode(node);
118+
}
119+
120+
return node;
121+
}
122+
123+
private Edge createEdge(Node source, Node target) {
124+
125+
Edge edge = graphModel.getGraph().getEdge(source, target);
126+
if (edge == null) {
127+
edge = graphModel.factory().newEdge(source, target, true);
128+
edge.setWeight(1.0);
129+
edge.setColor(Color.GRAY);
130+
graphModel.getGraph().addEdge(edge);
131+
}
132+
133+
return edge;
134+
}
135+
136+
private void fetchFollowerFollowsFromActor(String actor, boolean isFollowsActive, boolean isFollowersActive, boolean isDeepSearch) {
137+
// To avoid locking Gephi UI
138+
Thread t = new Thread() {
139+
private ProgressTicket progressTicket;
140+
Set<String> foaf = new HashSet<>();
141+
142+
private void process(String actor, boolean isDeepSearch) {
143+
if (isFollowsActive) {
144+
List<AppBskyGraphGetFollows> responses = client.appBskyGraphGetFollows(actor);
145+
146+
graphModel.getGraph().writeLock();
147+
for (var response : responses) {
148+
Identity subject = response.getSubject();
149+
Node source = createNode(subject);
150+
for (var follow : response.getFollows()) {
151+
if (isDeepSearch) {
152+
foaf.add(follow.getDid());
153+
}
154+
Node target = createNode(follow);
155+
createEdge(source, target);
156+
}
157+
158+
}
159+
graphModel.getGraph().writeUnlock();
160+
}
161+
162+
if (isFollowersActive) {
163+
List<AppBskyGraphGetFollowers> responses = client.appBskyGraphGetFollowers(actor);
164+
165+
graphModel.getGraph().writeLock();
166+
for (var response : responses) {
167+
Identity subject = response.getSubject();
168+
Node target = createNode(subject);
169+
for (var follower : response.getFollowers()) {
170+
if (isDeepSearch) {
171+
foaf.add(follower.getDid());
172+
}
173+
Node source = createNode(follower);
174+
createEdge(source, target);
175+
}
176+
}
177+
graphModel.getGraph().writeUnlock();
178+
}
179+
}
180+
181+
@Override
182+
public void run() {
183+
this.setName("Bluesky Gephi Fetching Data for " + actor);
184+
progressTicket = Lookup.getDefault()
185+
.lookup(ProgressTicketProvider.class)
186+
.createTicket(this.getName(), () -> {
187+
interrupt();
188+
Progress.finish(progressTicket);
189+
return true;
190+
});
191+
192+
Progress.start(progressTicket);
193+
Progress.switchToIndeterminate(progressTicket);
194+
195+
process(actor, isDeepSearch);
196+
if (isDeepSearch) {
197+
for (var foafActor : foaf) {
198+
process(foafActor, false);
199+
}
200+
}
201+
Progress.finish(progressTicket);
202+
}
203+
};
204+
t.start();
205+
206+
}
207+
208+
public void fetchFollowerFollowsFromActors(List<String> actors, boolean isFollowsActive, boolean isFollowersActive, boolean isBlocksActive) {
209+
actors.stream().forEach(actor -> fetchFollowerFollowsFromActor(actor, isFollowsActive, isFollowersActive, getIsDeepSearch()));
210+
}
211+
212+
public void fetchFollowerFollowsFromActors(List<String> actors) {
213+
actors.stream().forEach(actor -> fetchFollowerFollowsFromActor(actor, getIsFollowsActive(), getIsFollowersActive(), getIsDeepSearch()));
214+
}
215+
}

0 commit comments

Comments
 (0)