How to get Python Slack bot to reply within a thread? - python

I'm trying to get my Python Slack bot to automatically reply in a thread if I post it commands in one. However, regardless of where I post my commands - in a thread or otherwise, it still replies as a general message.
I wish to get it to reply in a thread. Here's my code so far (I've truncated most of the initializing and startup code for the sake of brevity):
import os, time, re, inspect, functools
from slackclient import SlackClient
class Bot(object):
def __init__(self, token):
...
def startup(self):
...
def parse_bot_commands(self, slack_events):
"""
Parses a list of events coming from the Slack RTM API to find bot commands.
If a bot command is found, this function returns a tuple of command and channel.
If its not found, then this function returns None, None.
"""
for event in slack_events:
if event["type"] == "message" and not "subtype" in event:
user_id, message = self.parse_direct_mention(event["text"])
if user_id == self.starterbot_id:
return message, event["channel"]
return None, None
def parse_direct_mention(self, message_text):
"""
Finds a direct mention (a mention that is at the beginning) in message text
and returns the user ID which was mentioned. If there is no direct mention, returns None
"""
matches = re.search(self.MENTION_REGEX, message_text)
# the first group contains the username, the second group contains the remaining message
return (matches.group(1), matches.group(2).strip()) if matches else (None, None)
def handle_command(self, command, channel):
"""
Executes bot command if the command is known
"""
# Default response is help text for the user
default_response = "Not sure what you mean. Try *{}*.".format(self.EXAMPLE_COMMAND)
# Finds and executes the given command, filling in response
response = None
# NOTE: This is where you start to implement more commands!
if command.lower().startswith("roll"):
response = 'Rock and Roll!"
# Sends the response back to the channel
self.slack_client.api_call("chat.postMessage", channel=channel, text=response or default_response)
'''START THE BOT!'''
#Initialize the token (when installing the app)
bot = Bot('xxx-xxx')
bot.startup()

Slash commands do not work properly in threads. Its a known issue which has so far not been fixed.
See also this answer: Can a Slack bot get the thread id that a slash command was sent from?

Related

Using Gravital discord chatbot and getting "TypeError: __init__() missing 1 required keyword-only argument: 'intents'" error

