diff options
| author | @syxhe <t.me/syxhe> | 2025-10-02 16:21:29 -0500 |
|---|---|---|
| committer | @syxhe <t.me/syxhe> | 2025-10-02 16:21:29 -0500 |
| commit | 82c6f12de286db3c89758f201f619a63accf17f4 (patch) | |
| tree | e32704f431c3c474fffcd94a227ecfcb40058922 | |
| parent | 267b35a813dd1ad5c3eec253f5d6f9d613bbb1ec (diff) | |
Begin the over-engineering process
| -rw-r--r-- | .gitmodules | 3 | ||||
| -rw-r--r-- | .idea/codeStyles/codeStyleConfig.xml | 5 | ||||
| -rw-r--r-- | README.md | 26 | ||||
| -rwxr-xr-x | build.bash | 31 | ||||
| -rw-r--r-- | src/main/java/ChannelNode.java | 89 | ||||
| -rw-r--r-- | src/main/java/IChannelNode.java | 23 | ||||
| m--------- | td | 0 |
7 files changed, 129 insertions, 48 deletions
diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 0000000..b641dcc --- /dev/null +++ b/.gitmodules | |||
| @@ -0,0 +1,3 @@ | |||
| 1 | [submodule "td"] | ||
| 2 | path = td | ||
| 3 | url = https://github.com/tdlib/td | ||
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml new file mode 100644 index 0000000..a55e7a1 --- /dev/null +++ b/.idea/codeStyles/codeStyleConfig.xml | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | <component name="ProjectCodeStyleConfiguration"> | ||
| 2 | <state> | ||
| 3 | <option name="PREFERRED_PROJECT_CODE_STYLE" value="Default" /> | ||
| 4 | </state> | ||
| 5 | </component> \ No newline at end of file | ||
| @@ -2,4 +2,28 @@ | |||
| 2 | 2 | ||
| 3 | ## Map the connections between Telegram channels using TDLib | 3 | ## Map the connections between Telegram channels using TDLib |
| 4 | 4 | ||
| 5 | TG Mapper is a tool for satiating my curiosity. For a long time, I've wanted to see how interconnected different TG channels are, and this is my solution. TG Mapper uses TDLib to search through channel posts, find forwarded posts, and store connections in a graph \ No newline at end of file | 5 | TG Mapper is a tool for satiating my curiosity. For a long time, I've wanted to see how interconnected different TG channels are, and this is my solution. TG Mapper uses TDLib to search through channel posts, find forwarded posts, and store connections in a graph |
| 6 | |||
| 7 | ## USAGE | ||
| 8 | |||
| 9 | ## DEPENDENCIES | ||
| 10 | |||
| 11 | ## BUILDING | ||
| 12 | |||
| 13 | Clone repo & submodules: | ||
| 14 | |||
| 15 | ```bash | ||
| 16 | git clone --recurse-submodules https://git.dabikers.online/tgmapper | ||
| 17 | ``` | ||
| 18 | |||
| 19 | Move into dir and run build script | ||
| 20 | |||
| 21 | ```bash | ||
| 22 | ./build.bash | ||
| 23 | ``` | ||
| 24 | |||
| 25 | This will output a .jar. Now, run the jar | ||
| 26 | |||
| 27 | ```bash | ||
| 28 | java run -jar tgmapper.jar | ||
| 29 | ``` \ No newline at end of file | ||
diff --git a/build.bash b/build.bash new file mode 100755 index 0000000..f2b188d --- /dev/null +++ b/build.bash | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | #!/usr/bin/env -vS bash | ||
| 2 | |||
| 3 | SOMEWHAT_REAL_DIR="$(realpath "$0")" | ||
| 4 | FILENAME="${SOMEWHAT_REAL_DIR##*/}" | ||
| 5 | PATH_TO="${SOMEWHAT_REAL_DIR/$FILENAME/}" | ||
| 6 | |||
| 7 | function runInLocal() { | ||
| 8 | if [[ "$(pwd)/" != "$PATH_TO" ]]; then | ||
| 9 | (cd "$PATH_TO" && bash "$FILENAME") | ||
| 10 | exit $? | ||
| 11 | fi | ||
| 12 | |||
| 13 | return 0 | ||
| 14 | } | ||
| 15 | |||
| 16 | function buildTDLIB() { | ||
| 17 | |||
| 18 | |||
| 19 | return 0 | ||
| 20 | } | ||
| 21 | |||
| 22 | function buildJar() { | ||
| 23 | |||
| 24 | return 0 | ||
| 25 | } | ||
| 26 | |||
| 27 | runInLocal && \ | ||
| 28 | buildTDLIB && \ | ||
| 29 | buildJar | ||
| 30 | |||
| 31 | exit "$?" | ||
diff --git a/src/main/java/ChannelNode.java b/src/main/java/ChannelNode.java index ddc1488..20a7ac0 100644 --- a/src/main/java/ChannelNode.java +++ b/src/main/java/ChannelNode.java | |||
| @@ -1,7 +1,6 @@ | |||
| 1 | import java.util.ArrayList; | 1 | import java.util.*; |
| 2 | import java.util.List; | 2 | import java.util.concurrent.atomic.AtomicInteger; |
| 3 | import java.util.Map; | 3 | import static java.util.Map.entry; |
| 4 | import java.util.TreeMap; | ||
| 5 | 4 | ||
| 6 | public class ChannelNode implements IChannelNode { | 5 | public class ChannelNode implements IChannelNode { |
| 7 | private Map<IChannelNode, Integer> incoming; | 6 | private Map<IChannelNode, Integer> incoming; |
| @@ -18,9 +17,10 @@ public class ChannelNode implements IChannelNode { | |||
| 18 | if(dir == null) throw new IllegalArgumentException("direction is null"); | 17 | if(dir == null) throw new IllegalArgumentException("direction is null"); |
| 19 | if(conmap.containsKey(this)) throw new IllegalArgumentException("connection map contains this node"); | 18 | if(conmap.containsKey(this)) throw new IllegalArgumentException("connection map contains this node"); |
| 20 | 19 | ||
| 21 | int dv = dir.getVal(); | 20 | Direction.overVals(Map.ofEntries( |
| 22 | if(dv >= Direction.INCOMING.getVal()) incoming = conmap; | 21 | entry(Direction.INCOMING, () -> incoming = conmap), |
| 23 | if(dv >= Direction.OUTGOING.getVal()) outgoing = conmap; | 22 | entry(Direction.OUTGOING, () -> outgoing = conmap) |
| 23 | ), dir); | ||
| 24 | } | 24 | } |
| 25 | 25 | ||
| 26 | @Override | 26 | @Override |
| @@ -30,9 +30,10 @@ public class ChannelNode implements IChannelNode { | |||
| 30 | if(dir == null) throw new IllegalArgumentException("dir is null"); | 30 | if(dir == null) throw new IllegalArgumentException("dir is null"); |
| 31 | if(num < 0) throw new IllegalArgumentException("num < 0"); | 31 | if(num < 0) throw new IllegalArgumentException("num < 0"); |
| 32 | 32 | ||
| 33 | int dv = dir.getVal(); | 33 | Direction.overVals(Map.ofEntries( |
| 34 | if(dv >= Direction.INCOMING.getVal()) incoming.put(node, num); | 34 | entry(Direction.INCOMING, () -> incoming.put(node, num)), |
| 35 | if(dv >= Direction.OUTGOING.getVal()) outgoing.put(node, num); | 35 | entry(Direction.OUTGOING, () -> outgoing.put(node, num)) |
| 36 | ), dir); | ||
| 36 | } | 37 | } |
| 37 | 38 | ||
| 38 | @Override | 39 | @Override |
| @@ -41,9 +42,10 @@ public class ChannelNode implements IChannelNode { | |||
| 41 | if(node == this) throw new IllegalArgumentException("node is self"); | 42 | if(node == this) throw new IllegalArgumentException("node is self"); |
| 42 | if(dir == null) throw new IllegalArgumentException("dir is null"); | 43 | if(dir == null) throw new IllegalArgumentException("dir is null"); |
| 43 | 44 | ||
| 44 | int dv = dir.getVal(); | 45 | Direction.overVals(Map.ofEntries( |
| 45 | if(dv >= Direction.INCOMING.getVal()) incoming.put(node, incoming.get(node) + 1); | 46 | entry(Direction.INCOMING, () -> incoming.put(node, incoming.get(node) + 1)), |
| 46 | if(dv >= Direction.OUTGOING.getVal()) outgoing.put(node, outgoing.get(node) + 1); | 47 | entry(Direction.OUTGOING, () -> outgoing.put(node, outgoing.get(node) + 1)) |
| 48 | ), dir); | ||
| 47 | } | 49 | } |
| 48 | 50 | ||
| 49 | @Override | 51 | @Override |
| @@ -52,14 +54,12 @@ public class ChannelNode implements IChannelNode { | |||
| 52 | if(dir == null) throw new IllegalArgumentException("dir is null"); | 54 | if(dir == null) throw new IllegalArgumentException("dir is null"); |
| 53 | nodes.forEach((node) -> {if(node == this) throw new IllegalArgumentException("one of the included nodes is this node");}); | 55 | nodes.forEach((node) -> {if(node == this) throw new IllegalArgumentException("one of the included nodes is this node");}); |
| 54 | 56 | ||
| 55 | int dv = dir.getVal(), | 57 | for(IChannelNode node: nodes) { |
| 56 | incomingV = Direction.INCOMING.getVal(), | 58 | Direction.overVals(Map.ofEntries( |
| 57 | outgoingV = Direction.OUTGOING.getVal(); | 59 | entry(Direction.INCOMING, () -> incoming.put(node, incoming.get(node) + 1)), |
| 58 | 60 | entry(Direction.OUTGOING, () -> outgoing.put(node, outgoing.get(node) + 1)) | |
| 59 | nodes.forEach((node) -> { | 61 | ), dir); |
| 60 | if(dv >= incomingV) incoming.put(node, incoming.get(node) + 1); | 62 | } |
| 61 | if(dv >= outgoingV) outgoing.put(node, outgoing.get(node) + 1); | ||
| 62 | }); | ||
| 63 | } | 63 | } |
| 64 | 64 | ||
| 65 | @Override | 65 | @Override |
| @@ -68,9 +68,10 @@ public class ChannelNode implements IChannelNode { | |||
| 68 | if(node == this) throw new IllegalArgumentException("node is self"); | 68 | if(node == this) throw new IllegalArgumentException("node is self"); |
| 69 | if(dir == null) throw new IllegalArgumentException("dir is null"); | 69 | if(dir == null) throw new IllegalArgumentException("dir is null"); |
| 70 | 70 | ||
| 71 | int dv = dir.getVal(); | 71 | Direction.overVals(Map.ofEntries( |
| 72 | if(dv >= Direction.INCOMING.getVal()) incoming.put(node, Math.max(incoming.get(node) - 1, 0)); | 72 | entry(Direction.INCOMING, () -> incoming.put(node, Math.max(incoming.get(node) - 1, 0))), |
| 73 | if(dv >= Direction.OUTGOING.getVal()) outgoing.put(node, Math.max(outgoing.get(node) - 1, 0)); | 73 | entry(Direction.OUTGOING, () -> outgoing.put(node, Math.max(outgoing.get(node) - 1, 0))) |
| 74 | ), dir); | ||
| 74 | } | 75 | } |
| 75 | 76 | ||
| 76 | @Override | 77 | @Override |
| @@ -79,23 +80,22 @@ public class ChannelNode implements IChannelNode { | |||
| 79 | if(dir == null) throw new IllegalArgumentException("dir is null"); | 80 | if(dir == null) throw new IllegalArgumentException("dir is null"); |
| 80 | nodes.forEach((node) -> {if(node == this) throw new IllegalArgumentException("one of the included nodes is this node");}); | 81 | nodes.forEach((node) -> {if(node == this) throw new IllegalArgumentException("one of the included nodes is this node");}); |
| 81 | 82 | ||
| 82 | int dv = dir.getVal(), | 83 | for(IChannelNode node: nodes) { |
| 83 | incomingV = Direction.INCOMING.getVal(), | 84 | Direction.overVals(Map.ofEntries( |
| 84 | outgoingV = Direction.OUTGOING.getVal(); | 85 | entry(Direction.INCOMING, () -> incoming.put(node, Math.max(incoming.get(node) - 1, 0))), |
| 85 | 86 | entry(Direction.OUTGOING, () -> outgoing.put(node, Math.max(outgoing.get(node) - 1, 0))) | |
| 86 | nodes.forEach((node) -> { | 87 | ), dir); |
| 87 | if(dv >= incomingV) incoming.put(node, Math.max(incoming.get(node) - 1, 0)); | 88 | } |
| 88 | if(dv >= outgoingV) outgoing.put(node, Math.max(outgoing.get(node) - 1, 0)); | ||
| 89 | }); | ||
| 90 | } | 89 | } |
| 91 | 90 | ||
| 92 | @Override | 91 | @Override |
| 93 | public void clearConnections(Direction dir) throws IllegalArgumentException { | 92 | public void clearConnections(Direction dir) throws IllegalArgumentException { |
| 94 | if(dir == null) throw new IllegalArgumentException("dir is null"); | 93 | if(dir == null) throw new IllegalArgumentException("dir is null"); |
| 95 | 94 | ||
| 96 | int dv = dir.getVal(); | 95 | Direction.overVals(Map.ofEntries( |
| 97 | if(dv >= Direction.INCOMING.getVal()) incoming.clear(); | 96 | entry(Direction.INCOMING, () -> incoming.clear()), |
| 98 | if(dv >= Direction.OUTGOING.getVal()) outgoing.clear(); | 97 | entry(Direction.OUTGOING, () -> outgoing.clear()) |
| 98 | ), dir); | ||
| 99 | } | 99 | } |
| 100 | 100 | ||
| 101 | @Override | 101 | @Override |
| @@ -115,12 +115,12 @@ public class ChannelNode implements IChannelNode { | |||
| 115 | public int getNumConnections(Direction dir) throws IllegalArgumentException { | 115 | public int getNumConnections(Direction dir) throws IllegalArgumentException { |
| 116 | if(dir == null) throw new IllegalArgumentException("dir is null"); | 116 | if(dir == null) throw new IllegalArgumentException("dir is null"); |
| 117 | 117 | ||
| 118 | int total = 0; | 118 | AtomicInteger total = new AtomicInteger(); |
| 119 | int dv = dir.getVal(); | 119 | Direction.overVals(Map.ofEntries( |
| 120 | 120 | entry(Direction.INCOMING, () -> {for(int i: incoming.values()) total.addAndGet(i);}), | |
| 121 | if(dv >= Direction.INCOMING.getVal()) for(int i: incoming.values()) total += i; | 121 | entry(Direction.OUTGOING, () -> {for(int i: outgoing.values()) total.addAndGet(i);}) |
| 122 | if(dv >= Direction.OUTGOING.getVal()) for(int i: outgoing.values()) total += i; | 122 | ), dir); |
| 123 | return total; | 123 | return total.get(); |
| 124 | } | 124 | } |
| 125 | 125 | ||
| 126 | @Override | 126 | @Override |
| @@ -137,10 +137,11 @@ public class ChannelNode implements IChannelNode { | |||
| 137 | public List<Map<IChannelNode, Integer>> getConnections(Direction dir) throws IllegalArgumentException { | 137 | public List<Map<IChannelNode, Integer>> getConnections(Direction dir) throws IllegalArgumentException { |
| 138 | if(dir == null) throw new IllegalArgumentException("dir is null"); | 138 | if(dir == null) throw new IllegalArgumentException("dir is null"); |
| 139 | 139 | ||
| 140 | int dv = dir.getVal(); | ||
| 141 | ArrayList<Map<IChannelNode, Integer>> res = new ArrayList<>(); | 140 | ArrayList<Map<IChannelNode, Integer>> res = new ArrayList<>(); |
| 142 | if(dv >= Direction.INCOMING.getVal()) res.add(incoming); | 141 | Direction.overVals(Map.ofEntries( |
| 143 | if(dv >= Direction.OUTGOING.getVal()) res.add(outgoing); | 142 | entry(Direction.INCOMING, () -> res.add(incoming)), |
| 143 | entry(Direction.OUTGOING, () -> res.add(outgoing)) | ||
| 144 | ), dir); | ||
| 144 | 145 | ||
| 145 | return res; | 146 | return res; |
| 146 | } | 147 | } |
diff --git a/src/main/java/IChannelNode.java b/src/main/java/IChannelNode.java index baf7572..a6446f1 100644 --- a/src/main/java/IChannelNode.java +++ b/src/main/java/IChannelNode.java | |||
| @@ -3,13 +3,30 @@ import java.util.Map; | |||
| 3 | 3 | ||
| 4 | public interface IChannelNode extends Comparable<IChannelNode> { | 4 | public interface IChannelNode extends Comparable<IChannelNode> { |
| 5 | enum Direction { | 5 | enum Direction { |
| 6 | INCOMING(0), // Users incoming from outside channel (aka: other channel forwarded this channel's post) | 6 | INCOMING(1), // Users incoming from outside channel (aka: other channel forwarded this channel's post) |
| 7 | OUTGOING(1), // Users outgoing from this channel (aka: this channel forwarded someone else's post) | 7 | OUTGOING(2), // Users outgoing from this channel (aka: this channel forwarded someone else's post) |
| 8 | BOTH(2); // Modify both incoming and outgoing counts at once | 8 | BOTH(3); // Modify both incoming and outgoing counts at once |
| 9 | 9 | ||
| 10 | private final int val; | 10 | private final int val; |
| 11 | Direction(int val) {this.val = val;} | 11 | Direction(int val) {this.val = val;} |
| 12 | public int getVal() {return this.val;} | 12 | public int getVal() {return this.val;} |
| 13 | |||
| 14 | public interface callback { | ||
| 15 | void cb(); | ||
| 16 | } | ||
| 17 | public static void overVals(Map<Direction, callback> cmap, Direction dir) throws IllegalArgumentException { | ||
| 18 | if(cmap == null) throw new IllegalArgumentException("callback map is null"); | ||
| 19 | if(dir == null) throw new IllegalArgumentException("dir is null"); | ||
| 20 | for(Direction cdir: cmap.keySet()) if(cdir == null) throw new IllegalArgumentException("One of the directions is null"); | ||
| 21 | for(callback cb: cmap.values()) if(cb == null) throw new IllegalArgumentException("One of the callbacks is null"); | ||
| 22 | |||
| 23 | // For each direction in the map, check that the given direction is gte, and if so run the callback | ||
| 24 | for(Direction cdir: cmap.keySet()) | ||
| 25 | if(dir.getVal() >= cdir.getVal()) | ||
| 26 | cmap.get(cdir).cb(); | ||
| 27 | } | ||
| 28 | // This is hilariously overengineered because java fucking sucks my entire cock and balls | ||
| 29 | // Barely even saves any space too | ||
| 13 | } | 30 | } |
| 14 | 31 | ||
| 15 | void setConnections(Map<IChannelNode, Integer> conmap, Direction dir); | 32 | void setConnections(Map<IChannelNode, Integer> conmap, Direction dir); |
diff --git a/td b/td new file mode 160000 | |||
| Subproject 369ee922b45bfa7e8da357e4d62e93925862d86 | |||
