I am trying to use the Slack-bolt API for python to listen to DMs to the slack bot that contain specific text. Here is my file that initiates the Slack-Bolt listener
import os
from server import *
from slack_bolt import App
from slack_bolt.adapter.socket_mode import SocketModeHandler
# Initializes your app with your bot token and socket mode handler
app = App(token=MY_TOKEN)
# Listens to incoming messages that contain "list"
#app.message("list")
def message_hello(message, say):
# say() sends a message to the channel where the event was triggered
res = requests.get(url + '/api/users/list')
say("The list of users is: ", res.json())
# Start your app
if __name__ == "__main__":
SocketModeHandler(app, "TOKEN").start()
When I send messages to my bot I am getting "127.0.0.1 - - [20/Mar/2022 00:23:47] "POST /api HTTP/1.1" 200 -" but the listener is not executing the code it contains. I cannot get it to say hello back inside of Slack in any way.
Thanks
Instead of setting the app to listen for every word posted, I would suggest using the "app_mention" event which triggers only when the message sent begins with
#your_bot_name followed by your message. This way you will avoid getting random responses from your bot when sending messages which contain specific keywords.
#app.event("app_mention")
def test(ack,event,logger):
ack()
name = event["user"] # gets the name of the user who triggered the event
channel =event["channel"] # gets the channel in which the event was triggered
text = event["text"].lower() # gets the lowercase text of your sent message
ts = event["ts"] # gets the timestamp of the message (this is used for replying in threads)
if any(x in text for x in ("users list","list of users")): # if you need specific combinations of keywords i would recommend using this method)
# if text == "list" :
try:
app.client.chat_postMessage(channel = channel, thread_ts=ts, text = f"*Hi <#{name}>, Here is a random response*")
except:
print(logger.info)
In the end you could trigger a response from your bot app by posting a message like so:
#your_bot_name show me the users list
or
#your_bot_name show me the list of users
Related
as the title states, I'm writing a Slack Bot in Python and using NGROK to host it locally. I'm not super experienced with decorators, and I can get the bot posting messages in slack, however I can't seem to handle two events at once. For example, I want to handle a message and have the message keep repeating in slack until a thumbs up reaction is added to that message. The issue is I cannot figure out how to handle an event while another event is still running, please see the following code:
rom slack import WebClient
import os
import time
from pathlib import Path
from dotenv import load_dotenv
from flask import Flask
from slackeventsapi import SlackEventAdapter
env_path = Path('.') / '.env'
load_dotenv(dotenv_path=env_path)
app = Flask(__name__)
slack_event_adapter = SlackEventAdapter(
os.environ['SIGNING_SECRET'],'/slack/events',app)
client = WebClient(token=os.environ['SLACK_TOKEN'])
BOT_ID = client.api_call("auth.test")['user_id']
state = {}
#slack_event_adapter.on('message')
def handle_message(event_data):
message = event_data.get('event', {})
channel_id = message.get('channel')
user_id = message.get('user')
text = message.get('text')
messageid = message.get('ts')
state[messageid] = {"channel_id": channel_id, "user_id": user_id, "text": text}
if BOT_ID != user_id:
if text[0:12] == ":red_circle:":
time.sleep(5)
client.chat_postMessage(channel=channel_id, text=text)
if text[0:21] == ":large_yellow_circle:":
client.chat_postMessage(channel=channel_id, text="it's a yellow question!")
if text[0:14] == ":white_circle:":
client.chat_postMessage(channel=channel_id, text="it's a white question!")
#slack_event_adapter.on('reaction_added')
def reaction_added(event_data):
reaction = event_data.get('event',{})
emoji = reaction.get('reaction')
emoji_id = reaction.get('item',{}).get('ts')
emoji_channel_id = reaction.get('item',{}).get('channel')
client.chat_postMessage(channel=emoji_channel_id, text=emoji)
for message_id, message_data in state.items():
channel_id = message_data["channel_id"]
text = message_data["text"]
client.chat_postMessage(channel=channel_id, text=text)
print(message_id,message_data)
if __name__ == "__main__":
app.run(debug=True)
I can handle individual events, but I cannot handle them while another is running. Please help! :)
Flask is a synchronous web framework.
When it's running a view handler, it uses up a web worker thread. If you does something like time.sleep(...), that worker thread will still be occupied and unavailable to handle other requests until the sleep finishes.
There are a couple options you can do here.
You can use Bolt for Python, which is a Python Slack library that natively support asynchronous even processing. Instead of time.sleep(), you can do await asyncio.sleep(...), which returns the thread to the async loop, and allow the worker thread to process other events.
If you already have an existing slack application and don't want to rewrite your entire codebase to Bolt, then you'll need to handle the event processing yourself. You can do this by doing your work in an ThreadLoopExecutor, or by building your own async event Queue mechanism, or use Celery. Or if your slack bot has very low volume, you can probably just add more web workers, and hope for the best that you don't run out of workers.
I'm trying to integrate my telegram bot with my webcam (DLINK DCS-942LB).
Using NIPCA standard (Network IP Camera Application Programming Interface) I managed to solve quite everything.
I'm now working on a polling mechanism.
The basic should be:
telegram bot keeps polling the camera using http://CAMERA_IP:CAMERA_PORT/config/notify_stream.cgi
when an event happens, telegram bot sends a notification to users
The problem is: the notify_stream.cgi page keeps updating every 1 second adding events.
I am not able to poll the notify_stream.cgi as I have requests hanging (doesn't get a response):
This can be reproduce with a simple script:
import requests
myurl = "http://CAMERA_IP:CAMERA_PORT/config/notify_stream.cgi"
response = requests.get(myurl, auth=("USERNAME", "PASSWORD"))
This results in requests hanging until I stop it manually.
Is it possible to keep listening the notify_stream.cgi and passing new lines to a function?
Thanks to the comment received, using session and strem works fine.
Here is the code:
import requests
def getwebcameventstream(webcam_url, webcam_username, webcam_password):
requestsession = requests.Session()
eventhandler = ["first_evet", "second_event", "third_event"]
with requestsession.get(webcam_url, auth=(webcam_username, webcam_password), stream=True) as webcam_response:
for event in webcam_response.iter_lines():
if event in eventhandler:
handlewebcamalarm(event)
def handlewebcamalarm(event):
print ("New event received :" + str(event))
url = 'http://CAMERA_IP:CAMERA_PORT/config/notify_stream.cgi'
username="myusername"
password="mypassword"
getwebcamstream(url, username, password)
Is it possible to delete all chat history(messages) of my chat with bot.
So the console version be like:
import os
os.sys("clear") - if Linux
os.sys("cls") - if Windows
All I want is to delete all messages in chat using bot.
def deleteChat(message):
#delete chat code
First of all, if you want to delete history with a bot you should save message ids.
Otherwise, you could use an userbot (using an user account) for clearing it.
You can iter all chat messages and get their ids, and delete them in chunks of 100 messages each iteration.
Warning: itering message history of a chat is not possible with bots and BotAPI, because of Telegram limits. So you should use an MTProto API framework, with an user account as said before.
First of all, pyrogram library is needed for doing this (you could also use telethon), and instance a Client, then you can add an handler or start Client using with keyword. Then get all messages ids by itering the chat, and save them in a list. Finally, delete them using delete_messages Client method:
import time, asyncio
from pyrogram import Client, filters
app = Client(
"filename", # Will create a file named filename.session which will contain userbot "cache"
# You could also change "filename" to ":memory:" for better performance as it will write userbot session in ram
api_id=0, # You can get api_hash and api_id by creating an app on
api_hash="", # my.telegram.org/apps (needed if you use MTProto instead of BotAPI)
)
#app.on_message(filters.me & filters.command("clearchat") & ~filters.private)
async def clearchat(app_, msg):
start = time.time()
async for x in app_.iter_history(msg.chat.id, limit=1, reverse=True):
first = x.message_id
chunk = 98
ids = range(first, msg.message_id)
for _ in (ids[i:i+chunk] for i in range(0, len(ids), chunk)):
try:
asyncio.create_task(app_.delete_messages(msg.chat.id, _))
except:
pass
end = time.time() - start
vel = len(ids) / end
await msg.edit_text(f"{len(ids)} messages were successfully deleted in {end-start}s.\n{round(vel, 2)}mex/s")
app.run()
Once you start the userbot, add it in a group, and send "/clearchat". If the userbot has delete messages permission, it will start deleting all messages.
For pyrogram documentation see https://docs.pyrogram.org.
(however, you should not print all messages in the terminal, to avoid server overloading)
And the right code for clearing the console is this:
import os
def clear():
os.system("cls" if os.name == "nt" else "clear")
as seen in How to clear the interpreter console?.
P.S.
You can use the same code, adding bot_token="" parameter to Client, and deleting iter_history part, for deleting messages with a bot if you have the messages ids.
If in the future you'll want to receive messages from a group and print them, but you don't receive the message update, add the bot as admin in the group or disable bot privacy mode in BotFather.
For better pyrogram performance, you should install tgcrypto library.
Alright guys, so here's my problem.
I am in the process of developing a Slack app with a packaged bot that allows the user to play a game in Slack. I have successfully built the bot and packaged it with the app as per the API guidelines. Once I discovered the Interactive Messages feature, I decided to implement said feature for a more user-friendly take on the game.
The interactive messages feature allows you to post messages with buttons, which the user can click to invoke an action. My bot script, let's call it bot.py, prompts the user (using the Slack chat.postMessage function) with a message containing some buttons from which to choose. This script has one class (I know it should be more modular but all in good time), which opens a web-socket for communication via the Slack RTM API. As such, when the script runs, it is always "listening" for a command from a user in a channel directed as follows: #botname command. The portion of the script that invokes this "always listening" state looks like this:
#bot.py
...
if slack_client.rtm_connect():
print("MYBOT v1.0 connected and running!")
while True:
command, channel, user = self.parse_slack_output(slack_client.rtm_read())
if command and channel:
if channel not in self.channel_ids_to_name.keys():
#this (most likely) means that this channel is a PM with the bot
self.handle_private_message(command, user)
else:
self.handle_command(command, channel, user)
time.sleep(READ_WEBSOCKET_DELAY)
else:
print("Connection failed. Invalid Slack token or bot ID?")
That's all good and fine. Now, let's say the user has used a command to successfully create a game instance and has started playing. At a certain point, the user is prompted for a trump suit like so:
#bot.py
...
attachments =[{
"title":"Please select index for trump suit:",
"fallback":"Your interface does not support interactive messages.",
"callback_id":"prompt_trump_suit",
"attachment_type":"default",
"actions":
[{"name":"diamonds","text":":diamonds:","type":"button","value":"0"},
{"name":"clubs","text":":clubs:","type":"button","value":"1"},
{"name":"hearts","text":":hearts:","type":"button","value":"2"},
{"name":"spades","text":":spades:","type":"button","value":"3"}]
}]
slack.chat.post_message(
channel=player_id,
as_user=True,
attachments=attachments
)
The interactive message looks like this. The action of clicking on one of the buttons in this message sends a payload via an HTTP POST to a web server. My other script in the project, which we will call app.py, is a Flask script which successfully receives this POST request when the user clicks one of the buttons. The portion of the script that receives the POST request looks like this:
#app.py
...
# handles interactive button responses for mybot
#app.route('/actions', methods=['POST'])
def inbound():
payload = request.form.get('payload')
data = json.loads(payload)
token = data['token']
if token == SLACK_VERIFICATION_TOKEN:
print 'TOKEN is good!'
response_url = data['response_url']
channel_info = data['channel']
channel_id = channel_info['id']
user_info = data['user']
user_id = user_info['id']
user_name = user_info['name']
actions = data['actions'][0]
value = actions['value']
print 'User sending message: ',user_name
print "Value received: ",value
return Response(), 200
When the button is clicked, I get the expected output:
TOKEN is good!
User sending message: my_username
Value received: 3
So everything is successful up to this point. Now, what I want to do is to take that POST information and use it to invoke a function in my bot.py script that handles the trump suit selection. The problem is that if I were to invoke that function, let's call it handle_trump_suit_selection(), I would first have to instantiate a Bot() object in the app.py file, which of course would not work as desired because the function would be called with a new Bot() instance, and therefore would not be in the same state as the current game.
So how the heck can I get the POST information back to the desired Bot() instance in bot.py for further processing? I'm new to OOP in Python and especially new to Flask and the Slack API, so go easy on me ;).
Thanks in advance.
Great Success!
tl;dr:
Basically, the solution was to create a Celery task that instantiated the bot instance from the Flask app using the Slack Events API. You set the task to start after the desired input has been entered, promptly return the required Response(200) back to Slack, while in the meantime the bot script (which starts up the RTM API web-socket) launches in parallel.
The nitty gritty:
So, as stated above, it turns out that what was required was a queuing service of some sort. I ended up going with Celery for its relative ease at integrating with Heroku (where I host the Slack app) and its easy-to-follow documentation.
Developing your Slack app this way requires setting up and using the Slack Events API to receive the command ("play my_game" in this example) from the Slack channel the message was posted in. The Flask app (app.py) portion of the program listens for this event, and when the input matches up with what you're looking for, it launches the Celery task in parallel (in tasks.py, which instantiates a Bot() instance of bot.py in this example). :) Now the bot can listen and respond using both the Slack RTM API and the Slack Events API. This allows you to build rich applications/services within the Slack framework.
If you are looking to set up something similar, below are my project layout and the important code details. Feel free to use them as a template.
Project Layout:
project_name_folder
app_folder
static_folder
templates_folder
__init__.py
my_app.py
bot.py
tasks.py
Procfile
requirements.txt
__init__.py:
from celery import Celery
app = Celery('tasks')
import os
app.conf.update(BROKER_URL=os.environ['RABBITMQ_BIGWIG_URL']) # Heroku Celery broker
my_app.py:
from flask import Flask, request, Response, render_template
import app
from app import tasks
app = Flask(__name__)
#app.route('/events', methods=['POST'])
def events():
"""
Handles the inbound event of a post to the main Slack channel
"""
data = json.loads(request.data)
try:
for k, v in data['event'].iteritems():
ts = data['event']['ts']
channel = data['event']['channel']
user_id = data['event']['user']
team_id = data['team_id']
if 'play my_game' in str(v):
tasks.launch_bot.delay(user_id, channel, ts, team_id) # launch the bot in parallel
return Response(), 200
except Exception as e:
raise
bot.py:
from slackclient import SlackClient
class Bot():
def main():
# opening the Slack web-socket connection
READ_WEBSOCKET_DELAY = 1 # 1 second delay between reading from firehose
if self.slack_client.rtm_connect():
while True:
command, channel, user, ts = self.parse_slack_output()
if command and channel:
if channel not in self.channel_ids_to_name.keys():
# this (most likely) means that this channel is a PM with the bot
self.handle_private_message(command, user, ts)
else:
self.handle_command(command, channel, user, ts)
time.sleep(READ_WEBSOCKET_DELAY)
tasks.py:
import bot
from bot import Bot
from app import app
#app.task
def launch_bot(user_id, channel, ts, team_id):
'''
Instantiates the necessary objects to play a game
Args:
[user_id] (str) The id of the user from which the command was sent
[channel] (str) The channel the command was posted in
[ts] (str) The timestamp of the command
'''
print "launch_bot(user_id,channel)"
app.control.purge()
bot = Bot()
bot.initialize(user_id, channel)
bot.main()
Procfile (if using Heroku):
web: gunicorn --pythonpath app my_app:app
worker: celery -A app.tasks worker -B --loglevel=DEBUG
Please let me know if you are having any issues. This took me a little while to figure out, and I would be happy to help you if you are banging your head on this one.
I am attempting to create a script that uses a value in a remote database to create a wol packet.
I need to pass the value of a (something that I honestly don't know what to call) to a variable. I am unable to just set it to the variable, and I can't figure out how to import it from somewhere else. I need "payload" which is printed and returned under "def message" to be saved to the variable "data"
Below is my code, and I will link the MQTT code that this relies on.
# Import standard python modules.
import sys
# Import Adafruit IO MQTT client.
from Adafruit_IO import MQTTClient
from Adafruit_IO import *
# Set to your Adafruit IO key & username below.
ADAFRUIT_IO_KEY = 'where the api key goes'
ADAFRUIT_IO_USERNAME = 'my username' # See https://accounts.adafruit.com
# to find your username.
# Set to the ID of the feed to subscribe to for updates.
FEED_ID = 'test1'
# Define callback functions which will be called when certain events happen.
def connected(client):
# Connected function will be called when the client is connected to Adafruit IO.
# This is a good place to subscribe to feed changes. The client parameter
# passed to this function is the Adafruit IO MQTT client so you can make
# calls against it easily.
print ('Connected to Adafruit IO! Listening for {0} changes...').format(FEED_ID)
# Subscribe to changes on a feed named DemoFeed.
client.subscribe(FEED_ID)
def disconnected(client):
# Disconnected function will be called when the client disconnects.
print ('Disconnected from Adafruit IO!')
sys.exit(1)
def message(client, feed_id, payload):
# Message function will be called when a subscribed feed has a new value.
# The feed_id parameter identifies the feed, and the payload parameter has
print ('Feed {0} received new value: {1}').format(FEED_ID, payload)
return (payload == payload)
data = message(x, x, x)
# Create an MQTT client instance.
client = MQTTClient(ADAFRUIT_IO_USERNAME, ADAFRUIT_IO_KEY)
# Setup the callback functions defined above.
client.on_connect = connected
client.on_disconnect = disconnected
client.on_message = message
# Connect to the Adafruit IO server.
client.connect()
# Start a message loop that blocks forever waiting for MQTT messages to be
# received. Note there are other options for running the event loop like doing
# so in a background thread--see the mqtt_client.py example to learn more.
if data == '1':
print('Latest value from Test: {0}'.format(data.value))
wol.send_magic_packet('my mac addy')
time.sleep(3)
# Send a value to the feed 'Test'.
aio.send('test1', 0)
print ("worked this time")
client.loop_blocking()
Here is the link to the other code it relies on https://github.com/adafruit/io-client-python/blob/master/Adafruit_IO/mqtt_client.py
First of all, you don't need to import the same module twice
from Adafruit_IO import MQTTClient # this imports part MQTTClient
from Adafruit_IO import * # this imports everything, including MQTTClient again
As for your problem, you've returned (payload == payload) which will always be True, i'm not sure what you're trying to do here but it should look something like this:
def message(client, feed_id, payload):
...
print ('Feed {0} received new value: {1}').format(FEED_ID, payload)
return payload == payload_from_database # what you return will be saved as the variable data
data = message("Steve the happy client", 85, "£100") # in this case, data will be payload