I'm trying to start this "Gravitar" discord ML chatbot, however every time I attempt to run my main.py I get this error. The main.py works fine, but when it refers to bot.py it starts to have some issues. I have tried several solutions to update what I believe to be outdated code (?) but I'm getting stuck.
File "D:\Gravital-master\Bot\bot.py", line 19, in __init__
super().__init__()
TypeError: __init__() missing 1 required keyword-only argument: 'intents'
This is the code it's referring to.
import random
import datetime
import discord
from .ai import ChatAI
from discord.ext import commands
class ChatBot(discord.Client):
"""ChatBot handles discord communication. This class runs its own thread that
persistently watches for new messages, then acts on them when the bots username
is mentioned. It will use the ChatAI class to generate messages then send them
back to the configured server channel.
ChatBot inherits the discord.Client class from discord.py
"""
intents = discord.Intents.all()
bot = commands.Bot(command_prefix='.',intents=intents)
def __init__(self, maxlines) -> None:
self.model_name = "355M" # Overwrite with set_model_name()
super().__init__()
self.maxlines = maxlines #see comment on main.py line 33
async def on_ready(self) -> None:
""" Initializes the GPT2 AI on bot startup """
print("Logged on as", self.user)
print(self.user.id)
self.chat_ai = ChatAI(self.maxlines) # Ready the GPT2 AI generator
async def on_message(self, message: discord.Message) -> None:
""" Handle new messages sent to the server channels this bot is watching """
if message.author == self.user:
# Skip any messages sent by ourselves so that we don't get stuck in any loops
return
# Check to see if bot has been mentioned
has_mentioned = False
for mention in message.mentions:
if str(mention) == self.user.name+"#"+self.user.discriminator:
has_mentioned = True
break
# Only respond randomly (or when mentioned), not to every message
if random.random() > float(self.response_chance) and has_mentioned == False:
return
async with message.channel.typing():
# Get last n messages, save them to a string to be used as prefix
context = ""
# TODO: make limit parameter # configurable through command line args
history = await message.channel.history(limit=9).flatten()
history.reverse() # put in right order
for msg in history:
# "context" now becomes a big string containing the content only of the last n messages, line-by-line
context += msg.content + "\n"
# probably-stupid way of making every line but the last have a newline after it
context = context.rstrip(context[-1])
# Print status to console
print("----------Bot Triggered at {0:%Y-%m-%d %H:%M:%S}----------".format(datetime.datetime.now()))
print("-----Context for message:")
print(context)
print("-----")
# Process input and generate output
processed_input = self.process_input(context)
response = ""
response = self.chat_ai.get_bot_response(processed_input)
print("----Response Given:")
print(response)
print("----")
await message.channel.send(response)# sends the response
def process_input(self, message: str) -> str:
""" Process the input message """
processed_input = message
# Remove bot's #s from input
return processed_input.replace(("<#!" + str(self.user.id) + ">"), "")
def set_response_chance(self, response_chance: float) -> None:
""" Set the response rate """
self.response_chance = response_chance
def set_model_name(self, model_name: str = "355M") -> None:
""" Set the GPT2 model name """
self.model_name = model_name
I tried following examples from other peoples code but I'm afraid I still don't exactly understand what's wrong.
#!/usr/bin/env python3
import argparse
from Bot.bot import ChatBot
from Bot.ai import ChatAI
def main():
"""Main function"""
parser = argparse.ArgumentParser(description="Amalgam")
parser.add_argument("--token", dest="MTA3MjMyNDc3OTgyODMxODIyOA.GKZNRE.1Wjpz1KPDzN6HohbaptBmYux-wzdU10u6DRKVM",
help="Your discord bot's token. Required for launching the bot in non-test mode!")
parser.add_argument("--response_chance",
dest="response_chance",
default=1,
help="How likely the bot is to respond to a message in which it is not pinged. For example: give 0.25 for a 25%% chance, give 0 for no random responses. Defaults to 0.")
parser.add_argument("--test", dest="test", action="store_true",
help="Test model by talking to the AI right in the terminal.")
parser.add_argument("--maxlines", dest="maxlines", help="The maximum number of lines that the AI will try to generate per message. Will always generate random amount up to this value, which defaults to 1.",
default=1)
parser.add_argument("--train", dest="train", action="store_true",
help="Trains the model on a file named dataset.txt. Only use this if you have a good NVIDIA GPU. Overwrites existing trained_model folder. Currently untested!")
args = parser.parse_args()
if args.test:
ai = ChatAI(args.maxlines) # see comment on line 33
print("Type \"exit!!\" to exit.")
while True:
inp = input("> ")
if(inp == "exit!!"):
return
print(ai.get_bot_response(message=inp))
elif args.train:
from aitextgen import aitextgen #lazily import aitextgen. idk if this matters, but i thought it might speed up start times for when you're not training the AI as opposed to having this at the top
ai = aitextgen(to_gpu=True)
ai.train("dataset.txt",
line_by_line=False,
from_cache=False,
num_steps=55000, #Takes less than an hour on my RTX 3060. Increase if you want, but remember that training can pick up where it left off after this finishes.
generate_every=1000,
save_every=1000,
learning_rate=1e-3,
fp16=True, #this setting improves memory efficiency, disable if it causes issues
batch_size=2,
)
else:
# probably a cleaner way to do this than to pass the maxlines param all the way through? submit PR if you know
client = ChatBot(args.maxlines)
client.set_response_chance(args.response_chance)
if args.token is None:
raise Exception(
"You are trying to launch the bot but have not included your discord bot's token with --token. Please include this and try again.")
client.run(args.token)
if __name__ == "__main__":
main()
This is the main.py that starts and runs the other auxiliary scripts.
The issue is that you're inheriting from discord.Client and never passing the intents to the parent class. Additionally, you're then creating a discord.Bot within your discord.Client subclass? You might as well inherit from discord.Bot directly.
class ChatBot(discord.Bot):
intents = discord.Intents.all()
def __init__(self, maxlines) -> None:
self.model_name = "355M" # Overwrite with set_model_name()
super().__init__(intents=self.intents, command_prefix='.')
self.maxlines = maxlines
Take a look at this example.
Solution
You'll need to pass in a value for the intents keyword-only argument: super().__init__(intents=self.intents) or super().__init__(intents=ChatBot.intents).
Note: I'm not sure if the value for the keyword-only argument (i.e.
intents) is supposed to be the class variable intents (i.e.
discord.Intents.all()). But
documentation
says, that the keyword-only argument, intents, is "The intents that
you want to enable for the session. This is a way of disabling and
enabling certain gateway events from triggering and being sent."
Explanation for the TypeError
You received a TypeError because super() returns an instance of the inherited class (i.e. Client) and the constructor in the Client class initializes the keyword-only attribute, intents. Thus, super().__init__() expected to receive the required attributes of the inherited class.
This Youtube video helped me better understand what was going on.

