Automatically edit last Telegram bot message after expiration period - python

I'm trying to figure out how I can set an "expiration timer" on a message sent by a Telegram bot, containing a few buttons.
Long story short, there's a function which selects a random picture from a folder, then sends it to a group, and in a separate message it sends an InlineKeyboard object with some buttons for the picture to be rated
def send_stuff(context: CallbackContext):
job = context.job
keyboard = [
[
InlineKeyboardButton("NEVER", callback_data="NEVER"),
InlineKeyboardButton("UNLIKELY", callback_data="UNLIKELY")
],
[
InlineKeyboardButton("MEH", callback_data="MEH"),
InlineKeyboardButton("MAYBE", callback_data="MAYBE")
],
[
InlineKeyboardButton("YES", callback_data="YES"),
InlineKeyboardButton("ABSOLUTELY", callback_data="ABSOLUTELY")
],
[
InlineKeyboardButton("RATHER NOT SAY", callback_data="UNKNOWN")
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
context.bot.send_photo(job.context, photo=open(PATH+thefile, 'rb'))
context.bot.send_message(job.context, text='RATE', reply_markup=reply_markup)
This function is being run by a run_daily job:
def start(update: Update, context: CallbackContext):
job = context.job
chat_id = update.message.chat_id
context.job_queue.run_daily(
send_stuff,
datetime.time(13, 45, 00, 000000, tzinfo=pytz.timezone('Europe/Bucharest')),
days=tuple(range(7)),
context=chat_id,
name='j1'
)
Then there is a handler for the user input, which edits the last message sent by the bot:
def main_handler(update: Update, context: CallbackContext):
update.callback_query.answer()
if update.callback_query.data is not None:
user_input = update.callback_query.data
update.effective_message.edit_text('VERDICT: ' + user_input)
What I'm trying to do is set some kind of "expiration" on the message containing the inline keyboard buttons, such that if there is no click by a user in say... 4 hours, it automatically edits itself into something like "NO ANSWER GIVEN".
I'm not super experienced with bots, and looking through the documentation of the telegram bot libraries I have not been able to find a way to do it.
Any suggestions are appreciated.
Thanks!

You apparently already know how to use PTBs JobQueue, so I'm sure that you can figure out how to schedule a new job from within the send_stuff function that edits the sent message :) All you need for that is context.job_queue.run_once and the return value of context.bot.send_message(job.context, text='RATE', reply_markup=reply_markup).
Disclaimer: I'm currently the maintainer of python-telegram-bot.

Thanks to user #CallMeStag I implemented the following solution.
def send_stuff(context: CallbackContext):
job = context.job
keyboard = [
[
InlineKeyboardButton("NEVER", callback_data="NEVER"),
InlineKeyboardButton("UNLIKELY", callback_data="UNLIKELY")
],
[
InlineKeyboardButton("MEH", callback_data="MEH"),
InlineKeyboardButton("MAYBE", callback_data="MAYBE")
],
[
InlineKeyboardButton("YES", callback_data="YES"),
InlineKeyboardButton("ABSOLUTELY", callback_data="ABSOLUTELY")
],
[
InlineKeyboardButton("RATHER NOT SAY", callback_data="UNKNOWN")
]
]
reply_markup = InlineKeyboardMarkup(keyboard)
context.bot.send_photo(job.context, photo=open(PATH+thefile, 'rb'))
# return values of send_message are saved in the 'msg' var
msg context.bot.send_message(job.context, text='RATE', reply_markup=reply_markup)
# the following job is created every time the send_stuff function is called
context.job_queue.run_once(
callback=cleanup,
when=5,
context=msg,
name='cleanup'
)
# the function called by the job
def cleanup(context: CallbackContext):
job = context.job
context.bot.edit_message_text(
chat_id=job.context.chat.id,
text='NO ANSWER PROVIDED',
message_id=job.context.message_id
)

Related

How do I fix this AttributeError in my interactions.py code?

In my interactions.py code, I have two different bot commands. One to get information about a user's products and one to retrieve a certain product. Whenever I run my code, I get AttributeError: 'Command' object has no attribute '_options'.
This is my code:
import interactions, requests
bot = interactions.Client(token="tokenhere")
#bot.command(
name="products",
description="Get a list of all your products",
scope=scopeid,
)
#interactions.option('Your roblox UserID',name='UserID', type=interactions.OptionType.INTEGER , required=True)
#bot.command(
name="retrieve",
description="Retrieve a certain product that you own",
scope=scopeid,
options=[
interactions.Option(
name='product',
description='the product you would like to retrieve',
type=interactions.OptionType.STRING,
required=True
)
]
)
async def products(ctx: interactions.CommandContext, userid: int):
a = str(requests.get('https://jedistuff22.pythonanywhere.com/products/' + str(userid)).text)
await ctx.send(a)
async def retrieve(ctx: interactions.CommandContext, product: str):
a = ctx.author.user.username + '#' + ctx.author.user.discriminator
print(a)
await ctx.send(a)
bot.start()
For some reason, my code works when i just have one command but just flat out stops working when I have two.
I'm really stumped with this error. I have been looking online for about the past day and I am yet to find something that could help me.
You've used the same bot.command() decorator to define two bot commands. Because the bot.command() method can only define one command at a time, you must define each command separately.
import interactions
import requests
bot = interactions.Client(token="tokenhere")
#bot.command(
name="products",
description="Get a list of all your products",
scope=scopeid,
)
#interactions.option('Your roblox UserID',name='UserID', type=interactions.OptionType.INTEGER , required=True)
async def products(ctx: interactions.CommandContext, userid: int):
a = str(requests.get('https://jedistuff22.pythonanywhere.com/products/' + str(userid)).text)
await ctx.send(a)
#bot.command(
name="retrieve",
description="Retrieve a certain product that you own",
scope=scopeid,
)
#interactions.option(
name='product',
description='the product you would like to retrieve',
type=interactions.OptionType.STRING,
required=True
)
async def retrieve(ctx: interactions.CommandContext, product: str):
a = ctx.author.user.username + '#' + ctx.author.user.discriminator
print(a)
await ctx.send(a)
bot.start()

