JS dispatchEvent not working - javascript

Usually i don't put this kind of so specific question in SO, but i'm struggling with this issue for days, so i'm seeking for some help here.
I'm building an app to automate a task in web version of Whatsapp (https://web.whatsapp.com/). My goal is to click on a button on the interface to show some options, and then click on the same button again to hide it.
To simulate what i want to do manually :
1 - Open Whatsapp Web.
2 - Click on the 'Attach' button on the upper right corner of the interface, as shown in the image below.
3 - The attach options will show, as the image below :
4 - Click on the 'Attach' button again, and the attach options will hide.
That's it, but i want do this programatically using Javascript (pure JS, no JQuery).
To achieve the task in step 2, i'm using the code below with success :
var nodes = document.getElementsByTagName('span');
if (typeof lastElementId == 'undefined')
var lastElementId = 0;
var result = undefined;
for (var i = 0; i < nodes.length; i++) {
var h = nodes[i].outerHTML;
var flag = false;
flag = (h.toLowerCase().indexOf('data-icon="clip') > -1);
if (flag) {
result = h;
lastElementId = i;
break;
}
}
if (result !== undefined) {
function triggerMouseEvent(node, eventType) {
var clickEvent = document.createEvent('MouseEvents');
clickEvent.initEvent(eventType, true, true);
node.dispatchEvent(clickEvent);
}
triggerMouseEvent(nodes[i], "mouseover");
triggerMouseEvent(nodes[i], "mousedown");
} else {
console.log('Not found');
}
;
The code above will work to do the step 2, but won't work to do step 4. Manually when i click in the Attach button after the options are show, the options will hide. But not using my JS code.
What am i missing here ?
Thanks in advance !

To fix the closing problem:
Right click on the attach element.
Select inspect element in chrome browser
In the right panel select Event Listeners tab and find mousedown section
Click the handler code and detect that we need to pass specific screenX and screenY to satisfy this particular business logic and pass through to n.uie.requestDismiss() part which apparently does what is says.
So now we have enough information to try a possible solution, which apparently works for now. Goes like this:
const toggleAttach = () => {
// select the span with reliable identification like data-*
const clipNode = document.querySelector('[data-icon="clip"]');
// take its element, i.e. the button itself
const clipButtonNode = clipNode.parentNode;
// extract the current offset position relative to the document
// more info here https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect
// we can use this for filling in the non-0 screenX and screenY
const clipButtonNodeClientRect = clipButtonNode.getBoundingClientRect();
clipButtonNode.dispatchEvent(new MouseEvent("mousedown", {
bubbles: true,
cancelable: true,
screenX: clipButtonNodeClientRect.x,
screenY: clipButtonNodeClientRect.y
}));
}
Now to understanding why the first mousedown works for opening:
This is much harder to reverse engineer, but what I managed to find is if you install React DevTools (since whatsapp web is written in React) extension and open its tab in DevTools you will see:
And there you will find:
So we can make a very vague conclusion that opening and closing is handled in separate functions. Rest is up to you to figure out.
Hope this helped.

Related

Google Tag Manager - Tags (type: event) stop working when browsing on a SPA