How to get the next telegram messages from specific users

I'm implementing a telegram bot that will serve users. Initially, it used to get any new message sequentially, even in the middle of an ongoing session with another user. Because of that, anytime 2 or more users tried to use the bot, it used to get all jumbled up. To solve this I implemented a queue system that put users on hold until the ongoing conversation was finished. But this queue system is turning out to be a big hassle. I think my problems would be solved with just a method to get the new messages from a specific chat_id or user. This is the code that I'm using to get any new messages:
def get_next_message_result(self, update_id: int, chat_id: str):
"""
get the next message the of a given chat.
In case of the next message being from another user, put it on the queue, and wait again for
expected one.
"""
update_id += 1
link_requisicao = f'{self.url_base}getUpdates?timeout={message_timeout}&offset={update_id}'
result = json.loads(requests.get(link_requisicao).content)["result"]
if len(result) == 0:
return result, update_id # timeout
if "text" not in result[0]["message"]:
self.responder(speeches.no_text_speech, message_chat_id)
return [], update_id # message without text
message_chat_id = result[0]["message"]["chat"]["id"]
while message_chat_id != chat_id:
self.responder(speeches.wait_speech, message_chat_id)
if message_chat_id not in self.current_user_queue:
self.current_user_queue.append(message_chat_id)
print("Queuing user with the following chat_id:", message_chat_id)
update_id += 1
link_requisicao = f'{self.url_base}getUpdates?timeout={message_timeout}&offset={update_id}'
result = json.loads(requests.get(link_requisicao).content)["result"]
if len(result) == 0:
return result, update_id # timeout
if "text" not in result[0]["message"]:
self.responder(speeches.no_text_speech, message_chat_id)
return [], update_id # message without text
message_chat_id = result[0]["message"]["chat"]["id"]
return result, update_id
On another note: I use the queue so that the moment the current conversation ends, it calls the next user in line. Should I just drop the queue feature and tell the concurrent users to wait a few minutes? While ignoring any messages not from the current chat_id?

The bot for VK answers in a private messages, and not in a conversation