Changing defaults in discord.py select view options

Im currently making a help command which should show the default embed which is the fun commands page, and having a select list with the default as "Fun commands". What I want to happen is so that when I choose the select list option as "General commands", it will edit the message with the general commands embed and the same select list but with the default set to "General commands instead of "Fun commands".
if message.content.startswith('c!help'):
select = Select(
placeholder = "Choose a category",
options=[
discord.SelectOption(
label="Fun commands",
description="j"),
discord.SelectOption(
label="General commands",
description="j3")
])
view = View()
view.add_item(select)
await message.channel.send(embed=fhelp,view=view)
async def call_back(interaction):
mainthing = select.values[0]
if mainthing == "Fun commands":
await interaction.response.edit_message(embed=fhelp,view=view)
elif mainthing == "General commands":
await interaction.response.edit_message(embed=ghelp,view=view)
select.callback = call_back

How to save input from a dbc.Modal and write to mySql database in Python Dash app?

I have created a button when clicked will pop up a dash boostrap component, dbc.Modal with a couple input fields. Now I'm wondering how can I save this input information and write to a database like MySQL.
I know how to read/write to a DB in the context of a regular html button in Dash with a callback function that triggers when button clicked. But lost on how to use with a modal and callback. I couldn't find any examples online and documentations don't show how to use a modal to save input data and connect with external DB. Please advise. Below is a segment of my code:
Edited Code:
dbc.Modal(
[
dbc.ModalHeader("Add Favorite Faculty"),
dbc.ModalBody(
dbc.Form(
[
dbc.Row(
[
dbc.Label("Enter Name:", id="fav-name"),
dbc.Input(type="text", placeholder="name")
]
),
html.Br(),
dbc.Row(
[
dbc.Label("Enter Email:", id="fav-email"),
dbc.Input(type="email", placeholder="email")
],
),
html.Br(),
dbc.Row(
[
dbc.Label("Enter Comment:", id="fav-comment"),
dbc.Input(type="text", placeholder="comment")
],
),
html.Br(),
dbc.Button("Submit", id='fav-submit', color="primary"),
],
)
),
dbc.ModalFooter(
dbc.Button("Close", color="secondary", id="close-add-faculty", className="ml-auto")
),
],
id="fav-modal",
is_open=False,
And my basic callback function that just opens and closes the modal. Essentially I want to save the input fields when a submit button is clicked and write to a databse.
# prompt modal
#app.callback(
Output("fav-modal", "is_open"),
[
Input("add-faculty-button", "n_clicks"),
Input("close-add-faculty", "n_clicks")
],
[State("fav-modal", "is_open")]
)
def toggle_modal(n1, n2, is_open):
if n1 or n2:
return not is_open
return is_open
# write to DB
#app.callback(
Output("written", "children"),
[
State("fav-name", "value"),
State("fav-email", "value"),
State("fav-comment", "value")
],
[
Input("fav-submit", "n_clicks")
]
)
def write_to_db(n_clicks, name, email, comment):
if n_clicks is not None:
....
An image of what it looks like:
The fact that these inputs are inside a modal doesn't matter in this case. You can still set up callbacks using those component IDs to get the value of each input, and then use that to pass along to your database.
You may want to consider adding a "submit" button or something like that, which would serve as the trigger for your callback, and take these inputs as State. Otherwise, your callback would fire every time the input updates, and you would likely send lots of unwanted values to your database.
Edit:
You need to add IDs to all the dbc.Input components and the submit button in order to hook them up to the callback. Here's an example.
# layout code
dbc.Input(type="text", placeholder="name", id='name-input')
# end of layout
#app.callback(
Output("some-notification-contaienr", "children"),
[
Input("submit-button", "n_clicks"),
],
[
State("name-input", "value"),
State("email-input", "value"),
State("comment-input", "value"),
]
)
def toggle_modal(submit_clicks, name, email, comment):
if not submit_clicks:
raise PreventUpdate
# some code here to send your stuff to the database
# ...
return 'Successfully submitted.'

