Python-Telegram-Bot: send message through bot in different module - python

I'm writing a Telegram bot using the python-telegram-bot library.
The bot should send a message to the user when a new YoutubeChannel is published.
I created a telegram_bot.py in which I created a TelegramBot class.
In this class I have this function:
telegram_bot.py
def __init__(self):
self.updater = Updater(token=telegram_token, use_context=True)
self.dispatcher = self.updater.dispatcher
self.updater.start_polling()
def send_message(self, text_message, context: CallbackContext):
context.bot.send_message(
chat_id="#<<my username>>", text=text_message)
And in the main.py I have a line of code that should send the message usign the aforementioned function, like this:
main.py
from telegram_bot import TelegramBot
tg_bot = TelegramBot()
tg_bot.send_message("New video!")
But, when I run the code above, I get this error:
TypeError: send_message() missing 1 required positional argument: 'context'
But in the send_message definition I already defined the context

Solved this way:
main.py
tg_bot = TelegramBot()
tg_bot.send_message("New video!", context=tg_bot.dispatcher)

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 use Blocks correctly to load AWS S3 credentials in Prefect?

I am using Prefect. And I tried to download a file from S3.
When I hard coded the AWS credentials, the file can be downloaded successfully:
import asyncio
from prefect_aws.s3 import s3_download
from prefect_aws.credentials import AwsCredentials
from prefect import flow, get_run_logger
#flow
async def fetch_taxi_data():
logger = get_run_logger()
credentials = AwsCredentials(
aws_access_key_id="xxx",
aws_secret_access_key="xxx",
)
data = await s3_download(
bucket="hongbomiao-bucket",
key="hm-airflow/taxi.csv",
aws_credentials=credentials,
)
logger.info(data)
if __name__ == "__main__":
asyncio.run(fetch_taxi_data())
Now I tried to load the credentials from Prefect Blocks.
I created a AWS Credentials Block:
However,
aws_credentials_block = AwsCredentials.load("aws-credentials-block")
data = await s3_download(
bucket="hongbomiao-bucket",
key="hm-airflow/taxi.csv",
aws_credentials=aws_credentials_block,
)
throws the error:
AttributeError: 'coroutine' object has no attribute 'get_boto3_session'
And
aws_credentials_block = AwsCredentials.load("aws-credentials-block")
credentials = AwsCredentials(
aws_access_key_id=aws_credentials_block.aws_access_key_id,
aws_secret_access_key=aws_credentials_block.aws_secret_access_key,
)
data = await s3_download(
bucket="hongbomiao-bucket",
key="hm-airflow/taxi.csv",
aws_credentials=credentials,
)
throws the error:
AttributeError: 'coroutine' object has no attribute 'aws_access_key_id'
I didn't find any useful document about how to use it.
Am I supposed to use Blocks to load credentials? If it is, what is the correct way to use Blocks correctly in Prefect? Thanks!
I just found the snippet in the screenshot in the question misses an await.
After adding await, it works now!
aws_credentials_block = await AwsCredentials.load("aws-credentials-block")
data = await s3_download(
bucket="hongbomiao-bucket",
key="hm-airflow/taxi.csv",
aws_credentials=aws_credentials_block,
)
UPDATE:
Got an answer from Michael Adkins on GitHub, and thanks!
await is only needed if you're writing an async flow or task. For users writing synchronous code, an await is not needed (and not possible). Most of our users are writing synchronous code and the example in the UI is in a synchronous context so it does not include the await.
I saw the source code at
https://github.com/PrefectHQ/prefect/blob/1dcd45637914896c60b7d49254a34e95a9ce56ea/src/prefect/blocks/core.py#L601-L604
#classmethod
#sync_compatible
#inject_client
async def load(cls, name: str, client: "OrionClient" = None):
# ...
So I think as long as the function has the decorator #sync_compatible, it means it can be used as both async and sync functions.

Issue running self objects in mqtt paho callback function