I am creating a reference bot for chatting in VK in Python using the Callback Api. The bot works correctly if you write to the group messages. However, if you write to a conversation (to which the bot is added), it replies to private messages. All rights to read, etc. issued. As I understand it (studying information on the internet), I use user_id, not chat_id. But I didn't understand how to fix it correctly (
p.s. It is advisable that the bot write both in private messages and in a conversation, depending on where they ask.
p.p.s perhaps the question seems ridiculous, but I just started to study this area, and I did not find the answer on the net :-)
The bot itself:
import vk
import random
import messageHandler
# app.route ('/', methods = ['POST'])
def processing ():
data = json.loads (request.data)
if 'type' not in data.keys ():
return 'not vk'
if data ['type'] == 'confirmation':
return confirmation_token
elif data ['type'] == 'message_new':
messageHandler.create_answer (data ['object'] ['message'], token)
return 'ok'
"Responder":
import importlib
from command_system import command_list
def load_modules ():
# path from the working directory, it can be changed in the application settings
files = os.listdir ("mysite / commands")
modules = filter (lambda x: x.endswith ('. py'), files)
for m in modules:
importlib.import_module ("commands." + m [0: -3])
def get_answer (body):
# Default message if unrecognizable
message = "Sorry, I don't understand you. Write '/ help' to see my commands."
attachment = ''
for c in command_list:
if body in c.keys:
message, attachment = c.process ()
return message, attachment
def create_answer (data, token):
load_modules ()
user_id = data ['from_id']
message, attachment = get_answer (data ['text']. lower ())
vkapi.send_message (user_id, token, message, attachment)
I don't speak English well, so I apologize for the crooked translation)
Use Peer_id, instead of from_id. (data->object->peer_id)
(i used php, but i had a similar problem. this is the solution)
probably something like this:
def create_answer (data, token):
load_modules ()
user_id = data ['peer_id'] # id source edited
message, attachment = get_answer (data ['text']. lower ())
vkapi.send_message (user_id, token, message, attachment)
from_id - person who sent the message
peer_id - in which dealogue message was received. (for groups it looks like 20000005)
So, you will send the message to conversation (does not matter is this PM or conversation with a lot of people)

Deleting Messages in Slack

Sooo, I'm relatively new to programming, and trying to learn how to consume API's. I figured I would start out by building a Slack bot for moderation purposes since I use Slack a lot. For the most part, everything works except for when I try to delete a message. The API returns saying it can't find the message even though it is there in the channel (the slack API uses timestamps to locate said message). The timestamps match, but proclaims the message doesn't exist. Here is my code:
def __init__(self, token):
self.token = token
self.users = {}
self.channels = {}
self.slack = SlackClient(self.token)
self.as_user = True
def connect(self):
if self.slack.rtm_connect():
self.post_message('#test', "*AUTOMOD* _v0.1_")
while True:
# print(self.slack.rtm_read())
self.parse_data(self.slack.rtm_read())
time.sleep(1)
def parse_data(self, payload):
if payload:
if payload[0]['type'] == 'message':
print(("user: {} message: {} channel: {}").format(payload[0]['user'], payload[0]['text'], payload[0]['channel']))
self.handle_message(payload[0])
def handle_message(self, data):
# these users can post whatever they want.
WHITELISTED = ["U4DU2TS2F", "U3VSRJJD8", "U3WLZUTQE", "U3W1Q2ULT"]
# get userid
sent_from = (data['user'])
# ignore whitelisted
if sent_from in WHITELISTED:
return
# if message was sent from someone not in WHITELISTED, delete it
else:
print(("\n\ntimestamp of message: {}").format(data['ts']))
self.delete_message(data['channel'], data['ts'])
self.post_message(data['channel'], "```" + random.choice(dongers) + "```")
def delete_message(self, channel, timestamp):
print(("deleting message in channel '{}'...").format(channel))
print("timestamp check (just to make sure): ", timestamp)
deleted = self.slack.api_call("chat.delete",
channel=channel,
timestamp=timestamp,
as_user=self.as_user
)
if deleted.get('ok'):
print("\nsuccessfully deleted.\n")
else:
print(("\ncouldn't delete message: {}\n").format(deleted['error']))
OUTPUT
timestamp of message: 1488822718.000040
deleting message in channel: 'G4DGYCW2X'
timestamp check (just to make sure...): 1488822718.000040
couldn't delete message: message_not_found
Any ideas on what could be happening? Here is the chat.delete method for context.
EDIT:
Due #pvg's recommendation of "Minimal, Complete, and Verifiable example", I have placed the ENTIRE code from the project in a gist.
One issue might be that you appear to be passing a timestamp parameter to chat.delete, when the API method takes a ts parameter instead. (See docs)

