pyTelegramBotAPI.How to edit/delete message with inline buttons only if message from the same step was already in chat? - structure

I want my TG bot to have inlinebuttons with categories and subcategories in chat with dynamic change.
But how to send message with subcategories if it doesnt exist and edit message with subcategories if it already exists?
Also, I wish to have a "back" inlinebutton among categories that will delete messages with categories and subcategories and turn to main menu.
Attached my code and illustration of goal.
Help me please!
import telebot
from telebot import types
bot = telebot.TeleBot(TELEGRAM_TOKEN,
parse_mode=None)
markup_menu = types.ReplyKeyboardMarkup(resize_keyboard=True)
btns = ["Order"]
for btn in btns:
markup_menu.add(types.KeyboardButton(btn))
menu = {"Snacks": {"Sweet Snack": 150, "Salt snak": 320}, "Drinks": {"Cola": 100, "Sprite": 90},
"Meat": {"Pork": 228, "Beef": 56}}
#bot.message_handler(commands=['start'])
def start(message):
bot.send_message(message.chat.id,
f"Wellcome, <b>{message.from_user.first_name}</b>",
reply_markup=markup_menu,
parse_mode="html")
#bot.message_handler(content_types="text")
def order(message):
# These are variable values to help change next message after "Choose category:"
order_counter = 0
product_message = 0
switcher = {product_message: order_counter}
if message.text == 'Order':
order_menu = types.InlineKeyboardMarkup()
order_menu.row_width = 2
for category in menu:
order_menu.add(types.InlineKeyboardButton(category,
callback_data=f"{category}"))
bot.send_message(message.chat.id,
"Choose category:",
reply_markup=order_menu)
if order_counter == 0:
#bot.callback_query_handler(func=lambda call: call.data in menu.keys())
def callback_product(call):
product_menu = types.InlineKeyboardMarkup()
product_menu.row_width = 4
for product in menu[call.data]:
product_menu.add(types.InlineKeyboardButton
(f"{product}" +
f" - {menu[call.data][product]}",
callback_data=f"{product}")
)
product_message = bot.send_message(message.chat.id,
f'Choose {call.data}:',
reply_markup=product_menu,
)
product_message
order_counter == 1
switcher = {product_message: order_counter}
return switcher
elif switcher.keys() == 1:
#bot.callback_query_handler(func=lambda call: call.data in menu.keys())
def callback_product1(call):
product_menu = types.InlineKeyboardMarkup()
product_menu.row_width = 4
for product in menu[call.data]:
product_menu.add(types.InlineKeyboardButton
(f"{product}" +
f" - {menu[call.data][product]}",
callback_data=f"{product}")
)
product_message = bot.edit_message_text(chat_id=switcher[0][0].message.chat.id,
message_id=switcher[0][0].message_id,
text=f'Choose {call.data}:',
reply_markup=product_menu
)
product_message
order_counter == 1
return order_counter, product_message
#bot.message_handler(content_types="text")
def missunderstand(message):
bot.reply_to(message, "I don't understand you", reply_markup=markup_menu)
bot.infinity_polling()
I have tried to create a switcher that would signal to func. if message with subcategories exists, but it didn't work. (it's "switcher" in code)

Related

How do I delay the execution of a function within tkinter button command

I am trying to simulate a computer player "clicking" a button after a human user clicks a button In a grid of buttons. If I use the .after method the 'state' of the button change is delayed but it executes my check_state() method which doesn't detect the change. When I try time.sleep() method it prolongs the human click but still immediately invokes the 'auto' click regardless of where I put it in my code. I want a delay between the human click and 'auto' click.
I have tried widget.after(1000) which gives the desired delay of 'auto' click, but doesn't allow my the change to be seen by my check_state() function. I have tried time.sleep() which delays the execution of the human button click but the 'auto' click is still immediately invokes regardless of which order I place the sleep() function in relation to the call to auto_click(). I know there are better practices for this code implementation like using class based structure which I plan on using once my logic and functionality issues are resolved. My code is as follows:
import tkinter as tk
import random
def app():
def auto_click():
grid_state = get_grid_state()
possible_clicks = []
for i in range(0, len(grid_state)):
if grid_state[i] == " ":
possible_clicks.append(i)
#debug.config(text=possible_moves)
click = random.choice(possible_clicks)
buttons[click].after(1000, lambda: buttons[click].config(text = "auto", state=tk.DISABLED))
#time.sleep(1)
check_grid_state()
check_grid_full()
debug.config(text="test")
def onclick(*arg):
global is_full
buttons[arg[0]].config(text = "clicked", state=tk.DISABLED)
check_grid_state()
check_grid_full()
if not is_full:
auto_click()
def check_grid_full():
global is_full
result=[]
for i in range(len(buttons)):
result.append(buttons[i].cget('state'))
r = [*set(result)]
if r == ['disabled']:
is_full = True
grid_status.config(text=is_full)
else:
is_full = False
Retrieve the current state of the grid
def get_grid_state():
grid_state =[]
for i in range(len(buttons)):
grid_state.append(buttons[i].cget('text'))
return grid_state
Check grid state
def check_grid_state():
grid_states.config(text=get_grid_state())
Global Variables
is_full = False
buttons = []
c=0
Window
root = tk.Tk()
root.title("Title")
Heading
label = tk.Label(root, text="grid state", font = ("Ariel black",22, "bold"))
label.pack()
Grid Frame
frame = tk.Frame(root)
frame.pack()
for row in range(3):
for column in range(3):
buttons.append(tk.Button(frame, text=f" ", font=("arial", "22"), state=tk.ACTIVE, height=2, width=2, command=lambda c=c: onclick(c)))
buttons[c].grid(row=row, column=column)
c += 1
Status bar
grid_states = tk.Label(root, text=f"")
grid_states.pack()
grid_status = tk.Label(root, text=f"")
grid_status.pack()
#btn_is = tk.Label(root, text=f"")
#btn_is.pack()
Debugging output label
debug = tk.Label(root, text="debug")
debug.pack()
Event loop
root.mainloop()
if name == "main":
app()

