Facebook chat bot sending same message multiple times (Python) - python

I am working on a facebook mini-chat bot and I am encountering a problem which consists on the bot to receive the same message over and over even though it has already answered the message.
it keeps receiving the same text from FB and replying to it over and over
def message_handler(request):
data = json.loads(request.body.decode('utf-8'))
if data and data['object'] == 'page':
for pageEntry in data['entry']:
print "nombre de message", len(pageEntry['messaging'])
for messagingEvent in pageEntry['messaging']:
if messagingEvent.get('optin'):
print "optin", messagingEvent
receivedAuthentication(messagingEvent)
elif messagingEvent.get('message'):
print "message", messagingEvent
receivedMessage(messagingEvent)
elif messagingEvent.get('delivery'):
print "delivery", messagingEvent
receivedDeliveryConfirmation(messagingEvent)
elif messagingEvent.get('postback'):
print "postback", messagingEvent
receivedPostback(messagingEvent)
else:
print "UnHandled"
return HttpResponse(status=200)
def receivedMessage(event):
senderID = event.get('sender').get('id')
message = event.get('message')
messageText = message.get('text')
messageAttachments = message.get('attachments')
if messageText:
if messageText == 'image':
sendImageMessage(senderID)
elif messageText == 'button':
sendButtonMessage(senderID)
elif messageText == 'generic':
sendGenericMessage(senderID)
elif messageText == 'receipt':
sendReceiptMessage(senderID)
elif messageText == 'hey':
sendTextMessage(senderID, "Get it. Gimme a moment to process it :). Will get back to you in a moment")
send_seen()
send_typing()
words = words_gen()
sendTextMessage(senderID, words)
def callSendAPI(messageData):
requests.post(
url='https://graph.facebook.com/v2.6/me/messages?access_token=' + config.page_token,
data=json.dumps(messageData),
headers={"Content-Type":"application/json"}
)
I get that I need to send a status 200 every time, which I did but still receiving the same text over and over
Here are the events I am subscribed to
conversations, message_deliveries, message_reads, messages, messaging_optins, messaging_postbacks, picture
I removed messaging_echoes because I thought it was the problem turned out to not

I have resolved this issue by writing a function and checking duplicate messages in my Web API service.
Here I am generating message unique id either by payload or message received from Facebook which user clicks or types and then comparing with earlier stored unique value from concurrent dictionary.
_messageUniqueKeysBySender is ConcurrentDictionary and I am caching values by Sender Id for 30 minutes.
private bool IsDuplicate(Messaging messaging)
{
var messageUniqueId = string.Empty;
var messageMessaging = messaging as MessageMessaging;
if (messageMessaging != null)
messageUniqueId = messageMessaging.Message.Id + messageMessaging.Message.SequenceNumber;
else if (messaging is PostbackMessaging)
messageUniqueId = ((PostbackMessaging)messaging).Postback.Payload +
((PostbackMessaging)messaging).TimestampUnix;
if (string.IsNullOrEmpty(messageUniqueId)) return false;
string existingUniqueId;
if (_messageUniqueKeysBySender.TryGetValue(messaging.Sender.Id, out existingUniqueId))
{
if (existingUniqueId == messageUniqueId)
{
return true;
}
else
{
_messageUniqueKeysBySender.TryUpdate(messaging.Sender.Id, messageUniqueId, existingUniqueId);
return false;
}
}
_messageUniqueKeysBySender.TryAdd(messaging.Sender.Id, messageUniqueId);
return false;
}
And then by checking in main code
try
{
if (!IsDuplicate(messaging))
{
var conversation = _conversationRepository[messaging.Sender.Id] ?? new Conversation(messaging.Sender.Id);
message = await _bot.RespondToMessagingAsync(conversation, messaging);
_conversationRepository[messaging.Sender.Id] = conversation;
_logger.ForContext("FacebookMessage", messagingJson).LogDuration("Processing Facebook message", sw);
}
else
_logger.ForContext("FacebookMessage", messagingJson).Warning("Duplicate message skipped");
}
catch (Exception ex)
{
_logger.ForContext("FacebookMessage", messagingJson).Error(ex, "Failed to process message");
message = new TextMessage(Resources.Error);
hasError = true;
}

Related

Python GRPC 13 Internal Error when trying to yield response

