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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
|
import logging
import json
import requests
import msal
class MSGraphApi(object):
"""Docstring for MSGraphApi. """
def __init__(self, config=None, flow="app"):
# TODO: make these private vars
self.config = {}
self.app = None
self.access_token = None
if config is not None:
self.config = config
else:
return
self.flow = flow
if self.flow == "obo":
app = msal.PublicClientApplication(
self.config["client_id"], authority=self.config["authority"]
)
else:
# no flow was given, assume app flow
app = msal.ConfidentialClientApplication(
self.config["client_id"],
authority=self.config["authority"],
client_credential=self.config["secret"],
)
self.app = app
def getAuth(self):
"""Docstring for getAuth.
Get the MS Authorization to use the Graph API
:returns: on success, the instance of this object - self
on failure, None
"""
# The pattern to acquire a token looks like this.
result = None
# 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"]),
)
else:
# Since we are looking for token for the current app, NOT for an end user,
# notice we give account parameter as None.
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."
)
if self.flow == "obo":
result = self.app.acquire_token_by_username_password(
self.config["username"],
self.config["password"],
scopes=self.config["scope"],
)
else:
result = self.app.acquire_token_for_client(scopes=self.config["scope"])
# auth succeeded
if "access_token" in result:
self.setAccessToken(result["access_token"])
return self
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
return None
def setAccessToken(self, tok):
"""Docstring for setAccessToken.
A setter method to set access_token for the object instance.
:tok: the ms access token
"""
self.access_token = tok
def getUsers(self):
"""Docstring for getUsers.
Get all the users in the ms org.
:returns: respose object containing ms response
"""
endpoint = "https://graph.microsoft.com/v1.0/users"
headers = {"Authorization": "Bearer " + self.access_token}
graph_data = requests.get(endpoint, headers=headers)
return graph_data
def getMyTeams(self):
"""Docstring for getUsers.
Get all the teams for this user.
:returns: respose object containing ms response
"""
endpoint = "https://graph.microsoft.com/beta/me/joinedTeams"
headers = {"Authorization": "Bearer " + self.access_token}
graph_data = requests.get(endpoint, headers=headers)
return graph_data
def getChatsByTeam(self, teamId):
"""Docstring for getChatsByTeam.
Get all chats in given team id.
:teamId: the team ID for the team to get the chats for
:returns: respose object containing ms response
"""
endpoint = "https://graph.microsoft.com/beta/teams/%s/channels/" % (teamId)
headers = {"Authorization": "Bearer " + self.access_token}
graph_data = requests.get(endpoint, headers=headers)
return graph_data
def getChatByTeamAndId(self, teamId, channelId):
"""Docstring for getChatByTeamAndId.
Get a specific channel given team and channel ids.
:teamId: the team ID for the team to get the chats for
:channelId: the channel ID for the channel to get
: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)
return graph_data
def writeChatMessageByTeamAndId(self, teamId, channelId, payload):
"""Docstring for writeChatMessageByTeamAndId.
:teamId: the team ID for the team to post the chat for
:channelId: the chat/channel ID for the channel to post to
:payload: the exact POST dict payload to be used
:returns: respose object containing ms response
"""
# 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)
# ms recommended pathway
endpoint = "https://graph.microsoft.com/beta/teams/%s/channels/%s/messages/" % (
teamId,
channelId,
)
headers = {
"Authorization": "Bearer " + self.access_token,
"Content-Type": "application/json",
}
graph_data = requests.post(endpoint, headers=headers, data=json.dumps(payload))
return graph_data
def getTeamByName(self, teamName):
"""TODO: Docstring for getTeamByName.
:teamName: TODO
:returns: TODO
"""
# get all my teams
teamsLib = self.getMyTeams().json()
if "value" in teamsLib:
teams = {}
for team in teamsLib["value"]:
teams[team["id"]] = team["displayName"]
# search lib for my team
teamid = self.searchValuesinDict(teams, teamName)
return teamid
return ""
def getChatByNameAndTeamId(self, teamId, channelName):
"""TODO: Docstring for getChatByName.
:channelName: TODO
:returns: TODO
"""
# get all channels in the Teams
channelsLib = self.getChatsByTeam(teamId).json()
if "value" in channelsLib:
channels = {}
for channel in channelsLib["value"]:
channels[channel["id"]] = channel["displayName"]
# search lib for my channel
chatId = self.searchValuesinDict(channels, channelName)
return chatId
return ""
@staticmethod
def buildTeamsMessage(customMessage):
"""Docstring for buildTeamsMessage.
Build a Teams compatible message as per spec:
https://docs.microsoft.com/en-us/graph/api/resources/chatmessage?view=graph-rest-beta
:customMessage: an dict containing the "name" and "text" fields
:returns: a valid Teams message dict payload
"""
# schema
teams_mssg = {
"messageType": "message",
"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>"
)
split_content = html_content.split("\n")
if len(split_content) > 1:
for i, line in enumerate(split_content):
split_content[i] = "<p>{}</p>".format(line)
split_content_str = "".join(split_content)
# TODO: sanitize string
# teams_mssg["body"]["content"] = html_content
teams_mssg["body"]["content"] = split_content_str
return teams_mssg
@staticmethod
def insertCodeBlocksInMessage(text):
"""Docstring for insertCodeBlocksInMessage.
Replace ``` code ``` to be html compliant, i.e. wrapped in blockquotes
:text: the input string possibly containing ```
:returns: a string with html codeblocks instead of ```
"""
toks = text.split("```")
for i, tok in enumerate(toks):
# IMPORTANT! this assumes that every ``` comes in pairs
if (i % 2) != 0:
toks[i] = "<blockquote>{}</blockquote>".format(tok)
quoted_str = "".join(toks)
return quoted_str
@staticmethod
def searchValuesinDict(lib, value):
"""TODO: Docstring for searchValuesinDict.
:returns: the key of the dict where the value matches
"""
for key in lib.keys():
if lib[key] == value:
return key
return ""
|