How to receive multiple messages in python-telegram-bot?

I am sending multiple images at a time to a bot in telegram. I am trying to create a conversational chatbot using python-telegram bot.
here is my code:
def main():
updater = Updater("1141074258:Axxxxxxxxxxxxxxxxxxxxxxxxg", use_context=True)
dp = updater.dispatcher
conv_handler = ConversationHandler(
entry_points = [CommandHandler('start',start)],
states = {
CHOSEN_OPTION: [MessageHandler(Filters.regex('^(Option2|Option3|Option4)$'),choose_option)],
PRODUCTS: [MessageHandler(Filters.text | Filters.photo,products)],
Option2: [MessageHandler(Filters.text,option2)],
Option3: [MessageHandler(Filters.text,option3)],
Option4: [CommandHandler('create', create_order)]
},
fallbacks=[CommandHandler('cancel', cancel)]
)
dp.add_handler(conv_handler)
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
#run_async
def products(update,context):
logger.info("update is %s",update)
input_message = update.message.text
if input_message:
data['products'] = input_message
logger.info("product text is:%s",input_message)
elif update.message.photo:
photo_list = []
bot = context.bot
length = len(update.message.photo)
for photo in range(0,length):
ident = update.message.photo[photo].file_id
getFile = context.bot.get_file(ident)
photo_list.append(getFile['file_path'])
data['products_image'] = photo_list
update.message.reply_text("Please type name.",)
return Option3
If i am send 2 images same time, i am getting one image with a different size (3 times), How can I receive the actual two messages?
if update contains photo return PRODUCTS for getting other photos else
get text and return to every state you want
#run_async
def products(update,context):
logger.info("update is %s",update)
if update.message.photo:
# to what you want with your photos
return PRODUCTS
if update.message.text:
# getting product text
return Option3

i need the list of countries that are supported africa's talking api

I actually want to use Africa's talking api on my ussd app. I am from bangladesh and i am confused if it supports bangladesh or not.there are mainly four service provider in bangladesh namely gameenphone, robi, banglalink, airtel. I want to send ussd from one of the operator to my application.
here is python code:
from flask import Flask, request
import africastalking
import os
app = Flask(__name__)
username = "sandbox"
api_key = "*384*89376#"
africastalking.initialize(username, api_key)
sms = africastalking.SMS
#app.route('/', methods=['POST', 'GET'])
def ussd_callback():
global response
session_id = request.values.get("sessionId", None)
service_code = request.values.get("serviceCode", None)
phone_number = request.values.get("phoneNumber", None)
text = request.values.get("text", "default")
sms_phone_number = []
sms_phone_number.append(phone_number)
#ussd logic
if text == "":
#main menu
response = "CON What would you like to do?\n"
response += "1. Check account details\n"
response += "2. Check phone number\n"
response += "3. Send me a cool message"
elif text == "1":
#sub menu 1
response = "CON What would you like to check on your account?\n"
response += "1. Account number"
response += "2. Account balance"
elif text == "2":
#sub menu 1
response = "END Your phone number is {}".format(phone_number)
elif text == "3":
try:
#sending the sms
sms_response = sms.send("Thank you for going through this tutorial",
sms_phone_number)
print(sms_response)
except Exception as e:
#show us what went wrong
print(f"Houston, we have a problem: {e}")
elif text == "1*1":
#ussd menus are split using *
account_number = "1243324376742"
response = "END Your account number is {}".format(account_number)
elif text == "1*2":
account_balance = "100,000"
response = "END Your account balance is USD {}".format(account_balance)
else:
response = "END Invalid input. Try again."
return response
if __name__ == "__main__":
app.run()
Africa's Talking USSD services are in the following countries:
Kenya,
Uganda,
Tanzania,
Rwanda,
Nigeria
Côte d'Ivoire,
Malawi,
Zambia,
South Africa
Hope this helps. Reach out if you have any more questions.

