Skip to content

Commit af35c3b

Browse files
author
dedmonds
committed
Initial version commit
1 parent 2cee12d commit af35c3b

12 files changed

+745
-0
lines changed

README.md

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# Slack Statistics
2+
## Overview ##
3+
Create some lighthearted stats using Slack data to find out which users post the most and make the most use of @channel/@here notifications.
4+
5+
## Usage
6+
```
7+
$ mvn clean package
8+
```
9+
At the same level as `pom.xml` create a `slack.properties` file with the following entries (substituting the <...> placeholders with your own data).
10+
```
11+
slack.token=<YOUR-TOKEN>
12+
```
13+
Run the report using the following command with optional CHANNEL-ID (default is all channels)
14+
```
15+
java -jar target/slack-statistics-1.0-SNAPSHOT-jar-with-dependencies.jar <CHANNEL-ID>
16+
```
17+
18+
## TODO
19+
- Handle message threads as separate messages
20+
- Remove messages from bots connected to a user
21+
- Stop treating join/leave channel notifications as messages
22+
- Parse out use of :ICON: tags to generate statistics on
23+
- Parse out @USER calls to generate statistics on
24+
- UserMessageCountPerDayProcessor is considering your earliest message in any slack channel, might be better to make it
25+
earliest in a specific channel (assuming X days spent in channel)

pom.xml

