PyTelegramBotApi next_step_handler without constant polling - python

I wanted to make a Telegram "quick response" function for my application, so that I would be able to call, for example
answer = get_answer(question, user_id) and the bot will send the question to the person and return their answer. I know that this is done using the next_step_handler in PyTelegramBotAPI, but I don't have and don't want to have bot.polling() enabled, because I want the bot to be active only when the function is called. Is there a way to do that?
I was thinking of something maybe like that, but this obviously doesn't work, sp consider it a pseudo code:
def get_answer(question, user_id):
msg = bot.send_message(user_id, question)
bot.polling(none_stop=True, interval=0)
return bot.register_next_step_handler(msg, __handle_answer__)
def __handle_answer__(msg):
bot.stop_polling()
return msg.text
Just to be clear, I don't mind having polling enabled, as long as it is disabled after the person answers the question.

Related

Detect when an (IRC) thread ends which otherwise is silent

I'm attempting to use the python Twitch-Python library for a simple Twitch bot to connect to chat and respond to messages. The bot works as expected, but about 1/6 times the bot fails to connect and the program simply ends. As explained on this error page, it seems to be because the thread ends and doesn't return anything at all https://github.com/PetterKraabol/Twitch-Python/issues/45
I have moved the Chat class into my own code, and have the following:
def __init__(self, channel: str, nickname: str, oauth: str, helix: Optional['twitch.Helix'] = None):
super().__init__()
self.helix: Optional['twitch.Helix'] = helix
self.irc = tc.IRC(nickname, password=oauth)
self.irc.incoming.subscribe(self._message_handler)
self.irc.start()
self.channel = channel.lstrip('#')
self.joined: bool = False
print(self.irc.is_alive())
The class is called with two different functions elsewhere, like so:
def handle_message(self, message : twitch.chat.Message) -> None:
print(message.sender, message.text)
def main(self):
ircConn = TCH(channel='#CHANNEL NAME", nickname="BOT NAME", oauth="SAMPLEOAUTH")
ircConn.subscribe(self.handle_message)
The issue is that about 1 in 6 times, the subscribe() doesn't actually do anything and the program ends. I'd like to find a way to detect when it fails, so that I may attempt a retry, but nothing I've found works. The function/class doesn't return anything, adding an on_error or on_completed argument to the subscribe() doesn't call anything, and using sys.excepthook also doesn't catch it failing.
Additionally, the IRC thread always prints True for irc.is_alive(), even when it fails afterwards.
How can I catch the thread failing? Thank you.

How to restart a coroutine after a websocket stream stops receiving data?

I'm writing an asyncio application to monitor prices of crypto markets and trade/order events, but for an unknown reason some streams stop receiving data after few hours. I'm not familiar with the asyncio package and I would appreciate help in finding a solution.
Basically, the code below establishs websocket connections with a crypto exchange to listen streams of six symbols (ETH/USD, BTC/USD, BNB/USD,...) and trades events from two accounts (user1, user2). The application uses the library ccxtpro. The public method watch_ohlcv get price steams, while private methods watchMyTrades and watchOrders get new orders and trades events at account level.
The problem is that one or several streams are interrupted after few hours, and the object response get empty or None. I would like to detect and restart these streams after they stops working, how can I do that ?
# tasks.py
#app.task(bind=True, name='Start websocket loops')
def start_ws_loops(self):
ws_loops()
# methods.py
def ws_loops():
async def method_loop(client, exid, wallet, method, private, args):
exchange = Exchange.objects.get(exid=exid)
if private:
account = args['account']
else:
symbol = args['symbol']
while True:
try:
if private:
response = await getattr(client, method)()
if method == 'watchMyTrades':
do_stuff(response)
elif method == 'watchOrders':
do_stuff(response)
else:
response = await getattr(client, method)(**args)
if method == 'watch_ohlcv':
do_stuff(response)
# await asyncio.sleep(3)
except Exception as e:
print(str(e))
break
await client.close()
async def clients_loop(loop, dic):
exid = dic['exid']
wallet = dic['wallet']
method = dic['method']
private = dic['private']
args = dic['args']
exchange = Exchange.objects.get(exid=exid)
parameters = {'enableRateLimit': True, 'asyncio_loop': loop, 'newUpdates': True}
if private:
log.info('Initialize private instance')
account = args['account']
client = exchange.get_ccxt_client_pro(parameters, wallet=wallet, account=account)
else:
log.info('Initialize public instance')
client = exchange.get_ccxt_client_pro(parameters, wallet=wallet)
mloop = method_loop(client, exid, wallet, method, private, args)
await gather(mloop)
await client.close()
async def main(loop):
lst = []
private = ['watchMyTrades', 'watchOrders']
public = ['watch_ohlcv']
for exid in ['binance']:
for wallet in ['spot', 'future']:
# Private
for method in private:
for account in ['user1', 'user2']:
lst.append(dict(exid=exid,
wallet=wallet,
method=method,
private=True,
args=dict(account=account)
))
# Public
for method in public:
for symbol in ['ETH/USD', 'BTC/USD', 'BNB/USD']:
lst.append(dict(exid=exid,
wallet=wallet,
method=method,
private=False,
args=dict(symbol=symbol,
timeframe='5m',
limit=1
)
))
loops = [clients_loop(loop, dic) for dic in lst]
await gather(*loops)
loop = asyncio.new_event_loop()
loop.run_until_complete(main(loop))
let me share with you my experience since I am dealing with the same problem.
CCXT is not expected to get stalled streams after some time running it.
Unfortunately practice and theory are different and error 1006 happens quite often. I am using Binance, OKX, Bitmex and BTSE ( BTSE is not supported by CCXT) and my code runs on AWS server so I should not have any connection issue. Binance and OKX are the worst as far as error 1006 is concerned.. Honestly, after researching it on google, I have only understood 1006 is a NetworkError and I know CCXT tries to resubscribe the channel automatically. All other explanations I found online did not convince me. If somebody could give me more info about this error I would appreciate it.
In any case, every time an exception is raised, I put it in an exception_list as a dictionary containing info like time in mls, method, exchange, description ecc. The exception_list is then passed to a handle_exception method. In this case, if the list contains two 1006 exception within X time handle_exception returns we are not on sync with market data and trading must stop. I cancel all my limit order and I emit a beep ( calling human intervention).
As for your second question:
restart these streams after they stops working, how can I do that
remember that you are Running Tasks Concurrently
If return_exceptions is False (default), the first raised exception is
immediately propagated to the task that awaits on gather(). Other
awaitables in the aws sequence won’t be cancelled and will continue to
run.
here you can find info about restarting individual task in a a gather()
In your case, since you are using a single exchange (Binance) and unsubscribe is not implemented in CCXT, you will have to close the connection and restart all the task. You can still use the above example in the link for automating it. In case you are using more then one exchange you can design your code in a way that let you close and restart only the Exchange that failed.
Another option for you would be defining the tasks with more granularity in the main so that every task is related to a single and well defined exchange/user/method/symbol and every task subscribes a single channel. This will result in a more verbose and less elegant code but it will help you catching the exception and eventually restart only a specific coroutine.
I am obviously assuming that after error 1006 the channel status is unsubscribed
final thought:
never leave a robot unattended
Professional market makers with a team of engineers working in London do not go to the pub while their algos ( usually co-located within the exchange ) execute thousands of trades.
I hope this can help you or, at least, get you in the right directions for handling exceptions and restart tasks
You need to use callbacks.
For example:
ws = self.ws = await websockets.connect(END_POINTS, compression=None) # step 1
await self.ws.send(SEND_YOUR_SUBSCRIPTION_MESSAGES) # step 2
while True:
response = await self.ws.recv()
if response:
await handler(response)
In the last like await handler(response) you are sending the response to the handler().
This handler() is the callback, it is the function that actually consumes your data that you receive from the exchange server.
In this handler(), what you can do is you check if the response is your desired data (bid/ask price etc) or it throws an exception like ConnectionClosedError, in which case you restart the websocket by doing STEP 1 and STEP 2 from within your handler.
So basically in the callback method, you need to either process the data
or restart the websocket and pass the handler to it again to receive the responses.
Hope this helps. I could not share the complete code as i need to clean it for sensitive business logic.

How to create a cyclic thread that can be started/stopped at any time via python-telegram-bot command?

I'm writing a Telegram bot (with python-telegram-bot) that based on a command, cyclically sends messages to the user every hour.
I want to start/stop this using bot commands, adding command handlers like /start_cycle and /stop_cycle. To clarify, this is what I have in mind:
def start_cycle()
# start in some way send_hourly_message()
def stop_cycle()
# stop in some way send_hourly_message()
def main():
"""Entrypoint of the bot"""
# Create updater and get dispatcher
updater = Updater(...)
dp = updater.dispatcher
# Add command handlers
dp.add_handler(CommandHandler("start_cycle", start_cycle))
dp.add_handler(CommandHandler("stop_cycle", stop_cycle))
# Start the bot until interrupt
updater.start_polling(timeout=3)
updater.idle()
The thing that puzzles me is that for how the Telegram library is conceived, there is already an event-based logic, started by updater.start_polling() and updater.idle(). I didn't find any documentation/specific information on how to make this work properly with triggerable time-based events.
What would be in your opinion the best way to do what I have in mind? I looked into asyncio a little but maybe is too complex for what I actually need?
Thanks in advance for any suggestion!
Thanks to #GaganTK I was able to find what i need:
def start_notify(update, context):
new_job = context.job_queue.run_repeating(my_callback, interval=3, first=0, name="my_job")
def stop_notify(update, context):
job = context.job_queue.get_jobs_by_name("my_job")
job[0].schedule_removal()
def my_callback(context: telegram.ext.CallbackContext):
print(datetime.datetime.now())

Extending the bot in python's IRC library

I am attempting to extend the example bot that comes with this IRC library here. I've repasted the code of said bot here.
My problem is that I don't quite see what needs to be modified in order to enable the bot to respond to events, like getting a message - there's no event dispatcher that I can see.
What I can do is
bot = irc.bot.SingleServerIRCBot(server_list = [('irc.whatever.net.', 6667)],realname = 'irclibbot',nickname = 'irclibbot',)
bot.start()
and it runs fine - connects to the network and all that, but it doesn't do anything. Doesn't even respond to the basic CTCP events like VERSION and PING.
How does this work?
Check out this example of what you need to do.
class TestBot(irc.bot.SingleServerIRCBot):
def __init__(self, channel, nickname, server, port=6667):
irc.bot.SingleServerIRCBot.__init__(self, [(server, port)], nickname, nickname)
def on_nicknameinuse(self, c, e):
c.nick(c.get_nickname() + "_")
def on_welcome(self, c, e):
c.join(self.channel)
def on_privmsg(self, c, e):
self.do_command(e, e.arguments[0])
Define your own class that inherits from the actual irc.bot.SingleServerIRCBot class. Then, the events will automatically be bound to the methods named on_'event' like on_privmsg, on_part, etc.
Here you can find the reference of the supported events.

Wait for specific server response command code in IRC

I've made an IRC bot in Python and I've been trying to figure out a way to wait for an IRC command and return the message to a calling function for a while now. I refuse to use an external library for various reasons including I'm trying to learn to make these things from scratch. Also, I've been sifting through documentation for existing ones and they're way too comprehensive. I'me trying to make a simple one.
For example:
def who(bot, nick):
bot.send('WHO %s' % nick)
response = ResponseWaiter('352') # 352 - RPL_WHOREPLY
return response.msg
Would return a an object of my Message class that parses IRC messages to the calling function:
def check_host(bot, nick, host):
who_info = who(bot, nick)
if who_info.host == host:
return True
return False
I have looked at the reactor pattern, observer pattern, and have tried implementing a hundred different event system designs for this to no avail. I'm completely lost.
Please either provide a solution or point me in the right direction. There's got to be a simple way to do this.
So what I've done is use grab messages from my generator (a bot method) from the bot's who method. The generator looks like this:
def msg_generator(self):
''' Provides messages until bot dies '''
while self.alive:
for msg in self.irc.recv(self.buffer).split(('\r\n').encode()):
if len(msg) > 3:
try: yield Message(msg.decode())
except Exception as e:
self.log('%s %s\n' % (except_str, str(e)))
And now the bot's who method looks like this:
def who(self, nick):
self.send('WHO %s' % nick)
for msg in self.msg_generator():
if msg.command == '352':
return msg
However, it's now taking control of the messages, so I need some way of relinquishing the messages I'm not using for the who method to their appropriate handlers.
My bot generally handles all messages with this:
def handle(self):
for msg in self.msg_generator():
self.log('◀ %s' % (msg))
SpaghettiHandler(self, msg)
So any message that my SpaghettiHandler would be handling is not handled while the bot's who method uses the generator to receive messages.
It's working.. and works fast enough that it's hard to lose a message. But if my bot were to be taking many commands at the same time, this could become a problem. I'm pretty sure I'll find a solution in this direction, but I didn't create this as the answer because I'm not sure it's a good way, even when I have it set to relinquish messages that don't pertain to the listener.

Categories