How to get different cache_times from different inline queries?

just started to use the python-telegram-bot library and I made my own bot using their examples and documentations, but still can't get my bot to do something that should be rather simple, which is to have different cache_times for different inline queries. This is the involved code:
def inline_opt(update, context):
results = [
InlineQueryResultArticle(
id=uuid4(),
title = "QUERY1",
input_message_content = InputTextMessageContent(
"blah blah")),
InlineQueryResultArticle(
id=uuid4(),
title = "QUERY2",
input_message_content = InputTextMessageContent(
"Blah blah "))
]
update.inline_query.answer(results, cache_time=0)
It works fine, except that I want the first query to have a cache_time of 0 seconds and the other one to have a cache_time of x seconds. Sorry if it's a dumb question but couldn't get an answer on the doc or in the telegram group.
cache_time is a parameter of inline_query.answer() which means you need to filter the queries you receive to create a tailored answer with its particular cache_time.
import time
def inlinequery(update, context):
query = update.inline_query.query
if query=="time":
results = [
InlineQueryResultArticle(
id=uuid4(),
title="time",
input_message_content=InputTextMessageContent(
"time({!s}): {!s}".format(query,time.asctime(time.localtime()))))
]
seconds = 1;
update.inline_query.answer(results,cache_time=seconds)
elif query=="hora":
results = [
InlineQueryResultArticle(
id=uuid4(),
title="hora",
input_message_content=InputTextMessageContent(
"Time({!s}): {!s}".format(query,time.asctime(time.localtime()))))
]
seconds = 60;
update.inline_query.answer(results,cache_time=seconds)

accessing Kafka metadata with Python KafkaConsumer

I have a simple Kafka reader class. I really don't remember where I got this code. Could have found it, or my previous self may have created it from various examples. Either way, it allows me to quickly read a kafka topic.
class KafkaStreamReader():
def __init__(self, schema_name, topic, server_list):
self.schema = get_schema(schema_name)
self.topic = topic
self.server_list = server_list
self.consumer = KafkaConsumer(topic, bootstrap_servers=server_list,
auto_offset_reset = 'latest',
security_protocol="PLAINTEXT")
def decode(self, msg, schema):
parsed_schema = avro.schema.parse(schema)
bytes_reader = io.BytesIO(msg)
decoder = avro.io.BinaryDecoder(bytes_reader)
reader = avro.io.DatumReader(parsed_schema)
record = reader.read(decoder)
return record
def fetch_msg(self):
event = next(self.consumer).value
record = self.decode(event, self.schema)
return record
To use it, I instantiate an object and loop forever reading data such as this:
consumer = KafkaStreamReader(schema, topic, server_list)
while True:
message = consumer.fetch_msg()
print message
I'm sure there are better solutions, but this works for me.
What I want to get out of this, is the meta data on the Kafka record. A coworker in another group used Java or Node and was able to see the following information on the record.
{
topic: 'clickstream-v2.origin.test',
value:
{
schema:payload_data/jsonschema/1-0-3',
data: [ [Object] ] },
offset: 16,
partition: 0,
highWaterOffset: 17,
key: null,
timestamp: 2018-07-25T17:01:36.959Z
}
}
I want to access the timestamp field using the Python KafkaConsumer.
I have a solution. If I change the fetch_msg method I can figure out how to access it.
def fetch_msg(self):
event = next(self.consumer)
timestamp = event.timestamp
record = self.decode(event.value, self.schema)
return record, timestamp
Not the most elegant solution as I personally don't like methods that return multiple values. However, it illustrates how to access the event data that I was after. I can work on more elegant solutions

Categories

Resources