When I print the response, everything seems to be correct, and the type is also correct.
Assertion: True
Response type: <class 'scrape_pb2.ScrapeResponse'>
But on postman I get "13 INTERNAL" With no additional information:
I can't figure out what the issue is, and I can't find out how to log or print the error from the server side.
Relevant proto parts:
syntax = "proto3";
service ScrapeService {
rpc ScrapeSearch(ScrapeRequest) returns (stream ScrapeResponse) {};
}
message ScrapeRequest {
string url = 1;
string keyword = 2;
}
message ScrapeResponse {
oneof result {
ScrapeSearchProgress search_progress = 1;
ScrapeProductsProgress products_progress = 2;
FoundProducts found_products = 3;
}
}
message ScrapeSearchProgress {
int32 page = 1;
int32 total_products = 2;
repeated string product_links = 3;
}
scraper.py
def get_all_search_products(search_url: str, class_keyword: str):
search_driver = webdriver.Firefox(options=options, service=service)
search_driver.maximize_window()
search_driver.get(search_url)
# scrape first page
product_links = scrape_search(driver=search_driver, class_keyword=class_keyword)
page = 1
search_progress = ScrapeSearchProgress(page=page, total_products=len(product_links), product_links=[])
search_progress.product_links[:] = product_links
# scrape next pages
while go_to_next_page(search_driver):
page += 1
print(f'Scraping page=>{page}')
product_links.extend(scrape_search(driver=search_driver, class_keyword=class_keyword))
print(f'Number of products scraped=>{len(product_links)}')
search_progress.product_links.extend(product_links)
# TODO: remove this line
if page == 6:
break
search_progress_response = ScrapeResponse(search_progress=search_progress)
yield search_progress_response
Server:
class ScrapeService(ScrapeService):
def ScrapeSearch(self, request, context):
print(f"Request received: {request}")
scrape_responses = get_all_search_products(search_url=request.url, class_keyword=request.keyword)
for response in scrape_responses:
print(f"Assertion: {response.HasField('search_progress')}")
print(f"Response type: {type(response)}")
yield response
Turns out it's just an issue with postman. I set up a python client and it worked.

Lambda and Lex integration no String-argument constructor/factory method to deserialize from String value

I'm new to Lex integration with Lambda, have used lambda before with Connect but not sure why this sample code is failing on me and don't understand the error message.
I have two slots setup in Lex (empid and fever). The utterance kicks of the bot and then I get the error message instead of the question "please enter your employee id?".
import json
def build_response(message):
return {
"dialogAction":{
"type":"Close",
"fulfillmentState":"Fulfilled",
"message":{
"contentType":"PlainText",
"content":message
}
}
}
def elicit_slot(intent_name, slots, slot_to_elicit, message):
return {
'dialogAction': {
'type': 'ElicitSlot',
'intentName': intent_name,
'slots': slots,
'slotToElicit': slot_to_elicit,
'message': message
}
}
def delegate(session_attributes, slots):
return {
'sessionAttributes': session_attributes,
'dialogAction': {
'type': 'Delegate',
'slots': slots
}
}
def perform_action(intent_request):
source = intent_request['invocationSource'] # DialogCodeHook or FulfillmentCodeHook
slots = intent_request['currentIntent']['slots'] # your slots
if source == 'DialogCodeHook':
# Perform basic validation on the supplied input slots.
if slots['empid'] is None: # or any other validation that you want to perform
return elicit_slot(
intent_request['currentIntent']['name'], # current intent name
slots, # current intent slots
'empid', # slot name
'Please enter your employee id' # prompt the user to empid
)
if slots['fever'] is None:
return elicit_slot(
intent_request['currentIntent']['name'], # current intent name
slots, # current intent slots
'fever', # slot name
'Do you have a fever?' # prompt the answer do you have a fever
)
# delegate means all slot validation are done, we can move to Fulfillment
return delegate(output_session_attributes, slots)
if source == 'FulfillmentCodeHook':
#result = your_api_call(slots['city'], slots['country'])
result = "Your employee id is you answered to having a fever."
return build_response(result) # display the response back to user
def dispatch(intent_request):
intent_name = intent_request['currentIntent']['name']
# Dispatch to your bot's intent handlers
if intent_name == 'covidqs':
return perform_action(intent_request)
raise Exception('Intent with name ' + intent_name + ' not supported')
def lambda_handler(event, context):
#logger.debug(event)
return dispatch(event)
Am getting this error:
An error has occurred: Invalid Lambda Response: Received invalid response from Lambda: Can not construct instance of Message: no String-argument constructor/factory method to deserialize from String value ('Please enter your employee id') at [Source: {"dialogAction": {"type": "ElicitSlot", "intentName": "covidqs", "slots": {"empid": null, "fever": null}, "slotToElicit": "empid", "message": "Please enter your employee id"}}; line: 1, column: 143]
I figured this out. The message response has to be in a specific format. So where it is
"message": message
it should have been
"message":{
"contentType":"PlainText",
"content":message
}