How to assert an element is present after a click in Hound?

I'm using Hound as webdriver framework for leveraging Selenium in Elixir. I'm testing facebook account creation. After I fill out a negative test (firstname = Roberto, lastname = asdlkfj;)I click submit and I am trying to get an error for not having a correct last name. The problem is Hound isn't waiting for the element to be displayed and returns an element not found error. How can I handle this and have the test wait until the element is loaded? Here's my code:
test "Last name with random characters" do
counter = :rand.uniform(100)
navigate_to "https://www.facebook.com"
first_name = find_element(:name, "firstname")
fill_field(first_name, "Jorge")
last_name = find_element(:name, "lastname")
fill_field(last_name, "asdfja;lsdf")
email_input = "robbie#{counter}#gmail.com"
email = find_element(:name, "reg_email__")
fill_field(email, email_input)
confirm_email = find_element(:name, "reg_email_confirmation__")
fill_field(confirm_email, email_input)
password_input = "123456Test"
password = find_element(:name, "reg_passwd__")
fill_field(password, password_input)
#Birthday
birth_month = find_element(:css, "#month > option:nth-child(5)")
birth_day = find_element(:css, "#day > option:nth-child(25)")
birth_year = find_element(:css, "#year > option:nth-child(22)")
click(birth_month)
click(birth_day)
click(birth_year)
#gender
select_gender = find_element(:css, "#u_0_s > span:nth-child(2)")
click(select_gender)
sign_up_button = find_element(:name, "websubmit")
click(sign_up_button)
search = find_element(:id, "#reg_error_inner")
# found = element_displayed?("#reg_error_inner")
IO.puts(search)
# assert found == true
# :timer.sleep(10000)
end```
Use search_element instead of find_element. search_element will return {:ok, element} on success and {:error, error} on failure. If you just want to assert the element exists, then you can:
assert {:ok, _} = search_element(:id, "#reg_error_inner")
If you want to also have it in a variable for further processing, then:
assert {:ok, element} = search_element(:id, "#reg_error_inner")
If you want to convert it to a boolean, then:
match?({:ok, _}, search_element(:id, "#reg_error_inner"))

All the links in the same row

I'm doing a database where it has 2 columns: event_name and event_URL. It doesn't get the name and puts all the urls on the event_URL column. Print: https://prnt.sc/fru1tr
Code:
import urllib2
from bs4 import BeautifulSoup
import psycopg2
page = urllib2.urlopen('https://www.meetup.com/find/outdoors-adventure/?allMeetups=false&radius=50&userFreeform=London%2C+&mcId=c1012717&change=yes&sort=default')
soup = BeautifulSoup(page, 'lxml')
events = soup.find('ul', class_='j-groupCard-list searchResults tileGrid tileGrid--3col tileGrid_atMedium--2col tileGrid_atSmall--1col')
A = []
B = []
try:
conn = psycopg2.connect("dbname='meetup' user='postgres' host='localhost' password='root'")
except:
print 'Unable to connect to the database.'
cur = conn.cursor()
for event in events.findAll('li'):
text = event.findAll('h3')
if len(text) != 0:
A.append(text[0].find(text = True))
url = event.find('a', href=True)
if len(url) != 0:
B.append(url['href'])
cur.execute("""INSERT INTO outdoors_adventure(event_name,event_url) VALUES(%s,%s)""", (tuple(A),tuple(B)))
conn.commit()
del A[:]
del B[:]
If the indentation is right in the posted code, the problem might be in the nested for-loop: for every event, you append the "B" list with all the links on the page. You could try:
for event in events.findAll('li'):
text = event.findAll('h3')
if len(text) != 0:
A.append(text[0].find(text = True))
for link in events.findAll('li'):
url = link.find('a', href=True)
if len(url) != 0:
B.append(url['href'])
Or better, keep the event-name and event-URL search in a single for-loop, fetching first the text and then the url of event
EDIT: You can simplify the name extraction, by using:
for event in events.findAll('li'):
text = event.h3.string.strip()
if len(text) != 0:
A.append(text)
url = event.find('a', href=True)
...
Let me know if that does the trick for you (it does on my side).
EDIT2: The problem might be just the fact that the extracted string starts with tabs (maybe that's why your DB seems "not to show the name" - its there, but you only see the tabs in the preview?). Just use strip() to remove them.