GTM tags that use the event type stop working properly as soon as a user browse my website. By "browse", I mean using client-side navigation, which doesn't refresh the page but only part of it (kinda the point of SPA)
You can reproduce this behaviour as follow:
Install Chrome plugin: https://chrome.google.com/webstore/detail/google-analytics-debugger/jnkmfdileelhofjcijamephohjechhna to be able to see the GTM events calls in the browser console
Go to https://staging.hep.loan-advisor.studylink.fr/idrac
Click on the top-left IDRAC logo, you should see logs on the console (event fired)
Go to https://staging.hep.loan-advisor.studylink.fr and select the "Idrac" choice from the select input, then click "Go", you should see logs in the console again (redirection detected, which creates a virtual pageview)
Click on the top-left IDRAC logo, you won't see any log this time
I believe that somehow GTM binds some DOM events upon loading that get cleaned when the DOM changes due to SPA internal redirection, which reloads the DOM, partially.
Those GTM tags (type: event) I'm talking about are basically triggers based on HTML class/id in the DOM. When I use the GTM Preview mode (debug), I can see that there is no gtm.linkClick fired when clicking the logo, if I got redirected from another page using a frontend-redirection. But if I refresh the page and click, it works just fine.
The simplest workaround I see is to disable client-side redirection but forcing a page refresh, but that's not a nice workaround and kills the point of building an SPA in the first place.
Maybe there is a way to force refresh the GTM script (need to clean it from the DOM and add it again because it won't trigger if already loaded), or to use a feature within GTM that binds everything again? I looked for the former and couldn't find any built-in function that would bind something to the DOM, and I'm not sure if it's the right approach.
Also, I may misunderstand what causes this behaviour, I'm just assuming here.
Workaround 1 - Hack GTM and force refresh DOM bindings:
I found a workaround, which also proves that the issues comes from Google Tag Manager itself.
By deleting the window.google_tag_manager global variable (which is obviously created by the GTM script) every time the user browses the app, it makes the GTM script think it isn't yet initialised, and it will init once more, binding events to the DOM once more.
delete window.google_tag_manager;
Beware! I also found out that doing so will mess up the number of events sent. The reason is still a bit unclear (I can see 2 reasons), but the side effect is that for every browser navigation done, one more event will be sent. For instance, if you don't do browser-side navigation, 1 event is sent (when an event triggers, which in my case happens when the user clicks on a link/image). If you navigate once through client-side navigation, then 2 events will be sent, if you browse once more (through client-side navigation) then 3 events will be sent in total, etc.
It's still better than getting nothing when browsing, but adds a side effect.
Here is the (uglyfied) piece of code, from the gtm.js script (with my own comments):
Ol.Dc = function() {
var a = y.google_tag_manager; // y is "window"
a || (a = y.google_tag_manager = {}); // Lookup window.google_tag_manager or default to `{}`
tb = a;
if (a[Ha.f]) { // Lookup for google_tag_manager[$GTM_ID], if found then it means GTM was already initialized
ub("Duplicate container installation abandoned: " + Ha.f); // Ha.f is the GTM ID as string
var b = tb.zones;
b && b.unregisterChild(Ha.f)
} else { // If nothing found, then initialize
a[Ha.f] = Ol;
hi();
th();
zk.push.apply(zk, Lk()); // Do some weird magic, I haven't dig deeper, but basically bind stuff
Ok(Ak, 0, "2:0,2:1,3:2,6:3,0:4,1:5,2:8,3:9,27:10,5:11,1:12,2:15,3:16,4:17,2:18,3:19,38:20,24:21,5:22,2:23,2:24,3:25,25:26,11:11,0:27,1:28,1:29,2:30,3:31,25:32,11:22,29:17,13:33,0:34,1:35,2:38,3:39,26:40,28:40,21:41,32:42,2:43,3:44,10:45,14:40,30:40,36:40,37:40,12:40,17:40,15:40,34:46,18:40,19:40,23:47,3:48,3:49,29:40,25:49,3:52,25:52,3:54,25:54,38:56,20:57,22:58,16:17,33:17,5:59,3:60,25:61,0:62,1:63,1:64,3:67,5:68,1:69,3:72,26:17,21:73,3:75,25:76,20:78,3:80,25:81,3:84,25:85,22:87,5:88,2:89,3:90,25:91,0:92,1:93,1:94,3:97,21:98,22:99,5:100,1:101,3:104,22:105,5:106,1:107,3:110,22:111,5:112,1:113,3:116,21:117,22:118,5:119,1:120,1:121,3:124,21:125,22:126,5:127,1:128,1:129,3:132,22:133,5:134,1:135,1:136,3:139,22:140,5:141,1:142,1:143,3:146,21:147,22:148,5:149,1:150,1:151,3:154,21:155,22:156,5:157,1:158,1:159,3:162,22:163,5:164,2:166,3:167,5:168,2:171,3:172,40:40,8:40,41:173,35:174,5:175,2:178,3:179,5:180,3:183,5:184,3:187,35:188,5:189,2:192,3:193,5:194,3:196,35:37,5:197,3:199,35:66,5:200,3:202,5:203,3:205,5:206,3:208,5:209,3:211,5:212,3:214,35:123,5:215,3:217,35:131,5:218,3:220,35:138,5:221,3:223,35:145,5:224,3:226,35:153,5:227,3:229,35:161,5:230,3:231,25:231,2:232,3:233,31:17,39:234,9:235,3:236,39:237,7:238,3:239,25:240,2:241,3:242,9:234,3:243,3:244,9:245,2:246,3:247,3:248,3:249,25:250,3:251,25:252,3:253,39:254,3:255,3:256,25:257,3:258,25:259,3:260,25:261,2:262,3:263");
Ok(Rg, 1, "O,IAc,IAA8,IAAEwH,IAAAAAg__,IAAEABAAAc,IAAEABAAAoB,IAAEABAAAIG,IAEAAAAAACI,IAAkAAAAAAAY,IAAkAAAAAAAAAM,IAAkAAAAAAAAAgB,IAAkAAAAAAAAAAG,IAAkAAAAAAAAAAAD,IAAEABAAAIAAAAAAAAAAAAAAAAAAAAAAAD,IAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAA8B,IAAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAEO,IAAEABAAAIAAAAAAAAAAAAAAAAAAAAAAAAw,IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAH,IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABJ,IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAx,IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAED,KAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAE,IAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAY,IAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgB,IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEAAAG,IAA0AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAI,IAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAw,IAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD,IAAkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAM,IAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAw");
Ok(Ck, 1, "x,RQ,AAACD,RAAAE,AAAAIY,AAACAAAAAAAgB,AAAAIIAAAAAAC,RAAAAAAAAAAAQ,AAAAAAAAAAAAAAgM,RAAAAAAAAAAAAAAQ,BAAAAAAAAAAgAAAAI,AAAAAAAAAAAAAAgEAC,AAACBAAAAAAAAAAAAg,AAACBAAAAAAAAAAAAAQ,AAAAIIAAAAAAAAAAAAg,BAAAAAAAAAAgAAAAAAAQ,AAAAIIAAAAAAAAAAAAAg,BAAAAAAAAAAgAAAAAAAAI,AAAAIIAAAAAAAAAAAAAAQ,AAACBAAAAAAAAAAAAAAAAE,AAAAIIAAAAAAAAAAAAAAAI,AAACBAAAAAAAAAAAAAAAAAE,AAAAIIAAAAAAAAAAAAAAAAI,AAACBAAAAAAAAAAAAAAAAAAE,AAAAIIAAAAAAAAAAAAAAAAAI");
Ok(Dk, 1, "IP,IgjB,IACAAgfAwBwH,IACAAgeAwBwDM,IACAAgUAwBADgTY,IACAAgWAwBQDAAAgH,IACAAgWAwBQDAAAAxB,IACAAgWAwBQDAAAABc,IACAAgWAwBQDAAAAAAP,IACAAgWAwBQDAAAAAAAP,IACAAgWAwBQDAAAAAAACH,IACAAgWAwBQDAAAAAAACgD,IACAAgWAwBQDAAAAAAAAAwD,IACAAgWAwBQDAAAAAAAAAAwD,IACAAgWAwBQDAAAAAAAAAAgwB,IAAAAAAAAAAAAAAAAAAAAAAAO,IAAAAAAAAAAAAAAAAAAAAAAAwf,IAAAAAAAAAAAAAAAAAAAAAAAAgD,IAAAAAAAAAAAAAAAAAAAAAAAAgM,IAAAAAAAAAAAAAAAAAAAAAAAQHwB,IAAAAAAAAAAAAAAAAAAAAAAAAAAO,IAAAAAAAAAAAAAAAAAAAAAAAQHAwB,IAAAAAAAAAAAAAAAAAAAAAAAQHAAO,IAAAAAAAAAAAAAAAAAAAAAAAAgAAw,IAAAAAAAAAAAAAAAAAAAAAAAAgAAAD,IAAAAAAAAAAAAAAAAAAAAAAAAgAAAM,IAAAAAAAAAAAAAAAAAAAAAAAAgAAAw,IAAAAAAAAAAAAAAAAAAAAAAAQHAAAAH,IAAAAAAAAAAAAAAAAAAAAAAAQHAAAA4,IAAAAAAAAAAAAAAAAAAAAAAAQHAAAAAH,IAAAAAAAAAAAAAAAAAAAAAAAQHAAAAA4,IAAAAAAAAAAAAAAAAAAAAAAAQHAAAAAAH,IAAAAAAAAAAAAAAAAAAAAAAAQHAAAAAA4");
Ok(ph, 2, "B:B::,C:C::,c:E::,oB:I::,AC:Q::,AM:g::,AY:AB::,Ao:AC::,AIB:AE::,IAG:AI::,IAY:AQ::,IAgB:Ag::,IAAG:AAB::,IAAY:AAC::,IAAgB:AAE::,C:AAI::,C:AAQ::,C:AAg::,C:AAAB::,C:AAAC::,C:AAAE::,C:AAAI::,C:AAAQ::,C:AAAg::,C:AAAAB::,C:AAAAC::,C:AAAAE::,C:AAAAI::,C:AAAAQ::,C:AAAAg::,C:AAAAAB::,C:AAAAAC::,C:AAAAAE::");
Ok(qh, 4, "7:,14:,37:,66:,71:,96:,103:,109:,115:,123:,131:,138:,145:,153:,161:,165:,170:,177:,182:,186:,191:,195:,198:,201:,204:,207:,210:,213:,216:,219:,222:,225:,228:");
for (var c = 0; c < Dk.length; c++) {
var d = Dk[c]
, e = 1;
d[ne] ? e = 2 : d[zf] && (e = 0);
Nb[c] = {
firingOption: e,
state: void 0
};
Ob[c] = []
}
u("//tagmanager.google.com/debug");
Ok(Vg, 3, "2,19,25,31,44,49,52,54,48,60,75,80,84,90,231,233,236,239,242,243,244,247,248,249,251,253,255,256,258,260,263");
Ok(Qk, 3, "9,16,39,67,72,97,104,110,116,124,132,139,146,154,162,167,172,179,183,187,193,196,199,202,205,208,211,214,217,220,223,226,229");
Ok(Rk, 4, "6:,13:,36:,65:,70:,95:,102:,108:,114:,122:,130:,137:,144:,152:,160:,70:,169:,176:,181:,185:,190:,36:,65:,95:,102:,108:,114:,122:,130:,137:,144:,152:,160:");
Ok(Sk, 5, "arg0,arg1,function,instance_name,once_per_event,tag_id,vendor_template_version,vtp_attribute,vtp_checkValidation,vtp_component,vtp_cookieDomain,vtp_dataLayerVersion,vtp_decorateFormsAutoLink,vtp_defaultValue,vtp_doubleClick,vtp_enableEcommerce,vtp_enableFirebaseCampaignData,vtp_enableLinkId,vtp_enableUaRlsa,vtp_enableUseInternalVersion,vtp_eventAction,vtp_eventCategory,vtp_eventLabel,vtp_gaSettings,vtp_hotjar_site_id,vtp_name,vtp_nonInteraction,vtp_originalTagType,vtp_overrideGaSettings,vtp_setDefaultValue,vtp_setTrackerName,vtp_stripWww,vtp_trackType,vtp_trackTypeIsEvent,vtp_trackingId,vtp_uniqueTriggerId,vtp_useDebugVersion,vtp_useHashAutoLink,vtp_value,vtp_varType,vtp_waitForTags,vtp_waitForTagsTimeout,vtp_fieldsToSet");
for (var f in Sk)
Sk.hasOwnProperty(f) && "function" == Sk[f] && (Pk = f);
var g = Pg("INIT"), h;
a: {
for (var k = [], l = 0; l < Rg.length; l++)
if (Rg[l]) {
var m = Sg(l);
k.push({
name: Vg[l],
type: m.type,
metadata: m
})
}
h = k;
break a;
h = void 0
}
g.macroInfo = h;
g.tagInfo = Vk();
Wg(g);
Hg();
Zg();
var n = y;
if ("interactive" == J.readyState && !J.createEventObject || "complete" == J.readyState)
jg();
else {
va(J, "DOMContentLoaded", jg);
va(J, "readystatechange", jg);
if (J.createEventObject && J.documentElement.doScroll) {
var p = !0;
try {
p = !n.frameElement
} catch (I) {}
p && lg()
}
va(n, "load", jg)
}
"complete" === J.readyState ? qg() : va(y, "load", qg);
a: {
if (!bl)
break a;
cl();
hl = 2;
Zk = void 0;
fl = {};
dl = {};
$k = void 0;
gl = {};
el = "";
y.setInterval(cl, 864E5);
y.setInterval(jl, 1E3);
}
Vl();
Pl();
a: {}
}
}
This is quite a hack, and doesn't play friendly with all tags, for instance, Hotjar that was loaded by GTM throws tons of errors about source-maps file not found and network errors to load files. I therefore removed it from GTM and I'll add it directly in my code, that's a small change.
I have no idea what side effects this hack may add, but based on my tests, all events trigger correctly no matter how deep I browse, and it works with previous/next browser features too.
I wish there was a way of doing this in a much cleaner way, like GTM would export a function so that developers can force refresh instead of doing this.

Insert a text into a textbox and simulate click on the button in Bot Framework's Web Chat

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

Detecting ReactJS Page Changes

I'm new to ReactJS so have been experimenting on some sites that use it. Right now, I'm trying to make an AJAX call to an API based on a pages URL. The problem is that whenever a link is clicked, my code breaks because it's not detecting a page change. My current approach is to listen for click events on all links and piggyback off two selectors I see appear when the page is loading (pageLoading--loading and pageLoading--finished). Is there a better way to detect page changes for single page applications? For situation reference since these are experiments, I have no access to the server. Below is my current code approach.
//When any link on the page is clicked
document.body.onclick = function(e){
e = e || event;
var from = findParent('a',e.target || e.srcElement);
if (from){
//check if pageLoading--finished ran then make an AJAX request
console.log(__reactContent['url']);
}
}
//find first parent with tagName [tagname]
function findParent(tagname,el){
if ((el.nodeName || el.tagName).toLowerCase()===tagname.toLowerCase()){
return el;
}
while (el = el.parentNode){
if ((el.nodeName || el.tagName).toLowerCase()===tagname.toLowerCase()){
return el;
}
}
return null;
}
Try using setState. That way react will notice page changes and handle events. Here's a quick example of adding new notes to a notes array.
addNote = () => {
this.setState({
notes: this.state.notes.concat([{
id: uuid.v4(),
task: 'new task'
}])
})
}
For some more info check out https://facebook.github.io/react/docs/interactivity-and-dynamic-uis.html

Post comments on Facebook page via console

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

Firefox add-on: how to tell if a window is in the background

In a Firefox Add-on SDK add-on, how do I tell whether a window is in the background, ie. visible but not focused?
For example, if I bring a different application to the foreground, the Firefox window becomes unfocused but is still visible.
The reason why I want to do this is because I have a CPU-intensive content script running in the active window, and I'd like to pause it to avoid unnecessary overhead whenever the user isn't actively engaged with the window - meaning it's in the background or minimized.
require("sdk/windows").activeWindow keeps returning the last clicked window even if it's in the background or minimized. There doesn't seem to be any property for the window's focus state.
I can also get use the following code to get an nsIDocShell:
var mostRecentWindow = require("sdk/window/utils").getMostRecentBrowserWindow();
var docShell = require("sdk/window/utils").getWindowDocShell(mostRecentWindow);
Now when I query the docShell.isActive property, it returns true even if the window is in the background.
The one advantage of docShell.isActive is that it returns false when the window is minimized, while activeWindow returns true even in this case. But it's still missing information about whether the window is in the background or not.
Based on the suggestion by #willlma, this code seems to do the trick:
const windows = require('sdk/windows').browserWindows;
const tabs = require("sdk/tabs");
var anyWindowActive = true;
var refreshTimeoutId;
windows.on('deactivate', function(window) {
if (window == windows.activeWindow) {
anyWindowActive = false;
}
clearTimeout(refreshTimeoutId);
refreshTimeoutId = setTimeout(refreshTabStates, 50);
});
windows.on('activate', function(window) {
anyWindowActive = true;
clearTimeout(refreshTimeoutId);
refreshTimeoutId = setTimeout(refreshTabStates, 50);
});
tabs.on('activate', function(tab) {
clearTimeout(refreshTimeoutId);
refreshTimeoutId = setTimeout(refreshTabStates, 50);
});
function refreshTabStates() {
refreshTimeoutId = null;
for (let win of windows) {
for (let tab of win.tabs) {
var shouldBeActive = anyWindowActive
&& tab == tabs.activeTab
&& win == windows.activeWindow;
notifyTab(tab, shouldBeActive);
}
}
}
where notifyTab() is a function that posts a message to that tab's content script (if any) about whether it should be running or not.
setTimeout is used to avoid multiple calls to refreshTabStates in quick succession. For example, if you click on an inactive tab in a window that's not the current one, that one click results in window.deactivate, window.activate and tab.activate events.
Also, the initial state is a problem. What if the user launches Firefox and puts it in the background before any script has managed to run?

Categories

Resources