Interactive button doesn't work properly when using pub/sub - hangouts-chat

I'm writing a Hangouts Chat bot in C# that uses pub/sub so I can host the bot on our side of a firewall. Everything seems to work well except interactive buttons within cards. If I create a button with a specific action method name, the bot does receive the CARD_CLICKED message with the appropriate action method name. However, it doesn't seem like the card in the Hangouts Chat app knows a response was sent because the bot ends up getting the CARD_CLICKED message three times before the Hangouts Chat app finally says "Unable to contact Bot. Try again later". I've been using the Google.Apis.HangoutsChat.v1 and Google.Cloud.PubSub.V1 packages from NuGet for the bot.
This is speculation, but it seems like the issue might be that interactive buttons don't work properly through pub/sub. Any help would be appreciated.
Here is a snippet of the code I have:
SubscriptionName subscriptionName = new SubscriptionName(PROJECT_ID, SUBSCRIPTION_ID);
SubscriberServiceApiClient client = SubscriberServiceApiClient.Create();
GoogleCredential credential = GoogleCredential.FromFile(CREDENTIALS_PATH_ENV_PROPERTY).CreateScoped(HANGOUTS_CHAT_API_SCOPE);
HangoutsChatService chatService = new HangoutsChatService(new BaseClientService.Initializer
{
ApplicationName = "My Bot",
HttpClientInitializer = credential
});
while (true)
{
PullResponse response = client.Pull(subscriptionName, false, 3, CallSettings.FromCallTiming(CallTiming.FromExpiration(Expiration.FromTimeout(TimeSpan.FromSeconds(90)))));
if ((response.ReceivedMessages == null) || (response.ReceivedMessages.Count == 0))
Console.WriteLine("Pulled no messages.");
else
{
foreach (ReceivedMessage message in response.ReceivedMessages)
{
try
{
byte[] jsonBytes = message.Message.Data.ToByteArray();
JObject json = JObject.Parse(Encoding.UTF8.GetString(jsonBytes));
string messageType = (string)json["type"];
switch (messageType)
{
case "MESSAGE":
{
// Get text
string messageText = (string)json["message"]["text"];
Console.WriteLine($"[{messageType}] {messageText}");
// Send response
string spaceName = (string)json["space"]["name"];
SpacesResource.MessagesResource.CreateRequest request = chatService.Spaces.Messages.Create(new Message
{
Cards = new[]
{
new Card
{
Header = new CardHeader
{
Title = "Message Received!"
},
Sections = new[]
{
new Section
{
Widgets = new[]
{
new WidgetMarkup
{
Buttons = new[]
{
new Button
{
TextButton = new TextButton
{
Text = "Click Me!",
OnClick = new OnClick
{
Action = new FormAction
{
ActionMethodName = "ClickedAction"
}
}
}
}
}
}
}
}
}
}
},
Thread = new Thread
{
Name = (string)json["message"]["thread"]["name"]
}
}, spaceName);
Message responseMsg = request.Execute();
break;
}
case "CARD_CLICKED":
{
string actionMethodName = (string)json["action"]["actionMethodName"];
Console.WriteLine($"[{messageType}] {actionMethodName} at {((DateTime)json["message"]["createTime"]).ToString()}");
// Send response
string spaceName = (string)json["space"]["name"];
SpacesResource.MessagesResource.CreateRequest request = chatService.Spaces.Messages.Create(new Message
{
ActionResponse = new ActionResponse
{
Type = "UPDATE_MESSAGE"
},
Text = $"You clicked on '{actionMethodName}'.",
Thread = new Thread
{
Name = (string)json["message"]["thread"]["name"]
}
}, spaceName);
Message responseMsg = request.Execute();
break;
}
default:
{
Console.WriteLine($"[{messageType}]");
break;
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error parsing message: {ex}");
}
}
// Acknowledge the message so we don't see it again.
string[] ackIds = new string[response.ReceivedMessages.Count];
for (int i = 0; i < response.ReceivedMessages.Count; ++i)
ackIds[i] = response.ReceivedMessages[i].AckId;
client.Acknowledge(subscriptionName, ackIds);
}
}

Using buttons with Hangouts Chat API requires a custom answer including:
{
'thread': {
'name': thread_id
},
'actionResponse': {
'type': 'UPDATE_MESSAGE'
}
}
I'd recommend using Hangouts Chat API with a bot URL.

Related

Django channels without redis

I have a django app based on this tutorial that works perfectly. It uses Redis in the Channel layers
CHANNEL_LAYERS = {
'default': {
'BACKEND': 'channels_redis.core.RedisChannelLayer',
'CONFIG': {
"hosts": [('127.0.0.1', 6379)],
},
},
}
The problem I have is that my web hosting provider will not allow Redis (unless I pay ££££).
Every example that I can find uses Redis in this role. Is there an alternative I could use?
there are a few options.
you can run your channel layer on a different service to were the main instance runs. AWS ElastiCache or many other redis hosts out there.
There is also a RabbitMQ channel layer but if your hosting provider charges a lot for reddis i expect they will also charge a lot for this ... https://github.com/CJWorkbench/channels_rabbitmq/
It turned out that channels is a non-starter on an affordable web-hosting platform. So I reverted to using Ajax and long polling. My application is based on this Django Tutorial.
models.py
class Message(models.Model):
room_name = models.CharField(null=False, blank=False, max_length=50)
sender = models.CharField(null=False, blank=False, max_length=50, default='Sender username')
datetime = models.DateTimeField(null=True, auto_now_add=True)
type = models.IntegerField(null=True, blank=True)
text = models.CharField(null=False, blank=False, max_length=250, default='message text')
context = models.TextField(null=True, blank=True)
urls.py
urlpatterns = [
path('<str:partner_pk>/check-message', views.CheckMessage.as_view(), name="check-message"),
path('<str:partner_pk>/send-message/<str:chat_text>', views.SendMessage.as_view(), name="send-message"),
]
views.py
class CheckMessage(View):
"""Duo check message."""
def get(self, request, partner_pk):
"""Render the GET request."""
pair, room_name = sort_pair(partner_pk, request.user.pk)
partner = User.objects.get(pk=partner_pk)
profile = get_object_or_404(Profile, user=request.user)
message = Message.objects.filter(room_name=room_name, sender=partner.username).earliest('datetime')
context = {'type': -1}
context = json.loads(message.context)
context['sender'] = message.sender
context['datetime'] = message.datetime
context['message_type'] = message.type
context['text'] = message.text
context['seat'] = profile.seat
message.delete()
return JsonResponse(context, safe=False)
class SendMessage(View):
def get(self, request, partner_pk, chat_text):
message_type = app.MESSAGE_TYPES['chat']
send_message(request, partner_pk, message_type, text=chat_text, context={})
return JsonResponse({}, safe=False)
chat.js
window.setInterval(checkMessage, 3000);
function checkMessage () {
$.ajax(
{
type:"GET",
url: "check-message",
cache: false,
success: function(message) {
processMessage(message);
}
}
)
}
// Take action when a message is received
function processMessage(context) {
switch (context.message_type) {
case 0:
sendMessage(context)
functionOne()
break;
case 1:
sendMessage(context)
functionTwo()
break;
case 2:
sendMessage(context)
functionThree()
break;
}
}
// Send a message to chat
function sendMessage (context) {
if (context.sender != username) {
var messageObject = {
'username': context.sender,
'text': context.text,
};
displayChat(context);
}
}
// Display a chat message in the chat box.
function displayChat(context) {
if (context.text !== '') {
var today = new Date();
var hours = pad(today.getHours(), 2)
var minutes = pad(today.getMinutes(), 2)
var seconds = pad(today.getSeconds(), 2)
var time = hours + ":" + minutes + ":" + seconds;
var chat_log = document.getElementById("chat-log");
chat_log.value += ('('+time+') '+context.sender + ': ' + context.text + '\n');
chat_log.scrollTop = chat_log.scrollHeight;
}
}
//pad string with leading zeros
function pad(num, size) {
var s = num+"";
while (s.length < size) s = "0" + s;
return s;
}
// Call submit chat message if the user presses <return>.
document.querySelector('#chat-message-input').focus();
document.querySelector('#chat-message-input').onkeyup = function (e) {
if (e.keyCode === 13) { // enter, return
document.querySelector('#chat-message-submit').click();
}
};
// Submit the chat message if the user clicks on 'Send'.
document.querySelector('#chat-message-submit').onclick = function (e) {
var messageField = document.querySelector('#chat-message-input'), text = messageField.value, chat_log = document.getElementById("chat-log");
context = {sender: username, text: messageField.value}
displayChat(context)
sendChat(messageField.value)
chat_log.scrollTop = chat_log.scrollHeight;
messageField.value = '';
};
// Call the send-chat view
function sendChat(chat_text) {
$.ajax(
{
type:"GET",
url: "send-message/"+chat_text,
cache: false,
}
)
}

Google Reaptcha v3 still letting spam through

I just added Google Recaptcha V3 to a site and a lot of spam is still getting through. I know that Google needs to build up some traffic on the site to determine if something is spam. How long should that take? Like if this is still happening after X days there is a problem? What is X?
Here is how I implemented this. On the client side I get the token:
function validateCaptcha(form, callback) {
$("#captcha-error").addClass('hidden');
$("#captcha-error").hide();
$("#captcha-error").html('');
var formName = $('#formName').val();
if (!formName) formName = 'AmeriGasGenericForm';
var siteKey = $('#siteKey').val();
if (grecaptcha) {
try {
grecaptcha.ready(function() {
grecaptcha.execute(siteKey, { action: formName }).then(function(token) {
$.ajax({
url: "/recaptcha-validate?recaptchaResponse=" + token + "&actionName=" + formName,
async: false,
success: function (response) {
console.log("score:" + response.score);
if (response.Success == false) {
$("#captcha-error").html('Unable to verify reCAPTCHA. Please try again.');
$("#captcha-error").removeClass('hidden');
$("#captcha-error").show();
} else {
callback(form);
}
},
error: function(e) {
console.log(e);
}
});
});
});
} catch (err) {
console.log(err);
}
}
}
That calls a server side check to validate:
public JsonNetResult ValidateRecaptcha(string recaptchaResponse, string actionName)
{
var result = new RecaptchaResult();
if (string.IsNullOrEmpty(recaptchaResponse))
{
result.Success = false;
return new JsonNetResult(result);
}
var secretKey = Settings.GetSetting("reCAPTCHA.Secret");
var url = "https://www.google.com/recaptcha/api/siteverify?secret=" + secretKey + "&response=" + recaptchaResponse;
var recaptchaThresholdString = ConfigurationManager.AppSettings["reCaptchaThreshold"];
if (!float.TryParse(recaptchaThresholdString, out var threshold))
{
threshold = 0.5f;
}
using (var reCaptchaHttpClient = new HttpClient())
{
try
{
var reCaptchaResponseString = reCaptchaHttpClient.GetStringAsync(url).Result;
var response = JsonConvert.DeserializeObject<RecaptchaResponseModel>(reCaptchaResponseString);
result.Score = response.score;
if (response.Success && response.score >= threshold && string.Equals(response.action, actionName,
StringComparison.CurrentCultureIgnoreCase))
result.Success = true;
else
result.Success = false;
}
catch (Exception ex)
{
result.Success = false;
}
}
return new JsonNetResult(result);
}
I have the threshold set to allow anything higher than .5 through. But, when I look at the reCaptcha admin console in Google, like 99% of the requests are scored at .9. So Im not sure what to do here to prevent spam. It seems recaptcha thinks everything is a human but when I look at actual submissions I am receiving, they are clearly spam.

How to re-establish connection between end users in UCMA?

After remote user closed his Lync chat window, the state of conversation is Terminated. how can i re-establish them?
what is wrong with my doing?( it throws "The operation is invalid in the current object state (Terminated)")
void conversation_StateChanged(object sender, StateChangedEventArgs<ConversationState> e)
{
if (e.State == ConversationState.Terminated)
{
_terminated = true;
}
if (e.State == ConversationState.Established)
{
_terminated = false;
}
}
if (_terminated)
{
imCall.BeginEstablish(null, null, (ar) =>
{
flow = imCall.Flow;
_callEstablishComplete.Set();
}, null);
_callEstablishComplete.WaitOne();
}
through dozens times of trials, i found it is very easy to solve. what i should do is to create a new IM call object:
conversation = new Conversation(endPoint, settings);
imCall = new InstantMessagingCall(conversation);
imCall.BeginEstablish("sip:xxx#abc.com", null, (ar) =>
{
imCall.EndEstablish(ar);
flow = imCall.Flow;
_callEstablishComplete.Set();
}, null);
that is enough!

Can you load the PopupAppender on demand?

I am trying to use log4javascript and was wondering if there is any way to load the PopupAppender on demand.
I am seeking functionality much like the in-browser tools, where there would be an icon in my application that indicates that something has been logged and when I click it, the PopupAppender opens and allows me to view the logs.
I'm thinking I could write my own very simple appender to show the icon if there are errors, but i'm not sure how I could load up the PopupAppender and show historic messages?
You'd have to have some kind of proxy appender, as you suggest, which stores logging messages and creates a PopUpAppender on demand. Something like this:
Demo: http://jsfiddle.net/hDRpT/
Code:
function OnDemandPopUpAppender() {
this.popUpAppender = new log4javascript.PopUpAppender();
this.poppedUp = false;
this.popperUpperDisplayed = false;
this.queuedLoggingEvents = [];
}
var proto = new log4javascript.Appender();
OnDemandPopUpAppender.prototype = proto;
proto.appendQueued = function() {
for (var i = 0, loggingEvent; loggingEvent = this.queuedLoggingEvents[i++]; ) {
this.popUpAppender.append(loggingEvent);
}
this.queuedLoggingEvents.length = 0;
};
proto.popUp = function() {
this.poppedUp = true;
this.appendQueued();
};
proto.append = function(loggingEvent) {
var appender = this;
this.queuedLoggingEvents.push(loggingEvent);
if (this.poppedUp) {
this.appendQueued();
} else if (!this.popperUpperDisplayed &&
loggingEvent.level.isGreaterOrEqual(log4javascript.Level.ERROR)) {
var popperUpper = document.createElement("div");
popperUpper.style.border = "solid red 2px";
popperUpper.innerHTML = "There are error messages in the log. Click to open.";
popperUpper.onclick = function() {
appender.popUp();
}
document.body.appendChild(popperUpper);
this.popperUpperDisplayed = true;
}
};
var log = log4javascript.getLogger("main");
log.addAppender(new OnDemandPopUpAppender());
log.debug("A debug message");
log.error("A horrible error!");

BlackBerry 10 Cascades: How do I load data into a DropDown?

I have managed to load data from a remote Json web service into a QML ListView, but there doesn't seem to be any such thing for the DropDown control.
Does someone have an example or an alternative method to accomplish a DropDown bound to attachedObjects data sources in Cascades?
I have an alternate method for you, Do note that I have used google's web service here for demonstration purpose, you need to replace it with your url & parse response according to that.
import bb.cascades 1.0
Page {
attachedObjects: [
ComponentDefinition {
id: optionControlDefinition
Option {
}
}
]
function getData() {
var request = new XMLHttpRequest()
request.onreadystatechange = function() {
if (request.readyState == 4) {
var response = request.responseText
response = JSON.parse(response)
var addressComponents = response.results[0].address_components
for (var i = 0; i < addressComponents.length; i ++) {
var option = optionControlDefinition.createObject();
option.text = addressComponents[i].long_name
dropDown.add(option)
}
}
}
// I have used goole's web service url, you can replace with your url
request.open("GET", "http://maps.googleapis.com/maps/api/geocode/json?address=" + "Ahmedabad" + "&sensor=false", true)
request.send()
}
Container {
DropDown {
id: dropDown
}
Button {
onClicked: getData()
}
}
}
Hope this helps.