SleekXMPP automatically accept all chat room invites

I want to use SleekXMPP and automatically accept all chat room invites that are sent to me. I know that the xep_0045 plugin can detect when I receive an invite, as I am notified in the debugger. I am still pretty new to Python and any help would be appreciated.
So far, I've found a function called handle_groupchat_invite in the xep_0045 plugin. Specifically, this code:
def plugin_init(self):
#...
self.xmpp.registerHandler(Callback('MUCInvite', MatchXMLMask("<message xmlns='%s'><x xmlns='http://jabber.org/protocol/muc#user'><invite></invite></x></message>" % self.xmpp.default_ns), self.handle_groupchat_invite))
#...
def handle_groupchat_invite(self, inv):
""" Handle an invite into a muc.
"""
logging.debug("MUC invite to %s from %s: %s", inv['from'], inv["from"], inv)
if inv['from'].bare not in self.rooms.keys():
self.xmpp.event("groupchat_invite", inv)
So I see this method at work as I see the "MUC invite to..." message in the Terminal log. From there, I would expect that I need to use self.plugin['xep_0045'].joinMUC() to join the chat room's URL (given by inv["from"]). However, I am not exactly sure where I should call this code in my script.
Thanks again for the help.
Update: I've also tried using add_event_handler in the __init__ function. Specifically my code is:
def __init__(self, jid, password, room, nick):
sleekxmpp.ClientXMPP.__init__(self, jid, password)
self.room = room
self.nick = nick
# The session_start event will be triggered when
# the bot establishes its connection with the server
# and the XML streams are ready for use. We want to
# listen for this event so that we we can initialize
# our roster.
self.add_event_handler("session_start", self.start)
# The groupchat_message event is triggered whenever a message
# stanza is received from any chat room. If you also also
# register a handler for the 'message' event, MUC messages
# will be processed by both handlers.
self.add_event_handler("groupchat_message", self.muc_message)
# The groupchat_presence event is triggered whenever a
# presence stanza is received from any chat room, including
# any presences you send yourself. To limit event handling
# to a single room, use the events muc::room#server::presence,
# muc::room#server::got_online, or muc::room#server::got_offline.
self.add_event_handler("muc::%s::got_online" % self.room,
self.muc_online)
self.add_event_hander("groupchat_invite", self.sent_invite)
From there, I created the sent_invite function, code is here:
def sent_invite(self, inv):
self.plugin['xep_0045'].joinMUC(inv["from"], self.nick, wait=True)
However, I get the following error when I do this:
File "muc.py", line 66, in init
self.add_event_hander("groupchat_invite", self.sent_invite) AttributeError: 'MUCBot' object has no attribute 'add_event_hander'
Yet in the xep_0045 plugin I see this code: self.xmpp.event("groupchat_invite", inv). According to the Event Handlers SleekXMPP wiki page,
Stream events arise whenever particular stanzas are received from the XML stream. Triggered events are created whenever xmpp.event(name, data) is called (where xmpp is a SleekXMPP object).
Can someone please explain why I am getting the error? I've also tried using
self.add_event_hander("muc::groupchat_invite", self.sent_invite)
but also without success.
I just downloaded SleekXMPP from git and add groupchat_invite handler like this and it works:
diff --git a/examples/muc.py b/examples/muc.py
index 5b5c764..e327fac 100755
--- a/examples/muc.py
+++ b/examples/muc.py
## -61,7 +61,10 ## class MUCBot(sleekxmpp.ClientXMPP):
# muc::room#server::got_online, or muc::room#server::got_offline.
self.add_event_handler("muc::%s::got_online" % self.room,
self.muc_online)
-
+ self.add_event_handler("groupchat_invite", self.accept_invite)
+
+ def accept_invite(self, inv):
+ print("Invite from %s to %s" %(inv["from"], inv["to"]))
def start(self, event):
"""

Categories

Resources