I am coding an Python Telegram Bot with python-telegram-bot. I created a custom inline menu.
I want that the User could press a button and will get an picture. The send_photo function needs an instance of bot an update.
But I don't know how to pass that on to the CallBackQuery handler.
Does anyone have an idea how to solve it?
The send photo function:
def gvu(bot, update):
bot.send_photo(update.message.chat_id, photo=open('botpic/gvu.jpg', 'rb'))
The Handler in Main Routine:
updater.dispatcher.add_handler(CallbackQueryHandler(pattern="1", callback=gvu))
return self.callback(dispatcher.bot, update, **optional_args)
The error:
TypeError: callback_handler() got an unexpected keyword argument 'chat_data'
This works for me:
buttonsMenu = [
[telegram.InlineKeyboardButton("UP", callback_data="UpVote")],
[telegram.InlineKeyboardButton("DOWN", callback_data="DownVote")],
]
keyboard_markup = telegram.InlineKeyboardMarkup(buttonsMenu)
context.bot.sendPhoto(chat_id=update.message.chat.id, photo=open('./imgpath.jpg'), 'rb'),caption='messageText', reply_markup=keyboard_markup)
This will send an image, with text and 2 butttons below the text msg.
Now for the callback query, i did this in main():
# Callbacks for the msg buttons
dp.add_handler(CallbackQueryHandler(vote, pattern="UpVote"))
dp.add_handler(CallbackQueryHandler(vote, pattern="DownVote"))
Where vote , is a def that runs the code i want for that callback.
hope it makes sense.
Read into documentation here:
https://core.telegram.org/bots/api#inlinequery
https://github.com/python-telegram-bot/python-telegram-bot/blob/master/examples/inlinekeyboard.py
Related
I need to send a user text input to the robot through the integrated tablet, and catch it somehow, for further processing in Choregraphe.
After reading the Aldebaran documentation about ALTabletService API, I found few methods which might be a solution to all this. The methods are ALTabletService::showInputTextDialog and ALTabletService::onInputText, but somehow I can't get them to work: they return absolutely nothing when I input some text through the tablet.
I need access to the string created when the user inputs a piece of text. Any advice how to do it?
i realized this without ALTabletService methods showInputTextDialog or onInputText
My Approach:
I made an html page with an input field and a button to send the input.
On button press I use the forceInput method from ALDialog doc via QiMessaging Javascript library. doc
I can't test it right now but this should help as a inspiration
function forceInput(input) {
QiSession(function(session) {
session.service('ALDialog').then(function(ALDialog) {
ALDialog.forceInput(input);
});
}
}
Now you can send the input to the topic.
This could be sth like "imput_from_tablet blablabla".
And in the Dialog you catch
u:(imput_from_tablet _*) $1
Where $1 should be blablabla.
Hope that helps
best regards
You can create a webpage for the tablet and package it in your application - see the documentation here; then on that webpage you can create a text input field (be careful that the bottom half of the screen will be hidden by the keyboard when the field is selected), and then use the javascript SDK to (for example) raise an ALMemory event with the inputted text value, that you can then get from Choregraphe.
I had exactly the same problem and I found this ALTabletService::onInputText method in the signal list. You can find examples how to use signals on the same page. Based on these examples I created the following script that can get a value from the input field:
import qi
import sys
def main(app):
try:
session = app.session
tabletService = session.service("ALTabletService")
tabletService.showInputTextDialog("Example dialog", "OK", "Cancel")
signal_id = 0
def callback(button_id, input_text):
if button_id == 1:
print "'OK' button is pressed."
print "Input text: " + input_text
if button_id == 0:
print "'Cancel' button is pressed"
tabletService.onInputText.disconnect(signal_id)
app.stop()
# attach the callback function to onJSEvent signal
signal_id = tabletService.onInputText.connect(callback)
print "Signal ID: {}".format(signal_id)
app.run()
except Exception, e:
print "Error was: ", e
if __name__ == "__main__":
ip = "10.0.10.254" # the IP of the robot
port = 9559
try:
connection_url = "tcp://{}:{}".format(ip, port)
app = qi.Application(url=connection_url)
app.start()
except RuntimeError:
print("Can't connect to Naoqi.")
sys.exit(1)
main(app)
I am trying to figure out a way to use progress bar in gdal.Warp() to show how much of a job is done. For progress bar, I am using Tqdm and gdal.Warp() is used to crop image from remote URL
def getSubArea(url):
vsicurl_url = '/vsicurl/' + url
output_file = 'someID_subarea.tif'
gdal.SetConfigOption('GDAL_HTTP_UNSAFESSL', 'YES')
gdal.Warp(output_file, vsicurl_url, dstSRS='EPSG:4326', cutlineDSName='area.geojson', cropToCutline=True)
I know there is callback argument that reports progress from 0 to 1, but its only called after gdal.warp has finished downloading cropped image.
You may add a callback function for progress through the 'kwargs' parameter in 'gdal.Warp' (documentation: https://gdal.org/python/).
Code:
def getSubArea(url):
vsicurl_url = '/vsicurl/' + url
output_file = 'someID_subarea.tif'
# Data you want to pass to your callback (goes in to unknown parameter)
es_obj = { ... }
kwargs = {
'dstSRS': 'EPSG:4326',
'cutlineDSName': 'area.geojson',
'cropToCutline': True,
'callback': progress_callback,
'callback_data': es_obj
}
gdal.SetConfigOption('GDAL_HTTP_UNSAFESSL', 'YES')
gdal.Warp(output_file, vsicurl_url, **kwargs)
def progress_callback(self, complete, message, unknown):
# Calculate percent by integer values (1, 2, ..., 100)
percent = floor(complete * 100)
# Code for saving or using percent value
...
About progress callback: https://gdal.org/api/cpl.html#_CPPv416GDALProgressFunc
See answer here around native gdal callback function.
If you wanted to report the download progress of the raster, you'd likely need to split that out as a separate step using something like requests and wrapping that with a progress bar like tqdm or progressbar2.
I am very much new to drupal 8, I am trying to alter a form under custom content type "Question". The form has an id "node-question-form".
I have setup a module and trying to add hook_FORM_ID_alter() but its never getting called. Even the simplest hook is not working. eg:
function constellator_form_alter(&$form, &$form_state, $form_id){
echo "alter the form"; exit;
}
where 'constellator' is the module name.
I have been stuck with since morning and nothing is working for me, Any help will be greatly appriciated.
Cheers
hook_form_alter() and hook_FORM_ID_alter() are called while a form is being created and before displaying it on the screen.
These functions are written in the .module file.
Always clear the cache after writing any hook function. This makes Drupal understand that such a function has been declared.
Use drush cr if using drush version 8 else click on Manage->Drupal 8 logo->Flush all Caches to clear the caches.
Now you can check if the function is being called or not.
The best way to check that is to install the Devel module, enable it. Along with Devel, Kint is installed. Enable Kint too from the Admin UI.
After doing that,you can check whether the hook is being called or not in the following way:
function constellator_form_alter(&$form, &$form_state, $form_id){
kint($form);
}
This will print all the form variables for all forms in the page.
If you want to target a particular form in the page, for eg. you form, node-question-form, type:
function node_question_form_form_alter(&$form, &$form_state, $form_id){
kint($form);
}
Using echo, the way you did, you can confirm whether the function is being called or not, without any hassle, by viewing the Source Code for the page and then searching for the text that you have echoed, using some search option of browser, like, Ctrl+f in case of Google Chrome.
If you want to change sorting options and/or direction (ASC / DESC), you can use this example (tested with Drupal 9).
Here I force the sorting direction according to the "sort by option" set by the user in an exposed filter. (if the user want to sort by relevance, we set the order to ASC, if the user want to sort by date, we set the order to DESC to have the latest content first).
/**
* Force sorting direction for search by date
*
*/
function MYTHEME_form_alter(&$form, &$form_state, $form_id)
{
if (!$form_id == 'views_exposed_form"' || !$form['#id'] == 'views-exposed-form-search-custom-page-1') {
return;
}
$user_input = $form_state->getUserInput();
if (empty($user_input['sort_by'])) {
return;
}
if ($user_input['sort_by'] == 'relevance') {
$user_input['sort_order'] = 'ASC';
} elseif ($user_input['sort_by'] == 'created') {
$user_input['sort_order'] = 'DESC';
}
$form_state->setUserInput($user_input);
}
Note that "views-exposed-form-search-custom-page-1" is the id of my form,
"relevance" and "created" are the sort field identifier set in drupal admin.
function hook_form_alter(&$form, \Drupal\Core\Form\FormStateInterface &$form_state, $form_id) {
echo 'inside form alter';
}
I have a Web Form for Marketer set up done for one of my Pages.
I have Custom Submit Action written for it as shown below in the code snippet -
public class **CustomFormSubmit : ISaveAction**
{
public void Execute(ID formid, AdaptedResultList fields, params object[] data)
{
try
{
**//var returnValue= Custom Logic to save form data // returns true or false**
}
catch (Exception ex)
{
Logger.Log(ex.Message + ":" + builder, ExceptionCategory.Error);
throw;
}
}
In my above Web form - Success Mode is - SuccessMode/Redirect and I have a success Page configured for it.
My requirement in above scenario is to keep user on the same Page(with Form) if returnValue is false . (as shown in above code snippet)
Can anyone Please guide me in the above scenario as - how to keep user on the same Page with values filled in the form so that user can submit it again.
Product Details - 7.2 rev. 141226 , Web Forms for Marketers 2.4 rev.140117
To add further details -
I am not sure how can I go back to my page instead of the redirection in case if return is false in the above code snippet.
As soon the submit button is clicked the above function- Execute- gets called.
How do I go back to the Page - Do I need to override any function or customize something.
If any exception comes while saving data- then the control goes back to the same Page with all values filled by user retained -with the Save Action Failed Message which is configured in Sitecore .
So my requirement will be to go to to the form as happening in case of Exception when false comes as return value while saving data and to put customised Error Messages which might change each time, so not statically configured ,rather a dynamic one.
Thanks!
Saurabh
One option will be to redirect to the original page with the Form on.
Enable your form to populate the fields via Query String using the ReadQueryString property, via Presentation Details of the Form Renderer:
So on false of your Save Action you create a collection of query strings with the name of each Field, as it appears in the Form, followed by the User's value.
The code below will loop through all your fields and arrange them into a QueryString with its FieldName and Value;
string urlOfForm = HttpContext.Current.Request.Url.AbsolutePath;
var queryString = new StringBuilder("?");
foreach (AdaptedControlResult field in fields)
{
queryString.Append(string.Format("{0}={1}&", field.FieldName, field.Value));
}
urlOfForm = urlOfForm + queryString;
HttpContext.Current.Response.Redirect(urlOfForm);
Sitecore will then automatically populate the appropriate fields with the values, achieving your requirement.
EDIT
I have found that most Exceptions thrown will take the user back to the Form with their values populated. You can then pass in the cause of the failure to write to your CRM. See below for Example
if (submitFailed)
{
throw new Exception("The email address entered already exists in our System");
}
The complexity then comes in dynamically swapping out the Save Action Failed Message to show this Exception Message. All posts I find about custom Save Action Message state the only real approach is to redirect via your Custom Save Action to a different page showing a different message. Which is not suitable to your requirements.
I have found the pipeline Args you are going to need to patch FormSubmitFailedArgs and SubmitFailedArgs. The Former will need the following change
public FormSubmitFailedArgs(ID formID, AdaptedResultList fields, ID actionFailed, Exception ex)
: base(formID, actionFailed, ex)
{
this.Fields = fields;
this.ErrorMessage = ex.Message;
}
and the Latter will need
public SubmitFailedArgs(ID formID, ID actionFailed, string errorMessage, Exception innerException)
{
this.FormID = formID;
this.ActionFailed = actionFailed;
this.ErrorMessage = innerException.Message;
this.InnerException = innerException;
}
Location and Styling of Submit Message:
You need to find the FormRender sublayout file, this is defaulted to website\sitecore modules\Web\Web Forms for Marketers\Control\SitecoreSimpleFormAscx.ascx inside there you will find a compont called SubmitSummary this renders out the submit message so move it to where you require.
Also note it references the CssClass scfSubmitSummary this is what you will need to target to change the styling of the Message. This Answer is already REALLY long so I won't give a blow by blow how to change the styling of that class, see here for example - http://www.awareweb.com/awareblog/10-1-13-wffmguide
Pipeline Patching
I've dug in deeper, in order to use the custom Args we created for using the exception error message you will need to control the Pipeline which ultimately uses those Args, this is the processor Sitecore.Form.Core.Pipelines.FormSubmit.FormatMessage, Sitecore.Forms.Core in the <errorSubmit> Pipeline.
From my investigation it shouldn't take much effort then its a matter of patching it, you can modify if the Sitecore.Forms.config directly or use patch:instead from a config file within the App_Config/Includes folder - see here for more info.
One option would be to create a Custom Form Verification Action. You could save the data here, although it would be better to verify the data against your API here and then save the data in custom save action, simply since this seems more logical as to how WFFM was meant to function.
using Sitecore.Data;
using Sitecore.Form.Core.Controls.Data;
using Sitecore.Form.Core.Submit;
using System;
using System.Collections.Generic;
namespace Custom.WFFM
{
public class CustomVerificationStep : BaseCheckAction
{
public string FailedMessage { get; set; }
public override void Execute(ID formid, IEnumerable<ControlResult> fields)
{
// Call your API
// You have access to the fields, so you can pass them through as parameters to your if needed
bool flag = ServiceAPI.ValidateUserData(param1, param2, etc);
if (!flag)
{
throw new Exception(string.Format(this.FailedMessage ?? "There was an error while verifying the data against the service call"));
}
}
public override ActionState QueryState(ActionContext context)
{
return ActionState.DisabledSingleCall;
}
}
}
Create the corresponding Verification Action under /sitecore/system/Modules/Web Forms for Marketers/Settings/Actions/Form Verification:
You can change the error message by setting it in the Parameters field as <FailedMessage>Custom Failed Error Message</FailedMessage>.
And then add your verification step to your form:
If you need a different error message per form then you can set the error message to display from the Error Messages tab.
The user will then be returned to the same without any of the save actions being called and the form fields still filled in.
I'm using formidable to receive a file upload with node.js. I send some fields together with a file in a multipart request.
As soon as certain fields arrived, I'm able to validate the authenticity of the request for instance, and I would like to abort the whole request if this is not correct to avoid waisting resources.
I have not found a right way to abort the incoming request. I tried to use req.connection.destroy(); as follow:
form
.on('field', function(field, value) {
fields[field] = value;
if (!fields['token'] || !fields['id'] || !fields['timestamp']) {
return;
}
if (!validateToken(fields['token'], fields['id'], fields['timestamp'])) {
res.writeHead(401, {'Content-Type' : 'text/plain' });
res.end('Unauthorized');
req.connection.destroy();
}
})
However, this triggers the following error:
events.js:45
throw arguments[1]; // Unhandled 'error' event
^
Error: Cannot resume() closed Socket.
at Socket.resume (net.js:764:11)
at IncomingMessage.resume (http.js:254:15)
at IncomingForm.resume (node_modules/formidable/lib/incoming_form.js:52:11)
at node_modules/formidable/lib/incoming_form.js:181:12
at node_modules/formidable/lib/file.js:51:5
at fs.js:1048:7
at wrapper (fs.js:295:17)
I also tried req.connection.end() but the file keeps uploading.
Any thoughts? Thanks in advance!
The problem is that formidable didn't understand that you want it to stop. Try this:
req.connection.destroy();
req.connection.resume = function(){};
Of course, this is a somewhat ugly workaround, I'd open an issue on github.