Django Channels group send (exclude the data sender) - django-channels

I'm using Django channels as an intermediate agent, which passes data from one browser(parent/sender) to other connected browsers(children/receivers). And in my consumers, I do a channel_layer.group_send(data) once data are received from the parent browser, so that children browsers can get the data from redis channel later.
However, what I really want is the data passed to the channel should be received by all the children, except the parent browser. My question is, how to exclude the data sender in the group?

Unfortunately, django channels does not offer a filtering like that. I have solved the problem by checking in the chat_message function whether the current connection is the sender.
async def receive(self, text_data):
text_data_json = json.loads(text_data)
# Send message to room group
await self.channel_layer.group_send(
self.GROUP_NAME,
{
'type': 'chat_message',
'data': text_data_json,
'sender_channel_name': self.channel_name
}
)
# Receive message from room group
async def chat_message(self, event):
# send to everyone else than the sender
if self.channel_name != event['sender_channel_name']:
await self.send(text_data=json.dumps(event))

I'm end up using session id to exclude sender in consumer (group send outside the consumer):
in application:
def emit_websocket_update_info():
sender_sessionid = info.context.COOKIES["sessionid"]
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
GraphQLUpdatesConsumer.group_name,
{
"type": GraphQLUpdatesConsumer.mutation_handler,
"text": json.dumps({"conditions": time.time()}),
"sender_sessionid": sender_sessionid,
},
)
in consumer:
async def mutation_event(self, event):
# send to all except sender itself...
if self.scope["cookies"]["sessionid"] != event["sender_sessionid"]:
await self.send(text_data=event["text"])

Related

Adding rules to running stream (tweepy, streamingClient)

I am trying to collect all Tweets of certain users and the replies to those Tweets. Collecting the original Tweets works out fine, but collecting the replies does not. As soon as on_tweet is called (when the stream receives a Tweet), I am trying to add a rule 'in_reply_to_tweet_id: 'id of incoming tweet'' to my stream so that it also stream those replies. Yet the code below doesn't work. I checked with get_rules after the stream was closed and there was no rule added. I also tried adding a simple 'OR: keyword' rule, which was also not added, so the ID is not the problem.
Thanks, any help is appreciated!:)
class stream(tweepy.StreamingClient):
def __init__(self, token):
tweepy.StreamingClient.__init__(self, token)
self.raw_tweets = []
self.raw_replies = []
#this method is called whenever the stream receives a tweet
def on_tweet(self, tweet):
#checking whether new tweet in stream is a original tweet or a reply to a tweet
if (tweet.conversation_id == tweet.id):
#add the id to the rules so that replies to the tweet are also streamed
#this is where the problem is
id_as_str = str(tweet.id)
new_rule = 'OR in_reply_to_tweet_id:' + id_as_str
self.add_rules(add= tweepy.StreamRule(new_rule), dry_run = True)
self.raw_tweets.append(tweet)
print('this is an original'+tweet.text)
else:
self.raw_replies.append(tweet)
print('this is a reply:'+tweet.text)
return self.raw_tweets

How to retrieve messages from Alpakka Mqtt Streaming client?

I was following document for writing a Mqtt client subscriber using alpakka.
https://doc.akka.io/docs/alpakka/3.0.4/mqtt-streaming.html?_ga=2.247958340.274298740.1642514263-524322027.1627936487
After the code marked in bold, I’m not sure how could I retrieve/interact with subscribed messages. Any lead?
Pair<SourceQueueWithComplete<Command>, CompletionStage> run =
Source.<Command>queue(3, OverflowStrategy.fail())
.via(mqttFlow)
.collect(
new JavaPartialFunction<DecodeErrorOrEvent, Publish>() {
#Override
public Publish apply(DecodeErrorOrEvent x, boolean isCheck) {
if (x.getEvent().isPresent() && x.getEvent().get().event() instanceof Publish)
return (Publish) x.getEvent().get().event();
else throw noMatch();
}
})
.toMat(Sink.head(), Keep.both())
.run(system);
SourceQueueWithComplete<Command> commands = run.first();
commands.offer(new Command<>(new Connect(clientId, ConnectFlags.CleanSession())));
commands.offer(new Command<>(new Subscribe(topic)));
session.tell(
new Command<>(
new Publish(
ControlPacketFlags.RETAIN() | ControlPacketFlags.QoSAtLeastOnceDelivery(),
topic,
ByteString.fromString(“ohi”))));
// for shutting down properly
commands.complete();
commands.watchCompletion().thenAccept(done → session.shutdown());
Also, in the following example, it shows how to subscribe to the client but nothing about how to get messages after the subscription.
https://github.com/pbernet/akka_streams_tutorial/blob/master/src/main/scala/alpakka/mqtt/MqttEcho.scala
Will be grateful if anyone knows the solution or can point to any resource which uses the same connector as mqtt client and can retrieve messages.
The code to retrieve messages for the subscriber is hidden in the client method which is used for both publisher and subscriber:
...
//Only the Publish events are interesting for the subscriber
.collect { case Right(Event(p: Publish, _)) => p }
.wireTap(event => logger.info(s"Client: $connectionId received: ${event.payload.utf8String}"))
.toMat(Sink.ignore)(Keep.both)
.run()
https://github.com/pbernet/akka_streams_tutorial/blob/3e4484c5356e55522366e65e42e1741c18830a18/src/main/scala/alpakka/mqtt/MqttEcho.scala#L136
I was struggling with this connector and then tried an example with the one based on Eclipse Paho, which in the end looks better:
https://github.com/pbernet/akka_streams_tutorial/blob/3e4484c5356e55522366e65e42e1741c18830a18/src/main/scala/alpakka/mqtt/MqttPahoEcho.scala#L41
Paul

