Skip to content

Commit

Permalink
Slack reactions and Message.extras['url'] (errbotio#985)
Browse files Browse the repository at this point in the history
* add slack urls, emoji functions

* add emoji functions to text backend

* made add_reaction, remove_reaction idempotent
  • Loading branch information
r3m0t authored and gbin committed Apr 8, 2017
1 parent 28b6969 commit eeefe9d
Show file tree
Hide file tree
Showing 3 changed files with 94 additions and 1 deletion.
22 changes: 22 additions & 0 deletions docs/user_guide/plugin_development/backend_specifics.rst
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,28 @@ Backend Mode value
:class:`~errbot.backends.xmpp` xmpp
============================================ ==========

Here's an example of using a backend-specific feature. In Slack, emoji reactions can be added to messages the bot
receives using the `add_reaction` and `remove_reaction` methods. For example, you could add an hourglass to messages
that will take a long time to reply fully to.

.. code-block:: python
from errbot import BotPlugin, botcmd
class PluginExample(BotPlugin):
@botcmd
def longcompute(self, mess, args):
if self._bot.mode == "slack":
self._bot.add_reaction(mess, "hourglass")
else:
yield "Finding the answer..."
time.sleep(10)
yield "The answer is: 42"
if self._bot.mode == "slack":
self._bot.remove_reaction(mess, "hourglass")
Getting to the underlying client library
----------------------------------------
Expand Down
63 changes: 62 additions & 1 deletion errbot/backends/slack.py
Original file line number Diff line number Diff line change
Expand Up @@ -503,6 +503,7 @@ def _message_event_handler(self, event):
msg.frm = SlackPerson(self.sc, user, event['channel'])
msg.to = SlackPerson(self.sc, self.username_to_userid(self.sc.server.username),
event['channel'])
channel_link_name = event['channel']
else:
if subtype == "bot_message":
msg.frm = SlackRoomBot(
Expand All @@ -515,6 +516,13 @@ def _message_event_handler(self, event):
else:
msg.frm = SlackRoomOccupant(self.sc, user, event['channel'], bot=self)
msg.to = SlackRoom(channelid=event['channel'], bot=self)
channel_link_name = msg.to.name

msg.extras['url'] = 'https://{domain}.slack.com/archives/{channelid}/p{ts}'.format(
domain=self.sc.server.domain,
channelid=channel_link_name,
ts=self._ts_for_message(msg).replace('.', '')
)

self.callback_message(msg)

Expand Down Expand Up @@ -632,14 +640,18 @@ def send_message(self, mess):
limit = min(self.bot_config.MESSAGE_SIZE_LIMIT, SLACK_MESSAGE_LIMIT)
parts = self.prepare_message_body(body, limit)

timestamps = []
for part in parts:
self.api_call('chat.postMessage', data={
result = self.api_call('chat.postMessage', data={
'channel': to_channel_id,
'text': part,
'unfurl_media': 'true',
'link_names': '1',
'as_user': 'true',
})
timestamps.append(result['ts'])

mess.extras['ts'] = timestamps
except Exception:
log.exception(
"An exception occurred while trying to send the following message "
Expand Down Expand Up @@ -849,6 +861,53 @@ def build_reply(self, mess, text=None, private=False):
response.to = mess.frm.room if isinstance(mess.frm, RoomOccupant) else mess.frm
return response

def add_reaction(self, mess: Message, reaction: str) -> None:
"""
Add the specified reaction to the Message if you haven't already.
:param mess: A Message.
:param reaction: A str giving an emoji, without colons before and after.
:raises: ValueError if the emoji doesn't exist.
"""
return self._react('reactions.add', mess, reaction)

def remove_reaction(self, mess: Message, reaction: str) -> None:
"""
Remove the specified reaction from the Message if it is currently there.
:param mess: A Message.
:param reaction: A str giving an emoji, without colons before and after.
:raises: ValueError if the emoji doesn't exist.
"""
return self._react('reactions.remove', mess, reaction)

def _react(self, method: str, mess: Message, reaction: str) -> None:
try:
# this logic is from send_message
if mess.is_group:
to_channel_id = mess.to.id
else:
to_channel_id = mess.to.channelid

ts = self._ts_for_message(mess)

self.api_call(method, data={'channel': to_channel_id,
'timestamp': ts,
'name': reaction})
except SlackAPIResponseError as e:
if e.error == 'invalid_name':
raise ValueError(e.error, 'No such emoji', reaction)
elif e.error in ('no_reaction', 'already_reacted'):
# This is common if a message was edited after you reacted to it, and you reacted to it again.
# Chances are you don't care about this. If you do, call api_call() directly.
pass
else:
raise SlackAPIResponseError(error=e.error)

def _ts_for_message(self, mess):
try:
return mess.extras['slack_event']['message']['ts']
except KeyError:
return mess.extras['slack_event']['ts']

def shutdown(self):
super().shutdown()

Expand Down Expand Up @@ -982,6 +1041,8 @@ def id(self):
self._id = self._channel.id
return self._id

channelid = id

@property
def name(self):
"""Return the name of this room"""
Expand Down
10 changes: 10 additions & 0 deletions errbot/backends/text.py
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,16 @@ def send_message(self, mess):
print(self.md_borderless_ansi.convert(mess.body))
print('\n\n')

def add_reaction(self, mess: Message, reaction: str) -> None:
# this is like the Slack backend's add_reaction
self._react('+', mess, reaction)

def remove_reaction(self, mess: Message, reaction: str) -> None:
self._react('-', mess, reaction)

def _react(self, sign, mess, reaction):
self.send(mess.frm, 'reaction {}:{}:'.format(sign, reaction), in_reply_to=mess)

def change_presence(self, status: str = ONLINE, message: str = '') -> None:
log.debug("*** Changed presence to [%s] %s", (status, message))

Expand Down

0 comments on commit eeefe9d

Please sign in to comment.