diff --git a/openpype/modules/slack/README.md b/openpype/modules/slack/README.md index 5e099be9e7..a189fdf978 100644 --- a/openpype/modules/slack/README.md +++ b/openpype/modules/slack/README.md @@ -43,4 +43,7 @@ Placeholders {} could be used in message content which will be filled during run Only keys available in 'anatomyData' are currently implemented. Example of message content: -```{SUBSET} for {Asset} was published.``` \ No newline at end of file +```{SUBSET} for {Asset} was published.``` + +Integration can upload 'thumbnail' file (if present in instance), for that bot must be +manually added to target channel by Slack admin! \ No newline at end of file diff --git a/openpype/modules/slack/plugins/publish/integrate_slack_api.py b/openpype/modules/slack/plugins/publish/integrate_slack_api.py index 3244df6b16..0c7d3c5ebd 100644 --- a/openpype/modules/slack/plugins/publish/integrate_slack_api.py +++ b/openpype/modules/slack/plugins/publish/integrate_slack_api.py @@ -1,3 +1,4 @@ +import os try: from slackclient import SlackClient python2 = True @@ -17,7 +18,10 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): 'collect_slack_family'. Expects configured profile in - Project settings > Slack > Publish plugins > Notification to Slack + Project settings > Slack > Publish plugins > Notification to Slack. + + If instance contains 'thumbnail' it uploads it. Bot must be present + in the target channel. Message template can contain {} placeholders from anatomyData. """ @@ -45,39 +49,91 @@ class IntegrateSlackAPI(pyblish.api.InstancePlugin): return + published_path = self._get_thumbnail_path(instance) + for channel in instance.data["slack_channel"]: if python2: self._python2_call(instance.data["slack_token"], channel, - message) + message, + published_path) else: self._python3_call(instance.data["slack_token"], channel, - message) + message, + published_path) - def _python2_call(self, token, channel, message): + def _get_thumbnail_path(self, instance): + """Returns abs url for thumbnail if present in instance repres""" + published_path = None + for comp in instance.data['representations']: + self.log.debug('component {}'.format(comp)) + + if comp.get('thumbnail') or ("thumbnail" in comp.get('tags', [])): + self.log.debug("thumbnail present") + + comp_files = comp["files"] + if isinstance(comp_files, (tuple, list, set)): + filename = comp_files[0] + else: + filename = comp_files + + published_path = os.path.join( + comp['stagingDir'], filename + ) + break + return published_path + + def _python2_call(self, token, channel, message, published_path): try: client = SlackClient(token) - response = client.api_call( - "chat.postMessage", - channel=channel, - text=message - ) + if not published_path: + response = client.api_call( + "chat.postMessage", + channel=channel, + text=message + ) + else: + response = client.api_call( + "files.upload", + channels=channel, + initial_comment=message, + file=published_path, + ) if response.get("error"): - self.log.warning("Error happened: {}".format( - response.get("error"))) + error_str = self._enrich_error(str(response.get("error")), + channel) + self.log.warning("Error happened: {}".format(error_str)) except Exception as e: # You will get a SlackApiError if "ok" is False - self.log.warning("Error happened: {}".format(str(e))) + error_str = self._enrich_error(str(e), channel) + self.log.warning("Error happened: {}".format(error_str)) - def _python3_call(self, token, channel, message): + def _python3_call(self, token, channel, message, published_path): try: client = WebClient(token=token) - _ = client.chat_postMessage( - channel=channel, - text=message - ) + if not published_path: + _ = client.chat_postMessage( + channel=channel, + text=message + ) + else: + _ = client.files_upload( + channels=channel, + initial_comment=message, + file=published_path, + ) except SlackApiError as e: # You will get a SlackApiError if "ok" is False - self.log.warning("Error happened {}".format(e.response[ - "error"])) + error_str = self._enrich_error(str(e.response["error"]), channel) + self.log.warning("Error happened {}".format(error_str)) + + def _enrich_error(self, error_str, channel): + """Enhance known errors with more helpful notations.""" + if 'not_in_channel' in error_str: + # there is no file.write.public scope, app must be explicitly in + # the channel + msg = " - application must added to channel '{}'.".format(channel) + error_str += msg + " Ask Slack admin." + + return error_str diff --git a/website/docs/module_slack.md b/website/docs/module_slack.md index c8e42ababa..de7014c203 100644 --- a/website/docs/module_slack.md +++ b/website/docs/module_slack.md @@ -53,3 +53,6 @@ Message could be delivered to one or multiple channels, by default app allows Sl to send messages to 'public' channels (eg. bot doesn't need to join the channel first). ![Configure module](assets/slack_system.png) + +Integration can upload 'thumbnail' file (if present in instance), for that bot must be +manually added to target channel by Slack admin! \ No newline at end of file