Python Telegram Bot ConversationHandler not working with webhook - telegram-bot

I want to make a ConversationHandler in my bot that is using a webhook, the ConversationHandler only runs the function at the entry point, after that neither does it run the state function, nor does it run the fallback function. This CommandHandler runs fine when bot is run by polling.
ConversationHandler:
conv_handler = ConversationHandler(
entry_points=[CommandHandler("start", start)],
states={
NEW_PROJECT: [CallbackQueryHandler(project_name)],
PROJECT_NAME: [MessageHandler(Filters.regex(".*"), store_name_maybe_project_type)],
PROJECT_TYPE: [CallbackQueryHandler(store_type_maybe_admin)]
},
fallbacks=[CommandHandler('cancel', cancel)],
)
All the required functions:
def start(update, context):
# Gives button of add project
# We can use for loop to display buttons
keyboard = [
[InlineKeyboardButton("Add Project", callback_data="add_project")],
]
reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text("You have no projects right now.", reply_markup=reply_markup)
# if existing project then PROJECT or else NEW_PROJECT
return NEW_PROJECT
def project_name(update, context):
# asks for project name
query = update.callback_query
update.message.reply_text(text="Okay, Please enter your project name:")
return PROJECT_NAME
def store_name_maybe_project_type(update, context):
# stores project name and conditionally asks for project type
print(update.message.text)
keyboard = [
[InlineKeyboardButton("Telegram Group", callback_data="group")],
[InlineKeyboardButton("Telegram Channel", callback_data="channel")]
]
reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text("What do you want to make?", reply_markup=reply_markup)
return PROJECT_TYPE
def store_type_maybe_admin(update, context):
# stores project type and conditonally asks for making admin
print(update.message.text)
keyboard = [[InlineKeyboardButton("Done", callback_data="done")]]
reply_markup = InlineKeyboardMarkup(keyboard)
update.message.reply_text(f"Make a private {update.message.text} and make this bot the admin", reply_markup=reply_markup)
return ConversationHandler.END
def cancel(update, context):
update.message.reply_text("Awww, that's too bad")
return ConversationHandler.END
This is how I set up the webhook(I think the problem is here somewhere):
#app.route(f"/{TOKEN}", methods=["POST"])
def respond():
"""Run the bot."""
update = telegram.Update.de_json(request.get_json(force=True), bot)
dispatcher = setup(bot, update)
dispatcher.process_update(update)
return "ok"
The setup function
def setup(bot, update):
# Create bot, update queue and dispatcher instances
dispatcher = Dispatcher(bot, None, workers=0)
##### Register handlers here #####
bot_handlers = initialize_bot(update)
for handler in bot_handlers:
dispatcher.add_handler(handler)
return dispatcher
And then I manually setup the webhook by using this route:
#app.route("/setwebhook", methods=["GET", "POST"])
def set_webhook():
s = bot.setWebhook(f"{URL}{TOKEN}")
if s:
return "webhook setup ok"
else:
return "webhook setup failed"
The add project button doesn't do anything.

ConversationHandler stores the current state in memory, so it's lost once the conv_handler reaches the end of it's lifetime (i.e. the variable is deleted or the process is shut down). Now your snippets don't show where you initialize the ConversationHandler, but I have the feeling that you create it anew for every incoming update - and every new instance doesn't have the knowledge of the previous one.
I have that feeling, because you create a new Dispatcher for every update as well. That's not necessary and in fact I'd strongly advise against it. Not only does it take time to initialize the Dispatcher, which you could save, but also if you're using chat/user/bot_data, the data get's lost every time you create a new instance.
The initialize_bot function is called in setup, where you create the new Dispatcher, which is why my guess would be that you create a new ConversationHandler for every update. Also it seems odd to me that the return value of that function seems to be dependent on the update - the handlers used by your dispatcher should be fixed ...
Disclaimer: I'm currently the maintainer of python-telegram-bot

Related

is it possible to overwrite parameters in a called feature file, which have been defined in background or scenario?

