aboutsummaryrefslogtreecommitdiff
path: root/slack_parser.py
blob: d99e7c2cb0ecb8504582369c24d252059bfe9574 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
import os
import json
import re
import time
from jsonpath_ng import parse


# jsonpath match will match in order of appearence.
# This mapping defines which position in the RESULT of match to look for which key.
# so. eg. at 0th position (of the match) will be the key "text", etc.
key_orderings = {"text": 0, "ts": 1, "name": 2}


class SlackParser(object):

    """Docstring for SlackParser. """

    def __init__(self, messagesDir=None, usersFile=None, keys=None):

        self.messagesDir = ""
        self.usersFile = ""
        self.keys = list()
        if messagesDir is not None:
            self.messagesDir += messagesDir
        if usersFile is not None:
            self.usersFile += usersFile
        if keys is not None:
            self.keys += keys

        self.usersLib = None

    def getSortedMessageFiles(self):
        """Docstring for getSortedMessageFiles.
        Get the files that contain channel data sorted.

        :returns: a sorted list of json files names for the channel

        """
        files = sorted(os.listdir(self.messagesDir))
        return files

    def getUsers(self):
        """Docstring for getUsers
        Get the parsed json that contains the raw user info.

        :returns: a dict with all users infos

        """
        path = self.usersFile
        # TODO: check for path validity
        content = json.load(open(path))
        return content

    def buildUsersLibrary(self):
        """Docstring for buildUsersLibrary
        Build a Users dict that keeps only usersid to name mappings.

        :returns: a dict user info

        """
        userLib = {}
        for userjson in self.getUsers():
            if "name" in userjson and "id" in userjson:
                userLib[userjson["id"]] = userjson["name"]
        self.setUsersLib(userLib)
        return self

    def setUsersLib(self, usersLib):
        """Docstring for buildUsersLibrary
        A setter method that sets the object lib to the given lib.

        """
        self.usersLib = usersLib

    def getMessages(self, slackFile):
        """Docstring for getMessages.
        Given a file, get the messages in that file.

        :slackFile: the json file name containing messages
        :returns: a list of all messages in the file

        """
        path = self.messagesDir + "/" + slackFile
        # TODO: check for path validity
        content = json.load(open(path))
        return content

    def getKeyValues(self, slackMessage):
        """Docstring for getKeyValues.
        Get the value for each key (defined during init) from the mssg.

        :slackMessage: the parsed json slack message
        :returns: on success, a dict with only required keys and values
                  on failure, None

        """
        data = None
        # we also check for "subtype" to filter out mssgs like:
        # "s joined the channel", "x left the channel" etc.
        if "type" in slackMessage and "subtype" not in slackMessage:
            if slackMessage["type"] == "message" and slackMessage["text"] != "":
                data = {}
                jsonpath_expression = parse("$.." + str(self.keys))
                # search slackMessage for each key, and add all matches (incl. dups.) in-order
                match = jsonpath_expression.find(slackMessage)
                # print(match[0])
                for key in self.keys:
                    # TODO: validation for matches possible "index out of range" here.
                    data[key] = match[key_orderings[key]].value
                # get the user id in the text
                userIds = self.getUserIdsFromMessage(data["text"])
                if userIds != []:
                    # find corresponding usernames from library
                    userNamesinMessageDict = self.getUserNamesFromIds(
                        self.usersLib, userIds
                    )
                    # inject usernames in the text replace userid
                    data["text"] = self.replaceMessageUserIdsWithNames(
                        userNamesinMessageDict, data["text"]
                    )
                # convert ts to a human readble time
                data["ts"] = time.strftime(
                    "%H:%M %b %d, %Y", time.localtime(float(data["ts"]))
                )

        return data

    @staticmethod
    def getUserIdsFromMessage(text):
        """Docstring for getUserIdsFromMessage.
        A helper method that searches a text for Slack user ids. It maps:
        "<@SLACKID1>adsfasdf <@SLACKID2>" -> ["SLACKID1", "SLACKID2"]

        :text: raw text from Slack message json
        :returns: a list of userids strings in the text in order of match

        """
        regex = r"<@(.+?)>"
        matches = re.findall(regex, text)
        return matches

    @staticmethod
    def getUserNamesFromIds(usersDict, userIds):
        """Docstring for getUserNamesFromIds.
        A helper method to search a dict for a keys, and get the values.

        :usersDict: a dict library of userid and name mappings
        :ids: a string list of userids
        :returns: dict of each userid and its username

        """
        userMap = {}
        for userId in userIds:
            if userId in usersDict:
                userMap[userId] = usersDict[userId]
        return userMap

    @staticmethod
    def replaceMessageUserIdsWithNames(messageUsersDict, text):
        """Docstring for replaceMessageUserIdsWithNames.
        A helper method to replace all Slackuser id tags with their @username. So it maps:
        { "USERID1" : "name1" , "USERID2": "name2" },"<@USERID1> blah blah <@USERID2>." -> "@name1 blah blah @name2."

        :messageUsersDict: the dict containing userid to username mappings for this text
        :text: the original string contianing Slack userids in it
        :returns: a string with each Slack userid replaced with mapping value from dict.

        """
        for userId in messageUsersDict.keys():
            text = re.sub(
                "<@{}>".format(userId),
                "<i style='color: #B18904;'>@{}</i>".format(messageUsersDict[userId]),
                text,
            )
        return text