Coder Social home page Coder Social logo

err-backend-slackv3's Introduction

errbot-backend-slackv3

Documentation Status

Slack Events and Real Time Messaging backend for Errbot.

Quick Start

It is recommended to install errbot into a Python virtual environment. The steps are as follows: Note: The examples assume the virtual environment is created as /opt/errbot but you can adapt this path to your needs.

  1. Create and activate the virtual environment.
python3 -m venv /opt/errbot
. /opt/errbot/bin/activate
  1. Install errbot and slackv3.
pip install errbot[slack]
  1. Initialise errbot.
errbot --init
  1. Edit config.py to configure the backend with the correct Slack credentials. (See the official documentation of details on how to configure the backend for RTM vs Events)
BACKEND = 'SlackV3'
BOT_IDENTITY = {
    'token': 'xoxb-000000000000-000000000000-xxxxxxxxxxxxxxxxxxxxxxxx',
    #'signing_secret': "xxxxxx",
    #'app_token': "xxxxx"
}
  1. Start errbot
errbot -c config.py

Documentation

See the slackv3 documentation for:

  • Installation
  • Configuration
  • User guide
  • Developer guide

Support

If you need help for an errbot-backend-slackv3 problem, open an issue at github repository

err-backend-slackv3's People

Contributors

alpinweis avatar duhow avatar gdelaney avatar jcfrt avatar msg4real avatar nzlosh avatar pdkhai avatar sijis avatar thejokersthief avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

err-backend-slackv3's Issues

Bot does not reconnect after receiving a goodbye message (rtm backend)