+52
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
2+
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+
<groupId>com.veracode.api.maven</groupId>
5+
<artifactId>slack-statistics</artifactId>
6+
<version>1.0-SNAPSHOT</version>
7+
<packaging>jar</packaging>
8+
9+
<build>
10+
<plugins>
11+
<plugin>
12+
<artifactId>maven-compiler-plugin</artifactId>
13+
<version>3.8.1</version>
14+
<configuration>
15+
<source>1.8</source>
16+
<target>1.8</target>
17+
</configuration>
18+
</plugin>
19+
<plugin>
20+
<artifactId>maven-assembly-plugin</artifactId>
21+
<version>3.1.1</version>
22+
<configuration>
23+
<descriptorRefs>
24+
<descriptorRef>jar-with-dependencies</descriptorRef>
25+
</descriptorRefs>
26+
<archive>
27+
<manifest>
28+
<mainClass>com.dledmonds.slack.ConsoleRunner</mainClass>
29+
</manifest>
30+
</archive>
31+
</configuration>
32+
<executions>
33+
<execution>
34+
<id>make-assembly</id>
35+
<phase>package</phase>
36+
<goals>
37+
<goal>single</goal>
38+
</goals>
39+
</execution>
40+
</executions>
41+
</plugin>
42+
</plugins>
43+
</build>
44+
45+
<dependencies>
46+
<dependency>
47+
<groupId>com.github.seratch</groupId>
48+
<artifactId>jslack</artifactId>
49+
<version>1.5.6</version>
50+
</dependency>
51+
</dependencies>
52+
</project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.dledmonds.slack;
2+
3+
import com.github.seratch.jslack.api.model.Conversation;
4+
5+
/**
6+
* @author dledmonds
7+
*/
8+
public interface ChannelProcessor {
9+
10+
void seenChannel(Conversation conversation);
11+
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.dledmonds.slack;
2+
3+
import java.io.File;
4+
import java.io.FileInputStream;
5+
import java.io.FileOutputStream;
6+
import java.io.IOException;
7+
import java.io.InputStream;
8+
import java.util.Properties;
9+
10+
/**
11+
* @author dledmonds
12+
*/
13+
public class ConsoleRunner {
14+
15+
16+
17+
public static void main(String[] args) {
18+
File configFile = new File("slack.properties");
19+
if (!configFile.exists()) {
20+
System.err.println("No slack.properties file exists in current directory");
21+
System.exit(1);
22+
}
23+
Properties p = new Properties();
24+
try (InputStream in = new FileInputStream(configFile)) {
25+
p.load(in);
26+
} catch (IOException ioe) {
27+
System.err.println("Unable to read slack.properties");
28+
System.exit(1);
29+
}
30+
String token = p.getProperty("slack.token");
31+
if (token == null || token.isEmpty()) {
32+
System.err.println("slack.token must be set");
33+
System.exit(1);
34+
}
35+
36+
try {
37+
SlackEngine st = new SlackEngine(token, args);
38+
//StreamOutputProcessor p1 = new StreamOutputProcessor(System.out);
39+
StreamOutputProcessor p1 = new StreamOutputProcessor(new FileOutputStream(System.currentTimeMillis() + ".log"));
40+
st.addChannelProcessor(p1);
41+
st.addUserProcessor(p1);
42+
st.addMessageProcessor(p1);
43+
44+
UserMessageCountProcessor p2 = new UserMessageCountProcessor(10);
45+
st.addChannelProcessor(p2);
46+
st.addUserProcessor(p2);
47+
st.addMessageProcessor(p2);
48+
49+
UserHereOrChannelMessageCountProcessor p3 = new UserHereOrChannelMessageCountProcessor(10);
50+
st.addChannelProcessor(p3);
51+
st.addUserProcessor(p3);
52+
st.addMessageProcessor(p3);
53+
54+
UserMessageCountPerDayProcessor p4 = new UserMessageCountPerDayProcessor(10);
55+
st.addChannelProcessor(p4);
56+
st.addUserProcessor(p4);
57+
st.addMessageProcessor(p4);
58+
59+
st.run(); // single threaded
60+
61+
System.out.println();
62+
p2.outputResults(System.out);
63+
64+
System.out.println();
65+
p4.outputResults(System.out);
66+
67+
System.out.println();
68+
p3.outputResults(System.out);
69+
} catch (Exception e) {
70+
e.printStackTrace(System.err);
71+
}
72+
}
73+
74+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
package com.dledmonds.slack;
2+
3+
import com.github.seratch.jslack.api.model.Message;
4+
5+
/**
6+
* @author dledmonds
7+
*/
8+
public interface MessageProcessor {
9+
10+
void seenMessage(String channel, Message message);
11+
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
package com.dledmonds.slack;
2+
3+
import com.github.seratch.jslack.Slack;
4+
import com.github.seratch.jslack.api.methods.SlackApiException;
5+
import com.github.seratch.jslack.api.methods.request.conversations.ConversationsHistoryRequest;
6+
import com.github.seratch.jslack.api.methods.request.conversations.ConversationsInfoRequest;
7+
import com.github.seratch.jslack.api.methods.request.conversations.ConversationsListRequest;
8+
import com.github.seratch.jslack.api.methods.request.users.UsersListRequest;
9+
import com.github.seratch.jslack.api.methods.response.conversations.ConversationsHistoryResponse;
10+
import com.github.seratch.jslack.api.methods.response.conversations.ConversationsInfoResponse;
11+
import com.github.seratch.jslack.api.methods.response.conversations.ConversationsListResponse;
12+
import com.github.seratch.jslack.api.methods.response.users.UsersListResponse;
13+
import com.github.seratch.jslack.api.model.Conversation;
14+
import com.github.seratch.jslack.api.model.ConversationType;
15+
import com.github.seratch.jslack.api.model.Message;
16+
import com.github.seratch.jslack.api.model.User;
17+
18+
import java.io.IOException;
19+
import java.util.ArrayList;
20+
import java.util.Arrays;
21+
import java.util.Iterator;
22+
import java.util.List;
23+
24+
/**
25+
* @author dledmonds
26+
*/
27+
public class SlackEngine implements Runnable {
28+
29+
private Slack slack;
30+
private String token;
31+
private List<String> allowedChannels;
32+
private List<ChannelProcessor> channelProcessors;
33+
private List<MessageProcessor> messageProcessors;
34+
private List<UserProcessor> userProcessors;
35+
36+
SlackEngine(String token, String ... channelIds) {
37+
this.token = token;
38+
this.allowedChannels = Arrays.asList(channelIds);
39+
this.slack = Slack.getInstance();
40+
this.channelProcessors = new ArrayList<>();
41+
this.messageProcessors = new ArrayList<>();
42+
this.userProcessors = new ArrayList<>();
43+
}
44+
45+
void addChannelProcessor(ChannelProcessor channelProcessor) {
46+
channelProcessors.add(channelProcessor);
47+
}
48+
49+
void addMessageProcessor(MessageProcessor messageProcessor) {
50+
messageProcessors.add(messageProcessor);
51+
}
52+
53+
void addUserProcessor(UserProcessor userProcessor) {
54+
userProcessors.add(userProcessor);
55+
}
56+
57+
public void run() {
58+
try {
59+
getUsers();
60+
getChannels(); // calls getMessages as it loops
61+
} catch (Exception e) {
62+
e.printStackTrace(System.err);
63+
}
64+
}
65+
66+
private void getChannels() throws IOException, SlackApiException {
67+
boolean keepGoing = true;
68+
String nextCursor = null;
69+
70+
List<Conversation> allChannels = new ArrayList<>();
71+
if (!allowedChannels.isEmpty()) {
72+
for (String channelId : allowedChannels) {
73+
ConversationsInfoRequest ciReq = ConversationsInfoRequest.builder().token(token).channel(channelId).build();
74+
ConversationsInfoResponse ciResp = slack.methods().conversationsInfo(ciReq);
75+
allChannels.add(ciResp.getChannel());
76+
}
77+
} else {
78+
ConversationsListRequest clReq = ConversationsListRequest.builder().token(token).types(Arrays.asList(ConversationType.PUBLIC_CHANNEL)).build();
79+
while (keepGoing) {
80+
clReq.setCursor(nextCursor);
81+
ConversationsListResponse clResp = slack.methods().conversationsList(clReq);
82+
System.out.println("Adding " + clResp.getChannels().size() + " channels");
83+
allChannels.addAll(clResp.getChannels());
84+
85+
nextCursor = clResp.getResponseMetadata().getNextCursor();
86+
if (nextCursor == null || nextCursor.isEmpty()) keepGoing = false;
87+
}
88+
}
89+
System.out.println("Got " + allChannels.size() + " channels");
90+
// sort by name
91+
allChannels.sort((Conversation c1, Conversation c2) -> c1.getName().compareTo(c2.getName()));
92+
93+
for (Conversation conversation : allChannels) {
94+
if (!conversation.isChannel() && !allowedChannels.contains(conversation.getId())) continue; // only process channels
95+
for (ChannelProcessor cp : channelProcessors) {
96+
cp.seenChannel(conversation);
97+
}
98+
99+
getMessages(conversation.getId());
100+
}
101+
}
102+
103+
private void getUsers() throws IOException, SlackApiException {
104+
boolean keepGoing = true;
105+
String nextCursor = null;
106+
107+
List<User> allUsers = new ArrayList<>();
108+
UsersListRequest uReq = UsersListRequest.builder().token(token).build();
109+
while (keepGoing) {
110+
uReq.setCursor(nextCursor);
111+
UsersListResponse uResp = slack.methods().usersList(uReq);
112+
113+
System.out.println("Adding " + uResp.getMembers().size() + " users");
114+
allUsers.addAll(uResp.getMembers());
115+
116+
nextCursor = uResp.getResponseMetadata() == null ? null : uResp.getResponseMetadata().getNextCursor();
117+
if (nextCursor == null || nextCursor.isEmpty()) keepGoing = false;
118+
}
119+
System.out.println("Got " + allUsers.size() + " users");
120+
// alphabetical sort
121+
allUsers.sort((User u1, User u2) -> u1.getName().compareTo(u2.getName()));
122+
123+
for (User user : allUsers) {
124+
for (UserProcessor up : userProcessors) {
125+
up.seenUser(user);
126+
}
127+
}
128+
}
129+
130+
private void getMessages(String channel) throws IOException, SlackApiException {
131+
boolean keepGoing = true;
132+
String nextCursor = null;
133+
long loopCount = 0;
134+
135+
List<Message> allMessages = new ArrayList<>();
136+
ConversationsHistoryRequest chReq = ConversationsHistoryRequest.builder().token(token).channel(channel).build();
137+
while (keepGoing) {
138+
chReq.setCursor(nextCursor);
139+
ConversationsHistoryResponse chResp = slack.methods().conversationsHistory(chReq);
140+
141+
System.out.println("Adding "+ (++loopCount) + "-" + chResp.getMessages().size() + " messages");
142+
allMessages.addAll(chResp.getMessages());
143+
144+
nextCursor = chResp.getResponseMetadata() == null ? null : chResp.getResponseMetadata().getNextCursor();
145+
if (nextCursor == null || nextCursor.isEmpty()) keepGoing = false;
146+
147+
//if (allMessages.size() >= 500) keepGoing = false;
148+
}
149+
//if (allMessages.size() < 500) return;
150+
151+
System.out.println("Got " + allMessages.size() + " messages for " + channel);
152+
// sort by timestamp
153+
allMessages.sort((Message m1, Message m2) -> m1.getTs().compareTo(m2.getTs()));
154+
155+
for (Message message : allMessages) {
156+
if (message.getUser() == null) continue; // only process messages sent by users/bots
157+
for (MessageProcessor mp : messageProcessors) {
158+
mp.seenMessage(channel, message);
159+
}
160+
}
161+
}
162+
163+
private void pauseFor(long millis) {
164+
try {
165+
Thread.sleep(millis);
166+
} catch (InterruptedException ie) {
167+
// ignore this
168+
}
169+
}
170+
171+
}

0 commit comments

Comments
 (0)