import Tracker from '../Tracker'; const md5 = require('md5'); export default class FacebookTracker extends Tracker{ constructor(worker, privacy, extensionfilter=[]){ super(worker, privacy); this.extensionfilter = extensionfilter; this.onStart = this.onStart.bind(this); this.rootSearch = "#contentArea div[data-gt='{\"ref\":\"nf_generic\"}']"; this.posts_seen = 0; this.posts_people_you_may_know = 0; this.posts_ignored = 0; this.posts_captured = 0; this.is_allowed = null; this.facebook_debug = false; this.facebook_events_debug = false; this.elements = []; this.elementStrings = ''; this.trackedToolbarButtons = []; this.eventElements = { allowNotToTracked: ['#leftCol ._3ph1.sp_387n34yO1ZQ', '#fbProfileCover'], //articles: ['#content_container [role="main"] .userContentWrapper', '#contentArea [role="article"] div[role="article"][data-testid="fbarticle_story"]'], likearticleButton: [ 'div[aria-label="Like"]:not(.buofh1pr)', 'div[aria-label="Remove Like"]:not(.buofh1pr)', 'div[aria-label="לייק"]:not(.buofh1pr)', 'div[aria-label="הסרת לייק"]:not(.buofh1pr)' ], likeComment: [{ query: '._6coi._6qw9 li:nth-child(1) a', parent: ['._4eek[role="article"]', 'div'], text: { parent: '._42ef', query: '._72vr > span'}, countComment: {parent: '._42ef', query: '._6cuq > span'}},{ query: '.UFILikeLink.UFIReactionLink', parent: '.UFIRow.UFIComment', text: { parent: '.UFICommentContentBlock', query: '.UFICommentBody' }, countComment: { parent: '.UFICommentContentBlock', query: '.UFICommentReactionsBling > span' } }], commentButton: [ '[aria-label="Leave a comment"]', '[aria-label="השאר תגובה"]', '._3hg-._42ft', '.comment_link', '._ipm._-56', '.UFIPagerLink', '._fmi._613v.UFIInputContainer' ], commentfields: [ '[aria-label="Write a comment"]', '[aria-label="כתיבת תגובה"]', '._5rpu' ], commentFromCommentButton: [ 'form.o6r2urh6.l9j0dhe7.b3i9ofy5.e72ty7fz.qlfml3jp.inkptoze.qmr60zad.rt8b4zig.n8ej3o3l.agehan2d.sk4xxmp2.j83agx80.bkfpd7mw' ], shareButtonBevor: [ 'a._2nj7', 'a.share_action_link' ], shareButton: [ 'a._2nj7', 'a.share_action_link' ], joinGroup: [ 'a._42ft._4jy0._21ku._4jy4' ] }; // the newsfeed and the public pages are treated differently this.is_newsfeed = false; this.is_public_page = false; this.is_profile = false; this.is_verified_page_or_profile = false; this.is_own_profile = false; this.public_arias = new Set([ 'Shared with Public', 'Shared with Public group', 'Mit Öffentlich geteilt', 'Mit Öffentliche Gruppe geteilt']); this.public_alts = new Set([ 'Public', "Öffentlich"]); this.verified_arias = new Set([ 'Verified Account', 'Verified account', 'Bestätigtes Konto']); this.custom_arias = new Set([ 'Shared with Custom', 'Mit Benutzerdefiniert geteilt']); // merge the two lists this.public_and_custom_arias = new Set([ ...this.public_arias, ...this.custom_arias]); this.people_you_may_know_arias = new Set([ 'People You May Know', 'Personen, die du vielleicht kennst']); this.create_pages_arias = new Set([ 'Create Page', 'Seite erstellen']); this.send_message_arias = new Set([ 'Message', 'Nachricht senden']); this.lastUrlPath = ''; this.startswith_allowlist = ['/spd/']; this.elite_accounts = new Set(['/spd/']); this.privacy_flags = { } this.logged_uid = null; this.logged_user_id = null; this.logged_username = null; this.username_from_url = null; this.logged_account_id = null; this.logged_fullname = null; this.logged_shortname = null; this.profile_id = null; this.reset_credentials(); } /** * [isAllow returns if the path is allowed in social media platforms] * @param {Location} [the location element to analyze the url] * @return {Boolean} [if it is allow according to social media platforms rules] */ get_is_sm_path_allowed(path){ if (!this.is_logged_in) { return true; } if (!path.endsWith('/')){ path = path + '/'; } return this._get_is_sm_path_allowed(path); } _is_newsfeed(path){ // if it is landing page if (this.is_logged_in){ if (!path.endsWith('/')){ path = path + '/'; } if (path == '/'){ return true; } } return false; } get_is_sm_path_allowed(path){ return true; //this.is_newsfeed || this.is_public_page || this.is_profile || this.is_verified_page_or_profile || !this.is_logged_in; } _is_public_page(){ let candidates = document.querySelectorAll(['a[role][aria-label]']); for (let i = 0; i < candidates.length; i++) { if (this.create_pages_arias.has(candidates[i].getAttribute('aria-label'))){ return true; } } return false; } _is_profile(){ let candidates = document.querySelectorAll(['div[role=button][aria-label]']); for (let i = 0; i < candidates.length; i++) { if (this.send_message_arias.has(candidates[i].getAttribute('aria-label'))){ return true; } } return false; } _is_verified_page_or_profile(){ let icon_title = document.querySelector('h1 i[aria-label]'); if (icon_title){ if (this.verified_arias.has(icon_title.getAttribute('aria-label'))){ return true; } } return false; } /** * [get_is_content_allowed check if url changed and search in dom if find some elements they not allowed and set this.allow] */ get_is_content_allowed() { // the content is always allowed because the path of the URL will // be used to control for everything return true; } /** * get the id given an anchor element using the img of the anchor * @param {Location} anchor html element (<a>) * @return {src} the user id that is taken from the id of the imate */ get_user_id_from_img(location){ if (location){ let imgid = location.querySelector('img').getAttribute('id'); if (imgid) { let imgid_parts = imgid.split('_'); if (imgid_parts.length > 0){ return imgid_parts[imgid_parts.length-1]; } } } return null; } /** * Setup the credentials for the logged user (if any) */ reset_credentials(){ // try now with the credentials let credentials = this.get_credentials_from_scripts(); if (credentials){ if (this.logged_user_id == null){ this.logged_user_id = credentials.USER_ID; } this.logged_account_id = credentials.ACCOUNT_ID; this.logged_fullname = credentials.NAME; this.logged_shortname = credentials.SHORT_NAME; } this.logged_username = this.get_logged_username_from_scripts(); if (this.logged_username){ this.logged_uid = this.logged_username; } else { this.logged_uid = this.logged_user_id; } // logged in if (this.logged_uid || credentials){ this.is_logged_in = true; this.is_content_allowed = this.get_is_content_allowed(); // not logged in } else { this.is_logged_in = false; this.is_content_allowed = true; } // check if it is the newsfeed page (cheap check) this.is_newsfeed = this._is_newsfeed(location.pathname); this.is_own_profile = this.logged_username == this.username_from_url; // check if this is a public page only if it is not the newsfeed // the check for public page might be heavy if (!this.is_newsfeed){ this.is_public_page = this._is_public_page(); } else { this.is_public_page = false; } // check if this is a profile page only if it is not the newsfeed // the check for public page might be heavy if (!this.is_newsfeed && !this.is_public_page){ this.is_profile = this._is_profile(); } else { this.is_profile = false; } // check if the page or profile are verified this.is_verified_page_or_profile = this._is_verified_page_or_profile() // check if the profile id parameter is in the url bar this.profile_id = this.get_profile_id_from_url(location); // is social media path allowed this.is_sm_path_allowed = this.get_is_sm_path_allowed(location.pathname); console.log('IS ALLOWED', location.pathname, this.is_sm_path_allowed); if(this.facebook_debug) { let style = ''; if (this.is_verified_page_or_profile) { style += "border-top:7px solid green !important;"; } else { style += "border-top:7px solid red !important;"; } if (this.is_newsfeed) { style += "border-right:7px solid green !important;"; } else { style += "border-right:7px solid red !important;"; } if (this.is_public_page) { style += "border-bottom:7px solid green !important;"; } else { style += "border-bottom:7px solid red !important;"; } if (this.is_profile) { style += "border-left:7px solid green !important;"; } else { style += "border-left:7px solid red !important;"; } if (this.is_sm_path_allowed) { style += "outline:7px solid blue !important;"; } else { style += "outline:7px solid red !important;"; } let logo = document.querySelector('a'); if (logo) { logo.setAttribute("style", style); } } } /** Load credentials (USER_ID, SHORT_NAME, NAME and ACCOUNT_ID) from the scripts in Facebook returns a dictionary with the credentials **/ get_credentials_from_scripts() { try{ let scripts = document.querySelectorAll('script:not([src])'); for (var i = 0; i < scripts.length; i++) { let sc = scripts[i].textContent; if (sc.startsWith('requireLazy(["JSScheduler","ServerJS')) { return JSON.parse('{' + sc.match(/"USER_ID":".*?"|"SHORT_NAME":".*?"|"NAME":".*?"|"ACCOUNT_ID":".*?"/g).join(',') + '}'); } } } catch(e){ } return null; } /** Load the username from the scripts in Facebook returns a dictionary with the credentials **/ get_logged_username_from_scripts() { try{ this.username_from_url = this.get_username_from_url(location); let scripts = document.querySelectorAll('script:not([src])'); for (var i = 0; i < scripts.length; i++) { let sc = scripts[i].textContent; if (sc.startsWith('requireLazy(["Bootloader')) { let user_match = sc.match(/"username":".*?"/g); if (user_match){ let username = JSON.parse('{' + user_match.join(',') + '}').username; // if the username in the url exist and it is different than the username // in the script, then return that one if (this.username_from_url && this.username_from_url != username){ return username; } } } } if (this.username_from_url){ return this.username_from_url; } } catch(e){ } return null; } /** * Comapare an anchor selector to the logged in user * @param {target} html element * @param {str} with the selector that will be compared to the logged user * @return {Boolean} if it is the same as the logged in user */ is_link_same_as_logged_user(target, selector){ if (this.logged_uid){ let profile_uid = this.get_username_or_id_from_url(target.querySelector(selector)); if (profile_uid){ return this.logged_uid == profile_uid; } } return null; } /** * get the username or id given an anchor element * @param {Location} anchor html element (<a>) * @return {[type]} the username or id found in the anchor elment */ get_username_or_id_from_url(location){ if (location) { let username = this.get_username_from_url(location); if (username) { return username; } return this.get_user_id(location); } return null; } /** * Get the username in an anchor * @param {Location} html anchor (<a>) element in which the username will be searched * @return {str} the username */ get_username_from_url(location){ if (location && location.pathname) { let username = location.pathname.split('/'); if (username.length > 1) { return username[1]; } } return null; } /** * Get the profile id in an anchor * @param {Location} html anchor (<a>) element in which the username will be searched * @return {str} the username */ get_profile_id_from_url(location){ return new URLSearchParams(location.search).get('profile_id'); } /** * Get the id of the user in an anchor * @param {Location} html anchor (<a>) element in which the id will be searched * @return {str} the id */ get_user_id(location){ if (location) { let id = this.findGetParameter('id', location.search); return id; } return null; } /** * [_getValues return values of article] * @param {Object} target * @return {Array} */ _getValues(target){ let search = [ { name: 'webtracker-article-id', default: undefined, }, { name: 'article-time', query: ['._5ptz'], default: undefined, filter: e => e.getAttribute('title') }, { name: 'article-link', query: ['.oajrlxb2.g5ia77u1.qu0x051f.esr5mh6w.e9989ue4.r7d6kgcz.rq0escxv.nhd2j8a9.nc684nl6.p7hjln8o.kvgmc6g5.cxmmr5t8.oygrvhab.hcukyx3x.jb3vyjys.rz4wbd8a.qt6c0cv9.a8nywdso.i1ao9s8h.esuyzwwr.f1sip0of.lzcic4wl.gmql0nx0.gpro0wi8.b1v8xokw'], default: undefined, filter: e => e.getAttribute('href').split('?')[0] }, { name: 'article-headertext', query: ['.kvgmc6g5.cxmmr5t8.oygrvhab.hcukyx3x.c1et5uql.ii04i59q'], default: undefined, filter: e => e.textContent }, { name: 'article-publisher-name', query: ['h4 a span'], default: undefined, filter: e => e.textContent }, { name: 'article-count-likes', query: ['.gpro0wi8.cwj9ozl2.bzsjyuwj.ja2t1vim'], default: undefined, filter: e => { let re = /\b(\d+\.?\d*)(K?)\b/g; const matches = e.textContent.match(re); console.log(matches); if (matches === null) { return 1; } else { let lastElement = matches[matches.length -1]; if (lastElement.charAt(lastElement.length-1) == 'K') { return parseFloat(lastElement.slice(0, -1))*1000; } else { return parseInt(lastElement); } } } }, { name: 'article-count-comments', query: ['.gtad4xkn > .oajrlxb2.g5ia77u1.qu0x051f.esr5mh6w.e9989ue4.r7d6kgcz.rq0escxv.nhd2j8a9.nc684nl6.p7hjln8o.kvgmc6g5.cxmmr5t8.oygrvhab.hcukyx3x.jb3vyjys.rz4wbd8a.qt6c0cv9.a8nywdso.i1ao9s8h.esuyzwwr.f1sip0of.lzcic4wl.l9j0dhe7.abiwlrkh.gpro0wi8.dwo3fsh8.ow4ym5g4.auili1gw.du4w35lb.gmql0nx0'], default: undefined, filter: e => { let num = e.textContent.split(' ')[0]; if (num.charAt(num.length-1) == 'K') { return parseFloat(num.slice(0, -1))*1000; } else { return parseInt(num); } } }, { name: 'article-count-shares', query: ['.gtad4xkn > .tojvnm2t.a6sixzi8.abs2jz4q.a8s20v7p.t1p8iaqh.k5wvi7nf.q3lfd5jv.pk4s997a.bipmatt0.cebpdrjk.qowsmv63.owwhemhu.dp1hu0rb.dhp61c6y.iyyx5f41'], default: undefined, filter: e => { let num = e.textContent.split(' ')[0]; if (num.charAt(num.length-1) == 'K') { return parseFloat(num.slice(0, -1))*1000; } else { return parseInt(num); } } }, { name: 'article-count-contentType', default: undefined, query: ['._3x-2'], filter: e => { if(e.querySelectorAll('a[target="_blank"][rel="noopener nofollow"]').length>0){ return 'link'; }else if(e.querySelectorAll('a._4-eo').length>0){ return 'picture'; }else if(e.querySelectorAll('video').length>0){ return 'video'; } return 'message'; } } ], values = []; for (let s of search) { try { let value = s.default; if (s.name == 'webtracker-article-id') { value = target.getAttribute('webtracker-article-id'); } else { for (let query of s.query) { var r = target.querySelectorAll(query); if(r.length>0){ r = r[0]; let data = s.filter(r); if(data!=null) value = data; } else if (s.name == 'article-count-comments' || s.name == 'article-count-shares' || s.name == 'article-count-likes') { value = 0; } }//for } values.push({name: s.name, value: value}) } catch (err) { console.log(err); console.log(target, err.toString()); } }//for return values; } is_verified(target){ let verified_icon = target.querySelector(["span > div > span > i[aria-label]", "h2 span span > i[aria-label]"]); if (verified_icon && this.verified_arias.has(verified_icon.getAttribute('aria-label'))){ return true; } return false; } _is_public_or_custom_verified(aria_label, target){ if (this.public_arias.has(aria_label)) { return true; } else if (this.custom_arias.has(aria_label)){ if (this.is_verified(target)){ return true; } } return false; } /** * [_isPublicArticle checks if element is for the public oder private] * @param {Object} target [DomElement] * @return {Boolean} */ _isPublicArticle(target){ // check if the icon has a public aria label let privacy_icon = target.querySelector(":not(h2) > span > span > span > i"); if (privacy_icon){ let aria_label = privacy_icon.getAttribute('aria-label'); if (aria_label) { // WARNING: the order of ifs are important, make sure that // is_verified_page_or_profile takes precedence if (this.is_newsfeed) { if (this._is_public_or_custom_verified(aria_label, target)){ return true; } } else if (this.is_verified_page_or_profile){ if (this.public_and_custom_arias.has(aria_label)) { return true; } } else if (this.is_public_page){ if (this.public_and_custom_arias.has(aria_label)) { return true; } // this must go after this.is_verified_page_or_profile } else if (this.is_profile){ if (this._is_public_or_custom_verified(aria_label, target)){ return true; } } else if (this.is_own_profile){ if (this.public_arias.has(aria_label)){ return true; } } else { if (this._is_public_or_custom_verified(aria_label, target)){ return true; } } } } // check if the icon has a public aria label privacy_icon = target.querySelector("div > div > img"); if (privacy_icon){ let alt_label = privacy_icon.getAttribute('alt'); if (alt_label) { if (this.public_alts.has(alt_label)) { return true; } } } return false; } /** * [_isPeopleYouMayKnowArticle check if it is a people you may know article] * @param {Object} target [DomElement] * @return {Boolean} */ _isPeopleYouMayKnowArticle(target){ // check if the icon has a public aria label let element = target.querySelector('[role^=region]'); if (element){ let aria_label = element.getAttribute('aria-label'); if (aria_label && this.people_you_may_know_arias.has(aria_label)) { return true; } } return false; } /** * [_isPrivate checks if element is for the public or private] * @param {Object} target [DomElement] * @return {Boolean} */ _isPrivate(target){ //let a_list = target.querySelectorAll('.fwn.fcg a'); let a_list = target.querySelectorAll('.sx_94649f'); let c = 0; for (let a in a_list.length) { let attr = a_list[a].getAttribute("data-hovercard"); if(attr != null && attr.indexOf('user') > 0){ c++; } } return c==0 && a_list.length > 0; } /** * [_getPublicArticels return elements of public articles] * @return {Array} found */ _getPublicArticels(){ // if it is not the newsfeed or the public page, there is nothing // to do here, get out. // if (!this.is_newsfeed && !this.is_public_page && !this.is_profile && !this.is_verified_page_or_profile){ // return []; // } let bucket = []; //for (let query of this.eventElements.articles) { //let found = document.querySelectorAll('.userContentWrapper:not(.tracked), div[role="article"]:not(.tracked)'); let found = document.querySelectorAll('[role="article"][aria-describedby]:not(.tracked)'); this.posts_seen += found.length; let length = found.length; for (var i = 0; i < length; i++) { found[i].classList.add('tracked'); found[i].setAttribute('webtracker-article-id', Math.random()); if (this._isPublicArticle(found[i])){ this.posts_captured += 1; if(this.facebook_debug) found[i].setAttribute("style", "border:3px solid green !important;"); // This have not been tested since April 2021: //this._setLikeEvent(found[i]); //this._setCommentEvent(found[i]); //this._eventcommentFromCommentButton(found[i]); // This has not been tested since 2020 //this._setLikeCommentEvent(found[i]); //haven't been fixed and currently unsupported //this._setShareEvent(found[i]); //currently unsupported and not working bucket.push(found[i]) }else{ if (this._isPeopleYouMayKnowArticle(found[i])){ this.posts_people_you_may_know += 1; if(this.facebook_debug) found[i].setAttribute("style", "border:3px solid yellow !important;"); } else { this.posts_ignored += 1; if(this.facebook_debug) found[i].setAttribute("style", "border:3px solid red !important;"); } } } //return bucket.filter(e => e!=undefined); return bucket; } /** * [_setCommentEvent set event handling of comment button fields] * @param {Object} article */ _setCommentEvent(article){ setTimeout(()=>{ for (let query of this.eventElements.commentButton) { let commentButtons = article.querySelectorAll(query+':not(.tracked)'); for (var i = 0; i < commentButtons.length; i++) { commentButtons[i].classList.add('tracked'); if(this.facebook_debug) commentButtons[i].setAttribute("style", "border:3px solid yellow !important;"); commentButtons[i].addEventListener('click', () => { this._eventComment(article, comment => { this.eventFn.onEvent( { event: 'comment', type: 'article', values: this._getValues(article).concat([ {name: 'comment', value: comment}, ]) } ) }); this._eventcommentFromCommentButton(article); this._setLikeCommentEvent(article, 100); // this._setCommentEvent(article); }) } } }, 1000); } /** * [_eventcommentFromCommentButton set event handling for comment from some comment field] * @param {Object} article * @param {Number} timeout [default: 1000] */ _eventcommentFromCommentButton(article, timeout=1000){ setTimeout(()=>{ for (let query of this.eventElements.commentFromCommentButton) { let commentButtons = article.querySelectorAll(query+':not(.tracked)'); for (var i = 0; i < commentButtons.length; i++) { commentButtons[i].classList.add('tracked'); if(this.facebook_debug) commentButtons[i].setAttribute("style", "border:3px solid red !important;"); commentButtons[i].addEventListener('click', e => { setTimeout(()=>{ this._eventComment(article, comment => { // console.log(found, comment); this.eventFn.onEvent( { event: 'comment', type: 'postanswer', values: this._getValues(article).concat([ {name: 'comment', value: comment}, //{name: 'postanswer-count-likes', value: null}, //{name: 'postanswer-text', value: null} ]) } ) //if(this.facebook_debug) console.log('commtent '+comment+' auf comment '+ text); }, 0); }, 500); }) } }//for commentFromCommentButton }, timeout) } /** * [_eventComment set keyup event to elements for write some comment] * @param {Object} article * @param {Function} fn [default: ()=>{}] * @param {Number} timeout [default: 1000] */ _eventComment(article, fn=()=>{}, timeout=1000){ setTimeout(()=>{ for (let query of this.eventElements.commentfields) { let commentfields = article.querySelectorAll(query+':not(.tracked)'); for (var i = 0; i < commentfields.length; i++) { commentfields[i].classList.add('tracked'); if(this.facebook_debug) commentfields[i].setAttribute("style", "border:3px solid pink !important;"); commentfields[i].addEventListener('keydown', e => { let spans = e.srcElement.querySelectorAll('span[data-text="true"]'); if(spans.length>0){ let comment = spans[spans.length-1].textContent; if(this.facebook_events_debug) fn('TEST '+comment); if(e.keyCode==13){ //if(this.facebook_debug) console.log('comment', comment); fn(comment); } } }) }//for }//for }, timeout); } /** * [_setShareEvent set the share event] * @param {Object} article * @param {Boolean} after [default: false] */ _setShareEvent(article, after=false){ let findShareButton = () => { let shareButton = document.querySelectorAll('[aria-label="Send this to friends or post it on your timeline."]:not(.tracked)'); setTimeout(()=>{ for (var i = 0; i < shareButton.length; i++) { shareButton[i].classList.add('tracked'); if(this.facebook_debug) shareButton[i].setAttribute("style", "border:3px solid red !important;"); let shares = shareButton[i].querySelectorAll('ul li a:not(.tracked)'); for (var i = 0; i < shares.length; i++) { shares[i].classList.add('tracked'); if(this.facebook_debug) shares[i].setAttribute("style", "border:3px solid red !important;"); shares[i].addEventListener('click', e => { this.eventFn.onEvent( { event: 'share', type: 'article', values: this._getValues(article).concat([ {name: 'choice', value: e.srcElement.textContent}, ]) } ) //console.log('share', e.srcElement.textContent); }); if(this.facebook_events_debug) shares[i].addEventListener('mouseover', e => { this.eventFn.onEvent( { event: 'share', type: 'postanswer', values: this._getValues(article).concat([ {name: 'value', value: e.srcElement.textContent}, ]) } ) }); } } }, 500) } setTimeout(()=>{ for (let query of this.eventElements.shareButtonBevor) { let shares = article.querySelectorAll(query+':not(.tracked)'); for (let i = 0; i < shares.length; i++) { shares[i].classList.add('tracked'); if(this.facebook_debug) shares[i].setAttribute("style", "border:3px solid red !important;"); if(after==false){ if(this.facebook_events_debug) shares[i].addEventListener('mouseover', e => { setTimeout(()=>this._setShareEvent(article, true), 100) }) } shares[i].addEventListener('click', e => { setTimeout(()=> findShareButton(), 500); }) }//for }//for }, 500) } /** * [getValueOfLikeNumber translate name of number from like event] * @param {Number} nr * @return {String} */ getValueOfLikeNumber(nr){ let value = ''; switch (nr) { case 1: value = 'like'; break; case 2: value = 'love'; break; case 3: value = 'wow'; break; case 4: value = 'haha'; break; case 7: value = 'sad'; break; case 8: value = 'angry'; break; case 16: value = 'care'; break; default: value = 'unkown'; } return value; } /** * [_setLikeCommentEvent set like Event for comment like Button] * @param {Object} article * @param {Number} timeout [default: 100] */ _setLikeCommentEvent(article, timeout=0){ setTimeout(() => { for (let s of this.eventElements.likeComment) { let buttons = article.querySelectorAll(s.query); for (var i = 0; i < buttons.length; i++) { if(this.facebook_debug) buttons[i].setAttribute("style", "border:3px solid red !important;"); buttons[i].addEventListener('click', e => { let text = this.getParentElement(e.srcElement, s.text.parent).querySelectorAll(s.text.query)[0].textContent; let count = 0, countElements = this.getParentElement(e.srcElement, s.countComment.parent).querySelectorAll(s.countComment.query); if(countElements.length>0) count = parseInt(countElements[0].textContent, 10); if (buttons[i].classList.contains('_3_16')){ this.eventFn.onEvent({ event: 'undo', type: 'postanswer', values: this._getValues(article).concat([ {name: 'reaction-value', value: 'undo'}, {name: 'postanswer-count-likes', value: count}, {name: 'postanswer-text', value: text} ]) }); } else { this.eventFn.onEvent({ event: 'like', type: 'postanswer', values: this._getValues(article).concat([ {name: 'like-value', value: this.getValueOfLikeNumber(1)}, {name: 'postanswer-count-likes', value: count}, {name: 'postanswer-text', value: text} ]) }); } //if(this.facebook_debug) console.log('like comment 1 text => ', text); }) buttons[i].addEventListener('mouseover', e => { let text = this.getParentElement(e.srcElement, s.text.parent).querySelectorAll(s.text.query)[0].textContent; let count = 0, countElements = this.getParentElement(e.srcElement, s.countComment.parent).querySelectorAll(s.countComment.query); if(countElements.length>0){ count = parseInt(countElements[0].textContent, 10); } this._toolbarHandler(nr => { this.eventFn.onEvent( { event: 'reaction', type: 'postanswer', values: this._getValues(article).concat([{ name: 'reaction-value', value: nr['data_reaction'], aria_label: nr['aria_label'], reaction: this.getValueOfLikeNumber(nr['data_reaction']) }, { name: 'postanswer-count-likes', value: count },{ name: 'postText', value: text } ]) } ) // console.log('like comment '+nr+' text => ', text); }) }) } } }, timeout) } /** * [_setLikeEvent like event] * @param {Object} article */ _setLikeEvent(article){ setTimeout(() => { for (let query of this.eventElements.likearticleButton) { let buttons = article.querySelectorAll(query); for (var i = 0; i < buttons.length; i++) { if(this.facebook_debug) buttons[i].setAttribute("style", "border:3px solid purple !important;"); let button = buttons[i]; button.addEventListener('click', e =>{ if (button.getAttribute('aria-label') === 'Remove Like'){ this.eventFn.onEvent({ event: 'undo', type: 'article', values: this._getValues(article).concat([ {name: 'reaction-value', value: 'undo'} ]) }); } else { this.eventFn.onEvent({ event: 'like', type: 'article', values: this._getValues(article).concat([ {name: 'like-value', value: this.getValueOfLikeNumber(1)} ]) }) } //if(this.facebook_debug) console.log('like 1', article); }) buttons[i].addEventListener('mouseenter', ()=> { setTimeout(() => { // console.log(this._getValues(article)); let toolbar = document.querySelector('div.j83agx80[role="toolbar"]'); let button = toolbar.querySelector('div') button.addEventListener('click', e =>{ if ((button.getAttribute('aria-label') === 'לייק') || (button.getAttribute('aria-label') === 'Like')){ this.eventFn.onEvent({ event: 'like', type: 'article', values: this._getValues(article).concat([ {name: 'like-value', value: this.getValueOfLikeNumber(1)} ]) }) } }) }, 700) }) } } }, 0) } /** * get the metadata from the file * @return {object} the metadata of the html */ getMetadata(){ let metadata = super.getMetadata(); let anonym = {}; if (this.logged_user_id) { anonym['user_id'] = this.logged_user_id; this.privacy_flags['has_user_id'] = true; } else { this.privacy_flags['has_user_id'] = false; } if (this.logged_username) { anonym['username'] = this.logged_username; this.privacy_flags['has_username'] = true; } else { this.privacy_flags['has_username'] = false; } if (this.logged_account_id) { anonym['account_id'] = this.logged_account_id; this.privacy_flags['has_account_id'] = true; } else { this.privacy_flags['has_account_id'] = false; } if (this.profile_id) { anonym['profile_id'] = this.profile_id; this.privacy_flags['has_profile_id'] = true; } else { this.privacy_flags['has_profile_id'] = false; } if (this.logged_fullname) { anonym['fullname'] = this.logged_fullname; this.privacy_flags['has_fullname'] = true; } else { this.privacy_flags['has_fullname'] = false; } if (this.logged_shortname) { anonym['shortname'] = this.logged_shortname; this.privacy_flags['has_shortname'] = true; } else { this.privacy_flags['has_shortname'] = false; } this.privacy_flags['is_profile'] = this.is_profile; this.privacy_flags['is_own_profile'] = this.is_own_profile; this.privacy_flags['is_newsfeed'] = this.is_newsfeed; this.privacy_flags['is_public_page'] = this.is_public_page; this.privacy_flags['is_verified_page_or_profile'] = this.is_verified_page_or_profile; this.privacy_flags['is_logged_in'] = this.is_logged_in; metadata['anonym'] = anonym; metadata['privacy_flags'] = this.privacy_flags; console.log('METADATA:', metadata); return metadata; } /** * [_toolbarHandler event for reaction of smileys] * @param {Function} fn [default: ()=>{}] */ _toolbarHandler(fn=()=>{}){ let remove = () => { let length = this.trackedToolbarButtons.length || 0 for (let b = 0; b < length; b++) { this.trackedToolbarButtons[b].classList.remove("tracked"); if(this.facebook_debug) this.trackedToolbarButtons[b].setAttribute("style", "border: green"); this.trackedToolbarButtons[b].onclick = e => {}; //this.trackedToolbarButtons[b].onmouseover = e => {}; delete this.trackedToolbarButtons[b]; } this.trackedToolbarButtons = this.trackedToolbarButtons.filter(e => e!= undefined); } let fetch = layer => { let buttons = layer.querySelectorAll('[aria-label="Reactions"]:not(.tracked)'); for (let a = 0; a < buttons.length; a++) { buttons[a].classList.add('tracked'); this.trackedToolbarButtons.push(buttons[a]); if(this.facebook_debug) buttons[a].setAttribute("style", "border:3px solid blue !important;"); buttons[a].onclick = e => { //if(this.facebook_debug) console.log('click', e.srcElement.parentElement.getAttribute("data-reaction")); fn({ arial_label: e.srcElement.parentElement.parentElement.getAttribute("aria-label"), data_reaction: parseInt(e.srcElement.parentElement.getAttribute("data-reaction"), 10) }) } // if(this.facebook_events_debug) buttons[a].onmouseover = e =>{ // console.log('mouseOver'); // layer.stop(); // fn(parseInt(e.srcElement.parentElement.getAttribute("data-reaction"), 10)) // } } } setTimeout(() =>{ let layer = document.querySelectorAll('.uiLayer div[role="toolbar"]'); for (let i = 0; i < layer.length; i++) { if(this.facebook_debug) layer[i].setAttribute("style", "border:3px solid red !important;"); layer[i].timeouts = []; layer[i].timeouts.push(setTimeout(()=>{ //if(this.facebook_debug) console.log('START REMOVE'); if(this.facebook_debug) layer[i].setAttribute("style", "border: none"); remove(); }, 2000)) //if(this.facebook_debug) console.log('start=>', layer[i].timeouts); layer[i].stop = () => { for (let c in layer[i].timeouts) { if(typeof layer[i].timeouts[c] == 'number'){ clearTimeout(layer[i].timeouts[c]); delete layer[i].timeouts[c]; } } layer[i].timeouts = layer[i].timeouts.filter(e => e!= undefined); //if(this.facebook_debug) console.log('STOP', layer[i].timeouts); } layer[i].onmouseleave = e => { layer[i].stop(); layer[i].timeouts.push(setTimeout(()=>{ if(this.facebook_debug) layer[i].setAttribute("style", "border: none"); remove(); }, 1100)); } layer[i].onmouseover = e => { layer[i].stop(); fetch(layer[i]) } fetch(layer[i]) }//for layer }, 1000) } /** * [getDom return html content from public article] * @return {String} */ async getDom(){ return new Promise(async (resolve, reject) => { try { let found = this._getPublicArticels(); // if no entries were found, then this is not a timeline or profile page if (this.posts_seen== 0) { // if the user is not logged in, then default to the normal tracker if (!this.is_logged_in){ resolve(this._getDom()); } else { resolve('<html><head></head><body>No entries found</body></html>'); } // otherwise, the dom can be assembled } else { for (var i = 0; i < found.length; i++) { let cloned = found[i].cloneNode(true); //is this being used anywhere? No, but could be used for the comments? this.elements.push(found[i]); this.elementStrings += cloned.outerHTML } resolve('<html posts_seen="'+this.posts_seen+ '" posts_people_you_may_know="'+this.posts_people_you_may_know+ '" posts_ignored="'+this.posts_ignored+ '" posts_captured="'+this.posts_captured+ '" ><head></head><body>' +this.elementStrings+'</body>'+'</html>'); } } catch (err) { reject(err) } }); } /** * [onStart on start event] * @param {Function} fn */ onStart(fn){ setTimeout(() => { //if(this.facebook_debug) console.log('START!!!!'); fn(2500); }, 1000); } }