I try to write a ChatBot program that will respond to each user differently.
So I implement like this: When there is a new user, ask the bot to do something and my bot needs to ask user back for more information and wait for the response message, my code will register a dict with a key of user_id and value of call_back function of class User like example code below.
class User:
api_dict = {}
def __init__(self, user_id):
self.user_id = user_id
def ask_username(self,chat_env):
chat_env.send_msg(self.user_id,"Please enter your username")
api_dict[self.user_id] = self.ask_birth_date
def ask_birth_date(self,message,chat_env)
chat_env.send_msg(self.user_id,"Mr. {} what is your birth date".format(message))
# do some thing
def hook_function(user_id,message,chat_env)
if is_first_hook(user_id):
user = User(user_id)
user.ask_username()
else:
User.api_dict[user_id](message,chat_env)
But it was not working as python threw an error that it didn't receive chat_env parameter in ask_birth_date() in which I think self wasn't passed to the function.
So is there any way to make self still attach with ask_birth_date()?
I think that you must be storing all the instances of User somewhere to be able to call ask_username when a connection is first made. Therefore you can transform api_dict into a state pattern.
Users = {} # I'm guessing you already have this!
class User:
def __init__(self, user_id):
self.user_id = user_id
def ask_username(self, chat_env):
chat_env.send_msg(self.user_id, "Please enter your username")
self.current = self.ask_birth_date
def ask_next_question(self, message, chat_env)
self.current(message, chat_env)
def ask_birth_date(self, message, chat_env)
chat_env.send_msg(self.user_id, "Mr. {} what is your birth date".format(message))
self.current = self.record_birth_date # for example
# There must be code that does this already
def new_connection(user_id, chat_env):
Users[user_id] = User(user_id)
Users[user_id].ask_username(chat_env)
# I assume this is called for every message that arrives from a user
def hook_function(user_id, message, chat_env)
Users[user_id].ask_next_question(message, chat_env)
Update:
Your hook function doesn't call ask_username() properly. That is why you are getting the error. That is why you should post all your code and the whole of the stack trace!
This code should fix your call site:
def hook_function(user_id, message, chat_env)
if is_first_hook(user_id):
user = User(user_id)
user.ask_username(chat_env) # add param here!
# btw the user instance here is thrown away!
else:
User.api_dict[user_id](message, chat_env)
If the above fixes your problems, then that means that the User class is unnecessary. You could just have api_dict as a global and the methods can become free functions.
Your code can be reduced to this:
api_dict = {}
def ask_username(chat_env, user_id):
chat_env.send_msg(user_id, "Please enter your username")
api_dict[user_id] = ask_birth_date
def ask_birth_date(chat_env, user_id, message)
chat_env.send_msg(user_id, "Mr. {} what is your birth date".format(message))
# do some thing
def hook_function(user_id, message, chat_env)
if is_first_hook(user_id):
ask_username(chat_env, user_id)
else:
api_dict[user_id](chat_env, user_id, message)
Related
class print_values:
def __init__(self,username,user_email,displayname):
self.name= username
self.email=user_email
self.DisplayName=displayname
def printing_content(self):
print(f"UserName: {self.name}\n"
f"UserEmail: {self.email}\n"
f"UserDisplayName:{self.DisplayName}\n")
user_one={'username':'userone',
'useremail':'userone#gmail.com',
'displayname':'User One'}
user_two={'username':'usertwo',
'useremail':'usertwo#gmail.com',
'displayname':'User Two'}
user_three={'username':'userthree',
'useremail':'userthree#gmail.com',
'displayname':'User Three'}
users_list=['user_one','user_two','user_three']
obj_name=print_values(user_one['username'],user_one['useremail'],user_one['displayname'])
obj_name.printing_content()
It's working fine, as am getting output as below
UserName: userone
UserEmail: userone#gmail.com
UserDisplayName:User One
Here am only using user_one dict, i want to do the same for multiple dict.
I have tried adding the dict names in list and try to loop through them, like below
for item in user_list:
obj_name=print_values(item['username'],item['useremail'],item['displayname'])
obj_name.printing_content()
But am getting below error
obj_name=print_values(item['username'],item['useremail'],item['displayname'])
TypeError: string indices must be integers
Any one do let me know what am i missing or anyother idea to get this done.
Thanks in advance!
This is because in users_list=['user_one', 'user_two', 'user_three'] you enter the variable name as a string.
class print_values:
def __init__(self,username,user_email,displayname):
self.name= username
self.email=user_email
self.DisplayName=displayname
def printing_content(self):
print(f"UserName: {self.name}\n"
f"UserEmail: {self.email}\n"
f"UserDisplayName:{self.DisplayName}\n")
user_one={'username':'userone',
'useremail':'userone#gmail.com',
'displayname':'User One'}
user_two={'username':'usertwo',
'useremail':'usertwo#gmail.com',
'displayname':'User Two'}
user_three={'username':'userthree',
'useremail':'userthree#gmail.com',
'displayname':'User Three'}
users_list=[user_one,user_two,user_three] # edited
obj_name=print_values(user_one['username'],user_one['useremail'],user_one['displayname'])
obj_name.printing_content()
for item in users_list:
obj_name=print_values(item['username'],item['useremail'],item['displayname'])
obj_name.printing_content()
Explanation
Your users_list=['user_one', 'user_two', 'user_three'] is a string containing the variable names as the string. When you loop on user_list
for item in user_list:
Here item is not the user_one, or user_two as a variable but these are as the string means 'user_one', or 'user_two', so when you try to get values like item['username'], here you got the error because the item is not a dictionary or json or ..., but it is a string here, you can get the only provide an integer inside these brackets [], like 1, 2, 3, 4,..., ∞.
I hope you understand well. Thanks.
Don't make a dictionary for every user.
Use this code
class Users:
def __init__(self) -> None:
self.userList = []
def addUser(self, user):
self.userList.append(user)
class User:
def __init__(self, username, email, name) -> None:
self.username = username
self.email = email
self.name = name
def __str__(self) -> str:
return f"Username = {self.username}\nEmail = {self.email}\nName = {self.name}\n"
users = Users()
users.addUser(User("username1", "email1", "name1"))
users.addUser(User("username2", "email2", "name2"))
# First way of printing
for user in users.userList:
print(user) # Printing user directly prints the formatted output
# Because I have changed the magic `__str__` method in user class
# You can return anything('string data type only') in __str__ it will print when you print the class object.
# Second way of printing.
for user in users.userList:
print("Username = " + user.username)
print("Email = " + user.email)
print("Name = " + user.name)
print() # for adding one extra line
I created a little program as part of my learning experience using python crash course and the code worked pretty well yesterday. But now that I woke up and tried to launch the thing it refuses to do anything and says that "self" is not defined. I honestly have no idea why it happens and would very much like to know exactly what causes error and where I mistaken. Sorry if the question format is wrong and thanks in advance for any help.
import json
class Save_user:
"""Greet the user if the username presents."""
"""Ask the name otherwise."""
def __init__(self):
"""Sets username; Calls greet_user()"""
self.file_path = 'username.json'
self.greet_user()
def get_stored_username(self):
"""Get the username if stored."""
try:
with open(self.file_path) as f:
self.username = json.load(f)
except FileNotFoundError:
return None
else:
return self.username
def greet_user(self):
"""Choose greet the user or store the username."""
self.get_stored_username()
if self.username:
self.if_same_user()
else:
self.store_name()
def store_name(self):
"""Store username."""
self.username = input("Enter your username: ")
with open(self.file_path, 'w') as f:
json.dump(self.username, f)
print("Great! We'll greet you next time!")
def if_same_user(self):
"""Check if the same user."""
print(f"Are you {self.username}?")
while True:
response = input("Please, Enter 'yes' or 'no': \n")
response = response.lower().strip()
if response == 'yes' or response == 'y':
print(f"Welcome back, {self.username}!")
break
elif response == 'no' or response == 'n':
self.store_name()
break
useame = Save_user()
The program should asks the user's name if the json file exists and create the file and store the name otherwise. I tried to set username to 0 in __init__ module and I could launch the thing with text editor and .py format, visual studio, however is giving me an error. Again, thanks in advance for any help!
UPD TraceBack:
Traceback (most recent call last):
File "c:\Users\Windows 10\Desktop\python_work\New folder\new.py", line 50, in <module>
username = Save_user()
^^^^^^^^^^^
File "c:\Users\Windows 10\Desktop\python_work\New folder\new.py", line 10, in __init__
self.greet_user()
File "c:\Users\Windows 10\Desktop\python_work\New folder\new.py", line 25, in greet_user
if self.username:
^^^^^^^^^^^^^
AttributeError: 'Save_user' object has no attribute 'username'
The problem arises when the file is not found: you return None, but don't actually asign it to self.username. So when you do if self.username, an error will rise. I tweaked two lines of your code, here are the functions to change:
def get_stored_username(self):
"""Get the username if stored."""
try:
with open(self.file_path) as f:
username = json.load(f)
return username
except FileNotFoundError:
return None
def greet_user(self):
"""Choose greet the user or store the username."""
self.username = self.get_stored_username()
if self.username:
self.if_same_user()
else:
self.store_name()
The traceback is very helpful here. The problem is that __init__ calls self.greet_user(), which expects self.username to exist. But self.username does not exist yet.
class Save_user:
def __init__(self):
"""Sets username; Calls greet_user()"""
self.file_path = 'username.json'
self.greet_user()
# NOTE: there is no self.username here
# ...
def greet_user(self):
"""Choose greet the user or store the username."""
self.get_stored_username() # May fail to set self.username
if self.username: # This will cause an error
self.if_same_user()
else:
self.store_name()
Typically, attempting to instantiate an object of this class when there is no file username.json will cause an AttributeError that self.username is not one of the attributes. One way to solve this would be to add a sentinel or default username, such as
# ...
def __init__(self):
self.file_path = 'username.json'
self.username = 'default'
self.greet_user()
# ...
A different default name (especially one that is not allowed to be set by standard means) would be a better choice.
Your issue is in the method get_stored_username
you are returning None instead of defining the property self.username
def get_stored_username(self):
"""Get the username if stored."""
try:
with open(self.file_path) as f:
self.username = json.load(f)
except FileNotFoundError:
return None # <- error
else: # <- not necessary
return self.username # <- not necessary
Define the property:
...
except FileNotFoundError:
self.username = None
complete method:
def get_stored_username(self):
"""Get the username if stored."""
try:
with open(self.file_path) as f:
self.username = json.load(f)
except FileNotFoundError:
self.username = None
I have a problem with FSM using pyTelegramBotAPI.
I want to modify code example from GitHub to return values (name, surname, age) from ready_for_answer function for further processing.
Usually, I would just call a function returning this values, but I can't figure out, how to do it using this code.
Here's how I imagine the code I want to get:
import telebot # telebot
from telebot import custom_filters
from telebot.handler_backends import State, StatesGroup #States
# States storage
from telebot.storage import StateMemoryStorage
# Now, you can pass storage to bot.
state_storage = StateMemoryStorage() # you can init here another storage
bot = telebot.TeleBot("TOKEN",
state_storage=state_storage)
# States group.
class MyStates(StatesGroup):
# Just name variables differently
name = State() # creating instances of State class is enough from now
surname = State()
age = State()
#bot.message_handler(commands=['start'])
def start_ex(message):
"""
Start command. Here we are starting state
"""
bot.set_state(message.from_user.id, MyStates.name, message.chat.id)
bot.send_message(message.chat.id, 'Hi, write me a name')
# Any state
#bot.message_handler(state="*", commands=['cancel'])
def any_state(message):
"""
Cancel state
"""
bot.send_message(message.chat.id, "Your state was cancelled.")
bot.delete_state(message.from_user.id, message.chat.id)
#bot.message_handler(state=MyStates.name)
def name_get(message):
"""
State 1. Will process when user's state is MyStates.name.
"""
bot.send_message(message.chat.id, 'Now write me a surname')
bot.set_state(message.from_user.id, MyStates.surname, message.chat.id)
with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
data['name'] = message.text
#bot.message_handler(state=MyStates.surname)
def ask_age(message):
"""
State 2. Will process when user's state is MyStates.surname.
"""
bot.send_message(message.chat.id, "What is your age?")
bot.set_state(message.from_user.id, MyStates.age, message.chat.id)
with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
data['surname'] = message.text
I want to return name, surname and age from ready_for_answer function for further use in code
# result
#bot.message_handler(state=MyStates.age, is_digit=True)
def ready_for_answer(message):
"""
State 3. Will process when user's state is MyStates.age.
"""
with bot.retrieve_data(message.from_user.id, message.chat.id) as data:
msg = ("Ready, take a look:\n<b>"
f"Name: {data['name']}\n"
f"Surname: {data['surname']}\n"
f"Age: {message.text}</b>")
bot.send_message(message.chat.id, msg, parse_mode="html")
bot.delete_state(message.from_user.id, message.chat.id)
I want to return name, surname and age from ready_for_answer function for further use in code
#incorrect number
#bot.message_handler(state=MyStates.age, is_digit=False)
def age_incorrect(message):
"""
Wrong response for MyStates.age
"""
bot.send_message(message.chat.id, 'Looks like you are submitting a string in the field age. Please enter a number')
# register filters
bot.add_custom_filter(custom_filters.StateFilter(bot))
bot.add_custom_filter(custom_filters.IsDigitFilter())
bot.infinity_polling(skip_pending=True)
Tell me, please, how to do it right? Thanks in advance!
Context:
I am using PyTelegramBotAPi or Python Telegram Bot
I have a code I am running when a user starts the conversation.
When the user starts the conversation I need to send him the first picture and a question if He saw something in the picture, the function needs to wait for the user input and return whether he saw it or not.
After that, I will need to keep sending the picture in a loop and wait for the answer and run a bisection algorithm on it.
What I have tried so far:
I tried to use reply markup that waits for a response or an inline keyboard with handlers but I am stuck because my code is running without waiting for the user input.
The code:
#bot.message_handler(func=lambda msg: msg in ['Yes', 'No'])
#bot.message_handler(commands=['start', 'help'])
def main(message):
"""
This is my main function
"""
chat_id = message.chat.id
try:
reply_answer = message.reply_to_message.text
except AttributeError:
reply_answer = '0'
# TODO : should wait for the answer asynchnonossly
def tester(n, reply_answer):
"""
Displays the current candidate to the user and asks them to
check if they see wildfire damages.
"""
print('call......')
bisector.index = n
bot.send_photo(
chat_id=chat_id,
photo=bisector.image.save_image(),
caption=f"Did you see it Yes or No {bisector.date}",
reply_markup=types.ForceReply(selective=True))
# I SHOUL WAIT FOR THE INPUT HERE AND RETURN THE USER INPUT
return eval(reply_answer)
culprit = bisect(bisector.count, lambda x: x, partial(tester, reply_answer=reply_answer) )
bisector.index = culprit
bot.send_message(chat_id, f"Found! First apparition = {bisector.date}")
bot.polling(none_stop=True)
The algorithm I am running on the user input is something like this :
def bisect(n, mapper, tester):
"""
Runs a bisection.
- `n` is the number of elements to be bisected
- `mapper` is a callable that will transform an integer from "0" to "n"
into a value that can be tested
- `tester` returns true if the value is within the "right" range
"""
if n < 1:
raise ValueError('Cannot bissect an empty array')
left = 0
right = n - 1
while left + 1 < right:
mid = int((left + right) / 2)
val = mapper(mid)
tester_values = tester(val) # Here is where I am using the ouput from Telegram bot
if tester_values:
right = mid
else:
left = mid
return mapper(right)
I hope I was clear explaining the problem, feel free to ask any clarification.
If you know something that can point me in the right direction in order to solve this problem, let me know.
I have tried a similar question but I am not getting answers.
You should save your user info in a database. Basic fields would be:
(id, first_name, last_name, username, menu)
What is menu?
Menu keeps user's current state. When a user sends a message to your bot, you check the database to find out about user's current sate.
So if the user doesn't exist, you add them to your users table with menu set to MainMenu or WelcomeMenu or in your case PictureMenu.
Now you're going to have a listener for update function, let's assume each of these a menu.
#bot.message_handler(commands=['start', 'help'])
so when the user sends start you're going to check user's menu field inside the function.
#bot.message_handler(commands=['start', 'help'])
def main(message):
user = fetch_user_from_db(chat_id)
if user.menu == "PictureMenu":
if message.photo is Not None:
photo = message.photo[0].file_id
photo_file = download_photo_from_telegram(photo)
do_other_things()
user.menu = "Picture2Menu";
user.save();
else:
send_message("Please send a photo")
if user.menu == "Picture2Menu":
if message.photo is Not None:
photo = message.photo[0].file_id
photo_file = download_photo_from_telegram(photo)
do_other_things()
user.menu = "Picture3Menu";
user.save();
else:
send_message("Please send a photo")
...
I hope you got it.
I have found the answer:
the trick was to use next_step_handler, and message_handler_function to handle command starting with start and help
Then as suggested by #ALi in his answer, I will be saving the user input answer as well as the question id he replied to in a dictionary where keys are questions and id are the answer.
Once the user has answered all questions, I can run the algorithms on his answer
Here is how it looks like in the code :
user_dict = {}
# Handle '/start' and '/help'
#bot.message_handler(commands=['help', 'start'])
def send_welcome(message):
# initialise the the bisector and
bisector = LandsatBisector(LON, LAT)
indice = 0
message = send_current_candidate(bot, message, bisector, indice)
bot.register_next_step_handler(
message, partial(
process_step, indice, bisector))
def process_step(indice, bisector, message):
# this run a while loop and will that send picture and will stop when the count is reached
response = message.text
user = User.create_get_user(message, bisector=bisector)
if indice < bisector.count - 1:
indice += 1
try:
# get or create
user.responses[bisector.date] = response # save the response
message = send_current_candidate(bot, message, bisector, indice)
bot.register_next_step_handler(
message, partial(
process_step, indice, bisector))
except Exception as e:
print(e)
bot.reply_to(message, 'oooops')
else:
culprit = bisect(bisector.count,
lambda x: x,
partial(
tester_function,
responses=list(user.responses.values())))
bisector.index = culprit
bot.reply_to(message, f"Found! First apparition = {bisector.date}")
I've written some code using the information here as a guide line,
List users in IRC channel using Twisted Python IRC framework
I can successfully log the NAMES list to the console, however I've so far been unable to
retrieve it to work with it further. Here is an excerpt of the code related to this matter:
class Alfred(irc.IRCClient):
""" An IRC bot """
# Voodoo magic for names
def __init__(self, *args, **kwargs):
self._namescallback = {}
def names(self, channel):
channel = channel.lower()
d = defer.Deferred()
if channel not in self._namescallback:
self. _namescallback[channel] = ([], [])
self._namescallback[channel][0].append(d)
self.sendLine("NAMES %s" % channel)
return d
def irc_RPL_NAMREPLY(self, prefix, params):
channel = params[2].lower()
nicklist = params[3].split(' ')
if channel not in self._namescallback:
return
n = self._namescallback[channel][1]
n += nicklist
def irc_RPL_ENDOFNAMES(self, prefix, params):
channel = params[1].lower()
if channel not in self._namescallback:
return
callbacks, namelist = self._namescallback[channel]
for cb in callbacks:
cb.callback(namelist)
del self._namescallback[channel]
# End of voodoo magic
def get_names(self, nicklist):
# Log the output to the log
log.msg(nicklist)
def has_op(self, channel, user):
self.names('#' + channel).addCallback(self.get_names)
def change_mode(self, channel, user, msg):
target = msg[5:-3]
params = [channel, '+o']
self.has_op(channel, user)
self.mode(channel, True, '+o', user=target)
What I want to achieve is to within the has_op function get a hold of the so called nicklist. So far I've been unable to do this using a trial and error approach, where I've tried to use print and return statements in reasonable places but this has yielded no output what so ever or instance / attribute errors. Obviously, I'm at a loss and I really need some guidance.
The idea of doing this "within" has_op is probably preventing you from making progress.
Make it work at all and then consider how you might improve the factoring of the implementation.
Notice that get_names has the data that you're interested in already. What do you want to do with that information? Check to see if it contains a certain name? For that, you'll need to know which name you're considering. To do this, you can use the feature of Deferred.addCallback that lets you pass an extra argument to the callback:
def get_names(self, nicklist, user):
if user in nicklist:
log.msg("%r has op!" % (user,))
def has_op(self, channel, user):
self.names('#' + channel).addCallback(self.get_names, user)
Now perhaps what you really want isn't to log this information but to make a decision based on it in change_mode (I'm only guessing, of course).
If this is so, then you want to take advantage of another Deferred feature - the ability of each callback attached to a Deferred to process and change the result of the Deferred.
You can change has_op so that instead of logging information it also returns a Deferred - but this Deferred can have a True result if the given user has op in the given channel and a False result of they don't.
def get_names(self, nicklist, user):
return user in nicklist
def has_op(self, channel, user):
return self.names('#' + channel).addCallback(self.get_names, user)
Now you can use this functionality in change_mode:
def change_mode(self, channel, user, msg):
target = msg[5:-3]
checking = self.has_op(channel, user)
checking.addCallback(self._ofIfNotOp, channel, target)
def _opIfNotOp(self, isOp, channel, user):
if not isOp:
self.mode(channel, True, '+o', user=user)