I'm trying to write a script that saves mqtt data and sends it to influxDB. The issue I'm having is that the callback function of the mqtt-paho module keeps giving the error:
AttributeError: 'Client' object has no attribute 'write_api'. I think this is because of the self in the internal 'Client' class of the mqtt-paho. My full script can be found below:
# Imported modules
# standard time module
from datetime import datetime
import time
# InfluxDB specific modules
from influxdb_client import InfluxDBClient, Point, WritePrecision
from influxdb_client.client.write_api import SYNCHRONOUS
#MQTT paho specific modules
import paho.mqtt.client as mqtt
class data_handler(): # Default namespaces are just for all the ESPs.
def __init__(self, namespace_list=["ESP01","ESP02","ESP03","ESP04","ESP05","ESP06","ESP07","ESP08"]):
# initialize influxdb client and define access token and data bucket
token = "XXXXXXXXXX" # robotlab's token
self.org = "Home"
self.bucket = "HomeSensors"
self.flux_client = InfluxDBClient(url="http://localhost:8086", token=token)
self.write_api = self.flux_client.write_api(write_options=SYNCHRONOUS)
# Initialize and establish connection to MQTT broker
broker_address="XXX.XXX.XXX.XXX"
self.mqtt_client = mqtt.Client("influx_client") #create new instance
self.mqtt_client.on_message=data_handler.mqtt_message #attach function to callback
self.mqtt_client.connect(broker_address) #connect to broker
# Define list of namespaces
self.namespace_list = namespace_list
print(self.namespace_list)
def mqtt_message(self, client, message):
print("message received " ,str(message.payload.decode("utf-8")))
print("message topic=",message.topic)
print("message qos=",message.qos)
print("message retain flag=",message.retain)
sequence = [message.topic, message.payload.decode("utf-8")]
self.write_api.write(self.bucket, self.org, sequence)
def mqtt_listener(self):
for namespace in self.namespace_list:
self.mqtt_client.loop_start() #start the loop
print("Subscribing to topics!")
message = namespace+"/#"
self.mqtt_client.subscribe(message, 0)
time.sleep(4) # wait
self.mqtt_client.loop_stop() #stop the loop
def main():
influxHandler = data_handler(["ESP07"])
influxHandler.mqtt_listener()
if __name__ == '__main__':
main()
The code works fine until I add self.someVariable in the callback function. What would be a good way to solve this problem? I don't really want to be making global variables hence why I chose to use a class.
Thanks in advance!
Dealing with self when there are multiple classes involved can get confusing. The paho library calls on_message as follows:
on_message(self, self._userdata, message)
So the first argument passed is the instance of Client so what you are seeing is expected (in the absence of any classes).
If the callback is a method object (which appears to be your aim) "the instance object is passed as the first argument of the function". This means your function would take four arguments and the definition be:
mqtt_message(self, client, userdata, msg)
Based upon this you might expect your application to fail earlier than it is but lets look at how you are setting the callback:
self.mqtt_client.on_message=data_handler.mqtt_message
datahandler is the class itself, not an instance of the class. This means you are effectively setting the callback to a static function (with no binding to any instance of the class - this answer might help). You need to change this to:
self.mqtt_client.on_message=self.mqtt_message
However this will not work as the method currently only takes three arguments; update the definition to:
def mqtt_message(self, client, userdata, msg)
with those changes I believe this will work (or at least you will find another issue :-) ).
An example might be a better way to explain this:
class mqtt_sim():
def __init__(self):
self._on_message = None
#property
def on_message(self):
return self._on_message
#on_message.setter
def on_message(self, func):
self._on_message = func
# This is what you are doing
class data_handler1(): # Default namespaces are just for all the ESPs.
def __init__(self):
self.mqtt = mqtt_sim()
self.mqtt.on_message = data_handler1.mqtt_message # xxxxx
def mqtt_message(self, client, message):
print("mqtt_message1", self, client, message)
# This is what you should be doing
class data_handler2(): # Default namespaces are just for all the ESPs.
def __init__(self):
self.mqtt = mqtt_sim()
self.mqtt.on_message = self.mqtt_message #attach function to callback
def mqtt_message(self, mqttself, client, message):
print("mqtt_message2", self, mqttself, client, message)
# Lets try using both of the above
d = data_handler1()
d.mqtt._on_message("self", "userdata", "message")
d = data_handler2()
d.mqtt._on_message("self", "userdata", "message")

Overide a GET function using python flask

I am developing a Python app and using flask.
I am now writing my GET functions.
Here's how it should work:
GET http://{host_ip}:{port}/GetMessage?applicationId=1
GET http://{host_ip}:{port}/GetMessage?sessionId=aaaa
GET http://{host_ip}:{port}/GetMessage?messageId=bbbb
Here is my code:
#app.route('/GetMessage')
def GetMessage():
application_id = request.args.get('application_id')
messages = Message.query.filter_by(user_id=application_id)
return render_template('get.html', messages=messages)
#app.route('/GetMessage')
def GetMessage():
message_id = request.args.get('message_id')
messages = Message.query.filter_by(message_id=message_id)
return render_template('get.html', messages=messages)
But it sends me such an error message:
AssertionError: View function mapping is overwriting an existing endpoint function: GetMessage
what can be done?
Thanks!
def GetMessage():
messages = Message.query.all()
application_id = request.args.get('application_id')
if application_id:
messages.filter_by(user_id=application_id)
message_id = request.args.get('message_id')
if message_id:
messages = message.filter_by(message_id=message_id)
return render_template('get.html', messages=messages)

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

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?

Categories

Resources