While we had the issue of the bot replying multiple times after running for 12/25 hours (see PR and issue here https://github.com/nzlosh/err-backend-slackv3/pull/11), after merging all PRs currently open (https://github.com/nzlosh/err-backend-slackv3/pull/17 https://github.com/nzlosh/err-backend-slackv3/pull/18 https://github.com/nzlosh/err-backend-slackv3/pull/19), the bot stops replying after receiving a goodbye message from the server.

I see this in the logs:

2021-02-02 17:55:21,772 INFO     errbot.plugins.ACLs       Matching ACL {} against username @username for command Commands:commands.
2021-02-02 17:55:21,772 INFO     errbot.core               Processing command "commands" with parameters "" from #yet-another-private/username
2021-02-02 20:29:19,925 INFO     errbot.backends.slackv3   Received 'goodbye' from slack server.
2021-02-02 20:29:19,926 INFO     errbot.core               Disconnect callback, deactivating all the plugins.

Note: I don't believe the PRs I merged are responsible for this behavior.

Move to RTM Client v2

The RTM Client has been troublesome with reconnection, restart and shutdown of the backend. slack-sdk from v3.3.0 has addressed these problems with the rtmclientv2. slackapi/python-slack-sdk#932

Update the backend to use the rtmclientv2 to improve reliability.

Errbot doesn't recognise you're talking to it when you use its name

We recently switched from the old slackclient to the slackv3 backend and it generally works pretty well, however:

when we try to issue errbot a command by mentioning it @errbot help instead of with the prefix !help it doesn't
recognize that it's being spoken to and doesn't respond.

I think the fault is in line 173 of slackv3.py:

converted_prefixes = []
for prefix in bot_prefixes:
try:
converted_prefixes.append(f"<@{self.username_to_userid(prefix)}>")
except Exception as e:
log.error(
f'Failed to look up Slack userid for alternate prefix "{prefix}": {str(e)}'
)
self.bot_alt_prefixes = tuple(
x.lower() for x in self.bot_config.BOT_ALT_PREFIXES
)

If I change it to: self.bot_alt_prefixes = tuple(x.lower() for x in converted_prefixes) we can talk to errbot again.

Readme install instructions issue

Instructions currently are:

git clone https://github.com/errbotio/err-backend-slackv3
# to get a specific release use `--branch <release-tag>`, e.g. `--branch v0.1.0`
git clone --depth 1 https://github.com/errbotio/err-backend-slackv3
pip install .

Should be more like

cd /opt/errbot/backend
git clone https://github.com/errbotio/err-backend-slackv3
#or to get a specific release use `--branch <release-tag>`, e.g. `--branch v0.1.0`
#git clone --depth 1 https://github.com/errbotio/err-backend-slackv3
cd err-backend-slackv3 && pip install .

Also the step before lists:

BOT_EXTRA_BACKEND_DIR="/opt/errbot/backend"

with instructions as written that needs to be:

BOT_EXTRA_BACKEND_DIR="/opt/errbot/backend/err-backend-slackv3"

KeyError exception in slack _reaction_event_handler

I am...

  • Reporting a bug
  • Suggesting a new feature
  • Requesting help with running my bot
  • Requesting help writing plugins
  • Here about something else

I am running...

  • Errbot version: 6.1.X
  • OS version: Ubuntu 2020
  • Python version: 3.8
  • Using a virtual environment: yes

Issue description

In the legacy slack backend, under _reaction_event_handler, there is a check for event["item_user"]. The item_user attribute of the event won't always be available for some events. Slack says the following:

Some messages aren't authored by "users," like those created by incoming webhooks. reaction_added events related to these messages will not include an item_user.

In its current state, Errbot will throw a KeyError exception on the check if event["item_user"] on line 613 of slack.py

Steps to reproduce

I believe the steps to reproduce would be to send a message to Slack via a webhook, then add a reaction to it.

Additional info

I will submit a PR fixing this issue.

Add support for editing messages in slack.

In order to let us help you better, please fill out the following fields as best you can:

I am...

  • Reporting a bug
  • Suggesting a new feature
  • Requesting help with running my bot
  • Requesting help writing plugins
  • Here about something else

I am running...

  • Errbot version: 5.0.1
  • OS version: Linux
  • Python version: 3
  • Using a virtual environment: yes/no

Issue description

I have a chatbot that has some long running commands. I would like to be able to implement a placeholder message that then gets edited when the command finishes executing. Slack appears to support editing a message via the api (https://api.slack.com/methods/chat.update). The errbot slack backend doesn't seem to support this yet. Additionally, the commands that send a message don't seem to return the information I'd need to call the update endpoint on my own.

Basically, I'm looking to get support for the update method. But, it would be pretty cool if there was native support for a promise like call where I can supply the starting text and a function which will take a while to run and then return the final text.

Steps to reproduce

In case of a bug, please describe the steps we need to take in order to reproduce your issue.
If you cannot easily reproduce the issue please let us know and provide as much information as you can which might help us pinpoint the problem.

Additional info

If you have any more information, please specify it here.

Issue when mentioning a slack channel in the bot command

Backend: slackV3
Errbot version: 6.1.8

My bot creates new slack channels. I have a command to rename a channel, e.g.

 !channel mv #old-name #new_name

Just noticed if #old_name is passed as an identified/recognized slack channel (like when it gets picked from a drop down menu) - the command is kind of ignored, nothing happens. Not sure why.

Below is the log snippet from running the command: !channel mv #chan1-xyz #chan-xyz
Notice that the command text gets converted to !channel mv <#CHAN111|chan1-xyz> #chan-xyz.

18:05:39 DEBUG    slack_sdk.socket_mode.bui on_message invoked: (message: {"envelope_id":"11223344556677889900","payload":{"token":"TTTTTTTTT","team_id":"TEAMID","api_app_id":"APPID","event":{"client_msg_id":"CCCCLLLIIIEEENNNTTT","type":"message","text":"!channel mv <#CHAN111|chan1-xyz> #chan-xyz","user":"UUUUUU","ts":"1638986738.006600","team":"TEAMID","blocks":[{"type":"rich_text","block_id":"EIJ3j","elements":[{"type":"rich_text_section","elements":[{"type":"text","text":"!channel mv "},{"type":"channel","channel_id":"CHAN111"},{"type":"text","text":" #chan-xyz"}]}]}],"channel":"CHCHCHCH","event_ts":"1638986738.006600","channel_type":"im"},"type":"event_callback","event_id":"EVENT_ID1111","event_time":1638986738,"authorizations":[{"enterprise_id":null,"team_id":"TEAMID","user_id":"UUUUUIDIDID","is_bot":true,"is_enterprise_install":false}],"is_ext_shared_channel":false,"event_context":"4-CONTXT"},"type":"events_api","accepts_response_payload":false,"retry_attempt":0,"retry_reason":""})
18:05:39 DEBUG    slack_sdk.socket_mode.bui A new message enqueued (current queue size: 1)
18:05:39 DEBUG    slack_sdk.socket_mode.bui A message dequeued (current queue size: 0)
18:05:39 DEBUG    slack_sdk.socket_mode.bui Message processing started (type: events_api, envelope_id: 11223344556677889900)
18:05:39 DEBUG    errbot.backends.slackv3   Event type: events_api
Envelope ID: 11223344556677889900
Accept Response Payload: False
Retry Attempt: 0
Retry Reason:

18:05:39 DEBUG    slack_sdk.socket_mode.bui Sending a message (session id: 123123123123, message: {"envelope_id": "11223344556677889900"})
18:05:39 DEBUG    errbot.backends.slackv3   Received event: {'token': 'TTTTTTTTT', 'team_id': 'TEAMID', 'api_app_id': 'APPID', 'event': {'client_msg_id': 'CCCCLLLIIIEEENNNTTT', 'type': 'message', 'text': '!channel mv <#CHAN111|chan1-xyz> #chan-xyz', 'user': 'UUUUUU', 'ts': '1638986738.006600', 'team': 'TEAMID', 'blocks': [{'type': 'rich_text', 'block_id': 'EIJ3j', 'elements': [{'type': 'rich_text_section', 'elements': [{'type': 'text', 'text': '!channel mv '}, {'type': 'channel', 'channel_id': 'CHAN111'}, {'type': 'text', 'text': ' #chan-xyz'}]}]}], 'channel': 'CHCHCHCH', 'event_ts': '1638986738.006600', 'channel_type': 'im'}, 'type': 'event_callback', 'event_id': 'EVENT_ID1111', 'event_time': 1638986738, 'authorizations': [{'enterprise_id': None, 'team_id': 'TEAMID', 'user_id': 'UUUUUIDIDID', 'is_bot': True, 'is_enterprise_install': False}], 'is_ext_shared_channel': False, 'event_context': '4-CONTXT'}
18:05:39 DEBUG    errbot.backends.slackv3   building an identifier from <#CHAN111|chan1-xyz>.
18:05:39 DEBUG    slack_sdk.web.base_client Sending a request - url: https://www.slack.com/api/conversations.info, query_params: {}, body_params: {'channel': 'CHAN111'}, files: {}, json_body: None, headers: {'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': '(redacted)', 'User-Agent': 'Python/3.6.9 slackclient/3.12.0 Linux/5.10.25-linuxkit'}
18:05:39 DEBUG    slack_sdk.web.base_client Received the following response - status: 200, headers: {'date': 'Wed, 08 Dec 2021 18:05:39 GMT', 'server': 'Apache', 'x-powered-by': 'HHVM/4.121.0', 'access-control-allow-origin': '*', 'referrer-policy': 'no-referrer', 'x-slack-backend': 'r', 'strict-transport-security': 'max-age=31536000; includeSubDomains; preload', 'access-control-allow-headers': 'slack-route, x-slack-version-ts, x-b3-traceid, x-b3-spanid, x-b3-parentspanid, x-b3-sampled, x-b3-flags', 'access-control-expose-headers': 'x-slack-req-id, retry-after', 'x-oauth-scopes': 'app_mentions:read,channels:history,channels:manage,channels:read,chat:write,chat:write.customize,chat:write.public,conversations.connect:read,conversations.connect:write,emoji:read,files:read,files:write,groups:history,groups:read,groups:write,im:history,im:read,im:write,incoming-webhook,links:read,links:write,mpim:history,mpim:read,mpim:write,pins:read,pins:write,reactions:read,reactions:write,reminders:read,remote_files:read,team.preferences:read,team:read,usergroups:read,users.profile:read,users:read,users:read.email,users:write', 'x-accepted-oauth-scopes': 'channels:read,groups:read,mpim:read,im:read,read', 'expires': 'Mon, 26 Jul 1997 05:00:00 GMT', 'cache-control': 'private, no-cache, no-store, must-revalidate', 'pragma': 'no-cache', 'x-xss-protection': '0', 'x-content-type-options': 'nosniff', 'x-slack-req-id': 'someid', 'vary': 'Accept-Encoding', 'content-type': 'application/json; charset=utf-8', 'x-envoy-upstream-service-time': '120', 'x-backend': 'main_normal main_bedrock_normal_with_overflow main_canary_with_overflow main_bedrock_canary_with_overflow main_control_with_overflow main_bedrock_control_with_overflow', 'x-server': 'slack-www-hhvm-main-iad-qwjs', 'x-slack-shared-secret-outcome': 'no-match', 'via': 'envoy-www-iad-4002, envoy-edge-fra-pf7u', 'x-edge-backend': 'envoy-www', 'x-slack-edge-shared-secret-outcome': 'no-match', 'connection': 'close', 'transfer-encoding': 'chunked'}, body: {"ok":true,"channel":{"id":"CHAN111","name":"chan1-xyz","is_channel":true,"is_group":false,"is_im":false,"is_mpim":false,"is_private":false,"created":1637617186,"is_archived":false,"is_general":false,"unlinked":0,"name_normalized":"chan1-xyz","is_shared":false,"is_org_shared":false,"is_pending_ext_shared":false,"pending_shared":[],"parent_conversation":null,"creator":"UUUUUIDIDID","is_ext_shared":false,"shared_team_ids":["TEAMID"],"pending_connected_team_ids":[],"is_member":true,"last_read":"0000000000.000000","topic":{"value":"topic name","creator":"UUUUUIDIDID","last_set":1637617187},"purpose":{"value":"somepurpose","creator":"UUUUUIDIDID","last_set":1637617187},"previous_names":["chan1-xyz","test-xyz"]}}
18:05:39 DEBUG    errbot.backends.slackv3   Someone mentioned channel #chan1-xyz
18:05:39 DEBUG    errbot.backends.slackv3   Event type message not supported.
18:05:39 DEBUG    slack_sdk.socket_mode.bui Message processing completed (type: events_api, envelope_id: 11223344556677889900)

errbot-backend-slackv3 module install via pip inaccessible without specifying BOT_EXTRA_BACKEND_DIR

Without specifying BOT_EXTRA_BACKEND_DIR, errbot does not know about errbot-backend-slackv3 when it is installed via pip.

Specifying something like BOT_EXTRA_BACKEND_DIR="/home/pico/err_test/lib/python3.10/site-packages" seems a bit ugly and unnecessary. Why not make it so site-packages are checked?

~/err_test » pip show errbot-backend-slackv3
Name: errbot-backend-slackv3
Version: 0.2.1
Summary: Errbot SlackV3 backend plugin
Home-page: UNKNOWN
Author: Errbot
Author-email:
License: UNKNOWN
Location: /home/pico/err_test/lib/python3.10/site-packages
Requires: aiohttp, markdown, slack-sdk, slackeventsapi
Required-by:
(err_test) ---------------------------------------------------------------------------------------------------------------------------------------------------------------
~/err_test » errbot
10:42:59 INFO errbot.bootstrap Found Storage plugin: Shelf.
Traceback (most recent call last):
File "/home/pico/err_test/bin/errbot", line 8, in
sys.exit(main())
File "/home/pico/err_test/lib/python3.10/site-packages/errbot/cli.py", line 396, in main
bootstrap(backend, root_logger, config, restore)
File "/home/pico/err_test/lib/python3.10/site-packages/errbot/bootstrap.py", line 265, in bootstrap
bot = setup_bot(bot_class, logger, config, restore)
File "/home/pico/err_test/lib/python3.10/site-packages/errbot/bootstrap.py", line 172, in setup_bot
backendpm = BackendPluginManager(
File "/home/pico/err_test/lib/python3.10/site-packages/errbot/backend_plugin_manager.py", line 53, in init
raise PluginNotFoundException(
errbot.backend_plugin_manager.PluginNotFoundException: Could not find the plugin named SlackV3 in ['/home/pico/err_test/lib/python3.10/site-packages/errbot/backends'].
(err_test) ---------------------------------------------------------------------------------------------------------------------------------------------------------------

Blacklisting a plugin: 'BotPluginManager' object has no attribute 'bot'.

In order to let us help you better, please fill out the following fields as best you can:

I am...

  • Reporting a bug
  • Suggesting a new feature
  • Requesting help with running my bot
  • Requesting help writing plugins
  • Here about something else

I am running...

  • Errbot version: 6.1.5
  • OS version: ubuntu 18.04
  • Python version: 3.6.9
  • Using a virtual environment: yes, errbot running in a docker container

Issue description

Using a slack backend, I tried to disable the notifications coming from the VersionChecker

> Errbot App: Version 6.1.6 of Errbot is available. http://pypi.python.org/pypi/errbot/6.1.6. To disable this check do: !plugin blacklist VersionChecker
> User: !plugin blacklist VersionChecker
> Errbot App:  Plugin VersionChecker is now blacklisted.

After a while the following error message gets printed by errbot in the DM channel:

> Errbot App: Error: VersionChecker failed to activate: 'BotPluginManager' object has no attribute 'bot'.

Slack: A message property returning if it's an edit

In order to let us help you better, please fill out the following fields as best you can:

I am...

  • Suggesting a new feature

I am running...

  • Errbot version: 5.1.3
  • OS version: Debian (python Docker)
  • Python version: 3.6
  • Using a virtual environment: no

Issue description

After errbotio/errbot#382, when messages are edited in Slack, Errbot plugins receive them again. If the bot responds to a message (eg. "ticket errbotio/errbot#1234" -> {details of ticket}) then the channel keeps getting additional replies over & over.

Would be nice to be able to detect a message as an edit so the botcmd can skip it. eg. if msg.is_edit: return

This doesn't solve all use cases (eg. "ticket errbotio/errbot#1234" edited to "ticket errbotio/errbot#1235" when the bot should respond with different content) but does help the common case, and could be built on to support that sort of thing.

Using org level tokens

When using the backend with a Org level scope token, the creation and population of the user cache expects all users to have a team_id field in the profile. Users (mostly bot) that use a Org level bot token do not have a team_id and the bot fails to start up.

I have raised a pr #95 but I am unsure if thats the correct way, as a user at the org level has a list of teams. Should we query the api for all the teams (workspaces) a user is a member of or just use the enterprise_id of the org user as the team to fetch the domain?

KeyError: 'event' in Slack Shotcuts (Global and Message)

I create 2 shortcuts in my slack app:
Create Incident (global)
Add message to timeline (message)

Each time I call them I got an error for KeyError, these are the outputs:
Global Shortcuts
errbot | 2022-06-29 14:57:08,686 DEBUG slack_sdk.socket_mode.builtin.client on_message invoked: (message: {"envelope_id":"5e52dff0-ffe0-4665-989c-1ce859a5b8ec","payload":{"type":"shortcut","token":"r54I0FHrqju1kznGgGBAFjfS","action_ts":"1656514628.533123","team":{"id":"T0JF373M3","domain":"bfaindustries"},"user":{"id":"U0316G1MU68","username":"jonathanlinenberg","team_id":"T0JF373M3"},"is_enterprise_install":false,"enterprise":null,"callback_id":"incident_create","trigger_id":"3722463424407.18513241717.56efdf268a4fc66f0f58e0ff1d1e3c4a"},"type":"interactive","accepts_response_payload":false}) errbot | 2022-06-29 14:57:08,688 DEBUG slack_sdk.socket_mode.builtin.client A new message enqueued (current queue size: 1) errbot | 2022-06-29 14:57:08,689 DEBUG slack_sdk.socket_mode.builtin.client A message dequeued (current queue size: 0) errbot | 2022-06-29 14:57:08,692 DEBUG slack_sdk.socket_mode.builtin.client Message processing started (type: interactive, envelope_id: 5e52dff0-ffe0-4665-989c-1ce859a5b8ec) errbot | 2022-06-29 14:57:08,695 DEBUG errbot.backends.slackv3 Event type: interactive errbot | Envelope ID: 5e52dff0-ffe0-4665-989c-1ce859a5b8ec errbot | Accept Response Payload: False errbot | Retry Attempt: None errbot | Retry Reason: None errbot | errbot | 2022-06-29 14:57:08,696 DEBUG slack_sdk.socket_mode.builtin.client Sending a message (session id: 5de778a8-5c59-4373-ad68-e1aa4b125f58, message: {"envelope_id": "5e52dff0-ffe0-4665-989c-1ce859a5b8ec"}) errbot | 2022-06-29 14:57:08,698 DEBUG errbot.backends.slackv3 Received event: {'type': 'shortcut', 'token': 'r54I0FHrqju1kznGgGBAFjfS', 'action_ts': '1656514628.533123', 'team': {'id': 'T0JF373M3', 'domain': 'bfaindustries'}, 'user': {'id': 'U0316G1MU68', 'username': 'jonathanlinenberg', 'team_id': 'T0JF373M3'}, 'is_enterprise_install': False, 'enterprise': None, 'callback_id': 'incident_create', 'trigger_id': '3722463424407.18513241717.56efdf268a4fc66f0f58e0ff1d1e3c4a'} errbot | 2022-06-29 14:57:08,698 ERROR slack_sdk.socket_mode.builtin.client Failed to run a request listener: 'event' errbot | Traceback (most recent call last): errbot | File "/usr/local/lib/python3.8/site-packages/slack_sdk/socket_mode/client.py", line 143, in run_message_listeners errbot | listener(self, request) # type: ignore errbot | File "/home/errbot/backends/slackv3/slackv3.py", line 377, in _sm_generic_event_handler errbot | self._generic_wrapper(req.payload) errbot | File "/home/errbot/backends/slackv3/slackv3.py", line 357, in _generic_wrapper errbot | event = event_data["event"] errbot | KeyError: 'event' errbot | 2022-06-29 14:57:08,713 DEBUG slack_sdk.socket_mode.builtin.client Message processing completed (type: interactive, envelope_id: 5e52dff0-ffe0-4665-989c-1ce859a5b8ec) errbot | 2022-06-29 15:18:43,083 ERROR slack_sdk.socket_mode.builtin.client on_error invoked (session id: 5de778a8-5c59-4373-ad68-e1aa4b125f58, error: ConnectionResetError, message: [Errno 104] Connection reset by peer)

Message Shortcuts
errbot | 2022-06-29 15:24:38,488 DEBUG errbot.backends.slackv3 Received event: {'type': 'message_action', 'token': 'r54I0FHrqju1kznGgGBAFjfS', 'action_ts': '1656516278.342702', 'team': {'id': 'T0JF373M3', 'domain': 'bfaindustries'}, 'user': {'id': 'U0316G1MU68', 'username': 'jonathanlinenberg', 'team_id': 'T0JF373M3', 'name': 'jonathanlinenberg'}, 'channel': {'id': 'D03JFG2HEA2', 'name': 'directmessage'}, 'is_enterprise_install': False, 'enterprise': None, 'callback_id': 'add_time_line', 'trigger_id': '3737206539171.18513241717.639ec83871e676c1339d70de41e4bcce', 'response_url': 'https://hooks.slack.com/app/T0JF373M3/3737257573314/vDWEyR03AUMiycPk1woYOhCF', 'message_ts': '1656445152.214729', 'message': {'bot_id': 'B03KBKYS7C0', 'type': 'message', 'text': 'Plugin Test reloaded.', 'user': 'U03JK0HQRQV', 'ts': '1656445152.214729', 'app_id': 'A03JF7E2KLN', 'team': 'T0JF373M3', 'bot_profile': {'id': 'B03KBKYS7C0', 'deleted': False, 'name': 'AEON Bot', 'updated': 1655924661, 'app_id': 'A03JF7E2KLN', 'icons': {'image_36': 'https://avatars.slack-edge.com/2022-06-22/3691515832135_3f2c588584a086c04e70_36.png', 'image_48': 'https://avatars.slack-edge.com/2022-06-22/3691515832135_3f2c588584a086c04e70_48.png', 'image_72': 'https://avatars.slack-edge.com/2022-06-22/3691515832135_3f2c588584a086c04e70_72.png'}, 'team_id': 'T0JF373M3'}, 'blocks': [{'type': 'rich_text', 'block_id': 'TMoV', 'elements': [{'type': 'rich_text_section', 'elements': [{'type': 'text', 'text': 'Plugin Test reloaded.'}]}]}]}} errbot | 2022-06-29 15:24:38,488 ERROR slack_sdk.socket_mode.builtin.client Failed to run a request listener: 'event' errbot | Traceback (most recent call last): errbot | File "/usr/local/lib/python3.8/site-packages/slack_sdk/socket_mode/client.py", line 143, in run_message_listeners errbot | listener(self, request) # type: ignore errbot | File "/home/errbot/backends/slackv3/slackv3.py", line 377, in _sm_generic_event_handler errbot | self._generic_wrapper(req.payload) errbot | File "/home/errbot/backends/slackv3/slackv3.py", line 357, in _generic_wrapper errbot | event = event_data["event"] errbot | KeyError: 'event' errbot | 2022-06-29 15:24:38,489 DEBUG slack_sdk.socket_mode.builtin.client Message processing completed (type: interactive, envelope_id: af9a3466-ec08-437b-a857-e296b25eb9b1)

Do we've any docs about who to manage this callbacks? ty

Multiple admins

The README covers some changes to setting admins compared to the old Slack backend:

Bot Admins
Slack changed the way users are uniquely identified from display name @some_name to user id Uxxxxxx. Errbot configuration will need to be updated before administrators can be correctly identified aginst the ACL sets.

The UserID is in plain text format. It can be found in the the Slack full profile page or using the !whoami command (person field).

I'm able to set one admin with either BOT_ADMINS = Uxxxxxxxx or BOT_ADMINS = (Uxxxxxxxx) in config.py, but seem unable to set multiple admins. I've tested comma-separated with and without ' and with and without parentheses.

The only docs mentioning how to set multiple admins are the base Errbot docs with BOT_ADMINS = ('@gbin', '@zoni'), but I have tested BOT_ADMINS = ('Uxxxxxxxx', 'Uxxxxxxxx') without success.

Support Slack's new 'workspace' tokens

Slack currently has an open beta for the new app flow they call 'workspace based slack apps'. https://api.slack.com/slack-apps-preview. Based on what I can see it appears that once it's all rolled out they will be getting rid of the current bot token type. Errbot should support this new flow. It looks like this will be rolled out generally sometime around Q1 2018, so there's no rush, but I wanted to make sure you were aware of it.

Drop support for `@person` in favour of `<@userid>`

Slack changed how to uniquely identify users from the display name to user id. The #31 unittests branch includes a change which alters how user information in the Person class is reported and used.

To illustrate the change here is the whois output from the original slack backend:

┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━━━┓
┃ key      ┃ value                ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━━━┩
│ person   │ `@nzlosh`            │
├──────────┼──────────────────────┤
│ nick     │ `nzlosh`             │
├──────────┼──────────────────────┤
│ fullname │ `Carlos`             │
├──────────┼──────────────────────┤
│ client   │ `DSKNFHVT5`          │
├──────────┼──────────────────────┤
│ email    │ `[email protected]`   │
└──────────┴──────────────────────┘

The slackv3 unittests branch reports the following for whois

┏━━━━━━━━━━┳━━━━━━━━━━━━━━━━━━━━┓
┃ key      ┃ value              ┃
┡━━━━━━━━━━╇━━━━━━━━━━━━━━━━━━━━┩
│ person   │ `UMV1SEYMA`        │
├──────────┼────────────────────┤
│ nick     │ `nzlosh`           │
├──────────┼────────────────────┤
│ fullname │ `Carlos`           │
├──────────┼────────────────────┤
│ client   │ `D018GUWHXN0`      │
├──────────┼────────────────────┤
│ email    │ ``                 │
└──────────┴────────────────────┘

The major difference is the person field shifts from @username to userid. User id's are meant to be wrapped in the form <@Uxxxxx> when highlighting a specific user. This change in the backend would impact existing plugins that depend on the @username behaviour that has been deprecated since 2017. For more information about this change see https://api.slack.com/changelog/2017-09-the-one-about-usernames

Support Slack's Interactivity Mode & Block Kit

In order to let us help you better, please fill out the following fields as best you can:

I am...

  • Reporting a bug
  • Suggesting a new feature
  • Requesting help with running my bot
  • Requesting help writing plugins
  • Here about something else

I am running...

  • Errbot version:5.2.0
  • OS version: linux
  • Python version: 3.6
  • Using a virtual environment: yes

Issue description

The new block kits add more possible interactivity between users and bots, yet to enable the interactive mode you need to supply endpoint to receive the POST and processing all the thing that get sent to this endpoint whenever user interact with interactive component, it all has nice explanation detail in this docs https://api.slack.com/messaging/interactivity

Is it possible for errbot slack backend to implement this as a wrapper to cover the interactivity instead of creating your own endpoint and provision a special server to host the endpoint ?

I've seen this request too https://github.com/errbotio/errbot/issues/794 and I think slack did some change to their button with interactivity.

By far, did this possible for errbot to support this slack interactivity option ? That would be nice addition to errbot feature.

Additional info

It's my first request of feature, please guide me

Slack backend be able to react on `message_deleted` event

In order to let us help you better, please fill out the following fields as best you can:

I am...

  • Reporting a bug
  • Suggesting a new feature
  • Requesting help with running my bot
  • Requesting help writing plugins
  • Here about something else

I am running...

  • Errbot version:6.1.8
  • OS version: macOS Catalina 10.15.6
  • Python version: 3.7.9
  • Using a virtual environment: yes

Issue description

Please describe your bug/feature/problem here.
I'd like to make actions when a certain message gets deleted. I think it would be nice if it could call a callback like the callback_reaction so devs could decide how to act on this event.

Additional info

I'm happy to work on it if you could help me out.

No setting online status

Using socket mode online status is not set by bot...

I am having to set the online status as always online in the slack app creation website

Org level bots.info requires team_id

Since switching to using a bot installed at the Org level in Slack, the call to bots.info requires the team_id of the bot/app when doing the look up. This is a follow on from #96

https://api.slack.com/methods/bots.info - The team_id is required if using a org level token.

The only place I could see to grab this info was in https://github.com/errbotio/err-backend-slackv3/blob/main/src/slackv3/slackv3.py#L354 when we have all of the event_data from the event api. After that we only pass back the event (message).

I am working on a PR for the change what would grab the team_id from the event_data and pass that into the handers with a default of None.

Collaboration

@duhow @AgarFu

I've moved the slackv3 backend out of the errbot tree to try and accelerate the development process. I've invited you both to be collaborators in this project since you're both actively working on Slack Events support. I think it would be great if we can combine our efforts here and eventually propose a merge to errbot once the backend is stable.

My Slack Events backend version

Compared against default Slack Backend from Errbot 6.1.6: https://github.com/errbotio/errbot/blob/6.1.6/errbot/backends/slack.py

--- a/errbot/backends/slack.py
+++ b/errbot/backends/slack.py
@@ -4,6 +4,7 @@
 import logging
 import re
 import sys
+import time
 import pprint
 from functools import lru_cache
 from typing import BinaryIO
@@ -17,16 +18,19 @@
 from errbot.core import ErrBot
 from errbot.utils import split_string_after
 from errbot.rendering.ansiext import AnsiExtension, enable_format, IMTEXT_CHRS
+from errbot.core_plugins import flask_app
+from errbot.core_plugins.wsview import WebView
 
 log = logging.getLogger(__name__)
 
 try:
-    from slackclient import SlackClient
+    from slack_sdk import WebClient as SlackClient
+    from slack_sdk.errors import SlackApiError
 except ImportError:
     log.exception("Could not start the Slack back-end")
     log.fatal(
-        "You need to install the slackclient support in order to use the Slack backend.\n"
-        "You can do `pip install errbot[slack]` to install it"
+        "You need to install the slack_sdk library in order to use the Slack backend.\n"
+        "You can do `pip install slack_sdk` to install it"
     )
     sys.exit(1)
 
@@ -98,7 +102,7 @@
     This class describes a person on Slack's network.
     """
 
-    def __init__(self, sc, userid=None, channelid=None):
+    def __init__(self, userid, channelid=None, bot=None):
         if userid is not None and userid[0] not in ('U', 'B', 'W'):
             raise Exception(f'This is not a Slack user or bot id: {userid} (should start with U, B or W)')
 
@@ -107,20 +111,37 @@
 
         self._userid = userid
         self._channelid = channelid
-        self._sc = sc
+        self._bot = bot
+        self._sc = getattr(bot, 'sc', None)
+        self._info = None
 
     @property
     def userid(self):
         return self._userid
 
     @property
+    def info(self):
+        """ Get and store information from user or channel """
+        if self._info is None:
+            try:
+                user = self._bot.get_users_info(self._userid)
+                if user is None:
+                    raise SlackApiError
+                self._info = user
+            except SlackApiError as e:
+                log.error(f'Cannot find user with ID {self._userid} - {e.response["error"]}')
+                return f'<{self._userid}>'
+        return self._info
+
+    @property
     def username(self):
         """Convert a Slack user ID to their user name"""
-        user = self._sc.server.users.find(self._userid)
-        if user is None:
-            log.error('Cannot find user with ID %s', self._userid)
-            return f'<{self._userid}>'
-        return user.name
+        # https://api.slack.com/changelog/2017-09-the-one-about-usernames
+        return (
+            self.info.get('profile', {}).get('display_name_normalized') or
+            self.info.get('profile', {}).get('real_name_normalized') or
+            f'<{self._userid}>'
+        )
 
     @property
     def channelid(self):
@@ -132,10 +153,12 @@
         if self._channelid is None:
             return None
 
-        channel = self._sc.server.channels.find(self._channelid)
-        if channel is None:
+        channel = self._bot.get_conversations_info(self._channelid)
+        if not channel:
             raise RoomDoesNotExistError(f'No channel with ID {self._channelid} exists.')
-        return channel.name
+        if channel['is_im']:
+            return channel['user']
+        return channel['name']
 
     @property
     def domain(self):
@@ -150,25 +173,17 @@
     def aclattr(self):
         # Note: Don't use str(self) here because that will return
         # an incorrect format from SlackMUCOccupant.
-        return f'@{self.username}'
+        return f'@{self.userid}'
 
     @property
     def email(self):
         """Convert a Slack user ID to their user email"""
-        user = self._sc.server.users.find(self._userid)
-        if user is None:
-            log.error("Cannot find user with ID %s" % self._userid)
-            return "<%s>" % self._userid
-        return user.email
+        return self.info.get('profile', {}).get('email', f'<{self._userid}>')
 
     @property
     def fullname(self):
         """Convert a Slack user ID to their user name"""
-        user = self._sc.server.users.find(self._userid)
-        if user is None:
-            log.error('Cannot find user with ID %s', self._userid)
-            return f'<{self._userid}>'
-        return user.real_name
+        return self.info.get('real_name', f'<{self._userid}>')
 
     def __unicode__(self):
         return f'@{self.username}'
@@ -178,7 +193,7 @@
 
     def __eq__(self, other):
         if not isinstance(other, SlackPerson):
-            log.warning('tried to compare a SlackPerson with a %s', type(other))
+            log.warning(f'tried to compare a SlackPerson with a {type(other)}')
             return False
         return other.userid == self.userid
 
@@ -197,9 +212,13 @@
     This class represents a person inside a MUC.
     """
 
-    def __init__(self, sc, userid, channelid, bot):
-        super().__init__(sc, userid, channelid)
-        self._room = SlackRoom(channelid=channelid, bot=bot)
+    def __init__(self, userid, channelid, bot):
+        if isinstance(channelid, SlackRoom):
+            super().__init__(userid, channelid.id, bot)
+            self._room = channelid
+        else:
+            super().__init__(userid, channelid, bot)
+            self._room = SlackRoom(channelid=channelid, bot=bot)
 
     @property
     def room(self):
@@ -213,7 +232,7 @@
 
     def __eq__(self, other):
         if not isinstance(other, RoomOccupant):
-            log.warning('tried to compare a SlackRoomOccupant with a SlackPerson %s vs %s', self, other)
+            log.warning(f'tried to compare a SlackRoomOccupant with a SlackPerson {self} vs {other}')
             return False
         return other.room.id == self.room.id and other.userid == self.userid
 
@@ -223,10 +242,10 @@
     This class describes a bot on Slack's network.
     """
 
-    def __init__(self, sc, bot_id, bot_username):
+    def __init__(self, bot_id, bot_username, bot):
         self._bot_id = bot_id
         self._bot_username = bot_username
-        super().__init__(sc=sc, userid=bot_id)
+        super().__init__(bot_id, bot=bot)
 
     @property
     def username(self):
@@ -251,9 +270,12 @@
     This class represents a bot inside a MUC.
     """
 
-    def __init__(self, sc, bot_id, bot_username, channelid, bot):
-        super().__init__(sc, bot_id, bot_username)
-        self._room = SlackRoom(channelid=channelid, bot=bot)
+    def __init__(self, bot_id, bot_username, channelid, bot):
+        super().__init__(bot_id, bot_username)
+        if isinstance(channelid, SlackRoom):
+            self._room = channelid
+        else:
+            self._room = SlackRoom(channelid=channelid, bot=bot)
 
     @property
     def room(self):
@@ -267,22 +289,22 @@
 
     def __eq__(self, other):
         if not isinstance(other, RoomOccupant):
-            log.warning('tried to compare a SlackRoomBotOccupant with a SlackPerson %s vs %s', self, other)
+            log.warning(f'tried to compare a SlackRoomBotOccupant with a SlackPerson {self} vs {other}')
             return False
         return other.room.id == self.room.id and other.userid == self.userid
 
 
-class SlackBackend(ErrBot):
+class SlackEventsBackend(ErrBot):
 
     room_types = 'public_channel,private_channel'
 
     @staticmethod
     def _unpickle_identifier(identifier_str):
-        return SlackBackend.__build_identifier(identifier_str)
+        return SlackEventsBackend.__build_identifier(identifier_str)
 
     @staticmethod
     def _pickle_identifier(identifier):
-        return SlackBackend._unpickle_identifier, (str(identifier),)
+        return SlackEventsBackend._unpickle_identifier, (str(identifier),)
 
     def _register_identifiers_pickling(self):
         """
@@ -292,15 +314,17 @@
         But for the unpickling to work we need to use bot.build_identifier, hence the bot parameter here.
         But then we also need bot for the unpickling so we save it here at module level.
         """
-        SlackBackend.__build_identifier = self.build_identifier
+        SlackEventsBackend.__build_identifier = self.build_identifier
         for cls in (SlackPerson, SlackRoomOccupant, SlackRoom):
-            copyreg.pickle(cls, SlackBackend._pickle_identifier, SlackBackend._unpickle_identifier)
+            copyreg.pickle(cls, SlackEventsBackend._pickle_identifier, SlackEventsBackend._unpickle_identifier)
 
     def __init__(self, config):
         super().__init__(config)
         identity = config.BOT_IDENTITY
+        self.slack_event_webhook = '/slack/events'
         self.token = identity.get('token', None)
         self.proxies = identity.get('proxies', None)
+        self.signing = identity.get('signing_secret', None)
         if not self.token:
             log.fatal(
                 'You need to set your token (found under "Bot Integration" on Slack) in '
@@ -340,10 +364,8 @@
         """
         if data is None:
             data = {}
-        response = self.sc.api_call(method, **data)
-        if not isinstance(response, collections.Mapping):
-            # Compatibility with SlackClient < 1.0.0
-            response = json.loads(response.decode('utf-8'))
+        method = method.replace('.', '_')
+        response = getattr(self.sc, method)(**data)
 
         if raise_errors and not response['ok']:
             raise SlackAPIResponseError(f"Slack API call to {method} failed: {response['error']}",
@@ -364,66 +386,103 @@
 
         converted_prefixes = []
         for prefix in bot_prefixes:
-            try:
-                converted_prefixes.append(f'<@{self.username_to_userid(prefix)}>')
-            except Exception as e:
-                log.error('Failed to look up Slack userid for alternate prefix "%s": %s', prefix, e)
+            converted_prefixes.append(f'<@{prefix}>')
 
         self.bot_alt_prefixes = tuple(x.lower() for x in self.bot_config.BOT_ALT_PREFIXES)
-        log.debug('Converted bot_alt_prefixes: %s', self.bot_config.BOT_ALT_PREFIXES)
+        log.debug(f'Converted bot_alt_prefixes: {self.bot_config.BOT_ALT_PREFIXES}')
 
-    def serve_once(self):
-        self.sc = SlackClient(self.token, proxies=self.proxies)
+    def serve_forever(self):
+        self.sc = SlackClient(self.token, proxy=self.proxies)
 
         log.info('Verifying authentication token')
-        self.auth = self.api_call("auth.test", raise_errors=False)
+        self.auth = self.api_call("auth_test", raise_errors=False)
         if not self.auth['ok']:
             raise SlackAPIResponseError(error=f"Couldn't authenticate with Slack. Server said: {self.auth['error']}")
         log.debug("Token accepted")
-        self.bot_identifier = SlackPerson(self.sc, self.auth["user_id"])
+        self.bot_identifier = SlackBot(self.auth["user_id"], self.auth["user"], self)
 
-        log.info("Connecting to Slack real-time-messaging API")
-        if self.sc.rtm_connect():
-            log.info("Connected")
-            # Block on reads instead of using the busy loop suggested in slackclient docs
-            # https://github.com/slackapi/python-slackclient/issues/46#issuecomment-165674808
-            self.sc.server.websocket.sock.setblocking(True)
-            self.reset_reconnection_count()
+        # Setup webhook to Errbot flask
+        callable_view = WebView.as_view(
+            self._dispatch_slack_message.__name__ + '_POST',
+            self._dispatch_slack_message,
+            None,
+            True
+        )
+        flask_app.add_url_rule(
+            self.slack_event_webhook,
+            view_func=callable_view,
+            methods=('POST', ),
+            strict_slashes=False
+        )
 
-            # Inject bot identity to alternative prefixes
-            self.update_alternate_prefixes()
+        log.info(f"Added webhook {self.slack_event_webhook} to Slack Events")
 
-            try:
-                while True:
-                    for message in self.sc.rtm_read():
-                        self._dispatch_slack_message(message)
-            except KeyboardInterrupt:
-                log.info("Interrupt received, shutting down..")
-                return True
-            except Exception:
-                log.exception("Error reading from RTM stream:")
-            finally:
-                log.debug("Triggering disconnect callback")
-                self.disconnect_callback()
-        else:
-            raise Exception('Connection failed, invalid token ?')
+        self.update_alternate_prefixes()
 
-    def _dispatch_slack_message(self, message):
+        log.info("Slack calls ready")
+        self.connect_callback()
+        self.running = True
+        self.callback_presence(Presence(identifier=self.bot_identifier, status=ONLINE))
+        try:
+            while self.running:
+                time.sleep(.5)
+        except KeyboardInterrupt:
+            log.info("Interrupt received, shutting down..")
+            return True
+        except Exception:
+            log.exception("Error exception logged while on hold")
+        finally:
+            log.debug("Triggering disconnect callback")
+            self.disconnect_callback()
+
+    def _dispatch_slack_message(self, request):
         """
         Process an incoming message from slack.
 
         """
+
+        if request.json:
+            message = request.json
+        elif request.form.get('payload', None):
+            try:
+                message = json.loads(request.form['payload'])
+                log.debug(message)
+            except ValueError:
+                log.warn("Request payload received is not JSON.")
+                return
+        else:
+            log.warn("Invalid request.")
+            return
+
+        if request.headers.get('X-Slack-Retry-Num', None):
+            log.warn(f'Received a Slack Retry message, rejecting. Reason: {request.headers["X-Slack-Retry-Reason"]}')
+            return
+
+        # Avoid replay attacks
+        timestamp = int(request.headers.get('X-Slack-Request-Timestamp', 0))
+        time_request = abs(time.time() - timestamp)
+        if time_request > 300:
+            log.warn(f'Received a Slack Request older than 300 seconds, ignoring.')
+            return
+
         if 'type' not in message:
-            log.debug("Ignoring non-event message: %s.", message)
+            log.debug(f'Ignoring non-event message: {message}.')
             return
 
-        event_type = message['type']
+        event_type = message.get('event', message)['type']
 
         event_handlers = {
             'hello': self._hello_event_handler,
             'presence_change': self._presence_change_event_handler,
             'message': self._message_event_handler,
+            'view_closed': self._views_event_handler,
+            'view_submission': self._views_event_handler,
+            'interactive_message': self._interactive_message_event_handler,
             'member_joined_channel': self._member_joined_channel_event_handler,
+            'member_left_channel': self._member_left_channel_event_handler,
+            'url_verification': self._url_verification_event_handler,
+            'user_change': self._user_change_event_handler,
+            'app_mention': self._message_event_handler,
             'reaction_added': self._reaction_event_handler,
             'reaction_removed': self._reaction_event_handler
         }
@@ -431,14 +490,28 @@
         event_handler = event_handlers.get(event_type)
 
         if event_handler is None:
-            log.debug('No event handler available for %s, ignoring this event', event_type)
+            log.debug(f'No event handler available for {event_type}, ignoring this event')
             return
         try:
-            log.debug('Processing slack event: %s', message)
-            event_handler(message)
+            log.debug(f'Processing slack event: {event_type}')
+            return event_handler(message)
         except Exception:
             log.exception(f'{event_type} event handler raised an exception')
 
+    def _user_change_event_handler(self, event):
+        """Event handler for the 'user_change' event"""
+        # event = event.get('event', event)
+        # user = event.get('user', None)
+
+        # nothing to handle at the moment
+        pass
+
+    def _url_verification_event_handler(self, message):
+        """Event handler for the 'url_verification' event"""
+        # If bot config contains verification_token and
+        # is the same as stored, otherwise return true
+        return message['challenge']
+
     def _hello_event_handler(self, event):
         """Event handler for the 'hello' event"""
         self.connect_callback()
@@ -447,7 +520,7 @@
     def _presence_change_event_handler(self, event):
         """Event handler for the 'presence_change' event"""
 
-        idd = SlackPerson(self.sc, event['user'])
+        idd = SlackPerson(event['user'], bot=self)
         presence = event['presence']
         # According to https://api.slack.com/docs/presence, presence can
         # only be one of 'active' and 'away'
@@ -460,17 +533,84 @@
             status = ONLINE
         self.callback_presence(Presence(identifier=idd, status=status))
 
+    def _views_event_handler(self, event):
+        """
+        Event handler for the 'views' event, for modals
+        """
+
+        data = {}
+        # Try to extract data
+        for key, vals in event['view']['state']['values'].items():
+            for skey in vals:
+                fkey = f'{key}_{skey}'
+                if 'selected_option' in vals[skey]:
+                    data[fkey] = vals[skey]['selected_option']['value']
+                elif 'selected_options' in vals[skey]:
+                    data[fkey] = [x.get('value') for x in vals[skey]['selected_options']]
+                else:
+                    data[fkey] = vals[skey].get('value', None)
+                log.debug(f'{fkey} = {data[fkey]}')
+
+        msg = Message(
+            frm=SlackPerson(event['user']['id'], bot=self),
+            to=self.bot_identifier,
+            extras={
+                'type': event['type'],
+                'state': event['view']['state']['values'],
+                'values': data,
+                'url': event.get('response_urls', []),
+                'trigger_id': event.get('trigger_id', None),
+                'callback_id': event.get(
+                    'callback_id', event['view'].get('callback_id', None)),
+                'slack_event': event
+            }
+        )
+
+        flow, _ = self.flow_executor.check_inflight_flow_triggered(msg.extras['callback_id'], msg.frm)
+        if flow:
+            log.debug("Reattach context from flow %s to the message", flow._root.name)
+            msg.ctx = flow.ctx
+
+        self.callback_message(msg)
+        log.debug(f'Data to be returned: {msg.body}')
+        return msg.body
+
+    def _interactive_message_event_handler(self, event):
+        """
+        Event handler for the 'interactive' event, used in attachments / buttons.
+        """
+        msg = Message(
+            frm=SlackPerson(event['user']['id'], event['channel']['id'], bot=self),
+            to=self.bot_identifier,
+            extras={
+                'actions': [{x['name']: x} for x in event['actions']],
+                'url': event['response_url'],
+                'trigger_id': event.get('trigger_id', None),
+                'callback_id': event.get('callback_id', None),
+                'slack_event': event
+            }
+        )
+
+        flow, _ = self.flow_executor.check_inflight_flow_triggered(msg.extras['callback_id'], msg.frm)
+        if flow:
+            log.debug("Reattach context from flow %s to the message", flow._root.name)
+            msg.ctx = flow.ctx
+
+        self.callback_message(msg)
+
     def _message_event_handler(self, event):
         """Event handler for the 'message' event"""
+        event = event.get('event', event)
         channel = event['channel']
         if channel[0] not in 'CGD':
-            log.warning("Unknown message type! Unable to handle %s", channel)
+            log.warning(f'Unknown message type! Unable to handle {channel}')
             return
 
         subtype = event.get('subtype', None)
 
-        if subtype in ("message_deleted", "channel_topic", "message_replied"):
-            log.debug("Message of type %s, ignoring this event", subtype)
+        if subtype in ("message_deleted", "channel_topic", "message_replied",
+                       "channel_join", "channel_leave"):
+            log.debug(f'Message of type {subtype}, ignoring this event')
             return
 
         if subtype == "message_changed" and 'attachments' in event['message']:
@@ -495,16 +635,21 @@
             text = event.get('text', '')
             user = event.get('user', event.get('bot_id'))
 
+        if (event['type'] == 'app_mention' and not event['channel'].startswith('G')):
+            log.debug('Ignoring app_mention event on non-private channel, message event will handle it.')
+            return
+
         text, mentioned = self.process_mentions(text)
 
         text = self.sanitize_uris(text)
 
         log.debug('Saw an event: %s', pprint.pformat(event))
-        log.debug('Escaped IDs event text: %s', text)
+        log.debug(f'Escaped IDs event text: {text}')
 
         msg = Message(
             text,
             extras={
+                'mentions': mentioned,
                 'attachments': event.get('attachments'),
                 'slack_event': event,
             },
@@ -513,30 +658,38 @@
         if channel.startswith('D'):
             if subtype == "bot_message":
                 msg.frm = SlackBot(
-                    self.sc,
                     bot_id=event.get('bot_id'),
-                    bot_username=event.get('username', '')
+                    bot_username=event.get('username', ''),
+                    bot=self
                 )
+                msg.to = SlackPerson(user, event['channel'], self)
             else:
-                msg.frm = SlackPerson(self.sc, user, event['channel'])
-            msg.to = SlackPerson(self.sc, self.username_to_userid(self.sc.server.username),
-                                 event['channel'])
+                if user == self.bot_identifier.userid:
+                    msg.frm = self.bot_identifier
+                    msg.to = self.bot_identifier
+                else:
+                    msg.frm = SlackPerson(user, event['channel'], self)
+                    msg.to = msg.frm
             channel_link_name = event['channel']
         else:
             if subtype == "bot_message":
                 msg.frm = SlackRoomBot(
-                    self.sc,
                     bot_id=event.get('bot_id'),
                     bot_username=event.get('username', ''),
                     channelid=event['channel'],
                     bot=self
                 )
+                msg.to = SlackRoom(channelid=event['channel'], bot=self)
             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
+                if user == self.bot_identifier.userid:
+                    msg.frm = self.bot_identifier
+                    msg.to = self.bot_identifier
+                else:
+                    msg.to = SlackRoom(channelid=event['channel'], bot=self)
+                    msg.frm = SlackRoomOccupant(user, msg.to, bot=self)
+            channel_link_name = event['channel']
 
-        msg.extras['url'] = f'https://{self.sc.server.domain}.slack.com/archives/' \
+        msg.extras['url'] = f'{self.auth["url"]}archives/' \
                             f'{channel_link_name}/p{self._ts_for_message(msg).replace(".", "")}'
 
         self.callback_message(msg)
@@ -544,20 +697,41 @@
         if mentioned:
             self.callback_mention(msg, mentioned)
 
+    def _member_left_channel_event_handler(self, event):
+        """Event handler for the 'member_left_channel' event"""
+        return self._member_channel_event_handler(event, 'left')
+
     def _member_joined_channel_event_handler(self, event):
         """Event handler for the 'member_joined_channel' event"""
-        user = SlackPerson(self.sc, event['user'])
+        return self._member_channel_event_handler(event, 'joined')
+
+    def _member_channel_event_handler(self, event, action):
+        event = event.get('event', event)
+        user = SlackPerson(event['user'], bot=self)
+        log.info(f'User {user} has {action} channel {event["channel"]}')
         if user == self.bot_identifier:
-            self.callback_room_joined(SlackRoom(channelid=event['channel'], bot=self))
+            user = self.bot_identifier
+
+        occupant = SlackRoomOccupant(
+            userid=user.userid,
+            channelid=event['channel'],
+            bot=self
+        )
+
+        if action == 'left':
+            self.callback_room_left(occupant)
+        elif action == 'joined':
+            self.callback_room_joined(occupant)
 
     def _reaction_event_handler(self, event):
         """Event handler for the 'reaction_added'
            and 'reaction_removed' events"""
 
-        user = SlackPerson(self.sc, event['user'])
+        event = event.get('event', event)
+        user = SlackPerson(event['user'], bot=self)
         item_user = None
         if event['item_user']:
-            item_user = SlackPerson(self.sc, event['item_user'])
+            item_user = SlackPerson(event['item_user'], bot=self)
 
         action = REACTION_ADDED
         if event['type'] == 'reaction_removed':
@@ -571,37 +745,77 @@
                             reacted_to=event['item']
                             )
 
+        log.debug(f'{user.userid} reaction {reaction.action} for {reaction.reaction_name}')
         self.callback_reaction(reaction)
 
+    @lru_cache(1024)
+    def email_to_userid(self, email):
+        """Convert an Email to Slack user ID"""
+        user = self.sc.users_lookupByEmail(email=email)
+        if user is None or not user['ok']:
+            raise UserDoesNotExistError(f'Cannot find user with email {email}.')
+        return user['user']['id']
+
     def userid_to_username(self, id_):
         """Convert a Slack user ID to their user name"""
-        user = self.sc.server.users.get(id_)
+        user = self.get_users_info(id_)
         if user is None:
             raise UserDoesNotExistError(f'Cannot find user with ID {id_}.')
-        return user.name
+        return user['name']
 
     def username_to_userid(self, name):
         """Convert a Slack user name to their user ID"""
         name = name.lstrip('@')
-        user = self.sc.server.users.find(name)
-        if user is None:
+        if name == self.auth['user']:
+            return self.bot_identifier.userid
+        try:
+            user = self.get_users_info(name)
+            if user is None:
+                raise SlackApiError
+            return user['id']
+        except SlackApiError as e:
+            log.error(f'Cannot find user {name} - {e.response["error"]}')
             raise UserDoesNotExistError(f'Cannot find user {name}.')
-        return user.id
+        return name
 
+    @lru_cache(1024)
     def channelid_to_channelname(self, id_):
         """Convert a Slack channel ID to its channel name"""
-        channel = [channel for channel in self.sc.server.channels if channel.id == id_]
-        if not channel:
+        try:
+            channel = self.sc.conversations_info(channel=id_)
+            if not channel:
+                raise SlackApiError
+            return channel['channel']['name']
+        except SlackApiError as e:
             raise RoomDoesNotExistError(f'No channel with ID {id_} exists.')
-        return channel[0].name
+
+    @lru_cache(1024)
+    def get_users_info(self, id_):
+        try:
+            user = self.sc.users_info(user=id_)
+            if user is None or not user['ok']:
+                raise UserDoesNotExistError(f'Cannot find user with ID {id_}.')
+            return user['user']
+        except SlackAPIResponseError:
+            raise UserDoesNotExistError(f'Cannot find user with ID {id_}.')
+
+    @lru_cache(1024)
+    def get_conversations_info(self, name):
+        try:
+            conv = self.sc.conversations_info(channel=name)
+            if conv is None or not conv['ok']:
+                raise RoomDoesNotExistError(f'No channel named {name} exists')
+            return conv['channel']
+        except SlackAPIResponseError:
+            raise RoomDoesNotExistError(f'No channel named {name} exists')
 
     def channelname_to_channelid(self, name):
         """Convert a Slack channel name to its channel ID"""
         name = name.lstrip('#')
-        channel = [channel for channel in self.sc.server.channels if channel.name == name]
+        channel = self.get_conversations_info(name)
         if not channel:
             raise RoomDoesNotExistError(f'No channel named {name} exists')
-        return channel[0].id
+        return channel['id']
 
     def channels(self, exclude_archived=True, joined_only=False, types=room_types):
         """
@@ -618,9 +832,19 @@
         See also:
           * https://api.slack.com/methods/conversations.list
         """
-        response = self.api_call('conversations.list', data={'exclude_archived': exclude_archived, 'types': types})
-        channels = [channel for channel in response['channels']
-                    if channel['is_member'] or not joined_only]
+        channels = list()
+        next_results = True
+
+        while next_results:
+            query = {
+                'exclude_archived': exclude_archived,
+                'limit': 1000,
+                'cursor': next_results if isinstance(next_results, str) else None,
+                'types': types
+            }
+            response = self.api_call('conversations.list', query)
+            channels.extend([x for x in response['channels'] if x['is_member'] or not joined_only])
+            next_results = response['response_metadata'].get('next_cursor', None)
 
         # There is no need to list groups anymore.  Groups are now identified as 'private_channel'
         # type using the conversations.list api method.
@@ -638,8 +862,8 @@
         try:
             response = self.api_call('conversations.open', data={'users': id_})
             return response['channel']['id']
-        except SlackAPIResponseError as e:
-            if e.error == "cannot_dm_bot":
+        except SlackApiError as e:
+            if e['error'] == "cannot_dm_bot":
                 log.info('Tried to DM a bot.')
                 return None
             else:
@@ -659,7 +883,7 @@
             to_channel_id = msg.to.channelid
             if to_channel_id.startswith('C'):
                 log.debug("This is a divert to private message, sending it directly to the user.")
-                to_channel_id = self.get_im_channel(self.username_to_userid(msg.to.username))
+                to_channel_id = self.get_im_channel(msg.to.userid)
         return to_humanreadable, to_channel_id
 
     def send_message(self, msg):
@@ -683,14 +907,14 @@
                 to_humanreadable = msg.to.username
                 if isinstance(msg.to, RoomOccupant):  # private to a room occupant -> this is a divert to private !
                     log.debug("This is a divert to private message, sending it directly to the user.")
-                    to_channel_id = self.get_im_channel(self.username_to_userid(msg.to.username))
+                    to_channel_id = self.get_im_channel(msg.to.userid)
                 else:
                     to_channel_id = msg.to.channelid
 
             msgtype = "direct" if msg.is_direct else "channel"
-            log.debug('Sending %s message to %s (%s).', msgtype, to_humanreadable, to_channel_id)
+            log.debug(f'Sending {msgtype} message to {to_humanreadable} ({to_channel_id}).')
             body = self.md.convert(msg.body)
-            log.debug('Message size: %d.', len(body))
+            log.debug(f'Message size: {len(body)}.')
 
             parts = self.prepare_message_body(body, self.message_size_limit)
 
@@ -702,13 +926,21 @@
                     'unfurl_media': 'true',
                     'link_names': '1',
                     'as_user': 'true',
+                    'attachments': msg.extras.get('attachments', None),
                 }
 
                 # Keep the thread_ts to answer to the same thread.
                 if 'thread_ts' in msg.extras:
                     data['thread_ts'] = msg.extras['thread_ts']
 
-                result = self.api_call('chat.postMessage', data=data)
+                method = 'chat.postMessage'
+                if msg.extras.get('ephemeral'):
+                    method = 'chat.postEphemeral'
+                    data['user'] = msg.to.userid
+                    if isinstance(msg.to, RoomOccupant):
+                        data['channel'] = msg.to.channelid
+
+                result = self.api_call(method, data=data)
                 timestamps.append(result['ts'])
 
             msg.extras['ts'] = timestamps
@@ -724,11 +956,11 @@
         """
         try:
             stream.accept()
-            resp = self.api_call('files.upload', data={
-                'channels': stream.identifier.channelid,
-                'filename': stream.name,
-                'file': stream
-            })
+            resp = self.sc.files_upload(
+                channels=stream.identifier.channelid,
+                filename=stream.name,
+                file=stream
+            )
             if 'ok' in resp and resp['ok']:
                 stream.success()
             else:
@@ -754,8 +986,7 @@
             :return Stream: object on which you can monitor the progress of it.
         """
         stream = Stream(user, fsource, name, size, stream_type)
-        log.debug('Requesting upload of %s to %s (size hint: %d, stream type: %s).',
-                  name, user.channelname, size, stream_type)
+        log.debug(f'Requesting upload of {name} to {user.channelid} (stream type: {stream_type})')
         self.thread_pool.apply_async(self._slack_upload, (stream,))
         return stream
 
@@ -795,7 +1026,7 @@
                 'as_user': 'true'
             }
             try:
-                log.debug('Sending data:\n%s', data)
+                log.debug(f'Sending data:\n{data}')
                 self.api_call('chat.postMessage', data=data)
             except Exception:
                 log.exception(f'An exception occurred while trying to send a card to {to_humanreadable}.[{card}]')
@@ -850,6 +1081,7 @@
         Supports strings with the following formats::
 
             <#C12345>
+            <#C12345|channel>
             <@U12345>
             <@U12345|user>
             @user
@@ -884,7 +1116,10 @@
                 else:
                     userid = text
             elif text[0] in ('C', 'G', 'D'):
-                channelid = text
+                if '|' in text:
+                    channelid, channelname = text.split('|')
+                else:
+                    channelid = text
             else:
                 raise ValueError(exception_message % text)
         elif text[0] == '@':
@@ -907,7 +1142,7 @@
         Supports strings with the formats accepted by
         :func:`~extract_identifiers_from_string`.
         """
-        log.debug('building an identifier from %s.', txtrep)
+        log.debug(f'building an identifier from {txtrep}.')
         username, userid, channelname, channelid = self.extract_identifiers_from_string(txtrep)
 
         if userid is None and username is not None:
@@ -915,9 +1150,11 @@
         if channelid is None and channelname is not None:
             channelid = self.channelname_to_channelid(channelname)
         if userid is not None and channelid is not None:
-            return SlackRoomOccupant(self.sc, userid, channelid, bot=self)
+            return SlackRoomOccupant(userid, channelid, bot=self)
         if userid is not None:
-            return SlackPerson(self.sc, userid, self.get_im_channel(userid))
+            if userid == self.bot_identifier.userid:
+                return self.bot_identifier
+            return SlackPerson(userid, self.get_im_channel(userid), bot=self)
         if channelid is not None:
             return SlackRoom(channelid=channelid, bot=self)
 
@@ -994,6 +1231,7 @@
             return msg.extras['slack_event']['ts']
 
     def shutdown(self):
+        self.running = False
         super().shutdown()
 
     @property
@@ -1019,7 +1257,7 @@
             A list of :class:`~SlackRoom` instances.
         """
         channels = self.channels(joined_only=True, exclude_archived=True,)
-        return [SlackRoom(channelid=channel['id'], bot=self) for channel in channels]
+        return [SlackRoom(name=channel['name'], channelid=channel['id'], bot=self) for channel in channels]
 
     def prefix_groupchat_reply(self, message, identifier):
         super().prefix_groupchat_reply(message, identifier)
@@ -1050,39 +1288,38 @@
         """
         mentioned = []
 
-        m = re.findall('<@[^>]*>*', text)
+        m = re.findall('<[@#][^>]*>*', text)
 
         for word in m:
             try:
                 identifier = self.build_identifier(word)
             except Exception as e:
-                log.debug("Tried to build an identifier from '%s' but got exception: %s", word, e)
+                log.debug(f"Tried to build an identifier from '{word}' but got exception: {e}")
                 continue
 
             # We only track mentions of persons.
             if isinstance(identifier, SlackPerson):
-                log.debug('Someone mentioned')
+                log.debug(f'Someone mentioned user {identifier}')
+                mentioned.append(identifier)
+                text = text.replace(word, f'@{identifier.userid}')
+            elif isinstance(identifier, SlackRoom):
+                log.debug(f'Someone mentioned channel {identifier}')
                 mentioned.append(identifier)
-                text = text.replace(word, str(identifier))
+                text = text.replace(word, f'#{identifier.channelid}')
 
         return text, mentioned
 
 
 class SlackRoom(Room):
     def __init__(self, name=None, channelid=None, bot=None):
-        if channelid is not None and name is not None:
-            raise ValueError("channelid and name are mutually exclusive")
-
         if name is not None:
-            if name.startswith('#'):
-                self._name = name[1:]
-            else:
-                self._name = name
+            self._name = name.replace('#', '')
         else:
             self._name = bot.channelid_to_channelname(channelid)
 
-        self._id = None
+        self._id = channelid
         self._bot = bot
+        self._info = None
         self.sc = bot.sc
 
     def __str__(self):
@@ -1097,22 +1334,23 @@
         """
         The channel object exposed by SlackClient
         """
-        id_ = self.sc.server.channels.find(self.name)
-        if id_ is None:
-            raise RoomDoesNotExistError(f"{str(self)} does not exist (or is a private group you don't have access to)")
-        return id_
+        if self._id:
+            return self
+        return self.info.get('id')
 
     @property
-    def _channel_info(self):
-        """
-        Channel info as returned by the Slack API.
-
-        See also:
-          * https://api.slack.com/methods/conversations.list
-            Removed the groups.info call.  Conversations.info covers it all
-        """
-
-        return self._bot.api_call('conversations.info', data={'channel': self.id})["channel"]
+    def info(self):
+        if self._info is not None:
+            return self._info
+        try:
+            info = self._bot.get_conversations_info(self.id)
+            if info:
+                self._info = info
+                return self._info
+        except SlackApiError:
+            raise RoomDoesNotExistError(f"{str(self)} does not exist (or is a private group you don't have access to)")
+            return dict()
+        return dict()
 
     @property
     def _channel_members(self):
@@ -1144,9 +1382,10 @@
         return self._name
 
     def join(self, username=None, password=None):
-        log.info("Joining channel %s", str(self))
+        log.info(f'Joining channel {self}')
         try:
             self._bot.api_call('conversations.join', data={'channel': self.id})
+            return True
         except SlackAPIResponseError as e:
             if e.error == 'user_is_bot':
                 raise RoomError(f'Unable to join channel. {USER_IS_BOT_HELPTEXT}')
@@ -1156,10 +1395,10 @@
     def leave(self, reason=None):
         try:
             if self.id.startswith('C'):
-                log.info('Leaving channel %s (%s)', self, self.id)
+                log.info(f'Leaving channel {self} ({self.id})')
                 self._bot.api_call('conversations.leave', data={'channel': self.id})
             else:
-                log.info('Leaving group %s (%s)', self, self.id)
+                log.info(f'Leaving group {self} ({self.id})')
                 self._bot.api_call('conversations.leave', data={'channel': self.id})
         except SlackAPIResponseError as e:
             if e.error == 'user_is_bot':
@@ -1171,10 +1410,10 @@
     def create(self, private=False):
         try:
             if private:
-                log.info('Creating private channel %s.', self)
+                log.info(f'Creating private channel {self}.')
                 self._bot.api_call('conversations.create', data={'name': self.name, 'is_private': True})
             else:
-                log.info('Creating channel %s.', self)
+                log.info(f'Creating channel {self}.')
                 self._bot.api_call('conversations.create', data={'name': self.name})
         except SlackAPIResponseError as e:
             if e.error == 'user_is_bot':
@@ -1185,10 +1424,10 @@
     def destroy(self):
         try:
             if self.id.startswith('C'):
-                log.info('Archiving channel %s (%s)', self, self.id)
+                log.info(f'Archiving channel {self} ({self.id})')
                 self._bot.api_call('conversations.archive', data={'channel': self.id})
             else:
-                log.info('Archiving group %s (%s)', self, self.id)
+                log.info(f'Archiving group {self} ({self.id})')
                 self._bot.api_call('conversations.archive', data={'channel': self.id})
         except SlackAPIResponseError as e:
             if e.error == 'user_is_bot':
@@ -1197,6 +1436,17 @@
                 raise RoomError(e)
         self._id = None
 
+    def rename(self, name):
+        try:
+            log.info(f'Renaming channel {self} to {name} ({self.id})')
+            self.sc.conversations_rename(channel=self.id, name=name)
+            self._name = name
+        except SlackAPIResponseError as e:
+            if e.error == 'user_is_bot':
+                raise RoomError(f'Unable to archive channel. {USER_IS_BOT_HELPTEXT}')
+            else:
+                raise RoomError(e)
+
     @property
     def exists(self):
         channels = self._bot.channels(joined_only=False, exclude_archived=False)
@@ -1209,55 +1459,73 @@
 
     @property
     def topic(self):
-        if self._channel_info['topic']['value'] == '':
+        if self.info['topic']['value'] == '':
             return None
         else:
-            return self._channel_info['topic']['value']
+            return self.info['topic']['value']
 
     @topic.setter
     def topic(self, topic):
         # No need to separate groups from channels here anymore.
 
-        log.info('Setting topic of %s (%s) to %s.', self, self.id, topic)
+        log.info(f'Setting topic of {self} ({self.id}) to {topic}.')
         self._bot.api_call('conversations.setTopic', data={'channel': self.id, 'topic': topic})
+        # update topic
+        self._info['topic']['value'] = topic
 
     @property
     def purpose(self):
-        if self._channel_info['purpose']['value'] == '':
+        if self.info['purpose']['value'] == '':
             return None
         else:
-            return self._channel_info['purpose']['value']
+            return self.info['purpose']['value']
 
     @purpose.setter
     def purpose(self, purpose):
         # No need to separate groups from channels here anymore.
 
-        log.info('Setting purpose of %s (%s) to %s.', str(self), self.id, purpose)
+        log.info(f'Setting purpose of {self} ({self.id}) to {purpose}.')
         self._bot.api_call('conversations.setPurpose', data={'channel': self.id, 'purpose': purpose})
 
     @property
     def occupants(self):
         members = self._channel_members['members']
-        return [SlackRoomOccupant(self.sc, m, self.id, self._bot) for m in members]
+        return [SlackRoomOccupant(m, self.id, self._bot) for m in members]
 
     def invite(self, *args):
-        users = {user['name']: user['id'] for user in self._bot.api_call('users.list')['members']}
         for user in args:
-            if user not in users:
-                raise UserDoesNotExistError(f'User "{user}" not found.')
-            log.info('Inviting %s into %s (%s)', user, self, self.id)
-            method = 'conversations.invite'
-            response = self._bot.api_call(
-                method,
-                data={'channel': self.id, 'user': users[user]},
-                raise_errors=False
-            )
-
-            if not response['ok']:
-                if response['error'] == 'user_is_bot':
+            if isinstance(user, SlackPerson):
+                if user == self._bot.bot_identifier:
+                    continue
+                user = user.userid
+            log.info(f'Inviting {user} into {self} ({self.id})')
+            try:
+                response = self.sc.conversations_invite(users=user, channel=self.id)
+            except SlackApiError as e:
+                if e.response.get('error') == 'already_in_channel':
+                    pass  # nothing to do
+                elif e.response.get('error') == 'not_in_channel':
+                    log.warning(f'Not in channel {self.id}. Trying to join.')
+                    if self.join():  # try to join and reinvite
+                        self.sc.conversations_invite(users=user, channel=self.id)
+                        pass
+                elif e.response.get('error') == 'user_is_bot':
                     raise RoomError(f'Unable to invite people. {USER_IS_BOT_HELPTEXT}')
-                elif response['error'] != 'already_in_channel':
-                    raise SlackAPIResponseError(error=f'Slack API call to {method} failed: {response["error"]}.')
+                else:
+                    raise SlackAPIResponseError(error=f'Slack API conversations.invite failed: {e.response["error"]}.')
+
+    def kick(self, user):
+        if isinstance(user, SlackPerson):
+            if user == self._bot.bot_identifier:
+                return self.leave()
+            user = user.userid
+
+        try:
+            response = self.sc.conversations_kick(channel=self.id, user=user)
+            if response['ok'] or response['error'] == 'not_in_channel':
+                return True
+        except SlackApiError as e:
+            return False
 
     def __eq__(self, other):
         if not isinstance(other, SlackRoom):

Error trying uload file on chats slack with new backend

i triying to upload a file to a slack chat. This is a complete log

2022-07-18 17:13:51,398 ERROR errbot.backends.slackv3 Upload of archivos-R107-H02.tar.gz to coordinación-entregas failed.
Traceback (most recent call last):
File "/errbot-dir/backend/err-backend-slackv3/slackv3.py", line 752, in _slack_upload
resp = self.slack_web.files_upload(
File "/usr/lib/python3.9/site-packages/slack_sdk/web/client.py", line 2975, in files_upload
return self.api_call("files.upload", files={"file": file}, data=kwargs)
File "/usr/lib/python3.9/site-packages/slack_sdk/web/base_client.py", line 156, in api_call
return self._sync_send(api_url=api_url, req_args=req_args)
File "/usr/lib/python3.9/site-packages/slack_sdk/web/base_client.py", line 187, in _sync_send
return self._urllib_api_call(
File "/usr/lib/python3.9/site-packages/slack_sdk/web/base_client.py", line 309, in _urllib_api_call
return SlackResponse(
File "/usr/lib/python3.9/site-packages/slack_sdk/web/slack_response.py", line 189, in validate
raise e.SlackApiError(message=msg, response=self)
slack_sdk.errors.SlackApiError: The request to the Slack API failed. (url: https://www.slack.com/api/files.upload)
The server responded with: {'ok': False, 'error': 'invalid_channel', 'channel': '<#GCP4E5A1G|coordinación-entregas>'}

Support Slack Message Buttons

Slack recently introduced the concept of message buttons.

Example Message Button

Blog post: https://slackhq.com/get-more-done-with-message-buttons-5fa5b283a59
Developer documentation: https://api.slack.com/docs/message-buttons

Here's their summary of the interaction model:

  1. Your application produces a message containing buttons. Maybe the message originated in response to an invoked slash command, or in response to a bot user's trigger phrase. Or maybe your app posted the message manually using an incoming webhook or chat.postMessage. In any case, your Slack app produced a message with buttons, offering users a chance to interact with it.
  2. Users encounter your message and, inspired by its call to action, clicks one of your buttons. This triggers an invocation of your application's associated Action URL.
  3. Slack sends a request to your Action URL, sending all the context needed to identify the originating message, the user that executed the action, and the specific values you've associated with the button. This request also contains a response_url you can use to continue interacting with the user or channel.
  4. Your application responds to the action. If you respond directly to the incoming invocation request, your provided message will replace the existing message. It's also possible to respond with an ephemeral message, visible only to the invoking user. Or you can just respond with HTTP 200 OK and wait to continue the interaction until later using the response_url provided as part of the action.
  5. Meanwhile: Your application does whatever it does as a result of the intended action to be taken: enqueue a process, save a database row, or continue interacting with users through additional message buttons.
  6. By using the response_url, your app can continue interacting with users up to 5 times within 30 minutes of the action invocation. Use this to continue through a workflow until it is complete.
  7. Messages can evolve. By using chat.update and your created message's message_ts field, you can modify the original interactive message (including all of its attachments) to add or remove buttons based on user interactions.

I opened this issue so there would be a place for conversation about if and how errbot could support message buttons.

ACL matching channel names or ID

When using the channel name e.g: #channel in ACLS with allowrooms the command is blocked/rejected.
Should we be using the slack channel id or the name for ACLs?

Exception occured while sending messages to channel

Hello,

I encountered the following exception while sending messages to channel:

2022-09-21 06:44:00.496UTC ERROR An exception occurred while trying to send the following message to channel: test messsage
Traceback (most recent call last):
  File "/opt/errbot/backend/err-backend-slackv3/slackv3.py", line 715, in send_message
    current_ts_length = len(msg.extras["ts"])
KeyError: 'ts'

Note: this error was seen only today. It was not seen in my previous testing.

Exception while using add_reaction in release v0.1.1

Hello,

I had cloned the latest release v0.1.1 and encountered the following bug while using add_reaction() method:

Traceback (most recent call last):
  File "/usr/local/lib/python3.8/dist-packages/errbot/core.py", line 574, in _execute_and_send
    reply = method(msg, match) if match else method(msg, args)
  File "/opt/errbot/extra_plugins/err-shellexec/shellexec.py", line 217, in new_method
    self._bot.add_reaction(msg,workingEmoji)
  File "/opt/errbot/backend/err-backend-slackv3/slackv3.py", line 998, in add_reaction
    return self._react("reactions.add", msg, reaction)
  File "/opt/errbot/backend/err-backend-slackv3/slackv3.py", line 1019, in _react
    self.api_call(
  File "/opt/errbot/backend/err-backend-slackv3/slackv3.py", line 120, in api_call
    response = self.slack_web.api_call(method, **data)
TypeError: api_call() got an unexpected keyword argument 'channel'

Note: The above exception is not seen when I use the code from main branch and things work as expected.

Please let me know if any further information is required.

Slack: add threading support to cards and file streams

In order to let us help you better, please fill out the following fields as best you can:

I am...

  • Reporting a bug
  • Suggesting a new feature
  • Requesting help with running my bot
  • Requesting help writing plugins
  • Here about something else

I am running...

  • Errbot version:
  • OS version: 5.1.2
  • Python version: 3.5.4
  • Using a virtual environment: yes

Issue description

Threading support was added to the Slack backend in errbotio/errbot#1057. It only adds threading support for regular messages, but not for cards or file streams. It would be helpful to add threading support to cards and streams as well.

slack RTM can send Slack Event like modals or messages with buttons?

Hi everyone,

I'm working with errbot to have it running to send slack modals or messages with buttons to make same automation from the channels. But for now the only thing I make it, is have this connected to slack.RTM as classic app, but when I try to run some modal or message with buttons I only see the plain text but nothing as buttons or modal.
Can someone share me an example as how can I call a modal from slack.rtm?

If this is not posible from RTM, can some one share an example on how link nginx with errbot and left him listen to /slack/events? because I search it but I didn't find any useful example. I saw errbot is using flask but I don't understand how I can add a new target beside to create a webhook or I need to make a webhook for each slack event?

Thanks!

Large channels triggering rate limits on conversations.info due to many SlackRoom instances created

I tried to migrate an errbot instance running a bot plugin from the old errbot slack backend to the SlackV3 backend. However, the instance pretty quickly hits a wall:

slack_sdk.errors.SlackApiError: The request to the Slack API failed. (url: https://www.slack.com/api/conversations.info)
The server responded with: {'ok': False, 'error': 'ratelimited'}

This happens while the backend calls conversations_info(). conversations.info is a Tier-3 method. Hence, the rate limit is about 50 calls/minute. However, conversations_info() is called each time a SlackRoom is created (via _cache_channel_info()). This happens for each SlackRoomOccupant that is created, e.g. when accessing SlackRoom.occupants(). Thus, the rate limit is easily hit in bigger channels. To address this issue, some kind of caching (#3) is required to prevent calling conversations.info when creating SlackRoom instances. Especially for occupants(), since the SlackRoom of each of the occupants is self and creating another instance is avoidable?

Full trace

Traceback (most recent call last):
File "/home/mjsvc/.virtualenvs/hserrbot/lib/python3.6/site-packages/errbot/plugin_manager.py", line 521, in activate_plugin
self._activate_plugin(plugin, plugin_info)
File "/home/mjsvc/.virtualenvs/hserrbot/lib/python3.6/site-packages/errbot/plugin_manager.py", line 474, in _activate_plugin
plugin.activate()
File "/home/mjsvc/etc/hserrbot/data/plugins/markusj/titlebot-ng/titlebot.py", line 1170, in activate
self.tryAddRoom(room)
File "/home/mjsvc/etc/hserrbot/data/plugins/markusj/titlebot-ng/titlebot.py", line 1122, in tryAddRoom
occupants = room.occupants # cache occupants
File "/home/mjsvc/local/src/err-backend/err-backend-slackv3/_slack/room.py", line 229, in occupants
SlackRoomOccupant(self._webclient, member, self.id, self._bot)
File "/home/mjsvc/local/src/err-backend/err-backend-slackv3/_slack/room.py", line 277, in init
self._room = SlackRoom(webclient=webclient, channelid=channelid, bot=bot)
File "/home/mjsvc/local/src/err-backend/err-backend-slackv3/_slack/room.py", line 48, in init
self._cache_channel_info(channelid)
File "/home/mjsvc/local/src/err-backend/err-backend-slackv3/_slack/room.py", line 92, in _cache_channel_info
res = self._webclient.conversations_info(channel=channelid)
File "/home/mjsvc/.virtualenvs/hserrbot/lib/python3.6/site-packages/slack_sdk/web/client.py", line 2242, in conversations_info
return self.api_call("conversations.info", http_verb="GET", params=kwargs)
File "/home/mjsvc/.virtualenvs/hserrbot/lib/python3.6/site-packages/slack_sdk/web/base_client.py", line 145, in api_call
return self._sync_send(api_url=api_url, req_args=req_args)
File "/home/mjsvc/.virtualenvs/hserrbot/lib/python3.6/site-packages/slack_sdk/web/base_client.py", line 189, in _sync_send
additional_headers=headers,
File "/home/mjsvc/.virtualenvs/hserrbot/lib/python3.6/site-packages/slack_sdk/web/base_client.py", line 323, in _urllib_api_call
status_code=response["status"],
File "/home/mjsvc/.virtualenvs/hserrbot/lib/python3.6/site-packages/slack_sdk/web/slack_response.py", line 205, in validate
raise e.SlackApiError(message=msg, response=self)
slack_sdk.errors.SlackApiError: The request to the Slack API failed. (url: https://www.slack.com/api/conversations.info)
The server responded with: {'ok': False, 'error': 'ratelimited'}

SlackV3 plugin fails to load alert but appears to be working since bot can actually connect to Slack

Hello,

I'm getting the following error from the bot (as a DM in slack) after it's loaded up:

Did not find any plugin in /Users/lanthos/repos/robo2/bot/backend/err-backend-slackv3/build/lib/slackv3.Error: SlackV3 failed to activate: 'NoneType' object has no attribute 'is_activated'.

I've tried digging around to see if I could figure out what the problem is but haven't been able to figure it out. The part of the code that is throwing this error is the activate_non_started_plugins method in plugin_manager.py from errbot (I'm running the latest from pip, 6.1.9), specifically line 444. And I've got errbot-backend-slackv3 0.2.1 installed for the backend.

I've setup things as per the read me:

created backend folder in same folder as config.py and plugins folder
inside backend folder I did git clone https://github.com/errbotio/err-backend-slackv3
I cded into the err-backend-slackv3 folder and ran pip install . (side note, the docs forget to mention you have to go into the directory with setup.py to actually run pip install . or it won't work)
I've added the slackv3 backend and extra backend directory as per the directions.

Inside the directory /Users/lanthos/repos/robo2/bot/backend/err-backend-slackv3/build/lib/slackv3 I can see the plugin and everything appears to be there.

The thing that is weird, is if I don't use the BACKEND="SlackV3" declaration the bot can't even connect to Slack at all. But if I use it I can connect but I get this error. The bot appears to be working normally, my other plugins work just fine, I just keep getting this error about SlackV3. Any ideas on what could be the problem?

Any help would be greatly appreciated, thank you!

Caching content

One of the main concers using WebClient is that every time a Person is created or a Room is named, Errbot has to call the Slack API several times to get each piece of information. Every. Single. Message.

From #2 I'm using lru_cache to address this, but a time-based cache would be the best option in here, or even having some control to drop cache for a user when the bot does some action, such as changing the topic of a channel.

`send_card` is only in-thread while `send_stream_request` is only in-channel

send_card (and send_stream_request) used to not reply in-thread (Errbot #1549), but within the last few months send_card is suddenly only reply in-thread when invoked.

As noted by @nzlosh this repo have recently made changes to its send_card implimentation:

This sounds specific to the slack backend, which was patched to reply to threads here errbotio/err-backend-slackv3#76

Do you mind opening an issue under the slackv3 repository please?

api_call complains about unexpected keyword argument

I'm using the SlackV3 backend (latest main branch), migrating from the old slack backend. All calls to self._bot.api_call seem to fail complaining about unexpected keyword arguments.

Examples:

self._bot.api_call("users.list", data={"limit": 1000})
# ERROR: api_call() got an unexpected keyword argument 'limit'

self._bot.api_call("conversation.create", data={"name": "test"})
# ERROR: api_call() got an unexpected keyword argument 'name'

Support for Interactive Mode over Websockets

Greetings,

I've been looking through the documentation, and the code itself. Unless I'm missing something, I do not see support for interactive messages over websocket (likely missing everywhere, but I'm only focused on websocket backend). Is this functionality there and I'm missing it? Or is this on the roadmap to be included?

Also, if there were any hints on doing this manually as a workaround, it would be much appreciated.

Happy New Year!

Unable to import custom python modules when using the slack backend.

Describe the bug
Errbot running on python 3.11 in docker, using the slackv3 backend is unable to load a python library into a plugin

To Reproduce
plugin code:

from lib.chatutils import ChatUtils
from errbot import BotPlugin, botcmd

chatutils = ChatUtils()

class MyTest(BotPlugin):
    """Perform system level commands"""

    @botcmd()
    def am_i_admin(self, msg, _):
        admin = chatutils.is_admin(msg)
        return f"{chatutils.handle(msg)} {admin}"

python library (in plugins/lib/chatutils.py)

import os
import re

class ChatUtils:
    """
    A collection of common utilities used throughout the repo
    """
    def is_admin(self, msg, admins):
        if self.handle(msg) in admins:
            return True
        return False

    def handle(self, msg):
        try:
            return str(msg.frm).split("/")[1]
        except:
            return msg.frm

Expected behavior
Errbot should load the plugin.

** Actual behavior **

2024-04-22 19:54:18,251 INFO     errbot.core               Some plugins failed to start during bot startup:

Traceback (most recent call last):
  File "/errbot-data/lib/python3.11/site-packages/errbot/plugin_manager.py", line 289, in _load_plugins_generic
    plugin_classes = plugin_info.load_plugin_classes(
                     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/errbot-data/lib/python3.11/site-packages/errbot/plugin_info.py", line 100, in load_plugin_classes
    spec.loader.exec_module(modu1e)
  File "<frozen importlib._bootstrap_external>", line 940, in exec_module
  File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
  File "/errbot-data/eks-ops/errbot/plugins/mytest/mytest.py", line 5, in <module>
    import lib.chatutils
ModuleNotFoundError: No module named 'lib.chatutils'; 'lib' is not a package
Error: MyTest failed to activate: 'NoneType' object has no attribute 'is_activated'.

Environment (please complete the following information):

  • Errbot version: 6.2.0
  • OS version: Debian 12
  • Python version: Python 3.11.2
  • Using a virtual environment: yes
  • Using Docker: yes

Additional context

Config.py includes a reference to plugins, and sys.path via errbot returns:

Path: ['/errbot-data/code', '/errbot-data/code', '/errbot-data/bin', '/usr/lib/python311.zip', '/usr/lib/python3.11', '/usr/lib/python3.11/lib-dynload', '/errbot-data/lib/python3.11/site-packages', '/errbot-data/lib/python3.11/site-packages/errbot/storage', '/errbot-data/eks-ops/errbot/backends/err-backend-slackv3/src/slackv3', '/errbot-data/lib/python3.11/site-packages/errbot/core_plugins', '/errbot-data/eks-ops/errbot/plugins/mytest', '/errbot-data/eks-ops/errbot/plugins/ops', '/errbot-data/eks-ops/errbot/plugins/Git', '/errbot-data/eks-ops/errbot/plugins']

This includes (last entry) the plugins/ dir in the search path.

When using backend Text (or Discord) this error does not occur.

msg.frm.channelname not working with Slack webhooks

While trying to respond to a webhook in slack channel, I have encountered an issue where re_botcmd matched message does not contain the dictionary a slack message should, more specifically the dictionary called _channel_info in the person.py file

log cleared of personal info (***** substitutes the personal info):

Traceback (most recent call last):
  File "*******/env38/lib/python3.8/site-packages/errbot/core.py", line 574, in _execute_and_send
    reply = method(msg, match) if match else method(msg, args)
  File "*******/testrunner/testrunner.py", line 458, in parse_rapid7_scan
    channel = msg.frm.channelname
  File "*******/env38/lib/python3.8/site-packages/slackv3/person.py", line 128, in channelname
    if self._channel_info["is_im"] is True:
KeyError: 'is_im'

As you can see trying to access the channelname this way causes an error, I have fixed it by utilising msg.frm.room.channelname instead which just takes the channelname without executing the problematic part of the code.

I suggest at least temporarily modifying the line to something like

if self._channel_info.get("is_im") is True:

or similar method that won't just stop the execution of the command due to KeyError

Thank you

Struggling to install in docker

When I run this plugin locally, it works perfectly. However, when I attempt to push up a docker image (so that I can run my bot in the cloud), it's not working.

Dockerfile:

FROM python:3.8-slim as BUILD
WORKDIR /wheel
COPY . .
RUN apt update && apt install -y build-essential
RUN pip3 wheel --wheel-dir=/wheel \
    errbot errbot[slack] errbot[slack-sdk]

FROM python:3.8-slim
COPY --from=BUILD /wheel /wheel
RUN apt update && \
    apt install -y git && \
    cd /wheel && \
    pip3 -vv install --no-cache-dir --no-index --find-links /wheel \
    errbot errbot[slack] errbot[slack-sdk] && \
    pip3 install --extra-index-url https://pypi.python.org/simple boto3 slack_sdk mysql-connector-python && \
    rm -rf /wheel /var/lib/apt/lists/*

RUN useradd -m errbot
USER errbot
EXPOSE 3141 3142 80
VOLUME /home/errbot
WORKDIR /home/errbot
COPY --chown=errbot:errbot data/ ./data/
COPY --chown=errbot:errbot backend/ ./backend/
COPY --chown=errbot:errbot plugins/ ./plugins/
COPY --chown=errbot:errbot config.py ./

RUN cd backend/err-backend-slackv3 && pip3 install -r requirements.txt && pip3 install errbot[slack-sdk]

ENTRYPOINT [ "/usr/local/bin/errbot" ]

I have used a git submodule to include the backend under the ./backend directory inside my git repository, where I have a config that was working fine when run locally, including fetching an api token from AWS SSM Parameter Store. Here is the error I am getting:

Successfully tagged platbot_platbot:latest
Recreating platbot_platbot_1 ... done
Attaching to platbot_platbot_1
platbot_1  | 2022-01-21 14:23:46,006 INFO     errbot.bootstrap          Found Storage plugin: Shelf.
platbot_1  | 2022-01-21 14:23:46,015 INFO     errbot.bootstrap          Found Backend plugin: SlackV3
platbot_1  | 2022-01-21 14:23:46,015 DEBUG    errbot.storage            Opening storage 'repomgr'
platbot_1  | 2022-01-21 14:23:46,015 DEBUG    errbot.storage.shelf      Open shelf storage ./data/repomgr.db
platbot_1  | 2022-01-21 14:23:46,039 ERROR    errbot.backends.slackv3   Could not start the SlackV3 backend
platbot_1  | Traceback (most recent call last):
platbot_1  |   File "backend/err-backend-slackv3/slackv3.py", line 37, in <module>
platbot_1  |     from slack_sdk.rtm.v2 import RTMClient
platbot_1  |   File "/usr/local/lib/python3.8/site-packages/slack_sdk/rtm/__init__.py", line 16, in <module>
platbot_1  |     import aiohttp
platbot_1  | ModuleNotFoundError: No module named 'aiohttp'
platbot_1  | 2022-01-21 14:23:46,040 CRITICAL errbot.backends.slackv3   You need to install python modules in order to use the Slack backend.
platbot_1  | You can do `pip install errbot[slack-sdk]` to install them.

RTM client blocks keyboard interrupt

Using the slacksdk v3 client has an undesirable side-effect of installing signal handlers which override they default Python signal handlers. This in-turn prevents ctrl-c from raising a keyboard interrupt exception with which errbot uses to exit.

https://github.com/slackapi/python-slack-sdk/blob/919febeaf660d001f0c81c961fb1ed29255a0162/slack_sdk/rtm/__init__.py#L203

A patch was proposed here but didn't make it through to master.
slackapi/python-slack-sdk#617

SlackApiError (user not found) on bot message, preventing errbot botcmd commands

When another bot we have in our organization, Standup Jack, posts messages, we get the following errors:

2022-05-25 13:39:57,151 ERROR    slack_sdk.socket_mode.builtin.client Failed to run a request listener: The request to the Slack API failed. (url: https://www.slack.com/api/users.info)
The server responded with: {'ok': False, 'error': 'user_not_found'}
Traceback (most recent call last):
  File "/usr/local/lib/python3.9/site-packages/slack_sdk/socket_mode/client.py", line 151, in run_message_listeners
    listener(self, request)  # type: ignore
  File "/err/backend/err-backend-slackv3/slackv3.py", line 377, in _sm_generic_event_handler
    self._generic_wrapper(req.payload)
  File "/err/backend/err-backend-slackv3/slackv3.py", line 362, in _generic_wrapper
    return event_handler(self.slack_web, event)
  File "/err/backend/err-backend-slackv3/slackv3.py", line 534, in _handle_message
    msg.frm = SlackRoomBot(
  File "/err/backend/err-backend-slackv3/_slack/room.py", line 334, in __init__
    super().__init__(webclient, bot_id, bot_username)
  File "/err/backend/err-backend-slackv3/_slack/room.py", line 308, in __init__
    super().__init__(webclient, userid=bot_id)
  File "/err/backend/err-backend-slackv3/_slack/person.py", line 50, in __init__
    self._cache_user_info()
  File "/err/backend/err-backend-slackv3/_slack/person.py", line 88, in _cache_user_info
    res = self._webclient.users_info(user=self._userid)
  File "/usr/local/lib/python3.9/site-packages/slack_sdk/web/client.py", line 4192, in users_info
    return self.api_call("users.info", http_verb="GET", params=kwargs)
  File "/usr/local/lib/python3.9/site-packages/slack_sdk/web/base_client.py", line 145, in api_call
    return self._sync_send(api_url=api_url, req_args=req_args)
  File "/usr/local/lib/python3.9/site-packages/slack_sdk/web/base_client.py", line 182, in _sync_send
    return self._urllib_api_call(
  File "/usr/local/lib/python3.9/site-packages/slack_sdk/web/base_client.py", line 316, in _urllib_api_call
    return SlackResponse(
  File "/usr/local/lib/python3.9/site-packages/slack_sdk/web/slack_response.py", line 205, in validate
    raise e.SlackApiError(message=msg, response=self)
slack_sdk.errors.SlackApiError: The request to the Slack API failed. (url: https://www.slack.com/api/users.info)
The server responded with: {'ok': False, 'error': 'user_not_found'}

Jack posts as other people - this may be relevant. It looks like slackv3 tries to look up a user by bot ID and fails.

This is an issue because any errbot command triggers from these bot messages fail.

Slack: send_card() does not support markdown

Using send_card() with the Slack backend does not render the given string to markdown prior to passing it to the underlying API.

For example:

self.send_card(                         
    in_reply_to=message,                
    body="`some text`",
)                                       

Results in the sent card actually containing

`some text`

rather than image in the usual Slack inline code style.

This applies to all of the markdown syntax that Slack supports.

The same issue also exists when using the fields argument of send_card().

Unable to receive messages from slack.

Hi Team,
I'm not able to receive commands from slack even though the log show the connection is successfully established.
PLease let know if you need any debug logs.

Successful connection log:
####################

21:17:44 INFO slack_sdk.socket_mode.bui A new session has been established (session id: 88436168-824f-4743-a3cf-84dc3f67d5ee) 21:17:44 DEBUG errbot.backends.slackv3 Initialised, waiting for events. 21:17:44 INFO slack_sdk.socket_mode.bui Starting to receive messages from a new connection (session id: 88436168-824f-4743-a3cf-84dc3f67d5ee) 21:17:44 DEBUG slack_sdk.socket_mode.bui on_message invoked: (message: {"type":"hello","num_connections":1,"debug_info":{"host":"applink-d85c5c684-fgz8q","build_number":18,"approximate_connection_time":18060},"connection_info":{"app_id":"A0195UU34LW"}}) 21:17:44 DEBUG slack_sdk.socket_mode.bui A new message enqueued (current queue size: 1) 21:17:44 DEBUG slack_sdk.socket_mode.bui A message dequeued (current queue size: 0) 21:17:44 DEBUG slack_sdk.socket_mode.bui Message processing started (type: hello, envelope_id: None) 21:17:44 DEBUG errbot.backends.slackv3 message listeners : [<bound method SlackBackend._sm_handle_hello of <errbot.backends.slackv3.SlackBackend object at 0x1061f6b70>>]
###################

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.