I use puppeteer-sharp to dump data received and send by page via websockets. The code to dump data in C#:
async Task Dump()
{
var client = await _browser.Page.Target.CreateCDPSessionAsync();
await client.SendAsync("Network.enable");
client.MessageReceived += OnChromeDevProtocolMessage;
}
void OnChromeDevProtocolMessage(object sender, MessageEventArgs eventArgs)
{
if (eventArgs.MessageID == "Network.webSocketCreated")
{
Logger.Trace($"Network.webSocketCreated: {eventArgs.MessageData}");
}
else if (eventArgs.MessageID == "Network.webSocketFrameSent")
{
Logger.Trace($"Network.webSocketFrameSent: {eventArgs.MessageData}");
}
else if (eventArgs.MessageID == "Network.webSocketFrameReceived")
{
var cdpMessage = JsonConvert.DeserializeObject<CdpMessage>(eventArgs.MessageData.ToString());
ProcessMessage(cdpMessage);
}
}
Is there any way to send data to websockets using puppeteer or using directly Chrome Dev Protocol messages?
EDIT:
Or is it possible to get somehow WebSocket instanse (or handle) to use it in JavaScript code to send data using EvaluateFunctionAsync?
QueryObjects can be used in the command line API and also in Puppeteer in order to find the instance. After that you just use EvaluateFunction, to execute send method on object. In PuppeteerSharp it looks something like this:
//CODE SNIPPET
var prototype = await page.EvaluateExpressionHandleAsync("WebSocket.prototype");
var socketInstances = await page.QueryObjectsAsync(prototype);
await page.EvaluateFunctionAsync("(instances) => {let instance = instances[0]; instance.send('Hello')}, new[] {socketInstances}");
More information can be found in the documentation.
in case someone comes to this question looking for an answer in javascript:
const prototype = await page.evaluateHandle("WebSocket.prototype");
const socketInstances = await page.queryObjects(prototype);
await page.evaluate((instances) => {
let instance = instances[0];
instance.send('Hello');
}, socketInstances);
and for ViniCoder in the comments, pyppeteer in python:
prototype = await page.evaluateHandle("WebSocket.prototype")
socketInstances = await page.queryObjects(prototype)
await page.evaluate('''(instances) => {
let instance = instances[0];
instance.send('Hello');
}''', socketInstances)
Related
I'm currently working on capturing crypto-currency price infos from OKEX. I found that, when a page navigate to https://www.okex.com/trade-spot/eth-usdt, it will initiate a websocket named public and send subscription orders via this websocket. Then, data corresponding sent subscriptions will flow through the same websocket.
My question is, in addition to passively inspecting dataflows in this websocket, is there any way to control it, namely to send subscriptions? If so, can this approch be automated(by puppeteer or something equivalent)?
After some research and learning, I solved this problem using evaluateHandle() and queryObjects(). However communication via selected websockets must be done in DOM context.
const puppeteer = require('puppeteer');
async function unsubscrible(page, wsHandle){
page.evaluate(arr=>{
arr[0].send(JSON.stringify({"op": "unsubscribe","args":[{"channel":"instruments","instType":"SPOT"},{"channel":"instruments","instType":"FUTURES"},{"channel":"instruments","instType":"SWAP"},{"channel":"instruments","instType":"OPTION"},{"channel":"tickers","instId":"ETH-USDT"},{"channel":"cup-tickers-3s","ccy":"USDT"},{"channel":"mark-price","instId":"ETH-USDT"},{"channel":"index-tickers","instId":"ETH-USDT"},{"channel":"itn-status"},{"channel":"optimized-books","instId":"ETH-USDT"},{"channel":"trades","instId":"ETH-USDT"}]}));
}, wsHandle);
};
async function ping(page, wsHandle){
page.evaluate(arr=>{
arr[0].send('ping');
}, wsHandle);
}
async function main() {
const browser = await puppeteer.launch({
headless : false,
args : [
'--auto-open-devtools-for-tabs',
]
});
const page = (await browser.pages())[0];
page.setDefaultNavigationTimeout(0);
await page.goto('https://www.okex.com/trade-spot/eth-usdt');
const wsHandle = await page.evaluateHandle(()=>WebSocket.prototype);
const ws = await page.queryObjects(wsHandle);
await unsubscrible(page, ws);
setTimeout(()=>{
for (i=0; i<10; i++) ping(page, ws);
}, 20000)
}
main();
I have a lambda function that returns a message to the client.
function replyToMessage (messageText,connectionId) {
const data = {message:messageText}
const params = {
ConnectionId : connectionId,
Data: Buffer.from(JSON.stringify(data))
}
return api.postToConnection(params).promise()
.then(data => {})
.catch(error => {console.log("error",error)})
}
This code is called once when the connection is made and I get a response to my client. When I call the function again with a different endpoint, it doesn't send a response to my client. However, when I call it a third time, I get the response to my client from the second call. Here's my switch when the Lambda function is called.
switch(route) {
case "$connect":
break
case "$disconnect":
break
case "connectTo":
await connectToService(JSON.parse(event.body).eventId,connectionId)
await replyToMessage("Connected eventId to connId",connectionId)
break
case "disconnectFrom":
await disConnectToService(JSON.parse(event.body).eventId,connectionId)
break
case "project":
responseItems = await getBroadcastIds (JSON.parse(event.body).eventId,JSON.parse(event.body).sourceId,connectionId)
console.log(responseItems)
responseItems.Items.forEach(async function(item) {
await replyToMessage(JSON.parse(event.body).sourceId,item.connectionId)
})
responseItems = []
break
default :
console.log("Unknown route", route)
The issue appears to be the async forEach loop. Switching to the following resolves the issue.
for (const item of responseItems.Items) {
console.log("Sending to:",item.connectionId);
await replyToMessage(JSON.parse(event.body).sourceId,item.connectionId)
}
See this post for the answer that led to this resolution. Using async/await with a forEach loop
Attaching the code snippet below. UniversalBot and ChatConnector has been deprecated in botbuilder 4.1.5.
var bot;
try {
bot = new BasicBot(conversationState, userState, botConfig);
} catch (err) {
console.error(`[botInitializationError]: ${ err }`);
process.exit();
}
// Create HTTP server
// let server = restify.createServer();
let server = express();
server.listen(process.env.port || process.env.PORT || 3978, function() {
console.log(`\n${ server.name } listening to ${ server.url }`);
console.log(`\nGet Bot Framework Emulator: https://aka.ms/botframework-emulator`);
console.log(`\nTo talk to your bot, open basic-bot.bot file in the Emulator`);
});
// Listen for incoming activities and route them to your bot main dialog.
server.post('/api/messages', (req, res) => {
// Route received a request to adapter for processing
adapter.processActivity(req, res, async (turnContext) => {
// route to bot activity handler.
await bot.onTurn(turnContext);
});
});
Your question is fairly general.
The session object from 3.x has been removed. Instead acccessors are used. You will want to do following in the bot class:
public onTurn = async (turnContext: TurnContext) => {
const userProfile = await this.userProfile.get(turnContext, new UserProfile());
const conversationData = await this.dialogStateAccessor.get(turnContext, { dialogStack: undefined });
// set vars in cache
userProfile.yourUserVarProp = "userValue";
conversationData.yourConversationVarProp = "conversationValue";
// persist userVars through dialog turn
await this.userProfile.set(turnContext, userProfile);
// persist conversationVars through dialog turn
await this.dialogStateAccessor.set(turnContext, conversationData);
//
// -> your dialogs here (await dc.beginDialog("dialogname");)
//
// save uservars to db at end of a turn
await this.userState.saveChanges(turnContext);
// save conversationVars to db at end of a turn
await this.conversationState.saveChanges(turnContext);
}
But there is some additional constructor stuff
#param {ConversationState} conversationState A ConversationState object used to store the dialog state.
#param {UserState} userState A UserState object used to store values specific to the user.
... and creating the userProfile and dialogStateAccessor itself.
For the whole picture have better a look at https://github.com/Microsoft/BotBuilder-Samples/tree/master/samples/javascript_nodejs .
Or try the generator: https://learn.microsoft.com/en-us/azure/bot-service/javascript/bot-builder-javascript-quickstart?view=azure-bot-service-4.0.
I'm developing a bot using the bot framework for node.js v4. Imagine the following scenario:
user: Hello
bot: How can I help you?
user: What is the deadline for completing the transfer?
bot: What is the value of the transfer?
user: $ 5,000
At this time, I am executing the textprompt to request the value of the transfer and I need to validate if the user entity ($ 5,000) has been identified as the money entity.
This is the dialog stack:
this.addDialog(new WaterfallDialog(DUVIDA_NIVEL_APROVACAO_DIALOG, [
this.initializeStateStep.bind(this),
this.moneyStep.bind(this),
this.captureMoney.bind(this),
this.endConversation.bind(this)
]));
this.addDialog(new TextPrompt(MONEY_PROMPT, this.validateMoneyInput));
And the validate method:
async validateMoneyInput(validatorContext) {
const value = validatorContext.recognized.value; //how to get entities?
if (value == 'money') {
return VALIDATION_SUCCEEDED;
} else {
await validatorContext.context.sendActivity(`What is the value of the transfer?`);
return VALIDATION_FAILED;
}
}
However, in the callback to validate the textprompt, I have only the text sent by the user.
How can I get the entities extracted by Luis within the textprompt validation method?
To get any LUIS results into the dialog waterfall, you first need to capture the results on the turnContext, like so:
if (turnContext.activity.type === ActivityTypes.Message) {
// Returns LUIS matched results
const results = await this.luisRecognizer.recognize(turnContext);
// Results are assigned to the turnContext object and is passed into the dialog stack
turnContext.topIntent = results.luisResult.topScoringIntent;
turnContext.topIntent.entities = results.luisResult.entities;
turnContext.topIntent.value = results.luisResult.query;
// Create a dialog context object.
const dc = await this.dialogs.createContext(turnContext);
const utterance = (turnContext.activity.text || '').trim().toLowerCase();
if (utterance === 'cancel') {
if (dc.activeDialog) {
await dc.cancelAllDialogs();
await dc.context.sendActivity(`Ok... canceled.`);
} else {
await dc.context.sendActivity(`Nothing to cancel.`);
}
}
// If the bot has not yet responded, continue processing the current dialog.
await dc.continueDialog();
// Start the sample dialog in response to any other input.
if (!turnContext.responded) {
await dc.beginDialog(DUVIDA_NIVEL_APROVACAO_DIALOG);
}
}
Now that the results have been passed in, you can access the results via the step.context object, like so:
this.dialogs.add(new TextPrompt(MONEY_PROMPT, this.validateMoneyInput.bind(this)));
async moneyStep(step) {
await step.prompt(MONEY_PROMPT, `What is the value of the transfer?`,
{
retryPrompt: 'Try again. What is the value of the transfer?'
}
);
}
async validateMoneyInput(step) {
// LUIS results passed into turnContext are retrieved
const intent = step.context.topIntent['intent'];
const entity = step.context.topIntent.entities;
console.log(entity);
// Validation based on matched intent
if (intent == 'Money') {
return await step.context.sendActivity('Validation succeeded');
} else if (intent != 'Money') {
return await step.context.sendActivity('Validation failed');
}
}
I also assigned the entities value to a variable for accessing since you were asking about it.
Hope of help!
I'm using Puppeteer to make the requests (it uses Chromium in the background):
;(async () => {
const browser = await puppeteer.launch()
const page = await browser.newPage()
await page.goto(`https://www.google.com/search?tbm=bks&q=%22this+is%22`)
const result = await page.evaluate(() => {
const stats = document.querySelector('#resultStats')
return stats.textContent
})
await browser.close()
res.send(result)
})()
Since I live in Taiwan, the results are being returned in Chinese.
How can I modify the URL or puppeteer's config so I get results in English?
Just add &hl=en in url:
await page.goto(`https://www.google.com/search?tbm=bks&q=%22this+is%22&hl=en`)