https://github.com/intuit/karate#calling-other-feature-files
The link above contains an example of calling a feature file in order to reuse the code. The feature file which is reused is called with the inputs
Background:
* configure headers = read('classpath:my-headers.js')
* def signIn = call read('classpath:my-signin.feature') { username:'john', password: 'secret' }
* def authToken = signIn.authToken
The called my-signin.feature:
Scenario:
Given url loginUrlBase
And request { userId: '#(username)', userPass: '#(password)' }
When method post
Then status 200
And def authToken = response
...
In this example the my-signin.feature must be run with the inputs username and password. I know that if you had the following:
Background:
* def username = "foo"
* def password = "secret"
at the top of the my-signing.feature file, the parameters input by the feature attempting to reuse the feature file would be overwritten.
My question is:
If reuse is the main interest of being able to call other feature files, is there a way to have the calling feature file overwrite the username and password parameters if they had been defined in the background?
It seems to me that having the background overwrite the input parameters instead of vice versa makes it harder to reuse *.feature files. I know I found it a little frustrating on my project not being able to reuse tests I had already written without refactoring out the reusable code into another file.
Any called feature in karate will have a magic variable __arg, you can check for this before assigning values to your variables in your called script.
Background:
* def username = (__arg == null) ? "foo" : __arg.username
* def password = (__arg == null)? "secret" : __arg.password
this will check for values passed,
if none passed it will assign default
* def signIn = call read('classpath:my-signin.feature')
if passed with arguments passed arguments will be assigned
* def signIn = call read('classpath:my-signin.feature') { username: 'notfoo', password: 'notsecret' }
For simplicity don't have anyother parameters that need to passed other than this.

POST a list to the API, update or create depending on the existence of that instance

I have a view which allows me to post multiple entries to a model. Currently if I add all new entries, they are added successfully. But if I add any entry for which the pk already exists, it naturally throws a serializer error and nothing gets updated.
I wish to write a method which will let me post multiple entries, but automatically either update an existing one OR add a new one successfully depending the existence of that instance.
The idea of a customized ListSerialzer is the closest thing I came across to achieve this but still not clear if I can do this.
Has anyone ever implemented anything like this ?
In views.py:
def post(self,request,format=None):
data = JSONParser().parse(request)
serializer = PanelSerializer(data=data,many=True)
if serializer.is_valid():
serializer.save()
return JsonResponse({"success":"true","content":serializer.data}, status=201)
return JsonResponse({'success': "false",'errorCode':"1",'errorMessage':serializer.errors}, status=400)
in serializers.py:
class PanelSerializer(serializers.ModelSerializer):
class Meta:
model = Panel
fields = ('pId','pGuid','pName', 'pVoltage', 'pAmperage','pPermission', 'pPresent', 'pSelf','pInfo')
def create(self, validated_data):
logger.info('Information incoming_1!')
print ("Atom")
return Panel.objects.create(**validated_data)
def update(self, instance, validated_data):
instance.pId = validated_data.get('pId', instance.pId)
instance.pGuid = validated_data.get('pId', instance.pGuid)
instance.pName = validated_data.get('pName', instance.pName)
instance.pVoltage = validated_data.get('pVoltage', instance.pVoltage)
instance.pAmperage = validated_data.get('pAmperage', instance.pAmperage)
instance.pPermission = validated_data.get('pPermission', instance.pPermission)
instance.pPresent = validated_data.get('pPresent', instance.pPresent)
instance.pSelf = validated_data.get('pSelf', instance.pSelf)
instance.pInfo = validated_data.get('pInfo', instance.pInfo)
instance.save()
return instance
This is how the code stands as of now. I believe I will mainly need to either work on the update method of my serializer or first change it to a ListSerializer and write some custom logic again in the update method.

Scrapy: How to send data to the pipeline from a custom filter without downloading

To catch all redirection paths, including when the final url was already crawled, I wrote a custom duplicate filter:
import logging
from scrapy.dupefilters import RFPDupeFilter
from seoscraper.items import RedirectionItem
class CustomURLFilter(RFPDupeFilter):
def __init__(self, path=None, debug=False):
super(CustomURLFilter, self).__init__(path, debug)
def request_seen(self, request):
request_seen = super(CustomURLFilter, self).request_seen(request)
if request_seen is True:
item = RedirectionItem()
item['sources'] = [ u for u in request.meta.get('redirect_urls', u'') ]
item['destination'] = request.url
return request_seen
Now, how can I send the RedirectionItem directly to the pipeline?
Is there a way to instantiate the pipeline from the custom filter so that I can send data directly? Or shall I also create a custom scheduler and get the pipeline from there but how?

Ruby on Rails 5 Action Cable: stream for current model instance (URL-based subscriptions)

