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), "@{}".format(messageUsersDict[userId]), text, ) return text