Forward the "Send and Wait for Reply" to a different phone number

We have a studio flow called "Google LA" that's triggered via Rest API. This flow has a Send and Wait for Reply so we hook this flow to "When a message comes in" so it will follow the rest of the flow when customer rates the service 1 to 5 stars. Now, within the Send and Wait for Reply, we want the customer's reply be forwarded to our main business phone number for tracking/recording purposes and so we can address their issues for rating us 1 to 3 stars. Here's our setup:
This is what we want:
Edited for philnash suggestion:
I created a function in Twilio with this code:
exports.handler = function(context, event, callback) {
const accountSid = context.ACCOUNT_SID;
const authToken = context.AUTH_TOKEN;
const client = require('twilio')(accountSid, authToken);
client.messages
.create({
body: widgets.negative1_3.inbound.Body,
from: '+12132779513',
to: '+12133885256'
})
.then(message => console.log(message.sid));
};
However, it did not send anything or the customer response. I renamed the negative1-3 widget to negative1_3 and published the studio flow.
I tried changing the body: 'Hello' to make sure that my function works, and yes. I received the 'Hello' sms to my verified caller ID phone number after it reaches the first_question -> check_response -> negative1_3.
Twilio developer evangelist here.
You don't necessarily need to forward the message here. You can make an API call to your own service with all the data you need from the message, so you can store and react to the information that way.
To do so you will want to add either an HTTP Request widget or a Run Function widget after the Send and Wait For Reply widget. Within those widgets, you can access the reply from the Send And Wait For Reply widget using liquid tags. You can see how to call on the variables in the docs for the Send and Wait For Reply widget. In the case of your widget, you should be able to get the body of the reply by referring to:
widgets.negative1-3.inbound.Body
(Although I am not sure how the name "negative1-3" will work, so you might try widgets["negative1-3"] instead, or rename the widget with underscores.)
Using the body of the inbound message, as well as the from number, you can send the data to your own application with the HTTP request widget or with a Run Function widget.
Edit
Your function can only access parameters that you set in the function widget config. You can then access those parameters in the event object. You also need to return once the message is sent successfully using the callback function. One other tip, you don't need to instantiate your own client, you can get it from the context. Like so:
exports.handler = function(context, event, callback) {
const client = context.getTwilioClient();
client.messages
.create({
body: event.Body,
from: '+12132779513',
to: '+12133885256'
})
.then(message => {
console.log(message.sid);
callback(null, "OK");
})
.catch(error => callback(error));
};

Save a user's pushSubscription info in Rails database?