I have searched the web far and wide (including reading many code examples for ActionCable) for what seems to be an answer to a very basic question. Alas, I have not solved my problem.
Suppose, I have a model Search and I have navigated to the URL /searches/1.
I would also have the search.coffee file under javascripts/channels/ which starts with:
App.instance = App.cable.subscriptions.create 'SearchChannel'
and a SearchChannel class that looks like this:
class SearchChannel < ApplicationCable::Channel
def subscribed
search = Search.find(params[:id])
stream_for search
end
def unsubscribed
end
end
Naturally, the code above produces an error because params[id] is nil.
Here are my questions:
How do I subscribe to the correct Search instance based on the URL?
How do I avoid trying to subscribe to SearchChannel if I am on another URL that doesn't require a subscription, e.g. /searches/1/results?
Thank you for help!
If you look at implementation of ActionCable.Subscriptions#create, it can be called in two ways.
Option 1 (with channelName):
App.cable.subscriptions.create 'SearchChannel'
Option 2 (with channel Object):
App.cable.subscriptions.create channel: 'SearchChannel', id: 'Best Room'
If you go with Option 1, internally, it gets converted into channel Object.
So, if you want to capture id on the server side, you need to use the 2nd option and pass the id of the search, then you should be able to capture it on the server side as you described:
class SearchChannel < ApplicationCable::Channel
def subscribed
search = Search.find(params[:id])
stream_for search
end
def unsubscribed
end
end
Refer to Client-Server Interactions and Subsciptions for more info.
Hello if you need to access the variables from the URL for server side handling of the connections, here is what you can do. In your search.coffee.erb file.
string = window.location.href
cut_string = "/"
if string.indexOf(cut_string) != -1
id = string.split(cut_string)[4]
else
id = // something else
App.instance = App.cable.subscriptions.create { channel: "SearchChannel" , id: id } , ...
In your channel file, you can succesfully use it.
class SearchChannel < ApplicationCable::Channel
def subscribed
search = Search.find(params[:id])
stream_for search
end
def unsubscribed
end
end
How do I subscribe to the correct Search instance based on the URL?
You can change the coffeescript code and make it so that it only creates an actioncable connection if the desired page is browsed.
string = window.location.href
cut_string = "/"
if string.indexOf(cut_string) != -1
id = string.split(cut_string)[4] IF BLOCK CREATE CONNECTION
App.instance = App.cable.subscriptions.create { channel: "SearchChannel" , id: id } , ...
How do I avoid trying to subscribe to SearchChannel if I am on another
URL that doesn't require a subscription, e.g. /searches/1/results
Add multiple conditions to the if block in the coffescript code.

How to test a helper Grok view that makes a redirect

I have a content type that needs to be modified in some way after calling a helper Grok view that checks some condition, makes some changes, sets a message and redirects to the original object.
my helper view only has a render method and I want to write some tests for it but I have no idea how to handle this.
I would like to check for an error message when some condition is not met, and for an info message when everything goes fine.
my code looks like this:
class MyHelperView(grok.View):
grok.context(IMyType)
grok.layer(IMyLayer)
grok.name('helper-view')
grok.require('my.permission')
def render(self):
variable = self.request.form.get('variable', None)
if not variable:
msg = _(u'Required input is missing.')
api.portal.show_message(message=msg, request=self.request, type='error')
else:
do_something()
msg = _(u'Information processed.')
api.portal.show_message(message=msg, request=self.request)
self.request.response.redirect(self.context.absolute_url())
when I call the view obviously I ended with a None object, as the view returns nothing. I don't know where to look for messages... request? response? any hint?
I would avoid using transaction commits in test code. The test framework is specifically designed to roll back the transactions at the end of each test. Your setUp override goes against this.
To check status messages in a unit test you should be able to do something like:
from Products.statusmessages.interfaces import IStatusMessage
IStatusMessage(request).show()
This is an adapter that adapts the request.
I ended up with test with a layer based on FunctionalTesting:
....
from plone.app.testing import TEST_USER_NAME
from plone.app.testing import TEST_USER_PASSWORD
from plone.testing.z2 import Browser
....
import transaction
...
class HelperViewTestCase(unittest.TestCase):
layer = FUNCTIONAL_TESTING
def setUp(self):
self.app = self.layer['app']
self.portal = self.layer['portal']
self.request = self.layer['request']
directlyProvides(self.request, IMyLayer)
with api.env.adopt_roles(['Manager']):
self.foo = api.content.create(self.portal, 'MyType', 'foo')
transaction.commit()
def test_response(self):
browser = Browser(self.app)
browser.handleErrors = False
browser.addHeader(
'Authorization',
'Basic {0}:{1}'.format(TEST_USER_NAME, TEST_USER_PASSWORD)
)
browser.open(self.foo.absolute_url())
browser.getControl('Do Something').click()
self.assertIn(
'Required input is missing.', browser.contents)
two things you need to check that make me spent some time debugging:
you must use transaction.commit() to reflect object creation on the ZODB
you must add an authorization header before trying to open the page
everything else is working.