Can't get FCM push notifications from my server on iOS (working on Android)

I'm new to iOS/Swift and can't get push notifications working. I have configured my server backend to push notifications to my app when some action happens. I have configured a data notification trough FCM because I need some custom data in the notification to open one activity/view or another. This is the code used to send the notification (python/django):
registration_id = profiletarget.device_token
message_title = "Ha llegado tu turno"
message_body = "Entra y escribe en el relato para el que estás en cola"
data_message = {
"title" : "¿Listo para escribir?",
"body" : "Ha llegado tu turno para escribir en el relato. Recuerda que tienes un minuto para aceptar tu turno y 3 para escribir.",
"bookid" : booktarget.pk,
"multimediaurl" : multimediaused.url
}
result = push_service.notify_single_device(registration_id=registration_id, data_message=data_message)
Everything inside this code is working, because I get them correctly on Android. But on iOS... I can't get it working.
I have get the notifications token, I have post it to my server, I have use the notification sender on FCM console to send test push notifications (the iPhone get them), but not the ones from my custom method, It doesn't show anything. Is anything wrong in the server method or am I missing something?
This is the swift code:
import UIKit
import KeychainSwift
import Firebase
import FirebaseMessaging
#UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate, UNUserNotificationCenterDelegate, MessagingDelegate {
var window : UIWindow?;
var storyboard : UIStoryboard?;
var token = ""
let gcmMessageIDKey = "gcm.message_id"
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
self.storyboard = UIStoryboard(name: "Main", bundle: Bundle.main);
UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.font: UIFont(name: "PT Sans", size: 12)!], for: .normal)
UITabBarItem.appearance().setTitleTextAttributes([NSAttributedString.Key.font: UIFont(name: "PT sans", size: 12)!], for: .selected)
let keychain = KeychainSwift()
token = keychain.get("token") ?? ""
if (token != ""){
print("log-token: ", token)
print("log-redirection: ", "no redirection needed!")
FirebaseApp.configure()
Messaging.messaging().delegate = self
if #available(iOS 10.0, *) {
// For iOS 10 display notification (sent via APNS)
UNUserNotificationCenter.current().delegate = self
let authOptions: UNAuthorizationOptions = [.alert, .badge, .sound]
UNUserNotificationCenter.current().requestAuthorization(
options: authOptions,
completionHandler: {_, _ in })
} else {
let settings: UIUserNotificationSettings =
UIUserNotificationSettings(types: [.alert, .badge, .sound], categories: nil)
application.registerUserNotificationSettings(settings)
}
application.registerForRemoteNotifications()
} else {
print("log-token: ", "noToken")
print("log-redirection: ", "redirection to LoginController")
window?.rootViewController = self.storyboard?.instantiateViewController(withIdentifier: "loginView");
}
return true
}
func application(
_ application: UIApplication,
didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data
) {
let tokenParts = deviceToken.map { data in String(format: "%02.2hhx", data) }
let token = tokenParts.joined()
print("Device Token: \(token)")
postNotificationToken(token: token)
}
func messaging(_ messaging: Messaging, didReceiveRegistrationToken fcmToken: String) {
print("Firebase registration token: \(fcmToken)")
let dataDict:[String: String] = ["token": fcmToken]
NotificationCenter.default.post(name: Notification.Name("FCMToken"), object: nil, userInfo: dataDict)
// TODO: If necessary send token to application server.
// Note: This callback is fired at each app startup and whenever a new token is generated.
}
func postNotificationToken(token:String) {
var request = URLRequest(url: URL(string: "https://myurl?myparam="+token)!)
request.httpMethod = "GET"
request.addValue("application/json", forHTTPHeaderField: "Content-Type")
request.addValue("Bearer "+self.token, forHTTPHeaderField: "myauth")
let session = URLSession.shared
let task = session.dataTask(with: request, completionHandler: { data, response, error -> Void in
let httpURLResponse = response as? HTTPURLResponse;
if (httpURLResponse?.statusCode == 200){
let string = String.init(data: data!, encoding: String.Encoding.utf8)
print(string)
} else {
print(httpURLResponse?.statusCode)
}
})
task.resume()
}
func messaging(_ messaging: Messaging, didReceive remoteMessage: MessagingRemoteMessage) {
print("Received data message: \(remoteMessage.appData)")
}
#available(iOS 10.0, *)
func userNotificationCenter(_ center: UNUserNotificationCenter, willPresent notification: UNNotification, withCompletionHandler completionHandler: #escaping (UNNotificationPresentationOptions) -> Void)
{
completionHandler([.alert, .badge, .sound])
}
// MARK: UISceneSession Lifecycle
}
This are my target capabilities:
And this is my key for APN notifications in my developer account:
With FCM console, notifications are Ok:
Thanks for reading this long post. Anything will help!
Ok, I have it working mixing some of the answers and some code found on other posts. First of all, I am using APN key, not APN certificates.
Second, I'm checking what OS has the user to I have to send the notification (iOS/Android) so I can configure different notification structure. This is the notification system in python/django using PyFCM library and sending the iOS notification as alert, as I found on this post :
if devicetype == "and":
registration_id = profiletarget.device_token
message_title = "default-title"
message_body = "default-body"
data_message = {
"title" : "title",
"body" : "body",
"bookid" : booktarget.pk,
"multimediaurl" : multimediaused.url
}
result = push_service.notify_single_device(registration_id=registration_id, data_message=data_message)
else:
registration_id = profiletarget.device_token
message_title = "title"
message_body = "body"
data_message = {
"bookid" : booktarget.pk,
"multimediaurl" : multimediaused.url,
}
result = push_service.notify_single_device(registration_id=registration_id,
message_title=message_title,
message_body=message_body,
data_message=data_message,
extra_kwargs={"apns_push_type": "alert"}
)
In Xcode I only had to ad capabilities for push notifications and background mode - remote notifications, as posted in the question pic.
In code, I missed some part of firebase tutorial corresponding to this methods:
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any]) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
// Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message ID.
if let messageID = userInfo["body"] {
print("Notif metod 1")
// gotoWritting(bookid: messageID)
}
// Print full message.
print("Notif metod 1")
}
func application(_ application: UIApplication, didReceiveRemoteNotification userInfo: [AnyHashable: Any],
fetchCompletionHandler completionHandler: #escaping (UIBackgroundFetchResult) -> Void) {
// If you are receiving a notification message while your app is in the background,
// this callback will not be fired till the user taps on the notification launching the application.
// TODO: Handle data of notification
// With swizzling disabled you must let Messaging know about the message, for Analytics
// Messaging.messaging().appDidReceiveMessage(userInfo)
// Print message ID.
if let messageID = userInfo["body"]{
print("Notif metod 2")
// gotoWritting(bookid: messageID)
}
// Print full message.
print(userInfo["bookid"]!)
gotoWritting(bookid: Int(userInfo["bookid"]! as! String) ?? 90)
completionHandler(UIBackgroundFetchResult.newData)
}
The second one is the one is getting triggered by the notification click (background, foreground and app closed...) and once its clicked, I can redirect the user with some notification params.
I cant test on previous version, but hope it works on iOS 9 to (If you know if that works or not, let me know please).
Thanks to everyone who helped!
Did you configured your app certificate with push notifications? 👇
Also you will need to configure the project in Xcode select one of your targets and go to -> SignIn and capabilities and add the Push Notifications capability for each target, the pushes ain't working in the simulator visible, but the method didReceiveRemoteNotification is triggered, you can debug with some log or breakpoint if you are receiving them.
Try to send this type of custom formate with custom payload
note : this is node.js code
let message = {
tokens: iosTokens,
notification: {
title: "title",
body: "",
},data = {
"title" : "¿Listo para escribir?",
"body" : "Ha llegado tu turno para escribir en el relato. Recuerda que tienes un minuto para aceptar tu turno y 3 para escribir.",
"bookid" : booktarget.pk
},apns : {
payload: {
aps: {
mutableContent: true,
contentAvailable: true,
category: "CustomSamplePush",
alert: {
launchImage: "imageURL.jpeg",
sound: "default"
}
}
}
}
};