I have followed this tutorial: https://dzone.com/articles/how-to-add-real-web-push-notifications-to-your-web to enable push notifications on my rails app. I am using the webpush gem to send the notifications.
So far, all I have managed to do is get the browser to ask for permission to send notifications, and when I try to call the method send_web_push_notification (shown below) line 2 is throwing up an error.
I think it is because I am not saving the user's pushSubscription info to the database, but I don't know how to do this. In the tutorial, there is this line at the end: 'We use a database JSON field called web_push_subscription to save the pushSubscription info on our users.'
Would someone be able to show me how to do this?
Any help would be appreciated. Thanks.
send_web_push_notification method:
def send_web_push_notification(user_id)
subscription = User.find(user_id).web_push_subscription
message = {
title: "You have a message!",
body: "This is the message body",
tag: "new-message"
}
unless subscription.nil?
Webpush.payload_send(
message: JSON.generate(message),
endpoint: subscription["endpoint"],
p256dh: subscription["keys"]["p256dh"],
auth: subscription["keys"]["auth"],
ttl: 15,
vapid: {
subject: 'mailto:admin#example.com',
public_key: Rails.application.config.webpush_keys[:public_key],
private_key: Rails.application.config.webpush_keys[:private_key]
}
)
end
end
serviceworker.js.erb:
function showNotification(event) {
return new Promise(resolve => {
const { body, title, tag } = JSON.parse(event.data.text());
self.registration
.getNotifications({ tag })
.then(existingNotifications => { // close? ignore? })
.then(() => {
const icon = `/path/to/icon`;
return self.registration
.showNotification(title, { body, tag, icon })
})
.then(resolve)
})
}
self.addEventListener("push", event => {
event.waitUntil(
showNotification(event)
);
}
});
self.addEventListener("notificationclick", event => {
event.waitUntil(clients.openWindow("/"));
});
application.js:
const permission = Notification.requestPermission();
if (permission !== 'granted') {
// no notifications
}else{
// yay notifications
}
function subscribeToPushNotifications(registration) {
return registration.pushManager
.subscribe({
userVisibleOnly: true,
applicationServerKey: window.vapidPublicKey
})
.then(pushSubscription => {
console.log(
"Received PushSubscription:",
JSON.stringify(pushSubscription)
);
return pushSubscription;
});
}
If you look closely codes you will notice that you have to create a Json field in database to save subscription. If there is subscription available than push notification will be sent. Actually there many more scenarios it is not necessary the you want to save one browser for user notification, if you plan multiple browser than you have to create separate table, but if you want to add one browser for push notification, than you can add this information in user table too. Create new migration to update your user table and add following column
t.json "web_push_subscription"
Run migration, Now you have Json column if you notice code clearly following are information you require in your user database, you will save this information when user subscribe for push notification
user. web_push_subscription[:endpoint] = what_ever_value_received
user.web_push_subscription[:auth] = what_ever_value_received
Unfortunately it is just idea as I have not implement it, but I should check JSON.stringify(pushSubscription) object recived, and there are chances all data would be in this response which you received you may need to save it as it is to your subscription.
You also need to save permission, that user really allowed you to send notification, if yes than one field in user as boolean notification = true, so you can check if user allow you to send notification, than you can send, otherwise don't send. You should also have way to remove these keys for specific user when they unsubscribe notifications.
You basically need to update a model, which is backend, but you do not want the user to go through all that process. This is where ajax comes in handy. I am not very comfortable with ajax but it is one of the best things provided by JS.
With the code in ajax function, you will hit the controller update action with the changed attribute and the update will change the model as necessary and update it. then your html will change accordingly without page refresh.
TLDR: I think you are looking for this.

websocket-rails Chat Rooms

I am trying to create sort of Whatsapp like messaging app server side in Rails, with private conversations. now, I am trying to implement the realtime part of the app - I am using websocket-rails - and I am not sure how to send a message only to the users in the private message - I saw a feature called private channels in websocket-rails - but after reading the documentation, I got under the impression that each private channel needs to be defined statically, and I cannot create channels realtime.
Do you know how can I implement private conversations in websocket-rails, like a guide or a direction? or any other websocket service I can use to implement it?
You can pass parameters from the client side to the server side when creating a subscription. For example:
# app/channels/chat_channel.rb
class ChatChannel < ApplicationCable::Channel
def subscribed
stream_from "chat_#{params[:room]}"
end
end
An object passed as the first argument to subscriptions.create becomes the params hash in the cable channel. The keyword channel is required:
# app/assets/javascripts/cable/subscriptions/chat.coffee
App.cable.subscriptions.create { channel: "ChatChannel", room: "Best Room" },
received: (data) ->
#appendLine(data)
appendLine: (data) ->
html = #createLine(data)
$("[data-chat-room='Best Room']").append(html)
createLine: (data) ->
"""
<article class="chat-line">
<span class="speaker">#{data["sent_by"]}</span>
<span class="body">#{data["body"]}</span>
</article>
"""
Somewhere in your app this is called, perhaps
ActionCable.server.broadcast(
"chat_#{room}",
sent_by: 'Paul',
body: 'This is a cool chat app.'
)

Resources