diff options
| -rw-r--r-- | README.md | 3 | ||||
| -rw-r--r-- | main.py | 30 | ||||
| -rw-r--r-- | msgraph.py | 93 | ||||
| -rw-r--r-- | slack_parser.py | 32 | ||||
| -rw-r--r-- | test.py | 41 |
5 files changed, 103 insertions, 96 deletions
@@ -1,5 +1,8 @@ # SlackToTeams +Code style: Black [](https://github.com/psf/black) +Code linter: Pyflakes [](https://github.com/PyCQA/pyflakes) +--- ## What it does Migrate Slack messages into Teams. The information that gets retained for each message: - the (user) sender of the message. @@ -13,7 +13,7 @@ from msgraph import MSGraphApi logging.basicConfig(level=logging.INFO) LIMIT_THROUGHPUT = False -if __name__ == '__main__': +if __name__ == "__main__": # run the tests first suite = unittest.TestLoader().loadTestsFromModule(test) @@ -24,7 +24,7 @@ if __name__ == '__main__': config = json.load(open(sys.argv[1])) - #msapi = MSGraphApi(config).getAuth() # "app flow" + # msapi = MSGraphApi(config).getAuth() # "app flow" msapi = MSGraphApi(config, flow="obo").getAuth() if msapi is not None: @@ -34,19 +34,21 @@ if __name__ == '__main__': teamId = msapi.getTeamByName(config["teamname"]) channelId = msapi.getChatByNameAndTeamId(teamId, config["channelname"]) - #graph_data = msapi.getUsers().json() - #graph_data = msapi.getChatByTeamAndId(teamId, channelId).json() - #graph_data = msapi.writeChatMessageByTeamAndId( + # graph_data = msapi.getUsers().json() + # graph_data = msapi.getChatByTeamAndId(teamId, channelId).json() + # graph_data = msapi.writeChatMessageByTeamAndId( # teamId=teamId, # channelId=channelId, # payload={ "body" : { "content" : "hello world" } } - #).json() + # ).json() - #print("Graph API call result: ") - #print(json.dumps(graph_data, indent=2)) - #sys.exit(0) + # print("Graph API call result: ") + # print(json.dumps(graph_data, indent=2)) + # sys.exit(0) - sp = SlackParser("resources/channels/devops", "resources/users.json", ["text", "ts", "name"]).buildUsersLibrary() + sp = SlackParser( + "resources/channels/devops", "resources/users.json", ["text", "ts", "name"] + ).buildUsersLibrary() if sp.usersLib is {}: logging.error("Failed to read users. Shutting down with code 1.") sys.exit(1) @@ -62,9 +64,7 @@ if __name__ == '__main__': if reduced_message is not None: teams_mssg = msapi.buildTeamsMessage(reduced_message) graph_data = msapi.writeChatMessageByTeamAndId( - teamId=teamId, - channelId=channelId, - payload=teams_mssg + teamId=teamId, channelId=channelId, payload=teams_mssg ) if graph_data.status_code != 201: logging.error("failed to write to Teams") @@ -82,5 +82,7 @@ if __name__ == '__main__': time.sleep(1) else: - logging.error("Failed to Authenticate with Ms Graph. Shutting down with code 1.") + logging.error( + "Failed to Authenticate with Ms Graph. Shutting down with code 1." + ) sys.exit(1) @@ -3,6 +3,7 @@ import json import requests import msal + class MSGraphApi(object): """Docstring for MSGraphApi. """ @@ -21,8 +22,7 @@ class MSGraphApi(object): self.flow = flow if self.flow == "obo": app = msal.PublicClientApplication( - self.config["client_id"], - authority=self.config["authority"] + self.config["client_id"], authority=self.config["authority"] ) else: # no flow was given, assume app flow @@ -46,8 +46,8 @@ class MSGraphApi(object): # Firstly, looks up a token from cache if self.flow == "obo": result = self.app.acquire_token_silent( - self.config["scope"], - account=self.app.get_accounts(self.config["username"]) + self.config["scope"], + account=self.app.get_accounts(self.config["username"]), ) else: # Since we are looking for token for the current app, NOT for an end user, @@ -55,13 +55,15 @@ class MSGraphApi(object): result = self.app.acquire_token_silent(self.config["scope"], account=None) if not result: - logging.info("No suitable token exists in cache. Let's get a new one from AAD.") + logging.info( + "No suitable token exists in cache. Let's get a new one from AAD." + ) if self.flow == "obo": result = self.app.acquire_token_by_username_password( - self.config["username"], - self.config["password"], - scopes=self.config["scope"] + self.config["username"], + self.config["password"], + scopes=self.config["scope"], ) else: result = self.app.acquire_token_for_client(scopes=self.config["scope"]) @@ -73,7 +75,9 @@ class MSGraphApi(object): else: logging.error(result.get("error")) logging.error(result.get("error_description")) - logging.error(result.get("correlation_id")) # may need this when reporting a bug + logging.error( + result.get("correlation_id") + ) # may need this when reporting a bug return None def setAccessToken(self, tok): @@ -93,12 +97,9 @@ class MSGraphApi(object): """ endpoint = "https://graph.microsoft.com/v1.0/users" - headers = { "Authorization": "Bearer " + self.access_token } + headers = {"Authorization": "Bearer " + self.access_token} - graph_data = requests.get( - endpoint, - headers=headers - ) + graph_data = requests.get(endpoint, headers=headers) return graph_data def getMyTeams(self): @@ -109,15 +110,11 @@ class MSGraphApi(object): """ endpoint = "https://graph.microsoft.com/beta/me/joinedTeams" - headers = { "Authorization": "Bearer " + self.access_token } + headers = {"Authorization": "Bearer " + self.access_token} - graph_data = requests.get( - endpoint, - headers=headers - ) + graph_data = requests.get(endpoint, headers=headers) return graph_data - def getChatsByTeam(self, teamId): """Docstring for getChatsByTeam. Get all chats in given team id. @@ -127,12 +124,9 @@ class MSGraphApi(object): """ endpoint = "https://graph.microsoft.com/beta/teams/%s/channels/" % (teamId) - headers = { "Authorization": "Bearer " + self.access_token } + headers = {"Authorization": "Bearer " + self.access_token} - graph_data = requests.get( - endpoint, - headers=headers - ) + graph_data = requests.get(endpoint, headers=headers) return graph_data def getChatByTeamAndId(self, teamId, channelId): @@ -144,13 +138,13 @@ class MSGraphApi(object): :returns: respose object containing ms response """ - endpoint = "https://graph.microsoft.com/beta/teams/%s/channels/%s" % (teamId, channelId) - headers = { "Authorization": "Bearer " + self.access_token } - - graph_data = requests.get( - endpoint, - headers=headers + endpoint = "https://graph.microsoft.com/beta/teams/%s/channels/%s" % ( + teamId, + channelId, ) + headers = {"Authorization": "Bearer " + self.access_token} + + graph_data = requests.get(endpoint, headers=headers) return graph_data def writeChatMessageByTeamAndId(self, teamId, channelId, payload): @@ -163,22 +157,21 @@ class MSGraphApi(object): """ # other pathways - #userId = "8a18e679-3603-4e10-a5e4-c75ad362fde0" - #endpoint = "https://graph.microsoft.com/beta/users/%s/chats/%s/messages" % (userId, channelId) - #endpoint = "https://graph.microsoft.com/beta/chats/%s/messages" % (channelId) + # userId = "8a18e679-3603-4e10-a5e4-c75ad362fde0" + # endpoint = "https://graph.microsoft.com/beta/users/%s/chats/%s/messages" % (userId, channelId) + # endpoint = "https://graph.microsoft.com/beta/chats/%s/messages" % (channelId) # ms recommended pathway - endpoint = "https://graph.microsoft.com/beta/teams/%s/channels/%s/messages/" % (teamId, channelId) + endpoint = "https://graph.microsoft.com/beta/teams/%s/channels/%s/messages/" % ( + teamId, + channelId, + ) headers = { "Authorization": "Bearer " + self.access_token, - "Content-Type": "application/json" + "Content-Type": "application/json", } - graph_data = requests.post( - endpoint, - headers=headers, - data=json.dumps(payload) - ) + graph_data = requests.post(endpoint, headers=headers, data=json.dumps(payload)) return graph_data def getTeamByName(self, teamName): @@ -234,19 +227,19 @@ class MSGraphApi(object): # schema teams_mssg = { "messageType": "message", - "body": { - "content": "", - "contentType": "html" - } + "body": {"content": "", "contentType": "html"}, } # convert ``` snippets into block quotes ie. #<blockquote> some code </blockquote> rich_html_content = MSGraphApi.insertCodeBlocksInMessage(customMessage["text"]) - html_content = "<HTML><BODY>" + \ - "<p>From: <strong>%s</strong> <small>%s</small></p>" % (customMessage["name"], customMessage["ts"]) + \ - "<p>%s</p>" % (rich_html_content) + \ - "</BODY></HTML>" + html_content = ( + "<HTML><BODY>" + + "<p>From: <strong>%s</strong> <small>%s</small></p>" + % (customMessage["name"], customMessage["ts"]) + + "<p>%s</p>" % (rich_html_content) + + "</BODY></HTML>" + ) split_content = html_content.split("\n") if len(split_content) > 1: @@ -257,7 +250,7 @@ class MSGraphApi(object): # TODO: sanitize string - #teams_mssg["body"]["content"] = html_content + # teams_mssg["body"]["content"] = html_content teams_mssg["body"]["content"] = split_content_str return teams_mssg diff --git a/slack_parser.py b/slack_parser.py index 8756992..d99e7c2 100644 --- a/slack_parser.py +++ b/slack_parser.py @@ -2,17 +2,14 @@ import os import json import re import time -from jsonpath_ng import jsonpath, parse +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 -} +key_orderings = {"text": 0, "ts": 1, "name": 2} + class SlackParser(object): @@ -39,7 +36,7 @@ class SlackParser(object): :returns: a sorted list of json files names for the channel """ - files = sorted(os.listdir( self.messagesDir )) + files = sorted(os.listdir(self.messagesDir)) return files def getUsers(self): @@ -54,7 +51,6 @@ class SlackParser(object): content = json.load(open(path)) return content - def buildUsersLibrary(self): """Docstring for buildUsersLibrary Build a Users dict that keeps only usersid to name mappings. @@ -107,7 +103,7 @@ class SlackParser(object): 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]) + # 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 @@ -115,11 +111,17 @@ class SlackParser(object): userIds = self.getUserIdsFromMessage(data["text"]) if userIds != []: # find corresponding usernames from library - userNamesinMessageDict = self.getUserNamesFromIds(self.usersLib, userIds) + userNamesinMessageDict = self.getUserNamesFromIds( + self.usersLib, userIds + ) # inject usernames in the text replace userid - data["text"] = self.replaceMessageUserIdsWithNames(userNamesinMessageDict, data["text"]) + 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"])) ) + data["ts"] = time.strftime( + "%H:%M %b %d, %Y", time.localtime(float(data["ts"])) + ) return data @@ -165,5 +167,9 @@ class SlackParser(object): """ for userId in messageUsersDict.keys(): - text = re.sub( "<@{}>".format(userId), "<i style='color: #B18904;'>@{}</i>".format(messageUsersDict[userId]), text ) + text = re.sub( + "<@{}>".format(userId), + "<i style='color: #B18904;'>@{}</i>".format(messageUsersDict[userId]), + text, + ) return text @@ -3,10 +3,12 @@ import unittest from msgraph import MSGraphApi from slack_parser import SlackParser + class Test(unittest.TestCase): """ The basic class that inherits unittest.TestCase """ + msapi = MSGraphApi() sp = SlackParser() @@ -15,19 +17,19 @@ class Test(unittest.TestCase): input_string = "sdfsdf" expected = "sdfsdf" actual = self.msapi.insertCodeBlocksInMessage(input_string) - self.assertEqual( expected, actual ) + self.assertEqual(expected, actual) def test_1_insert_block_quotes(self): input_string = "``` CODE1 ```" expected = "<blockquote> CODE1 </blockquote>" actual = self.msapi.insertCodeBlocksInMessage(input_string) - self.assertEqual( expected, actual ) + self.assertEqual(expected, actual) def test_2_insert_block_quotes(self): input_string = " there and ``` CODE 1 ``` asdfasdf ``` CODE 2 ``` and then again ``` CODE 3 ``` end." expected = " there and <blockquote> CODE 1 </blockquote> asdfasdf <blockquote> CODE 2 </blockquote> and then again <blockquote> CODE 3 </blockquote> end." actual = self.msapi.insertCodeBlocksInMessage(input_string) - self.assertEqual( expected, actual ) + self.assertEqual(expected, actual) def test_3_insert_long_block_quotes(self): input_string = """<@U5F956SJH> a long running query on the master from job01.....job-consumer-2 logs showing\n```>1|ERROR|2020-03-17 15:50:00,018-0400|org.springframework.jms.listener.DefaultMessageListenerContainer#0-3|GenerateEmailNotificationTemplate:106|Unexpected error in processing. Abending.\norg.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:\n### Error querying database. Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.acuityads.notification.dao.NotificationMapper.findNotifications\n### Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.acuityads.notification.dao.NotificationMapper.findNotifications\n at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:77)\n at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)\n at com.sun.proxy.$Proxy49.selectList(Unknown Source)\n at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230)\n at com.acuityads.persistence.dao.SelectQuery.selectList(SelectQuery.java:74)```\n @@ -36,7 +38,7 @@ class Test(unittest.TestCase): """ actual = self.msapi.insertCodeBlocksInMessage(input_string) - self.assertEqual( expected, actual ) + self.assertEqual(expected, actual) def test_4_insert_block_quotes(self): input_string = """<@U5F956SJH> a long running query on the master from job01.....job-consumer-2 logs showing<p>```>1|ERROR|2020-03-17 15:50:00,018-0400|org.springframework.jms.listener.DefaultMessageListenerContainer#0-3|GenerateEmailNotificationTemplate:106|Unexpected error in processing. Abending.</p>org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:<p>### Error querying database. Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.acuityads.notification.dao.NotificationMapper.findNotifications</p>### Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.acuityads.notification.dao.NotificationMapper.findNotifications<p> at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:77)</p> at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)<p> at com.sun.proxy.$Proxy49.selectList(Unknown Source)</p> at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230)<p> at com.acuityads.persistence.dao.SelectQuery.selectList(SelectQuery.java:74)```</p> @@ -44,53 +46,54 @@ class Test(unittest.TestCase): expected = """<@U5F956SJH> a long running query on the master from job01.....job-consumer-2 logs showing<p><blockquote>>1|ERROR|2020-03-17 15:50:00,018-0400|org.springframework.jms.listener.DefaultMessageListenerContainer#0-3|GenerateEmailNotificationTemplate:106|Unexpected error in processing. Abending.</p>org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.exceptions.PersistenceException:<p>### Error querying database. Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.acuityads.notification.dao.NotificationMapper.findNotifications</p>### Cause: java.lang.IllegalArgumentException: Mapped Statements collection does not contain value for com.acuityads.notification.dao.NotificationMapper.findNotifications<p> at org.mybatis.spring.MyBatisExceptionTranslator.translateExceptionIfPossible(MyBatisExceptionTranslator.java:77)</p> at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:446)<p> at com.sun.proxy.$Proxy49.selectList(Unknown Source)</p> at org.mybatis.spring.SqlSessionTemplate.selectList(SqlSessionTemplate.java:230)<p> at com.acuityads.persistence.dao.SelectQuery.selectList(SelectQuery.java:74)</blockquote></p> """ actual = self.msapi.insertCodeBlocksInMessage(input_string) - self.assertEqual( expected, actual ) + self.assertEqual(expected, actual) def test_5_parse_user_ids(self): input_string = "a long history of thisg" expected = [] actual = self.sp.getUserIdsFromMessage(input_string) - self.assertEqual( expected, actual ) + self.assertEqual(expected, actual) def test_6_parse_user_ids(self): input_string = "<@U32498ksjl> a long history of thisg" expected = ["U32498ksjl"] actual = self.sp.getUserIdsFromMessage(input_string) - self.assertEqual( expected, actual ) + self.assertEqual(expected, actual) def test_7_parse_user_ids(self): input_string = " what <p> <@U32498ksjl> a long history </p> of thisg <@kumar12>" expected = ["U32498ksjl", "kumar12"] actual = self.sp.getUserIdsFromMessage(input_string) - self.assertEqual( expected, actual ) + self.assertEqual(expected, actual) def test_8_get_user_name_from_user_ids(self): - usersDict = { "U32498ksjl" : "kd1" , "UBLAH" : "kd2" } + usersDict = {"U32498ksjl": "kd1", "UBLAH": "kd2"} test_input = ["U32498ksjl"] - expected = { "U32498ksjl" : "kd1" } + expected = {"U32498ksjl": "kd1"} actual = self.sp.getUserNamesFromIds(usersDict, test_input) - self.assertEqual( expected, actual ) + self.assertEqual(expected, actual) def test_9_get_user_name_from_user_ids(self): - usersDict = { "U32498ksjl" : "kd1" , "UBLAH" : "kd2" } + usersDict = {"U32498ksjl": "kd1", "UBLAH": "kd2"} test_input = ["U32498ksjl", "UBLAH"] - expected = { "U32498ksjl" : "kd1", "UBLAH" : "kd2" } + expected = {"U32498ksjl": "kd1", "UBLAH": "kd2"} actual = self.sp.getUserNamesFromIds(usersDict, test_input) - self.assertEqual( expected, actual ) + self.assertEqual(expected, actual) def test_10_replace_userids_with_names(self): - usersDict = { "U32498ksjl" : "kd1" } + usersDict = {"U32498ksjl": "kd1"} input_string = "<@U32498ksjl> a long history of thisg" expected = "<i style='color: #B18904;'>@kd1</i> a long history of thisg" actual = self.sp.replaceMessageUserIdsWithNames(usersDict, input_string) - self.assertEqual( expected, actual ) + self.assertEqual(expected, actual) def test_11_replace_userids_with_names(self): - usersDict = { "U32498ksjl" : "kd1" , "UBLAH" : "kd2" } + usersDict = {"U32498ksjl": "kd1", "UBLAH": "kd2"} input_string = "<@U32498ksjl> a long history of thisg from <@UBLAH> and this <@UBLAH> again." expected = "<i style='color: #B18904;'>@kd1</i> a long history of thisg from <i style='color: #B18904;'>@kd2</i> and this <i style='color: #B18904;'>@kd2</i> again." actual = self.sp.replaceMessageUserIdsWithNames(usersDict, input_string) - self.assertEqual( expected, actual ) + self.assertEqual(expected, actual) + -if __name__ == '__main__': +if __name__ == "__main__": unittest.main() |
