]> git.somenet.org - pub/jan/mattermost-api-python.git/blob - mattermost/__init__.py
various small changes
[pub/jan/mattermost-api-python.git] / mattermost / __init__.py
1 #!/usr/bin/env python3
2 """
3 Someone's Mattermost API v4 bindings.
4   Copyright (c) 2016-2021 by Someone <someone@somenet.org> (aka. Jan Vales <jan@jvales.net>)
5   published under MIT-License
6 """
7
8 import logging
9 import json
10 import random
11 import warnings
12
13 import requests
14
15 from .version import __version__
16
17 logger = logging.getLogger("mattermost")
18
19
20 class ApiException(Exception):
21     """ if exc == True: Thrown when any status_code >=400 gets returned """
22
23
24 class MMApi:
25     """Mattermost API v4 bindings."""
26
27     def __init__(self, url):
28         self._url = url
29         self._bearer = None
30
31         # temp/retrieved data.
32         self._my_user_id = None
33
34         # the only way to detect our session :/
35         self._my_user_agent = "SomeMMApi-"+__version__+"-"+str(random.randrange(100000000000, 999999999999))
36         self._headers = requests.utils.default_headers()
37         self._headers.update({"User-Agent": self._headers["User-Agent"]+" "+self._my_user_agent})
38
39
40     def _get(self, endpoint, params=None, raw=False, exc=True):
41         """
42         Do a get-request.
43
44         Args:
45             endpoint (string): API-Endpoint to call, including /v4/..., excluding /api/
46             params (dict, optional): url-parameters-dict
47             raw (bool, optional): return result raw (useful if it is a file download)
48             exc (bool, optional): Throw exceptions if error code >= 400 (default=True. Can be disabled for backward-compatability for a while)
49
50         Returns:
51             dict: requested data.
52
53         Raises:
54             ApiException: If exc==True and a non-OK HTTP-Statuscode is received.
55         """
56         res = requests.get(self._url + endpoint, headers=self._headers, params=params)
57         if raw:
58             return res
59
60         logger.info("[GET] %s --> %d", endpoint, res.status_code)
61         logger.debug(json.dumps(json.loads(res.text), indent=4))
62         if exc and res.status_code >= 400:
63             raise ApiException(json.loads(res.text))
64
65         return json.loads(res.text)
66
67
68     def _put(self, endpoint, params=None, data=None, exc=True):
69         """
70         Do a put-request.
71
72         Args:
73             endpoint (string): API-Endpoint to call, including /v4/..., excluding /api/
74             params (dict, optional): url-parameters-dict
75             data (dict, optional): json-data to put, if any
76             raw (bool, optional): return result raw (useful if it is a file download)
77             exc (bool, optional): Throw exceptions if error code >= 400 (default=True. Can be disabled for backward-compatability for a while)
78
79         Returns:
80             dict: requested data.
81
82         Raises:
83             ApiException: If exc==True and a non-OK HTTP-Statuscode is received.
84         """
85         logger.debug(json.dumps(data, indent=4))
86         res = requests.put(self._url + endpoint, headers=self._headers, params=params, data=json.dumps(data))
87         logger.info("[PUT] %s --> %d", endpoint, res.status_code)
88         logger.debug(json.dumps(json.loads(res.text), indent=4))
89         if exc and res.status_code >= 400:
90             raise ApiException(json.loads(res.text))
91
92         return json.loads(res.text)
93
94
95     def _post(self, endpoint, params=None, data=None, multipart_formdata=None, exc=True):
96         """
97         Do a post-request.
98
99         Args:
100             endpoint (string): API-Endpoint to call, including /v4/..., excluding /api/
101             params (dict, optional): url-parameters-dict
102             data (dict, optional): json-data to post, if any
103             exc (bool, optional): Throw exceptions if error code >= 400 (default=True. Can be disabled for backward-compatability for a while)
104
105         Returns:
106             dict: requested data.
107
108         Raises:
109             ApiException: If exc==True and a non-OK HTTP-Statuscode is received.
110         """
111         logger.debug(json.dumps(data, indent=4))
112         if data is not None:
113             data = json.dumps(data)
114
115         res = requests.post(self._url + endpoint, headers=self._headers, params=params, data=data, files=multipart_formdata)
116         logger.info("[POST] %s --> %d", endpoint, res.status_code)
117         logger.debug(json.dumps(json.loads(res.text), indent=4))
118         if exc and res.status_code >= 400:
119             raise ApiException(json.loads(res.text))
120
121         return json.loads(res.text)
122
123
124     def _delete(self, endpoint, params=None, data=None, exc=True):
125         """
126         Do a delete-request.
127
128         Args:
129             endpoint (string): API-Endpoint to call, including /v4/..., excluding /api/
130             params (dict, optional): url-parameters-dict
131             data (dict, optional): json-data to delete, if any
132             exc (bool, optional): Throw exceptions if error code >= 400 (default=True. Can be disabled for backward-compatability for a while)
133
134         Returns:
135             dict: requested data.
136
137         Raises:
138             ApiException: If exc==True and a non-OK HTTP-Statuscode is received.
139         """
140         logger.debug(json.dumps(data, indent=4))
141         res = requests.delete(self._url + endpoint, headers=self._headers, params=params, data=json.dumps(data))
142         logger.info("[DELETE] %s --> %d", endpoint, res.status_code)
143         logger.debug(json.dumps(json.loads(res.text), indent=4))
144         if exc and res.status_code >= 400:
145             raise ApiException(json.loads(res.text))
146
147         return json.loads(res.text)
148
149
150 ################################################
151 #+ **LOGIN/LOGOUT**
152
153
154     # login is special - dont use helpers above. We also need login to be called, even if bearer is used, so we know out user-id and session-id.
155     def login(self, login_id=None, password=None, token=None, bearer=None):
156         """
157         Login to the corresponding (self._url) mattermost instance.
158         Use login_id and password, optionally token (totp)
159         If bearer is passed this token is used instead.
160
161         Unlike the MM-api this package requires to login() with bearer-tokens, because otherwise we cannot reliably and/or non-ugly get stuff like the user's id.
162         """
163         if self._bearer:
164             logger.warning("Already logged in. Ignoring new attempt.")
165             return None
166
167         # note: bearer vs self._bearer
168         if not bearer:
169             props = {"login_id": login_id, "password": password, "token":token}
170             res = requests.post(self._url + "/v4/users/login", headers=self._headers, data=json.dumps(props))
171
172             if res.status_code != 200:
173                 logger.critical("User-Login failed: %d", res.status_code)
174                 return None
175
176             self._bearer = str(res.headers["Token"])
177
178         else:
179             self._bearer = bearer
180
181         logger.info("Token Bearer: %s", self._bearer)
182         self._headers.update({"Authorization": "Bearer "+self._bearer})
183
184         if bearer:
185             res = requests.get(self._url + "/v4/users/me", headers=self._headers)
186             if res.status_code != 200:
187                 logger.critical("Bearer-Login failed: %d", res.status_code)
188                 return None
189
190         # also store our user_id
191         ret = json.loads(res.text)
192         self._my_user_id = ret["id"]
193         return ret
194
195
196     def logout(self, **kwargs):
197         """
198         This will end the session at the server and invalidate this MMApi-object.
199         """
200         return self._post("/v4/users/logout", **kwargs)
201
202
203
204 ################################################
205 #+ **USERS**
206
207
208     #def create_user() #NOT_IMPLEMENTED
209
210
211
212     def get_users(self, in_team=None, not_in_team=None, in_channel=None, not_in_channel=None, group_constrained=None, without_team=None, sort=None, **kwargs):
213         """
214         Generator: iterates over all users. Results can be restricted based on other parameters.
215
216         Args:
217             in_team (string, optional): see MM-API doc.
218             not_in_team (string, optional): see MM-API doc.
219             in_channel (string, optional): see MM-API doc.
220             not_in_channel (string, optional): see MM-API doc.
221             group_constrained (bool, optional): see MM-API doc.
222             without_team (bool, optional): see MM-API doc.
223             sort (string, optional): see MM-API doc.
224
225         Returns:
226             generates: One User at a time.
227
228         Raises:
229             ApiException: Passed on from lower layers.
230         """
231         page = 0
232         while True:
233             data_page = self._get("/v4/users", params={
234                 "page":str(page),
235                 "per_page":"200",
236                 **({"in_team": in_team} if in_team else {}),
237                 **({"not_in_team": not_in_team} if not_in_team else {}),
238                 **({"not_in_team": not_in_team} if not_in_team else {}),
239                 **({"in_channel": in_channel} if in_channel else {}),
240                 **({"not_in_channel": not_in_channel} if not_in_channel else {}),
241                 **({"group_constrained": group_constrained} if group_constrained else {}),
242                 **({"without_team": without_team} if without_team else {}),
243                 **({"sort": sort} if sort else {}),
244             }, **kwargs)
245
246             if data_page == []:
247                 break
248             page += 1
249
250             for data in data_page:
251                 yield data
252
253
254
255     def get_users_by_ids_list(self, user_ids_list, **kwargs): #UNTESTED
256         """
257         Get a list of users based on a provided list of user ids.
258
259         Args:
260             user_ids_list (list): see MM-API doc.
261
262         Returns:
263             list: of Users.
264
265         Raises:
266             ApiException: Passed on from lower layers.
267         """
268         return self._post("/v4/users/ids", data=user_ids_list, **kwargs)
269
270
271
272     def get_users_by_group_channel_ids_list(self, group_channel_ids_list, **kwargs): #UNTESTED
273         """
274         Get an object containing a key per group channel id in the query and its value as a list of users members of that group channel.
275
276         Args:
277             group_channel_ids_list (list): see MM-API doc.
278
279         Returns:
280             list: of channel_ids: list of Users.
281
282         Raises:
283             ApiException: Passed on from lower layers.
284         """
285         return self._post("/v4/users/group_channels", data=group_channel_ids_list, **kwargs)
286
287
288
289     def get_users_by_usernames_list(self, usernames_list, **kwargs):
290         """
291         Get a list of users based on a provided list of usernames.
292
293         Args:
294             usernames_list (list): see MM-API doc.
295
296         Returns:
297             list: of Users.
298
299         Raises:
300             ApiException: Passed on from lower layers.
301         """
302         return self._post("/v4/users/usernames", data=usernames_list, **kwargs)
303
304
305
306     #def search_users() #NOT_IMPLEMENTED
307     #def autocomplete_users() #NOT_IMPLEMENTED
308     #def get_user_ids_of_known_users() #NOT_IMPLEMENTED
309     #def get_total_count_of_users_in_system() #NOT_IMPLEMENTED
310
311
312
313     def get_user(self, user_id=None, **kwargs):
314         """
315         Get a list of users based on a provided list of usernames.
316
317         Args:
318             user_id (string, optional): if not given, returns Data on calling user. (/me)
319
320         Returns:
321             dict: User.
322
323         Raises:
324             ApiException: Passed on from lower layers.
325         """
326         if user_id is None:
327             return self._get("/v4/users/me", **kwargs)
328
329         return self._get("/v4/users/"+user_id, **kwargs)
330
331
332
333     #def update_user() #NOT_IMPLEMENTED: # use patch_user
334     #def deactivate_user() #NOT_IMPLEMENTED
335
336
337
338     def patch_user(self, user_id, props=None, **kwargs):
339         """
340         Partially update a user by providing only the fields you want to update.
341
342         Args:
343             user_id (string): User to patch.
344             props (dict, optional): fields you want to update.
345
346         Returns:
347             dict: User.
348
349         Raises:
350             ApiException: Passed on from lower layers.
351         """
352         return self._put("/v4/users/"+user_id+"/patch", data=props, **kwargs)
353
354
355
356     #def update_user_roles() #NOT_IMPLEMENTED
357     #def update_user_active_status() #NOT_IMPLEMENTED
358     #def get_user_profile_image() #NOT_IMPLEMENTED
359     #def set_user_profile_image() #NOT_IMPLEMENTED
360     #def delete_user_profile_image() #NOT_IMPLEMENTED
361     #def get_user_default_profile_image() #NOT_IMPLEMENTED
362
363
364
365     def get_user_by_username(self, username, **kwargs):
366         """
367         Get a user object by providing a username.
368         Sensitive information will be sanitized out.
369
370         Args:
371             username (string): User's username.
372
373         Returns:
374             dict: User.
375
376         Raises:
377             ApiException: Passed on from lower layers.
378         """
379         return self._get("/v4/users/username/"+username, **kwargs)
380
381
382
383     #def reset_user_password() #NOT_IMPLEMENTED
384     #def update_user_mfa() #NOT_IMPLEMENTED
385     #def generate_user_mfa_secret() #NOT_IMPLEMENTED
386
387
388
389     def demote_a_user(self, user_id, **kwargs):
390         """
391         Convert a regular user into a guest.
392         This will convert the user into a guest for the whole system while retaining their existing team and channel memberships.
393
394         Args:
395             user_id (string): User to demote.
396
397         Returns:
398             string: Status.
399
400         Raises:
401             ApiException: Passed on from lower layers.
402         """
403         return self._post("/v4/users/"+user_id+"/demote", **kwargs)
404
405
406
407     def promote_a_guest(self, user_id, **kwargs):
408         """
409         Convert a guest into a regular user.
410         This will convert the guest into a user for the whole system while retaining any team and channel memberships and automatically joining them to the default channels.
411
412         Args:
413             user_id (string): User to promote   .
414
415         Returns:
416             string: Status.
417
418         Raises:
419             ApiException: Passed on from lower layers.
420         """
421         return self._post("/v4/users/"+user_id+"/promote", **kwargs)
422
423
424
425     #def check_user_mfa() #NOT_IMPLEMENTED
426     #def update_user_password() #NOT_IMPLEMENTED
427     #def send_user_password_reset_mail() #NOT_IMPLEMENTED
428     #def get_user_by_email() #NOT_IMPLEMENTED
429
430
431
432     def get_user_sessions(self, user_id=None, **kwargs):
433         """
434         Get a list of sessions by providing the user GUID. Sensitive information will be sanitized out.
435
436         Args:
437             user_id (string, optional): if not given, returns Data on logged in user. (/me)
438
439         Returns:
440             list: of dicts of Sessions.
441
442         Raises:
443             ApiException: Passed on from lower layers.
444         """
445         if user_id is None:
446             user_id = self._my_user_id
447
448         return self._get("/v4/users/"+user_id+"/sessions", **kwargs)
449
450
451
452     def revoke_user_session(self, user_id=None, session_id=None, **kwargs):
453         """
454         Revokes a user session from the provided user id and session id strings.
455         Previously this was the only way to logout.
456         Please migrate to logout()
457
458         """
459         if not user_id and not session_id:
460             warnings.warn("revoke_user_session() without arguments is deprecated; use logout().", category=DeprecationWarning)
461
462         if user_id is None:
463             user_id = self._my_user_id
464
465         if session_id is None:
466             session_id = self._bearer
467
468         return self._post("/v4/users/"+user_id+"/sessions/revoke", data={"session_id": session_id}, **kwargs)
469
470
471
472     #def revoke_all_user_sessions() #NOT_IMPLEMENTED
473     #def attach_mobile_device_to_user_session() #NOT_IMPLEMENTED
474     #def get_user_audit() #NOT_IMPLEMENTED
475     #def admin_verify_user_email_() #NOT_IMPLEMENTED
476     #def verify_user_email_() #NOT_IMPLEMENTED
477     #def send_user_email_verification() #NOT_IMPLEMENTED
478     #def switch_user_login_method() #NOT_IMPLEMENTED
479     #def create_user_access_token() #NOT_IMPLEMENTED
480     #def get_user_access_tokens() #NOT_IMPLEMENTED
481     #def revoke_user_access_token() #NOT_IMPLEMENTED
482     #def get_user_access_token() #NOT_IMPLEMENTED
483     #def disable_user_access_token() #NOT_IMPLEMENTED
484     #def enable_user_access_token() #NOT_IMPLEMENTED
485     #def search_user_access_tokens() #NOT_IMPLEMENTED
486     #def update_user_auth_method() #NOT_IMPLEMENTED
487     #def record_user_action_custom_tos() #NOT_IMPLEMENTED
488     #def fetch_user_latest_accepted_custom_tos() #NOT_IMPLEMENTED
489     #def revoke_all_users_all_sessions() #NOT_IMPLEMENTED #MM, ARE YOU INSANE?!
490
491
492
493 ################################################
494 #+ **BOTS** #NOT_IMPLEMENTED
495
496
497
498 ################################################
499 #+ **TEAMS**
500
501
502
503     #def create_team() #NOT_IMPLEMENTED
504
505
506
507     def get_teams(self, include_total_count=None, **kwargs):
508         """
509         Generator: Get regular users only returns open teams. Users with the "manage_system" permission will return teams regardless of type.
510
511         Args:
512             include_total_count (bool, optional): see MM-API doc.
513
514         Returns:
515             generates: One Team at a time.
516
517         Raises:
518             ApiException: Passed on from lower layers.
519         """
520         page = 0
521         while True:
522             data_page = self._get("/v4/teams", params={
523                 "page":str(page),
524                 **({"include_total_count": include_total_count} if include_total_count else {}),
525             }, **kwargs)
526
527             if data_page == []:
528                 break
529             page += 1
530
531             for data in data_page:
532                 yield data
533
534
535
536     def get_team(self, team_id, **kwargs):
537         """
538         Get a team on the system.
539
540         Args:
541             team_id (string): team_id.
542
543         Returns:
544             dict: Team.
545
546         Raises:
547             ApiException: Passed on from lower layers.
548         """
549         return self._get("/v4/teams/"+team_id, **kwargs)
550
551
552
553     #def update_team() #NOT_IMPLEMENTED
554     #def delete_team() #NOT_IMPLEMENTED
555     #def patch_team() #NOT_IMPLEMENTED
556     #def update_team_privacy() #NOT_IMPLEMENTED
557     #def restore_team() #NOT_IMPLEMENTED
558     #def get_team_by_name() #NOT_IMPLEMENTED
559     #def search_teams() #NOT_IMPLEMENTED
560     #def exists_team() #NOT_IMPLEMENTED
561     #def get_teams_for_user() #NOT_IMPLEMENTED
562
563
564
565     def get_team_members(self, team_id, **kwargs):
566         """
567         Generator: Get a page team members list.
568
569         Args:
570             team_id (string): team_id.
571
572         Returns:
573             generates: One User at a time.
574
575         Raises:
576             ApiException: Passed on from lower layers.
577         """
578         page = 0
579         while True:
580             data_page = self._get("/v4/teams/"+team_id+"/members", params={"page":str(page)}, **kwargs)
581
582             if data_page == []:
583                 break
584             page += 1
585
586             for data in data_page:
587                 yield data
588
589
590
591     def add_user_to_team(self, team_id, user_id, **kwargs):
592         """
593         Add user to the team by user_id.
594
595         Args:
596             team_id (string): team_id to add the user to.
597             user_id (string): user_id to add to team.
598
599         Returns:
600             dict: Teammembership.
601
602         Raises:
603             ApiException: Passed on from lower layers.
604         """
605         return self._post("/v4/teams/"+team_id+"/members", data={
606             "team_id": team_id,
607             "user_id": user_id,
608         }, **kwargs)
609
610
611
612     #def add_user_to_team_from_invite() #NOT_IMPLEMENTED
613     #def add_multiple_users_to_team() #NOT_IMPLEMENTED WHY?!
614     #def get_team_members_for_a_user() #NOT_IMPLEMENTED WHY NOT NAMING STUFF USEFULLY?!
615
616
617
618     def get_team_member(self, team_id, user_id, **kwargs):
619         """
620         Add user to the team by user_id.
621
622         Args:
623             team_id (string): team_id to add the user to.
624             user_id (string): user_id to add to team.
625
626         Returns:
627             dict: Teammembership.
628
629         Raises:
630             ApiException: Passed on from lower layers.
631         """
632         return self._get("/v4/teams/"+team_id+"/members/"+user_id, **kwargs)
633
634
635
636     def remove_user_from_team(self, team_id, user_id, **kwargs):
637         """
638         Delete the team member object for a user, effectively removing them from a team.
639
640         Args:
641             team_id (string): team_id to remove the user from.
642             user_id (string): user_id to remove.
643
644         Returns:
645             dict: status.
646
647         Raises:
648             ApiException: Passed on from lower layers.
649         """
650         return self._delete("/v4/teams/"+team_id+"/members/"+user_id, **kwargs)
651
652
653
654     #def get_team_members_by_id() #NOT_IMPLEMENTED
655     #def get_team_stats() #NOT_IMPLEMENTED
656     #def regenerate_team_invite_id() #NOT_IMPLEMENTED
657     #def get_team_icon() #NOT_IMPLEMENTED
658     #def set_team_icon() #NOT_IMPLEMENTED
659     #def remove_team_icon() #NOT_IMPLEMENTED
660     #def update_team_members_roles() #NOT_IMPLEMENTED
661
662
663
664     def update_team_members_scheme_roles(self, team_id, user_id, props, **kwargs):
665         """
666         Update a team member's scheme_admin/scheme_user properties.
667         Typically this should either be {scheme_admin=false, scheme_user=true} for ordinary team member, or {scheme_admin=true, scheme_user=true} for a team admin.
668
669         Args:
670             team_id (string): obvious
671             user_id (string): obvious
672             props (dict): see MM-API docs.
673
674         Returns:
675             dict: status.
676
677         Raises:
678             ApiException: Passed on from lower layers.
679         """
680         return self._put("/v4/teams/"+team_id+"/members/"+user_id+"/schemeRoles", data=props, **kwargs)
681
682
683
684     #def get_team_unreads_for_user() #NOT_IMPLEMENTED
685     #def get_team_unreads() #NOT_IMPLEMENTED
686     #def invite_users_to_team_by_email() #NOT_IMPLEMENTED
687
688
689
690     def invite_guests_to_team_by_email(self, team_id, guest_email, channels, message, **kwargs):
691         """
692         Invite guests to existing team channels usign the user's email.
693         The number of emails that can be sent is rate limited to 20 per hour with a burst of 20 emails. If the rate limit exceeds, the error message contains details on when to retry and when the timer will be reset.
694
695         Args:
696             team_id (string): obvious
697             guest_email (strings): whom to invite.
698             channels (list of channel_ids): into which channels.
699             message (string, optional): optional message
700
701         Returns:
702             dict: status.
703
704         Raises:
705             ApiException: Passed on from lower layers.
706         """
707         return self._post("/v4/teams/"+team_id+"/invite-guests/email", data={
708             "emails": [guest_email],
709             "channels": channels,
710             **({"message": message} if message else {}),
711         }, **kwargs)
712
713
714     #def invalidate_invites_to_team_by_email() #NOT_IMPLEMENTED
715     #def import_team() #NOT_IMPLEMENTED
716     #def get_team_invite_info() #NOT_IMPLEMENTED
717     #def set_team_scheme() #NOT_IMPLEMENTED
718     #def get_team_members_minus_group_members() #NOT_IMPLEMENTED
719
720
721
722     def get_team_channels(self, team_id, **kwargs): #This belongs here, not to channels!
723         """
724         Generator: Get a page of public channels on a team.
725
726         Args:
727             team_id (string): team to get channels from.
728
729         Returns:
730             generates: Channel.
731
732         Raises:
733             ApiException: Passed on from lower layers.
734         """
735         page = 0
736         while True:
737             data_page = self._get("/v4/teams/"+team_id+"/channels", params={"page":str(page)}, **kwargs)
738
739             if data_page == []:
740                 break
741             page += 1
742
743             for data in data_page:
744                 yield data
745
746
747
748 ################################################
749 #+ **CHANNELS**
750
751
752
753     #def get_all_channels() #NOT_IMPLEMENTED NOT USEFUL AT ALL!
754
755
756
757     def create_channel(self, team_id, name, display_name, purpose=None, header=None, chan_type="O", **kwargs):
758         """
759         Create a new channel.
760
761         Args:
762             team_id (string): The team ID of the team to create the channel on.
763             name (string): The unique handle for the channel, will be present in the channel URL.
764             display_name (string): see MM-API docs.
765             purpose (string, optional): see MM-API docs.
766             header (string, optional): see MM-API docs.
767             chan_type (string, default: public): see MM-API docs.
768
769         Returns:
770             dict: created Channel.
771
772         Raises:
773             ApiException: Passed on from lower layers.
774         """
775         return self._post("/v4/channels", data={
776             "team_id": team_id,
777             "name": name,
778             "display_name": display_name,
779             **({"purpose": purpose} if purpose else {}),
780             **({"header": header} if header else {}),
781             "type": chan_type,
782         }, **kwargs)
783
784
785
786     def create_dm_channel_with(self, other_user_id, **kwargs):
787         """
788         Create a new direct message channel between two users.
789
790         Args:
791             other_user_id (string): The other user_id to create the cannel with.
792
793         Returns:
794             dict: created Channel.
795
796         Raises:
797             ApiException: Passed on from lower layers.
798         """
799         return self._post("/v4/channels/direct", data=[self._my_user_id, other_user_id], **kwargs)
800
801
802
803     def create_group_channel_with(self, other_user_ids_list, **kwargs): #UNTESTED
804         """
805         Create a new direct message channel between two users.
806
807         Args:
808             other_user_ids_list (list): List of user_ids to create the cannel with.
809
810         Returns:
811             dict: created Channel.
812
813         Raises:
814             ApiException: Passed on from lower layers.
815         """
816         return self._post("/v4/channels/group", data=other_user_ids_list, **kwargs)
817
818
819
820     #def search_all_private_and_public_channels() #NOT_IMPLEMENTED
821     #def search_all_users_group_channels() #NOT_IMPLEMENTED
822     #def get_team_channels_by_id() #NOT_IMPLEMENTED
823     #def get_timezones_of_users_in_channel() #NOT_IMPLEMENTED
824
825
826
827     def get_channel(self, channel_id, **kwargs):
828         """
829         Get channel from the provided channel id string.
830
831         Args:
832             channel_id (string): channel_id to get.
833
834         Returns:
835             dict: Channel.
836
837         Raises:
838             ApiException: Passed on from lower layers.
839         """
840         return self._get("/v4/channels/"+channel_id, **kwargs)
841
842
843
844     def update_channel(self, channel_id, props, **kwargs):
845         """
846         Update a channel. The fields that can be updated are listed as parameters. Omitted fields will be treated as blanks.
847
848         Args:
849             channel_id (string): channel_id to get.
850             props (dict, optional): fields you want to update.
851
852         Returns:
853             dict: Channel.
854
855         Raises:
856             ApiException: Passed on from lower layers.
857         """
858         return self._put("/v4/channels/"+channel_id, data=props, **kwargs)
859
860
861
862     def patch_channel(self, channel_id, props, **kwargs):
863         """
864         Partially update a channel by providing only the fields you want to update. Omitted fields will not be updated. The fields that can be updated are defined in the request body, all other provided fields will be ignored.
865
866         Args:
867             channel_id (string): channel_id to get.
868             props (dict, optional): fields you want to update.
869
870         Returns:
871             dict: Channel.
872
873         Raises:
874             ApiException: Passed on from lower layers.
875         """
876         return self._put("/v4/channels/"+channel_id+"/patch", data=props, **kwargs)
877
878
879
880     def get_channel_posts_pinned(self, channel_id, **kwargs):
881         """
882         Get a list of pinned posts for channel.
883
884         Args:
885             channel_id (string): channel_id to get pinned posts for.
886
887         Returns:
888             dict: Results.
889
890         Raises:
891             ApiException: Passed on from lower layers.
892         """
893         return self._get("/v4/channels/"+channel_id+"/pinned", **kwargs)
894
895
896
897     def search_channel(self, team_id, term, **kwargs):
898         """
899         Search public channels on a team based on the search term provided in the request body.
900
901         Args:
902             team_id (string): team_id to search in.
903             term (string): The search term to match against the name or display name of channels.
904
905         Returns:
906             list: of Channels.
907
908         Raises:
909             ApiException: Passed on from lower layers.
910         """
911         return self._post("/v4/teams/"+team_id+"/channels/search", data={"term": term}, **kwargs)
912
913
914
915     def get_channel_by_name(self, team_id, channel_name, include_deleted=None, **kwargs):
916         """
917         Gets channel from the provided team id and channel name strings.
918
919         Args:
920             team_id (string): team_id to search in.
921             term (string): The search term to match against the name or display name of channels.
922             include_deleted (bool, optional): see MM-API doc.
923
924         Returns:
925             dict: Channel.
926
927         Raises:
928             ApiException: Passed on from lower layers.
929         """
930         return self._get("/v4/teams/"+team_id+"/channels/name/"+channel_name, params={
931             **({"include_deleted": include_deleted} if include_deleted else {}),
932         }, **kwargs)
933
934
935
936     def get_channel_members(self, channel_id, **kwargs):
937         """
938         Generator: Members for a channel.
939
940         Args:
941             channel_id (string): channel_id to get the members for.
942
943         Returns:
944             generates: One Member at a time.
945
946         Raises:
947             ApiException: Passed on from lower layers.
948         """
949         page = 0
950         while True:
951             data_page = self._get("/v4/channels/"+channel_id+"/members", params={"page":str(page)}, **kwargs)
952
953             if data_page == []:
954                 break
955             page += 1
956
957             for data in data_page:
958                 yield data
959
960
961
962     def add_user_to_channel(self, channel_id, user_id, **kwargs):
963         """
964         Add a user to a channel by creating a channel member object.
965
966         Args:
967             channel_id (string): channel_id to add the user to.
968             user_id (string): user_id to add.
969
970         Returns:
971             dict: Membership.
972
973         Raises:
974             ApiException: Passed on from lower layers.
975         """
976         return self._post("/v4/channels/"+channel_id+"/members", data={"user_id": user_id}, **kwargs)
977
978
979
980     def get_channel_member(self, channel_id, user_id, **kwargs):
981         """
982         Gets channel from the provided team id and channel name strings.
983
984         Args:
985             channel_id (string): channel_id to get the members for.
986             user_id (string): user_id to get the member-data for.
987
988         Returns:
989             dict: Membership.
990
991         Raises:
992             ApiException: Passed on from lower layers.
993         """
994         return self._get("/v4/channels/"+channel_id+"/members/"+user_id, **kwargs)
995
996
997
998     def remove_user_from_channel(self, channel_id, user_id, **kwargs):
999         """
1000         Add a user to a channel by creating a channel member object.
1001
1002         Args:
1003             channel_id (string): channel_id to remove the user from.
1004             user_id (string): user_id to remove.
1005
1006         Returns:
1007             dict: status.
1008
1009         Raises:
1010             ApiException: Passed on from lower layers.
1011         """
1012         return self._delete("/v4/channels/"+channel_id+"/members/"+user_id, **kwargs)
1013
1014
1015
1016     def update_channel_members_scheme_roles(self, channel_id, user_id, props, **kwargs):
1017         """
1018         Update a channel member's scheme_admin/scheme_user properties. Typically this should either be scheme_admin=false, scheme_user=true for ordinary channel member, or scheme_admin=true, scheme_user=true for a channel admin.
1019
1020         Args:
1021             channel_id (string): see MM-API doc.
1022             user_id (string): see MM-API doc.
1023             props (dict): see MM-API doc.
1024
1025         Returns:
1026             list: of Channels.
1027
1028         Raises:
1029             ApiException: Passed on from lower layers.
1030         """
1031         return self._put("/v4/channels/"+channel_id+"/members/"+user_id+"/schemeRoles", data=props, **kwargs)
1032
1033
1034
1035     def get_channel_memberships_for_user(self, user_id, team_id, **kwargs):
1036         """
1037         Get all channel memberships and associated membership roles (i.e. channel_user, channel_admin) for a user on a specific team.
1038
1039         Args:
1040             user_id (string): see MM-API doc.
1041             team_id (string): see MM-API doc.
1042
1043         Returns:
1044             list: of Memberships.
1045
1046         Raises:
1047             ApiException: Passed on from lower layers.
1048         """
1049         return self._get("/v4/users/"+user_id+"/teams/"+team_id+"/channels/members", **kwargs)
1050
1051
1052
1053     def get_channels_for_user(self, user_id, team_id, **kwargs):
1054         """
1055         Get all the channels on a team for a user.
1056
1057         Args:
1058             user_id (string): see MM-API doc.
1059             team_id (string): see MM-API doc.
1060
1061         Returns:
1062             list: of Channels.
1063
1064         Raises:
1065             ApiException: Passed on from lower layers.
1066         """
1067         return self._get("/v4/users/"+user_id+"/teams/"+team_id+"/channels", **kwargs)
1068
1069
1070
1071 ################################################
1072 #+ **POSTS**
1073
1074
1075     def create_post(self, channel_id, message, props=None, filepaths=None, root_id=None, **kwargs):
1076         """
1077         Create a new post in a channel. To create the post as a comment on another post, provide root_id.
1078
1079         Args:
1080             channel_id (string): The channel ID to create the post in.
1081             message (string): The message text.
1082             props (string, optional): see MM-API docs.
1083             filepaths (list, optional): Paths to upload files from and attach to post.
1084             root_id (string, optional): see MM-API docs.
1085
1086         Returns:
1087             dict: created Post.
1088
1089         Raises:
1090             ApiException: Passed on from lower layers.
1091         """
1092         file_ids = []
1093         if filepaths:
1094             for filename in filepaths:
1095                 file_ids.append(self.upload_file(channel_id, filename, **kwargs)["id"])
1096
1097         return self._post("/v4/posts", data={
1098             "channel_id": channel_id,
1099             "message": message,
1100             **({"props": props} if props else {"props": {"from_webhook":"true"}}),
1101             "root_id":root_id,
1102             "file_ids": file_ids,
1103         }, **kwargs)
1104
1105
1106
1107     def create_ephemeral_post(self, channel_id, message, user_id, **kwargs):
1108         """
1109         Create a new ephemeral post in a channel.
1110
1111         Args:
1112             channel_id (string): The channel ID to create the post in.
1113             message (string): The message text.
1114             user_id (string): The user ID to display the post to.
1115
1116         Returns:
1117             dict: created Post.
1118
1119         Raises:
1120             ApiException: Passed on from lower layers.
1121         """
1122         return self._post("/v4/posts/ephemeral", data={
1123             "user_id": user_id,
1124             "post":{
1125                 "channel_id": channel_id,
1126                 "message": message,
1127             }
1128         }, **kwargs)
1129
1130
1131
1132     def get_post(self, post_id, **kwargs):
1133         """
1134         Get a single post.
1135
1136         Args:
1137             post_id (string): The post ID to get.
1138
1139         Returns:
1140             dict: Post.
1141
1142         Raises:
1143             ApiException: Passed on from lower layers.
1144         """
1145         post = self._get("/v4/posts/"+post_id, **kwargs)
1146         if "has_reactions" not in post:
1147             post["has_reactions"] = False
1148         return post
1149
1150
1151
1152     def delete_post(self, post_id, **kwargs):
1153         """
1154         Soft deletes a post, by marking the post as deleted in the database. Soft deleted posts will not be returned in post queries.
1155
1156         Args:
1157             post_id (string): The post ID to delete.
1158
1159         Returns:
1160             string: status.
1161
1162         Raises:
1163             ApiException: Passed on from lower layers.
1164         """
1165         return self._delete("/v4/posts/"+post_id, **kwargs)
1166
1167
1168     def update_post(self, post_id, message, is_pinned, has_reactions, props, **kwargs):
1169         """
1170         Update a post. Only the fields listed below are updatable, omitted fields will be treated as blank.
1171
1172         Args:
1173             post_id (string): The post ID to patch.
1174             message (string): see MM-API doc.
1175             is_pinned (bool): see MM-API doc.
1176             has_reactions (list of has_reactions): see MM-API doc.
1177             props (dict): see MM-API doc.
1178
1179         Returns:
1180             dict: Post.
1181
1182         Raises:
1183             ApiException: Passed on from lower layers.
1184         """
1185         return self._put("/v4/posts/"+post_id, data={
1186             "id": post_id,
1187             "message": message,
1188             "is_pinned": is_pinned,
1189             "has_reactions": has_reactions,
1190             "props": props,
1191         }, **kwargs)
1192
1193
1194     def patch_post(self, post_id, message=None, is_pinned=None, file_ids=None, has_reactions=None, props=None, **kwargs):
1195         """
1196         Partially update a post by providing only the fields you want to update. Omitted fields will not be updated. The fields that can be updated are defined in the request body, all other provided fields will be ignored.
1197
1198         Args:
1199             post_id (string): The post ID to patch.
1200             message (string, optional): see MM-API doc.
1201             is_pinned (bool, optional): see MM-API doc.
1202             file_ids (list of file_ids, optional): see MM-API doc.
1203             has_reactions (list of has_reactions, optional): see MM-API doc.
1204             props (dict, optional): see MM-API doc.
1205
1206         Returns:
1207             dict: Post.
1208
1209         Raises:
1210             ApiException: Passed on from lower layers.
1211         """
1212         return self._put("/v4/posts/"+post_id+"/patch", data={
1213             **({"message": message} if message else {}),
1214             **({"is_pinned": is_pinned} if is_pinned else {}),
1215             **({"file_ids": file_ids} if file_ids else {}),
1216             **({"has_reactions": has_reactions} if has_reactions else {}),
1217             **({"props": props} if props else {}),
1218         }, **kwargs)
1219
1220
1221
1222     def get_posts_for_channel(self, channel_id, **kwargs):
1223         """
1224         Generator: Get a page of posts in a channel. Use the query parameters to modify the behaviour of this endpoint.
1225
1226         Args:
1227             channel_id (string): The channel ID to iterate over.
1228
1229         Returns:
1230             generates: Post.
1231
1232         Raises:
1233             ApiException: Passed on from lower layers.
1234         """
1235         page = 0
1236         while True:
1237             data_page = self._get("/v4/channels/"+channel_id+"/posts", params={"page":str(page)}, **kwargs)
1238
1239             if data_page["order"] == []:
1240                 break
1241             page += 1
1242
1243             for order in data_page["order"]:
1244                 yield data_page["posts"][order]
1245
1246
1247
1248 ################################################
1249 #+ **FILES**
1250
1251
1252     def upload_file(self, channel_id, filepath, **kwargs):
1253         """
1254         Uploads a file that can later be attached to a post.
1255
1256         Args:
1257             channel_id (string): The channel ID to upload to.
1258             filepath (string): The local path of the source.
1259
1260         Returns:
1261             dict: Uploaded File.
1262
1263         Raises:
1264             ApiException: Passed on from lower layers.
1265         """
1266         return self._post("/v4/files", multipart_formdata={'files':open(filepath, "rb"), "channel_id":(None, channel_id)}, **kwargs)["file_infos"][0]
1267
1268
1269
1270     def get_file(self, file_id, **kwargs):
1271         """
1272         Uploads a file that can later be attached to a post.
1273
1274         Args:
1275             file_id (string): The file ID to get.
1276
1277         Returns:
1278             binary: file-content.
1279
1280         Raises:
1281             ApiException: Passed on from lower layers.
1282         """
1283         return self._get("/v4/files/"+file_id, raw=True, **kwargs)
1284
1285
1286
1287 ################################################
1288 #+ **PREFERENCES** #NOT_IMPLEMENTED
1289
1290 ################################################
1291 #+ **STATUS** #NOT_IMPLEMENTED
1292
1293 ################################################
1294 #+ **EMOJI** #NOT_IMPLEMENTED
1295
1296 ################################################
1297 #+ **REACTIONS**
1298
1299
1300     def create_reaction(self, user_id, post_id, emoji_name, **kwargs):
1301         """
1302         Create a reaction.
1303
1304         Args:
1305             user_id (string): The ID of the user that made this reaction.
1306             post_id (string): The ID of the post to which this reaction was made.
1307             emoji_name (string): The name of the emoji that was used for this reaction.
1308
1309         Returns:
1310             dict: created Reaction.
1311
1312         Raises:
1313             ApiException: Passed on from lower layers.
1314         """
1315         return self._post("/v4/reactions", data={
1316             "user_id": user_id,
1317             "post_id": post_id,
1318             "emoji_name": emoji_name,
1319         }, **kwargs)
1320
1321
1322
1323 ################################################
1324 #+ **WEBHOOKS**
1325
1326
1327     def create_outgoing_hook(self, team_id, display_name, trigger_words, callback_urls, channel_id=None, description=None, trigger_when=0, **kwargs):
1328         """
1329         Create an outgoing webhook for a team.
1330
1331         Args:
1332             team_id (string): The ID of the team that the webhook watchs.
1333             display_name (string): The display name for this outgoing webhook.
1334             trigger_words (list): List of words for the webhook to trigger on.
1335             callback_urls (list): The URLs to POST the payloads to when the webhook is triggered.
1336             channel_id (string, optional): The ID of a public channel that the webhook watchs.
1337             description (string, optional): The description for this outgoing webhook.
1338             trigger_when (string, default int(0)): When to trigger the webhook, 0 when a trigger word is present at all and 1 if the message starts with a trigger word.
1339
1340         Returns:
1341             dict: created Webhook.
1342
1343         Raises:
1344             ApiException: Passed on from lower layers.
1345         """
1346         return self._post("/v4/hooks/outgoing", data={
1347             "team_id": team_id,
1348             "display_name": display_name,
1349             "trigger_words": trigger_words,
1350             "callback_urls": callback_urls,
1351             **({"channel_id": channel_id} if channel_id else {}),
1352             **({"description": description} if description else {}),
1353             "trigger_when": trigger_when,
1354             "content_type": "application/json",
1355         }, **kwargs)
1356
1357
1358
1359     def list_outgoing_hooks(self, team_id, channel_id=None, **kwargs):
1360         """
1361         Generator: Get a page of a list of outgoing webhooks. Optionally filter for a specific channel using query parameters.
1362
1363         Args:
1364             team_id (string): The ID of the team to get hooks for.
1365             channel_id (string, optional): The ID of the channel to get hooks for.
1366
1367         Returns:
1368             generates: One Webhook at a time.
1369
1370         Raises:
1371             ApiException: Passed on from lower layers.
1372         """
1373         return self._get("/v4/hooks/outgoing", params={
1374             "team_id":team_id,
1375             **({"channel_id": channel_id} if channel_id else {}),
1376         }, **kwargs)
1377
1378
1379
1380     def delete_outgoing_hook(self, hook_id, **kwargs):
1381         """
1382         Delete an outgoing webhook given the hook id.
1383
1384         Args:
1385             hook_id (string): The ID of the hook to delete.
1386
1387         Returns:
1388             string: status.
1389
1390         Raises:
1391             ApiException: Passed on from lower layers.
1392         """
1393         return self._delete("/v4/hooks/outgoing/"+hook_id, **kwargs)
1394
1395
1396
1397 ################################################
1398 #+ **COMMANDS**
1399
1400
1401     def create_slash_command(self, team_id, trigger, url, **kwargs):
1402         """
1403         Create a command for a team.
1404
1405         Args:
1406             team_id (string): Team ID to where the command should be created.
1407             trigger (string): Activation word to trigger the command.
1408             url (string): The URL that the command will make the request.
1409
1410         Returns:
1411             dict: created Command.
1412
1413         Raises:
1414             ApiException: Passed on from lower layers.
1415         """
1416         return self._post("/v4/commands", data={
1417             "team_id": team_id,
1418             "trigger": trigger,
1419             "url": url,
1420             "method": "P",
1421         }, **kwargs)
1422
1423
1424
1425     def list_custom_slash_commands_for_team(self, team_id, **kwargs):
1426         """
1427         List commands for a team.
1428
1429         Args:
1430             team_id (string): The ID of the team to get hooks for.
1431
1432         Returns:
1433             list: of Commands.
1434
1435         Raises:
1436             ApiException: Passed on from lower layers.
1437         """
1438         return self._get("/v4/commands", params={
1439             "team_id":team_id,
1440             "custom_only":True,
1441         }, **kwargs)
1442
1443
1444
1445     def update_slash_command(self, data, **kwargs):
1446         """
1447         Update a single command based on command id string and Command struct.
1448
1449         Args:
1450             data (dict): Command to update.
1451
1452         Returns:
1453             dict: updated Command.
1454
1455         Raises:
1456             ApiException: Passed on from lower layers.
1457         """
1458         return self._put("/v4/commands/"+data["id"], data=data, **kwargs)
1459
1460
1461
1462     def delete_slash_command(self, command_id, **kwargs):
1463         """
1464         Delete a command based on command id string.
1465
1466         Args:
1467             command_id (string): ID of the command to delete.
1468
1469         Returns:
1470             string: status.
1471
1472         Raises:
1473             ApiException: Passed on from lower layers.
1474         """
1475         return self._delete("/v4/commands/"+command_id, **kwargs)
1476
1477
1478
1479 ################################################
1480 #+ **OPENGRAPH** #NOT_IMPLEMENTED
1481
1482 ################################################
1483 #+ **SYSTEM** #NOT_IMPLEMENTED
1484
1485 ################################################
1486 #+ **BRAND** #NOT_IMPLEMENTED
1487
1488 ################################################
1489 #+ **OAUTH** #NOT_IMPLEMENTED
1490
1491 ################################################
1492 #+ **SAML** #NOT_IMPLEMENTED
1493
1494 ################################################
1495 #+ **LDAP** #NOT_IMPLEMENTED
1496
1497 ################################################
1498 #+ **GROUPS** #NOT_IMPLEMENTED
1499
1500 ################################################
1501 #+ **COMPLIANCE** #NOT_IMPLEMENTED
1502
1503 ################################################
1504 #+ **CLUSTER** #NOT_IMPLEMENTED
1505
1506 ################################################
1507 #+ **ELASTICSEARCH** #NOT_IMPLEMENTED
1508
1509 ################################################
1510 #+ **BLEVE** #NOT_IMPLEMENTED
1511
1512 ################################################
1513 #+ **DATARETENTION** #NOT_IMPLEMENTED
1514
1515 ################################################
1516 #+ **JOBS** #NOT_IMPLEMENTED
1517
1518 ################################################
1519 #+ **PLUGINS** #NOT_IMPLEMENTED
1520
1521 ################################################
1522 #+ **ROLES** #NOT_IMPLEMENTED
1523
1524 ################################################
1525 #+ **SCHEMES** #NOT_IMPLEMENTED
1526
1527 ################################################
1528 #+ **INTEGRATION_ACTIONS**
1529
1530
1531     def open_dialog(self, trigger_id, response_url, dialog, **kwargs):
1532         """
1533         Open an interactive dialog using a trigger ID provided by a slash command, or some other action payload. See https://docs.mattermost.com/developer/interactive-dialogs.html for more information on interactive dialogs.
1534
1535         Args:
1536             trigger_id (string): Trigger ID provided by other action.
1537             response_url (string): The URL to send the submitted dialog payload to.
1538             dialog (dict): Dialog definition.
1539
1540         Returns:
1541             string: status
1542
1543         Raises:
1544             ApiException: Passed on from lower layers.
1545         """
1546         return self._post("/v4/actions/dialogs/open", data={
1547             "trigger_id": trigger_id,
1548             "url": response_url,
1549             "dialog": dialog,
1550         }, **kwargs)
1551
1552
1553
1554 ################################################
1555 #+ **TERMS_OF_SERVICE** #NOT_IMPLEMENTED