Fiddle here: https://jsfiddle.net/chpaeeL9/1/
Microsoft Bot Framework has a webchat module that allows you to talk to your bot.
When the user clicks the Say Hi button, I want to place some text into the webchat's textbox, and click the Send button inside the webchat using JavaScript.
Sounds like something too easy, but it wasn't. Here's the code that I currently have, and it doesn't work: the click event somehow is not triggered.
$('#sayhibutton').click(function() {
$('.wc-console').addClass('has-text'); // works
$('.wc-shellinput').val("Hi bot!").change(); // works
$('.wc-send').click(); // doesn't work!
$('.wc-send svg').click(); // doesn't work either
});
Update: if that helps, it seems the interface is written using React.
Update: my question is different from my previous question about how to avoid iframes in webchat.
OK, for the lack of a better option my solution was a pretty dirty and ugly one.
Save the code in botchat.js into a file and reference that saved file from the HTML, rather than the CDN version.
Pretty-print the file in Chrome and find the line that says:
e.prototype.sendMessage = function() {
this.props.inputText.trim().length > 0 && this.props.sendMessage(this.props.inputText)
}
Replace the middle line with this:
this.textInput.value.trim().length > 0 && this.props.sendMessage(this.textInput.value)
This basically means to take the message text from this.textInput.value rather than from this.props.inputText, or in other words, take it directly from the textinput's DOM node.
Somehow triggering the change event on a textbox doesn't cause an actual change event on the textbox, which is why we need to do this.
this is an issue with react try this,
var input = document.getElementsByClassName("wc-shellinput")[0];
var lastValue = input.value;
input.value = 'YOUR MESSAGE';
var event = new CustomEvent('input', { bubbles: true });
// hack React15
event.simulated = true;
// hack React16
var tracker = input._valueTracker;
if (tracker) {
tracker.setValue(lastValue);
}
input.dispatchEvent(event);
//send the message
$(".wc-send:first").click();
to read more see this post: https://github.com/Microsoft/BotFramework-WebChat/issues/680
Like in the image, the Facebook comment box has no submit button, when you write something and press Enter button, the comment posted.
I want to submit the comment via JavaScript that running in console, but I tried to trigger Enter event, submit event of the DOM. Could not make it work.
The current comment boxes aren't a traditional <textarea> inside of a <form>. They're using the contenteditable attribute on a div. In order to submit in this scenario, you'd want to listen to one of the keyboard events (keydown, keypress, keyup) and look for the Enter key which is keycode 13.
Looks like FB is listening to the keydown evt in this case, so when I ran this code I was able to fake submit a comment:
function fireEvent(type, element) {
var evt;
if(document.createEvent) {
evt = document.createEvent("HTMLEvents");
evt.initEvent(type, true, true);
} else {
evt = document.createEventObject();
evt.eventType = type;
}
evt.eventName = type;
evt.keyCode = 13;
evt.which = 13;
if(document.createEvent) {
element.dispatchEvent(evt);
} else {
element.fireEvent("on" + evt.eventType, evt);
}
}
fireEvent('keydown', document.querySelector('[role="combobox"]._54-z span span'));
A couple of things to note about this. The class ._54-z was a class they just happened to use on my page. Your mileage may vary. Use dev tools to make sure you grab the right element (it should have the aria role "combobox"). Also, if you're looking to support older browsers, you're going to have to tweak the fireEvent function code above. I only tested the above example in the latest Chrome.
Finally, to complicate matters on your end, Facebook is using React which creates a virtual DOM representation of the current page. If you're manually typing in the characters into the combobox and then run the code above, it'll work as expected. But you will not be able to set the combobox's innermost <span>'s innerHTML to what you're looking to do and then trigger keydown. You'll likely need to trigger the change event on the combobox to ensure your message persists to the Virtual DOM.
That should get you started! Hope that helps!
Some years after, this post remains relevant and is actually the only one I found regarding this, whilst I was toying around trying to post to FB groups through JS code (a task similar to the original question).
At long last I cracked it - tested and works:
setTimeout(() => {
document.querySelector('[placeholder^="Write something"]').click();
setTimeout(() => {
let postText = "I'm a Facebook post from Javascript!";
let dataDiv = document.querySelector('[contenteditable] [data-offset-key]');
let dataKey = dataDiv.attributes["data-offset-key"].value;
//Better to construct the span structure exactly in the form FB does it
let spanHTML = `<span data-offset-key="${dataKey}"><span data-text="true">${postText}</span></span>`;
dataDiv.innerHTML = spanHTML;
let eventType = "input";
//This can probably be optimized, no need to fire events for so many elements
let div = document.querySelectorAll('div[role=presentation]')[1].parentElement.parentElement;
let collection = div.getElementsByTagName("*");
[...collection].forEach(elem => {
let evt = document.createEvent("HTMLEvents");
evt.initEvent(eventType, true, true); //second "true" is for bubbling - might be important
elem.dispatchEvent(evt);
});
//Clicking the post button
setTimeout(()=>{
document.querySelector('.rfloat button[type=submit][value="1"]').click();
},2000);
}, 4000);
}, 7000);
So here's the story, as I've learned from previous comments in this post and from digging into FB's code. FB uses React, thus changes to the DOM would not "catch on" as React uses virtual DOM. If you were to click "Post" after changing the DOM from JS, the text would not be posted. That's why you'd have to fire the events manually as was suggested here.
However - firing the right event for the right element is tricky business and has almost prevented me from succeeding. After some long hours I found that this code works, probably because it targets multiple elements, starting from a parent element of the group post, and drilling down to all child elements and firing the event for each one of them (this is the [...collection].forEach(elem => { bit). As written this can be obviously be optimized to find the one right element that needs to fire the event.
As for which event to fire, as was discussed here, I've experimented with several, and found "input" to be the one. Also, the code started working after I changed the second argument of initEvent to true - i.e. evt.initEvent(eventType, true, true). Not sure if this made a difference but I've had enough hours fiddling with this, if it works, that enough for me. BTW the setTimeouts can be played around with, of course.
(Unsuccessfully) Digging into FB's React Data Structure
Another note about a different path I tried to go and ended up being fruitless: using React Dev Tools Chrome extension, you're able to access the components themselves and all their props and states using $r. Surprisingly, this also works outside of the console, so using something like TamperMonkey to run JS code also works. I actually found where FB keeps the post text in the state. For reference, it's in a component called ComposerStatusAttachmentMentionsInputContainer that's in charge of the editor part of the post, and below is the code to access it.
$r actually provides access to a lot of React stuff, like setState. Theoritically I believed I could use that to set the state of the post text in React (if you know React, you'd agree that setState would be the right way to trigger a change that would stick).
However, after some long hours I found that this is VERY hard to do, since FB uses a framework on top of React called Draft.js, which handles all posts. This framework has it's own methods, classes, data structures and what not, and it's very hard to operate on those from "outside" without the source code.
I also tried manually firing the onchange functions attached to the components, which didn't work because I didn't have the right parameters, which are objects in the likes of editorContent and selectionContent from Draft.Js, which need to be carefully constructed using methods like Modifier from Draft.js that I didn't have access to (how the hell do you externally access a static method from a library entangled in the source code?? I didn't manage to).
Anyway, the code for accessing the state variable where the text is stored, provided you have React dev tools and you've highlighted ComposerStatusAttachmentMentionsInputContainer:
let blockMap = $r["state"].activeEditorState["$1"].currentContent.blockMap;
let innerObj = JSON.parse(JSON.stringify(blockMap)); //this is needed to get the next property as it's not static or something
let id = Object.keys(innerObj)[0]; //get the id from the obj property
console.log(innerObj[id].text); //this is it!
But as I wrote, this is pretty much useless :-)
as I wasn't able to post comments through the "normal" facebook page, I remembered that they also have the mobile version, which is on m.facebook. com, there, they still have the submit Button, so depending on your needs, this may be a good option
so, you could go to the mobile facebook post (eg https://m.facebook.com/${author}/posts/${postId}) and do
// Find the input element that saves the message to be posted
document.querySelector("input[name='comment_text']").value='MESSAGE TO POST';
// find the submit button, enable it and click it
const submitButton = document.querySelector("button[name='submit']");
submitButton.disabled = false;
submitButton.click();
Here is a working solution after 3 weeks of experimenting (using #Benjamin Solum's fireEvent function):
this version posts a comment only for the first post on the page (by using querySelector method)
this version can be used only on your personal wall (unless you change the query selectors)
function fireEvent(type, element, keyCode) {
var evt;
if(document.createEvent) {
evt = document.createEvent("HTMLEvents");
evt.initEvent(type, true, true);
} else {
evt = document.createEventObject();
evt.eventType = type;
}
evt.eventName = type;
if (keyCode !== undefined){
evt.keyCode = keyCode;
evt.which = keyCode;
}
if(document.createEvent) {
element.dispatchEvent(evt);
} else {
element.fireEvent("on" + evt.eventType, evt);
}
}
// clicking the comment link - it reveals the combobox
document.querySelector(".fbTimelineSection .comment_link").click();
setTimeout(function(){
var combobox = document.querySelector(".fbTimelineSection [role='combobox']");
var spanWrapper = document.querySelector(".fbTimelineSection [role='combobox'] span");
// add text to the combobox
spanWrapper.innerHTML = "<span data-text='true'>Thank you!</span>";
var spanElement = document.querySelector(".fbTimelineSection [role='combobox'] span span");
fireEvent("blur", combobox);
fireEvent("focus", combobox);
fireEvent("input", combobox);
fireEvent("keydown", spanElement, 13); // pushing enter
},2000);
function fireEvent(type, element) {
var evt;
if(document.createEvent) {
evt = document.createEvent("HTMLEvents");
evt.initEvent(type, true, true);
} else {
evt = document.createEventObject();
evt.eventType = type;
}
evt.eventName = type;
evt.keyCode = 13;
evt.which = 13;
if(document.createEvent) {
element.dispatchEvent(evt);
} else {
element.fireEvent("on" + evt.eventType, evt);
}
}
fireEvent('keydown', document.
to solve your question may you see this link, there is a example how to "Auto comment on a facebook post using JavaScript"
"Below are the steps:
Go to facebook page using m.facebook.com
Sign in and open any post.
Open developer mode in Chrome by pressing Ctrl+Shift+I
Navigate to the console.
Now, run the below script."
var count = 100;
var message = "Hi";
var loop = setInterval(function(){
var input = document.getElementsByName("comment_text")[0];
var submit = document.querySelector('button[type="submit"]');
submit.disabled = false;
input.value = message;
submit.click();
count -= 1;
if(count == 0)
{
clearInterval(loop);
}
}, 10000);
Kind regards
ref.: source page
I use websockets along with javascript and html5
I have the following code.
<input type="text" onFocus=" so = new websocket('ws://localhost:1234');" onBlur="so.close();" onKeyUp="keyup();" >
<script type='text/javascript'>
var so; //this is global...
//wait a little (user stops typing)
function keyup(){
if (timeout) {clearTimeout(timeout);}
timeout = setTimeout(lookup, 250);
}
function lookup(){
//it's global, so use it right away
so.onopen = function(){
//send data to server to get responce...
So, websockets open/close if user clicks/or not a textfield. User types something on textfield. The value of textfield is sended to the server, a query is executed and if there are matching results, they render on the screen of the user.
If I click on the text field I see in the console "connected" and if I click anyware else I see "closed normally", as I should. That's ok.
But when I type letteres to the textfield, to send data to server, nothing is sended. I see nothing in the console. I see no errors.
What am I missing? It's like so.onopen never get executed.
Any advice?
Thanks in advance
The problem is that you recreate the socket but don't bind the onopen event handler.
On focus, you should call a function doing both : create the websocket and bind the onopen event handler :
<input id=someid type="text" onBlur="so.close();" >
<script type='text/javascript'>
var so, field = document.getElementById('someid');
field.onfocus = function(){
so = new websocket('ws://localhost:1234');
so.onopen = function(){
// do things
}
}
I have a feed that uses AJAX to load in posts once the document is ready. Because the elements aren't ready at code execution, I have to use delegate to bind a lot of functions.
$(posts).delegate('.edit_comment_text','keyup',function(e){
return false;
if (e.keyCode == 13 && e.shiftKey == false) {
//Post the new comment and replace the textbox with a paragraph.
var message_area = $(this).parent('.comment_message');
var new_message = $(this).val();
var comment_id = $(this).closest('.group_comment').attr('data-comment');
var url = 'functions/edit_comment.php';
var array = {
'comment':comment_id,
'message':new_message
}
$.post(url, array, function(data){
console.log(data);
$(message_area).html("");
$(message_area).text(new_message);
});
}
})
This is the code I execute on the event. I've been trying to get the browser to stop dropping down a line when the user hits enter, but this action is performed before my code is even triggered. To prove it, I put the 'return false' at the very top of the block. With that example, none of my code is run when the user hits enter, but the textarea still drops a line.
Is it something to do with JQuery's delegate that causes my function to be called after the default events? They give examples of preventing default events in their documentation, so maybe it's a version bug or something?
Anyone have any ideas?
Thanks!
The input on my html form has a problem. Curently I am taking the input which is a twitter name into an Ajax function that calls tweets via php from the twitter api.
As I have a setInterval function to keep checking for updates, the input is passed again and again into the function to get tweets.
The problem is that the function seems to be reading the input directly from what is in the text input box. So if the user changes the text without pressing enter or hitting the button, the function keeps reading that text as the input. So the input entered initially is not fixed after pressing enter or hitting the button to submit.
Here is the html form taking in the input:
<div id="topdiv">Input Twitter ID: <input type="text" id="userid" onkeydown="if(event.keyCode===13) {document.getElementById('tweet-button').click();}">
<input type="submit" id="tweet-button" onclick="getStatusesX();" value="Get recent tweets">
<p id="tweetbox"></p>
</div>
Here are the functions:
function getStatusesX() {
var userID = document.getElementById("userid").value;
getStatuses(userID);
var intervalstop = setInterval(function() {getStatuses(userID);}, 20000);
clearInterval(intervalstop);}
//Create a cross-browser XMLHttp Request object
function getXMLHttp() {
var xmlhttp;
if (window.ActiveXObject) {
XMLHttp = new ActiveXObject("Microsoft.XMLHTTP");
} else if (window.XMLHttpRequest) {
XMLHttp = new XMLHttpRequest();
} else {
alert("Your browser does not support XMLHTTP!");
}
return XMLHttp;
}
//function that searches for the tweets via php
function getStatuses(userID){
XMLHttp1 = getXMLHttp();
//ajax call to a php file that will extract the tweets
XMLHttp1.open( 'GET', 'TwitterGlimpsePHP.php?userid='userID, true);
// Process the data when the ajax object changes its state
XMLHttp1.onreadystatechange = function() {
if( XMLHttp1.readyState == 4 ) {
if( XMLHttp1.status ==200 ) { //no problem has been detected
document.getElementById("tweetbox").innerHTML=XMLHttp1.responseText;
}
}
}
XMLHttp1.send(null);
}
I want the input to be taken as the text after enter is pressed. I have tried assigning it to variables but cannot work out why it keeps reading from the input field. Any help appreciated.
This is not an official answer - just trying to clear up my comments
This is what I mean by declaring outside the function...
var intervalstop;
function getStatusesX() {
clearInterval(intervalstop);
var userID = document.getElementById("userid").value;
getStatuses(userID);
intervalstop = setInterval(function() {getStatuses(userID);}, 20000);
}
that way you initialize the var and inside the function you clear first to ensure it's not compounding. Then you set the var to a new interval to begin again.
You said twitter doesn't like something about this code if the user clicks many times - Makes perfect sense. They will want to throttle the API to prevent someone from making 50,000 requests per minute cause of improper coding. You should check the API specs to make sure you're within a realistic zone and consider caching the results locally if you are pushing boundaries.
The issue is that you are re-reading the value of the textbox every time getStatuses is called.
Try capturing the value of the textbox first, and passing it into your getStatuses function:
So your new getStatusesX is:
function getStatusesX() {
var userID = document.getElementById("userid").value;
getStatuses(userID);
setInterval(function() {
getStatuses(userID);
}, 20000);
}
Update getStatuses to take a userID parameter and delete the line where you're reading the textbox's value inside of getStatuses.
That having been said, it might be an issue if this is possible to begin with - what if the user clicks the button to automatically refresh statuses multiple times? You might want to disable the button/textbox after it's been clicked, or have it clearInterval the old interval first.