I'm trying to develop a web chat with Flask and Firestore. I set a flow to receive new messages from firestore (when something changes at the database) and send through websockets to UI. Something like that:
Python:
#sockets.route('/messages')
def chat_socket(ws):
message = None
def callback_snapshot(col_snapshot, changes, read_time):
with app.app_context():
Messages = []
for change in changes:
if change.type.name == 'ADDED':
Messages.append(change.document)
conversation = render_template(
'conversation.html',
Messages = Messages,
)
numberID = None
if len(col_snapshot) > 0:
for i in col_snapshot:
a = i
numberID = a.reference.parent.parent.id
response = json.dumps({
'conversation': conversation,
'numberID': numberID
})
ws.send(response)
while not ws.closed:
response = json.loads(ws.receive())
newNumberID = response['newNumberID'].strip()
query_snapshot = fn.GetMessages(newNumberID)
doc_watch = query_snapshot.on_snapshot(callback_snapshot)
if message is None:
continue
Javascript:
function messages(numberID) {
var scheme = window.location.protocol == "https:" ? 'wss://' : 'ws://';
var webSocketUri = scheme
+ window.location.hostname
+ (location.port ? ':'+location.port: '')
+ '/messages';
/* Get elements from the page */
var form = $('#chat-form');
var textarea = $('#chat-text');
var output = $('.messages');
var status = $('.messages');
var websocket = new WebSocket(webSocketUri);
websocket.onopen = function() {};
websocket.onclose = function() {};
websocket.onmessage = function(e) {
numberID = JSON.parse(e.data).numberID
conversation = JSON.parse(e.data).conversation
output.append(conversation);
if (numberID == null){
output.empty();
}};
websocket.onerror = function(e) {console.log(e);};
websocket.onopen = () => websocket.send(numberID);
};
The problem is: When I use col_snapshot as Messages, everything is ok besides I get the whole firestore Collection sent every time to the user when a message is sent. So it's totally not efficient. When I set callback only for changes, as described above, if I trigger the function more than one time, somehow I set multiple listeners for the same collection, so I get multiple "changes updates" in UI. How I can keep track of those listeners so I only set one listener per Collection?
As you can see from the documentation, you should only call GetMessages and on_snapshot once per document.
#sockets.route('/messages')
def chat_socket(ws):
message = None
def callback_snapshot(col_snapshot, changes, read_time):
with app.app_context():
# Rest of the function ...
ws.send(response)
response = json.loads(ws.receive())
numberID = response['newNumberID'].strip()
query_snapshot = fn.GetMessages(numberID)
doc_watch = query_snapshot.on_snapshot(callback_snapshot)
while not ws.closed:
newNumberID = response['newNumberID'].strip()
response = json.loads(ws.receive())
if newNumberID != numberID:
numberID = newNumberID
query_snapshot = fn.GetMessages(numberID)
doc_watch = query_snapshot.on_snapshot(callback_snapshot)
Related
I made a python api that I'm trying to get my app to connect to, but for the login function I need to send the username and password but I'm not sure how to do this. This is the python code:
username = data.get('username')
email = data.get('email')
password = data.get('password')
And the Kotlin Code:
private fun sendData(username:String, password:String): Thread {
return Thread {
val url = URL("https://127.0.0.1:5000/login")
val connection = url.openConnection() as HttpsURLConnection
connection.setRequestProperty("username", username)
connection.setRequestProperty("password", password)
if (connection.responseCode == 200) {
val inputSystem = connection.inputStream
println(inputSystem.toString())
Toast.makeText(applicationContext, "It worked", Toast.LENGTH_SHORT).show()
}
else {
var code: String = "ERROR"
Toast.makeText(applicationContext, "NO CONNECTION", Toast.LENGTH_SHORT).show()
}
}
}
The connection is opened but I can get any data across and I haven't tried anything so far as I can't find good documentation on this.
You could, for example, first set up a class to handle your credentials:
class LoginData(
val userID: String,
val pw: String
){
/** Returns a hashmap of the data stored in the class object. */
fun getHashmap(): Map<String,String> {
val params = HashMap<String,String>()
params["username"] = userID
params["password"] = pw
return params
}
/** Obtains a JSONObject of the data stored in the class object. */
fun getJson(): JSONObject {
val params = this.getHashmap()
return JSONObject(params)
}
}
And then, utilising Volley (don't forget to add it to your build gradle: implementation 'com.android.volley:volley:1.2.0'), do something like this:
fun sendData(username: String?, password: String?) {
val url = "https://127.0.0.1:5000/login"
var loginData = LoginData(
userID = username!!,
pw = password!!
)
val queue = Volley.newRequestQueue(this)
val jsonRequest = JsonObjectRequest(
Request.Method.POST,
url,
loginData.getJson(),
Response.Listener {
response -> handleResponse(response)//do something with the response
},
Response.ErrorListener { error -> println("That didn't work: $error")})
queue.add(jsonRequest)
}
with handleResponse() containing your logic to evaluate what comes back from the server:
fun handleResponse(response: JSONObject) {
//your evaluation logic
}
I created SQS service with terraform
resource "aws_sqs_queue" "ses_queue" {
name = "ses_queue"
message_retention_seconds = 86400
receive_wait_time_seconds = 1
visibility_timeout_seconds = 15
}
resource "aws_lambda_event_source_mapping" "send_email_message" {
event_source_arn = aws_sqs_queue.ses_queue.arn
function_name = aws_lambda_function.send_email_message.function_name
batch_size = 5
}
I am sending emails using lambda function
for record in event.get("Records"):
receipt_handle = record.get("receiptHandle", "")
request_body = record.get("body")
response = send_email(request_body)
if response:
sqs_client.delete_message(QueueUrl=constants.SES_QUEUE_URL, ReceiptHandle=receipt_handle)
I am wondering why number of deleted messages is twice as many as received messages
The Lambda Event Source Mapping already deletes the messages from the Queue if your Lambda Function terminates without errors:
If your function successfully processes the batch, Lambda deletes the messages from the queue.
— Source
In your Function Code you explicitly delete the messages you processed as well, this means the delete happens twice.
Fixed it with reporting batch item failures
terraform
resource "aws_lambda_event_source_mapping" "send_email_message" {
event_source_arn = aws_sqs_queue.ses_queue.arn
function_name = aws_lambda_function.send_email_message.function_name
batch_size = 5
function_response_types = ["ReportBatchItemFailures"]
}
lambda
reprocess_messages = []
for record in event.get("Records"):
receipt_handle = record.get("receiptHandle", "")
message_id = record.get("messageId", "")
request_body = record.get("body")
response = send_email(request_body)
if not response:
reprocess_messages.append({"itemIdentifier": message_id})
return {"batchItemFailures": reprocess_messages}
I have a list of phone numbers to call on google spreadsheet and I have coded up on apps script to call the numbers.
However, I would like twilio to be able to call the first number, get its call status (completed, busy, failed etc), update it on the spreadsheet and then proceed to call the second number.
This is how I want my spreadsheet output to look like.
Currently, I am unable to get the call status from twilio using Apps Script. Would love to have some feedback on what I'm doing wrongly.
Thanks.
This is my Apps Script code
// Enter your Twilio account information here.
var TWILIO_ACCOUNT_SID = '';
var TWILIO_NUMBER = '';
var TWILIO_AUTH_TOKEN = '';
// Other variables
var STARTROW = 2
var STARTCOL = 1
// Creates the Call button on the menu bar
function onOpen() {
// To learn about custom menus, please read:
// https://developers.google.com/apps-script/guides/menus
var ui = SpreadsheetApp.getUi();
ui.createMenu('Call')
.addItem('Call Selected', 'confirmCall')
.addToUi();
};
// Ensuring variables are entered
function checkSetup() {
if (TWILIO_ACCOUNT_SID && TWILIO_AUTH_TOKEN) {
return true;
} else {
var ui = SpreadsheetApp.getUi();
var response = ui.alert('Please check your Twilio account details in AppScript');
return false;
}
}
// Confirmation Pop-up alert
// Asks user if they have updated ngrok website
function confirmCall(){
// Display a dialog box with a message and "Yes" and "No" buttons.
var ui = SpreadsheetApp.getUi();
var response = ui.alert('Is the Phone Number the last column and have you updated the ngrok website in the Apps Script?', ui.ButtonSet.YES_NO);
// Process the user's response.
if (response == ui.Button.YES) {
Logger.log('The user clicked "Yes."');
// Starts making the calls
all_selected();
} else {
ui.alert('Please go to the Tools menu bar > Script Editor to update. \n If unsure, please check manual.');
Logger.log('The user clicked "No" or the dialog\'s close button.');
}
}
// Intiates calls to the selected users
function all_selected() {
var sheet = SpreadsheetApp.getActiveSheet();
var ui = SpreadsheetApp.getUi();
var totalRows = sheet.getLastRow();
var numRows = sheet.getLastRow() - STARTROW + 1;
var numColumns = sheet.getLastColumn();
var nextColumn = numColumns + 1;
// Create new column
sheet.getRange(1,nextColumn).setValue("Response")
if (!checkSetup) {
return
}
if (numRows <= 0) {
Logger.log("No one to call");
var response = ui.alert('No calls to make');
return;
}
for (var i = STARTROW; i <= (totalRows); i++) {
var number = sheet.getRange(i,numColumns).getValue();
var response = makeCall(number);
//Browser.msgBox(response);
// sheet.getRange(i,nextColumn + 1).setValue(response["status"])
// If there is an error, update cell value to Nil
if(response.code){
sheet.getRange(i,nextColumn).setValue("Nil")
Logger.log("Error has occured.");
}
}
}
// Calls a (ONE) person
function makeCall(to) {
var call_url = "https://api.twilio.com/2010-04-01/Accounts/" + TWILIO_ACCOUNT_SID + "/Calls.json";
var payload = {
"To": "+" + String(to),
"From" : TWILIO_NUMBER,
"Url": "http://821f72d16be7.ngrok.io/" + "voice",
"status_callback" : 'http://821f72d16be7.ngrok.io/' + "status",
"Method": "GET",
"status_callback_event" : ['queued', 'initiated'],
"status_callback_method" : 'POST'
};
var options = {
"method" : "post",
"payload" : payload
};
options.headers = {
"Authorization" : "Basic " + Utilities.base64Encode(TWILIO_ACCOUNT_SID + ":" + TWILIO_AUTH_TOKEN)
};
var response = UrlFetchApp.fetch(call_url, options);
return JSON.parse(response);
}
This is my python Colab code
from flask import Flask, request, session
from flask_ngrok import run_with_ngrok
import os
from twilio.rest import Client
from twilio import twiml
from twilio.twiml.voice_response import VoiceResponse, Gather
from google.colab import auth
import gspread
from oauth2client.client import GoogleCredentials
# Change this if using a new sheet
sheet_id = ""
# Accesses the google spreadsheet
auth.authenticate_user()
gc = gspread.authorize(GoogleCredentials.get_application_default())
# Opens the spreadsheet(****First sheet****)
worksheet = gc.open_by_key(sheet_id).sheet1
# Gets the number of columns in the current sheet
num_col = len(worksheet.get_all_values()[0])
col = num_col + 1
####################################################################
TWILIO_ACCOUNT_SID = ''
TWILIO_NUMBER = ''
TWILIO_AUTH_TOKEN = ''
client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
# Makes connection using FLASK
app = Flask(__name__)
run_with_ngrok(app)
#app.route("/status", methods=['GET', 'POST'])
def status():
# Need to get the number of rows in that column
row = len(worksheet.col_values(num_col))
worksheet.update_cell(row, col+1, request.values['Status'])
# client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
# record = client.calls.list(limit=1)
# status = record.status
# if status == 'failed':
# worksheet.update_cell(row, col, "failed")
#app.route("/voice", methods=['GET', 'POST'])
def voice():
"""Respond to incoming phone calls with a menu of options"""
# Start our TwiML response
resp = VoiceResponse()
# Start our <Gather> verb
gather = Gather(num_digits=1, action='/gather')
gather.say('If you are interested in our service, press 1. If not, press 2.')
resp.append(gather)
# If the user doesn't select an option, redirect them into a loop
resp.redirect('/voice')
return str(resp)
#app.route('/gather', methods=['GET', 'POST'])
def gather():
"""Processes results from the <Gather> prompt in /voice"""
# Start our TwiML response
resp = VoiceResponse()
# Need to get the number of rows in that column
row = len(worksheet.col_values(num_col))
#row = rows_col + 1
# If Twilio's request to our app included already gathered digits,
# process them
if 'Digits' in request.values:
# Get which digit the caller chose
choice = request.values['Digits']
# <Say> a different message depending on the caller's choice
if choice == '1':
resp.say('You have indicated your interest. We will get back to you soon.')
# Updating the cells (row, column, data)
worksheet.update_cell(row, col, "Yes")
return str(resp)
elif choice == '2':
resp.say('We understand your choice. Thank you.')
# Updating the cells (row, column, data)
worksheet.update_cell(row, col, "No")
return str(resp)
else:
# If the caller didn't choose 1 or 2, apologize and ask them again
# Updating the cells (row, column, data)
###### sheet.update_cell(row, num_col, "Nil")
worksheet.update_cell(row, col, "Never indicate")
resp.say("Sorry, I don't understand that choice. Please try again.")
# If the user didn't choose 1 or 2 (or anything), send them back to /voice
resp.redirect('/voice')
return str(resp)
# if __name__ == "__main__":
# app.run(debug=True, use_reloader=False)
app.run()
I am writing a program for doing Bloomberg data-feed check using the subscription method of Python API. I am close to finishing it and I am now trying to cover edge cases such as a failed subscription.
I want to check if a subscription has failed. If it fails, I will write it into a file named BadSubscription.txt.
One of he example programs that come with Bloomberg API package, SimpleSubcriptionExample.py, has just 1 line of code for Subscription Status so it doesn't give me a clear idea.
try:
# Process received events
eventCount = 0
while(True):
# We provide timeout to give the chance to Ctrl+C handling:
event = session.nextEvent(15000)
for msg in event:
if event.eventType() == blpapi.Event.SUBSCRIPTION_STATUS or \
event.eventType() == blpapi.Event.SUBSCRIPTION_DATA:
print("%s - %s" % (msg.correlationIds()[0].value(), msg))
else:
print(msg)
The above code prints the following when a subscription fails for subscribing to a security/equity that doesn't exist:
SubscriptionFailure = {
reason = {
errorCode = 2
description = "Invalid security, rcode = -11"
category = "BAD_SEC"
source = " [nid:3924]:bbdbm10"
}
}
And when a subscription is successful it prints:
SubscriptionStarted = {
exceptions[] = {
}
streamIds[] = {
"1"
}
receivedFrom = {
address = "localhost:8194"
}
reason = "Subscriber made a subscription"
}
What I want to do is write an if statement for my program to catch the SubscriptionFailure and write the message to the file:
for msg in event:
if (event.eventType() == blpapi.Event.SUBSCRIPTION_STATUS
and (**the condition to catch the error**)):
f = open("BadSubscription.txt", "a+")
f.write(msg)
I am looking for a condition to use in my if statement.
I tried reading the following repository but it doesn't explain much, too.
https://bloomberg.github.io/blpapi-docs/python/3.13/_autosummary/blpapi.Session.html?highlight=subscription%20status
I first tried
msg.correlationIds()[0].value().find("SubscriptionFailure")!=-1
as the condition but that didn't work.
Thanks to #assylias I found the solution.
for msg in event:
if (event.eventType() == blpapi.Event.SUBSCRIPTION_STATUS
and msg.messageType() == "SubscriptionFailure"):
f = open("BadSubscription.txt", "a+")
s = ""
if msg.getElement("reason").getElement("errorCode").getValueAsInteger() !=12:
s = msg.toString()
f.write(s)
The above code writes the following to my file:
SubscriptionFailure = {
reason = {
errorCode = 2
description = "Invalid security, rcode = -11"
category = "BAD_SEC"
source = " [nid:235]:bbdbm10"
}
}
The API returns both JSON and also render the template and when i call $.getJSON it will only return that render template but not JSON value. I have tried this
if request.args['type'] == 'json':
return json.dumps(group)
else:
return render_template("/c.., summary=json.dumps(group))
but it says
bad request
Is there any way I can get that JSON value whenever I need it?
This is my view
#cms.route('/add/asset/<client_id>', methods=["GET"])
#login_required
def asset_add(client_id):
if int(current_user.id_) == int(client_id):
group = {}
group['id'] = []
group['pid'] = []
group['name'] = []
for index in range(len([r.id_ for r in db.session.query(Assetgroup.id_)])):
for asset in (Assetgroup.query.filter_by(parent_id=(index or ''))):
group['id'].append(asset.id_)
group['pid'].append(asset.parent_id)
group['name'].append(asset.name)
if request.args['type'] == 'json':
return json.dumps(group)
else:
return render_template("/cms/asset_add.html", action="/add/asset", asset=None,
client_id=client_id,
types=Type.query.all())
else:
return 'permission denied'
and this is my ajax request
$(document).ready(function () {
$('#group_id').click(function () {
$.getJSON(
'/add/asset/' + {{ client_id }},
function (data) {
$('#group_id').find('option').remove();
var len = data.id.length;
for (var i = 0; i < len; i++) {
var option_item = '<option value="' + data.id[i] + '">' + data.name[i] + "</option>";
$('#group_id').append(option_item);
}
}
);
});
});
You can add parameter in html call to get the json result...
i.e)
const Endpoint = '/add/asset/?'
$.getJSON(Endpoint, {type: 'json'}).done(function(data...)
I believe this is what you are looking for
http://flask.pocoo.org/docs/0.12/api/#flask.Request.is_json
That is a flask method that checks if the request is json
Then you can use jsonify still in flask to return json (you need to import it though)
from flask import jsonify
so your code becomes
if request.is_json:
return jsonify(group)
Hope you find that useful and more elegant
One of the easier ways to debug is just return json alone for a start to see how the response looks in a browser. So you can remove login required (assuming you are not yet in production), do not check if the request is_json, then call the api and see what it returns. So assuming your client id is 1
#cms.route('/add/asset/<client_id>', methods=["GET"])
def asset_add(client_id):
if int(current_user.id_) == int(client_id):
group = {}
group['id'] = []
group['pid'] = []
group['name'] = []
for index in range(len([r.id_ for r in db.session.query(Assetgroup.id_)])):
for asset in (Assetgroup.query.filter_by(parent_id=(index or ''))):
group['id'].append(asset.id_)
group['pid'].append(asset.parent_id)
group['name'].append(asset.name)
return jsonify(group)
Now you can visit http://yoursite.com/add/asset/1 to see your response