Python if-else mess

Is there a cleaner way to create msg? For each event (Ping, Pull request, Issue, Issue comment, Repo, Create, Delete, Pull requset review, Push, Commit comment) is there an if clause that checks the event and creates a message according to it's action.
data = request.json
event = request.headers['X-Github-Event']
msg = ""
...
# Pull request
elif event == "pull_request":
if data['action'] == "opened":
msg = PullRequest(data).opened()
elif data['action'] == "closed":
msg = PullRequest(data).closed()
elif data['action'] == "assigned":
msg = PullRequest(data).assigned()
# Issue
elif event == "issues":
if data['action'] == "opened":
msg = Issue(data).opened()
elif data['action'] == "reopened":
msg = Issue(data).reopened()
elif data['action'] == "closed":
msg = Issue(data).closed()
elif data['action'] == "labeled":
msg = Issue(data).labeled()
elif data['action'] == "assigned":
msg = Issue(data).assigned()
...
What about doing so dynamically, e.g.
getattr(PullRequest(data), data['action'], lambda:None)()
In summary
elif event == "pull_request":
getattr(PullRequest(data), data['action'], lambda:None)()
elif event == "issues":
getattr(Issue(data), data['action'], lambda:None)()
Where the idea behind lambda:None is that it stands for a default callable if data['action'] is actually not a method of PullRequest(data) or Issue(data).
Or if you do not like if-else statements, something like
callables = {
"pull_request":PullRequest,
"issues":Issue,
}
getattr(callables[event](data), data['action'], lambda:None)()
Replace the conditionals with a dicts that map strings to the appropriate objects. (This is a generalization of what Kanak proposes, in the strings you care examining don't need to match the names of your methods.)
functions = {
"pull_requests": {
"opened" : methodcaller("opened"),
"closed" : methodcaller("closed"),
"assigned" : methodcaller("assigned")
},
"issues": {
"opened" : methodcaller("opened"),
"reopened" : methodcaller("reopened"),
"closed" : methodcaller("closed"),
"assigned" : methodcaller("assigned"),
"labeled": methodcaller("labeled")
}
}
classes = {
"pull_requests": PullRequest,
"issues": Issue
}
obj = classes[event]
msg = functions[event][data['action']](obj)

Appengine channels automatically disconnected on production

On production, a soon as I open a channel with the javascript, it disconnects a seccond after.
Everything works super fine on devserver. The callback works on the server but not on the client. We are using flask, backbone, requirejs and sourcemap.
Client code:
window.channel = new goog.appengine.Channel(window.PLAY_SETTINGS.CHANNEL_TOKEN);
window.gae_websocket = window.channel.open({
onopen: function() {
return console.log('onopen');
},
onclose: function() {
return console.log('onclose');
},
onerror: function() {
return console.log('onerror');
},
onmessage: function() {
return console.log('onmessage');
}
});
Server code:
class Connection(ndb.Model):
user_key = ndb.KeyProperty()
scope = ndb.IntegerProperty(indexed=True, choices=range(0, 2))
target_key = ndb.KeyProperty(indexed=True) # Event ou debate
channel_id = ndb.StringProperty(indexed=True)
#staticmethod
def open_channel():
channel_id = str(uuid4())
channel_token = channel.create_channel(client_id=channel_id, duration_minutes=480)
return channel_token, channel_id
Logs from the appengine production console.
The client callbacks (js) dont works. These are the server callbacks that create the logs:
#app.route('/_ah/channel/disconnected/', methods=['POST'])
def channel_disconnection():
client_id = request.form.get('from')
ndb.delete_multi(Connection.query(Connection.channel_id == client_id).fetch(keys_only=True))
logging.info("Channel closed : %s" % client_id)
return make_response('ok', '200')
#app.route('/_ah/channel/connected/', methods=['POST'])
def channel_connection():
client_id = request.form.get('from')
logging.info("Channel open : %s" % client_id)
return make_response('ok', '200')

Categories

Resources