From 073dacde80a23cba5a3d4328296c2d4ac86e9fbd Mon Sep 17 00:00:00 2001 From: Ilya Kreymer Date: Fri, 29 Nov 2024 17:52:08 -0800 Subject: [PATCH] add autoclick behavior! - support all 'a' links - store seen elements, not just links to avoid duplicate clicks --- dist/behaviors.js | 53 +++++++++++++++++- src/autoclick.ts | 133 ++++++++++++++++++++++++++++++++++++++++++++++ src/index.ts | 12 ++++- 3 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 src/autoclick.ts diff --git a/dist/behaviors.js b/dist/behaviors.js index 81e6131..0fa3278 100644 --- a/dist/behaviors.js +++ b/dist/behaviors.js @@ -1 +1,52 @@ -/*! behaviors.js is part of Webrecorder project. Copyright (C) 2021-2024, Webrecorder Software. Licensed under the Affero General Public License v3. */(()=>{var t={480:(t,e,i)=>{"use strict";function o(t,e=document,i=null){return s(t,!0,e,i)}function s(t,e,i,o=null){t=function(t){function e(){o&&(a.length>0&&/^[~+>]$/.test(a[a.length-1])&&a.push(" "),a.push(o))}var i,o,s,n,a=[],r=[0],l=0,c=/(?:[^\\]|(?:^|[^\\])(?:\\\\)+)$/,h=/^\s+$/,d=[/\s+|\/\*|["'>~+[(]/g,/\s+|\/\*|["'[\]()]/g,/\s+|\/\*|["'[\]()]/g,null,/\*\//g];for(t=t.trim();;){if(o="",(s=d[r[r.length-1]]).lastIndex=l,!(i=s.exec(t))){o=t.substr(l),e();break}if((n=l)<(l=s.lastIndex)-i[0].length&&(o=t.substring(n,l-i[0].length)),r[r.length-1]<3){if(e(),"["===i[0])r.push(1);else if("("===i[0])r.push(2);else if(/^["']$/.test(i[0]))r.push(3),d[3]=new RegExp(i[0],"g");else if("/*"===i[0])r.push(4);else if(/^[\])]$/.test(i[0])&&r.length>0)r.pop();else if(/^(?:\s+|[~+>])$/.test(i[0])&&(a.length>0&&!h.test(a[a.length-1])&&0===r[r.length-1]&&a.push(" "),1===r[r.length-1]&&5===a.length&&"="===a[2].charAt(a[2].length-1)&&(a[4]=" "+a[4]),h.test(i[0])))continue;a.push(i[0])}else a[a.length-1]+=o,c.test(a[a.length-1])&&(4===r[r.length-1]&&(a.length<2||h.test(a[a.length-2])?a.pop():a[a.length-1]=" ",i[0]=""),r.pop()),a[a.length-1]+=i[0]}return a.join("").trim()}(t);let s=i.querySelector(t);if(document.head.createShadowRoot||document.head.attachShadow){if(!e&&s)return s;return n(t,",").reduce(((t,s)=>{if(!e&&t)return t;const l=n(s.replace(/^\s+/g,"").replace(/\s*([>+~]+)\s*/g,"$1")," ").filter((t=>!!t)).map((t=>n(t,">"))),c=l.length-1,h=function(t=null,e,i=null){let o=[];if(i)o=i;else{const t=function(e){for(let i=0;ie.matches(t))):o}(l[c][l[c].length-1],i,o),d=function(t,e,i){return o=>{let s=e,n=o,l=!1;for(;n&&!a(n);){let e=!0;if(1===t[s].length)e=n.matches(t[s]);else{const o=[].concat(t[s]).reverse();let a=n;for(const t of o){if(!a||!a.matches(t)){e=!1;break}a=r(a,i)}}if(e&&0===s){l=!0;break}e&&s--,n=r(n,i)}return l}}(l,c,i);return e?t=t.concat(h.filter(d)):(t=h.find(d))||null}),e?[]:null)}return e?i.querySelectorAll(t):s}function n(t,e){return t.match(/\\?.|^$/g).reduce(((t,i)=>('"'!==i||t.sQuote?"'"!==i||t.quote?t.quote||t.sQuote||i!==e?t.a[t.a.length-1]+=i:t.a.push(""):(t.sQuote^=1,t.a[t.a.length-1]+=i):(t.quote^=1,t.a[t.a.length-1]+=i),t)),{a:[""]}).a}function a(t){return t.nodeType===Node.DOCUMENT_FRAGMENT_NODE||t.nodeType===Node.DOCUMENT_NODE}function r(t,e){const i=t.parentNode;return i&&i.host&&11===i.nodeType?i.host:i===e?null:i}i.d(e,{Jp:()=>o})},894:(t,e,i)=>{"use strict";i.r(e),i.d(e,{AutoFetcher:()=>c});var o=i(480),s=i(841),n=i(721);const a=/\s*(\S*\s+[\d.]+[wx]),|(?:\s*,(?:\s+|(?=https?:)))/,r=/(url\s*\(\s*[\\"']*)([^)'"]+)([\\"']*\s*\))/gi,l=/(@import\s*[\\"']*)([^)'";]+)([\\"']*\s*;?)/gi;class c extends s.BackgroundBehavior{constructor(t=!1,e=null,i=!1){super(),this.urlSet=new Set,this.pendingQueue=[],this.waitQueue=[],this.numPending=0,this.numDone=0,this.running=!1,this.headers=e||{},this._donePromise=new Promise((t=>this._markDone=t)),this.active=t,this.active&&i&&document.addEventListener("DOMContentLoaded",(()=>this.initObserver()))}get numFetching(){return this.numDone+this.numPending+this.pendingQueue.length}async start(){this.active&&(this.initObserver(),this.run(),(0,n.sleep)(500).then((()=>{this.pendingQueue.length||this.numPending||this._markDone(null)})))}done(){return this._donePromise}async run(){this.running=!0;for(const t of this.waitQueue)this.doFetch(t);this.waitQueue=[],this.extractSrcSrcSetAll(document),this.extractStyleSheets(),this.extractDataAttributes(document)}isValidUrl(t){return t&&(t.startsWith("http:")||t.startsWith("https:"))}queueUrl(t,e=!1){try{t=new URL(t,document.baseURI).href}catch(t){return!1}return!!this.isValidUrl(t)&&(!this.urlSet.has(t)&&(this.urlSet.add(t),this.running||e?this.doFetch(t):this.waitQueue.push(t),!0))}async doFetchStream(t){try{const e=await fetch(t,{credentials:"include",referrerPolicy:"origin-when-cross-origin"});this.debug(`Autofetch: started ${t}`);const i=e.body.getReader();let o=null;for(;(o=await i.read())&&!o.done;);return this.debug(`Autofetch: finished ${t}`),!0}catch(t){return this.debug(t),!1}}async doFetchNonCors(t){try{const e=new AbortController;await fetch(t,{mode:"no-cors",credentials:"include",referrerPolicy:"origin-when-cross-origin",headers:this.headers,abort:e}),e.abort(),this.debug(`Autofetch: started non-cors stream for ${t}`)}catch(e){this.debug(`Autofetch: failed non-cors for ${t}`)}}async doFetch(t){if(this.pendingQueue.push(t),this.numPending<=6){for(;this.pendingQueue.length>0;){const t=this.pendingQueue.shift();this.numPending++,!1||await this.doFetchNonCors(t),this.numPending--,this.numDone++}this.numPending||this._markDone(null)}}initObserver(){this.mutationObserver||(this.mutationObserver=new MutationObserver((t=>this.observeChange(t))),this.mutationObserver.observe(document.documentElement,{characterData:!1,characterDataOldValue:!1,attributes:!0,attributeOldValue:!0,subtree:!0,childList:!0,attributeFilter:["srcset","loading"]}))}processChangedNode(t){switch(t.nodeType){case Node.ATTRIBUTE_NODE:if("srcset"===t.nodeName&&this.extractSrcSetAttr(t.nodeValue),"loading"===t.nodeName&&"lazy"===t.nodeValue){const e=t.parentNode;"IMG"===e.tagName&&e.setAttribute("loading","eager")}break;case Node.TEXT_NODE:t.parentNode&&"STYLE"===t.parentNode.tagName&&this.extractStyleText(t.nodeValue);break;case Node.ELEMENT_NODE:t.sheet&&this.extractStyleSheet(t.sheet),this.extractSrcSrcSet(t),setTimeout((()=>this.extractSrcSrcSetAll(t)),1e3),setTimeout((()=>this.extractDataAttributes(t)),1e3)}}observeChange(t){for(const e of t)if(this.processChangedNode(e.target),"childList"===e.type)for(const t of e.addedNodes)this.processChangedNode(t)}extractSrcSrcSetAll(t){const e=(0,o.Jp)("img[srcset], img[data-srcset], img[data-src], noscript > img[src], img[loading='lazy'], video[srcset], video[data-srcset], video[data-src], audio[srcset], audio[data-srcset], audio[data-src], picture > source[srcset], picture > source[data-srcset], picture > source[data-src], video > source[srcset], video > source[data-srcset], video > source[data-src], audio > source[srcset], audio > source[data-srcset], audio > source[data-src]",t);for(const t of e)this.extractSrcSrcSet(t)}extractSrcSrcSet(t){if(!t||t.nodeType!==Node.ELEMENT_NODE)return void console.warn("No elem to extract from");const e=t.getAttribute("data-src");e&&this.queueUrl(e),"lazy"===t.getAttribute("loading")&&t.setAttribute("loading","eager");const i=t.getAttribute("srcset");i&&this.extractSrcSetAttr(i);const o=t.getAttribute("data-srcset");o&&this.extractSrcSetAttr(o);const s=t.getAttribute("src");s&&(i||o||"NOSCRIPT"===t.parentElement.tagName)&&this.queueUrl(s)}extractSrcSetAttr(t){for(const e of t.split(a))if(e){const t=e.trim().split(" ");this.queueUrl(t[0])}}extractStyleSheets(t){t=t||document;for(const e of t.styleSheets)this.extractStyleSheet(e)}extractStyleSheet(t){let e;try{e=t.cssRules||t.rules}catch(t){return void this.debug("Can't access stylesheet")}for(const t of e)t.type===CSSRule.MEDIA_RULE&&this.extractStyleText(t.cssText)}extractStyleText(t){const e=(t,e,i,o)=>(this.queueUrl(i),e+i+o);t.replace(r,e).replace(l,e)}extractDataAttributes(t){for(const e of(0,n.xpathNodes)("//@*[starts-with(name(), 'data-') and (starts-with(., 'http') or starts-with(., '/') or starts-with(., './') or starts-with(., '../'))]",t))this.queueUrl(e.value)}}c.id="AutoFetcher"},376:(t,e,i)=>{"use strict";i.r(e),i.d(e,{Autoplay:()=>a});var o=i(480),s=i(841),n=i(721);class a extends s.BackgroundBehavior{constructor(t,e=!1){super(),this.running=!1,this.polling=!1,this.mediaSet=new Set,this.autofetcher=t,this.numPlaying=0,this.promises=[],this._initDone=()=>null,this.promises.push(new Promise((t=>this._initDone=t))),e&&document.addEventListener("DOMContentLoaded",(()=>this.pollAudioVideo()))}async start(){this.running=!0,this.pollAudioVideo(),this._initDone()}async pollAudioVideo(){if(!this.polling){for(this.polling=!0;;){for(const[,t]of(0,o.Jp)("video, audio, picture").entries())if(!t.__bx_autoplay_found){if(!this.running){this.processFetchableUrl(t)&&(t.__bx_autoplay_found=!0);continue}await this.loadMedia(t),t.__bx_autoplay_found=!0}await(0,n.sleep)(500)}this.polling=!1}}fetchSrcUrl(t){const e=t.src||t.currentSrc;return!!e&&(!(!e.startsWith("http:")&&!e.startsWith("https:"))&&(this.mediaSet.has(e)||(this.debug("fetch media source URL: "+e),this.mediaSet.add(e),this.autofetcher.queueUrl(e)),!0))}processFetchableUrl(t){let e=this.fetchSrcUrl(t);const i=t.querySelectorAll("source");for(const t of i){const i=this.fetchSrcUrl(t);e=e||i}return e}async loadMedia(t){this.debug("processing media element: "+t.outerHTML);const e=this.processFetchableUrl(t);t.play?e?t.paused||(t.pause(),this.debug("media URL found, pausing playback")):t.paused||t.currentTime?(t.paused?this.debug("no src url found, attempting to click or play: "+t.outerHTML):this.debug("media already playing, waiting for full playback to finish: "+t.outerHTML),this.attemptMediaPlay(t).then((async e=>{let i=!0;for(e&&e.then((()=>i=!1));i;)this.processFetchableUrl(t)&&(i=!1),this.debug("Waiting for fixed URL or media to finish: "+t.currentSrc),await(0,n.sleep)(1e3)}))):t.currentSrc&&this.debug("media playing from non-URL source: "+t.currentSrc):this.debug("media not playable, skipping")}async attemptMediaPlay(t){let e;const i=new Promise((t=>{e=t}));let o;const s=new Promise((t=>{o=t}));if(s.then((()=>this.promises.push(i))),!t.paused&&t.currentTime>0&&o(),t.addEventListener("loadstart",(()=>{this.debug("media event: loadstart"),o(!0)})),t.addEventListener("playing",(()=>{this.debug("media event: playing"),o(!0)})),t.addEventListener("loadeddata",(()=>this.debug("media event: loadeddata"))),t.addEventListener("ended",(()=>{this.debug("media event: ended"),e()})),t.addEventListener("pause",(()=>{this.debug("media event: pause"),e()})),t.addEventListener("abort",(()=>{this.debug("media event: abort"),e()})),t.addEventListener("error",(()=>{this.debug("media event: error"),e()})),t.addEventListener("stalled",(()=>{this.debug("media event: stalled"),e()})),t.addEventListener("suspend",(()=>{this.debug("media event: suspend"),e()})),t.muted=!0,!t.paused&&t.currentTime>0)return i;return!t.closest("a")&&(t.click(),await Promise.race([s,(0,n.sleep)(1e3)]))?(this.debug("play started after media.click()"),i):(t.play(),await Promise.race([s,(0,n.sleep)(1e3)])&&this.debug("play started after media.play()"),i)}done(){return Promise.allSettled(this.promises)}}a.id="Autoplay"},234:(t,e,i)=>{"use strict";i.r(e),i.d(e,{AutoScroll:()=>n});var o=i(841),s=i(721);class n extends o.Behavior{constructor(t){super(),this.autoFetcher=t,this.showMoreQuery="//*[contains(text(), 'show more') or contains(text(), 'Show more')]",this.state={segments:1},this.lastScrollPos=-1,this.samePosCount=0,this.origPath=document.location.pathname}currScrollPos(){return Math.round(self.scrollY+self.innerHeight)}canScrollMore(){const t=self.document.scrollingElement||self.document.body;return this.currScrollPos()n&&(this.state.segments++,n=a),e||i||(e=(0,s.xpathNode)(this.showMoreQuery)),e&&(0,s.isInViewport)(e)&&(yield this.getState("Clicking 'Show More', awaiting more content"),e.click(),await(0,s.sleep)(s.waitUnit),await Promise.race([(0,s.waitUntil)((()=>self.document.scrollingElement.scrollHeight>a),500),(0,s.sleep)(3e4)]),self.document.scrollingElement.scrollHeight===a&&(i=!0),e=null),self.scrollBy(o),await(0,s.sleep)(75),1===this.state.segments)yield this.getState(`Scrolling down by ${o.top} pixels every 0.075 seconds`),t=2;else{const e=t/(this.state.segments-1);this.debug(`Waiting up to ${e} seconds for more scroll segments`);const i=Date.now();await Promise.race([(0,s.waitUntil)((()=>this.canScrollMore()),75),(0,s.sleep)(e)]),t+=2*(Date.now()-i)}const r=this.currScrollPos();if(r===this.lastScrollPos){if(++this.samePosCount>=2)break}else this.samePosCount=0;this.lastScrollPos=r}}async*scrollUp(){const t={top:-Math.min(.1*self.document.scrollingElement.clientHeight,30),left:0,behavior:"auto"};let e=self.document.scrollingElement.scrollHeight;for(;self.scrollY>0;){const i=self.document.scrollingElement.scrollHeight;i>e&&(this.state.segments++,e=i),self.scrollBy(t),await(0,s.sleep)(75),1===this.state.segments?yield this.getState(`Scrolling up by ${t.top} pixels every 0.075 seconds`):await Promise.race([(0,s.waitUntil)((()=>self.scrollY>0),75),(0,s.sleep)(2e3*(this.state.segments-1))])}}}n.id="Autoscroll"},607:(t,e,i)=>{"use strict";i.r(e),i.d(e,{BehaviorManager:()=>h});var o=i(894),s=i(376),n=i(234),a=i(721),r=i(841),l=i(954);const c={autofetch:!0,autoplay:!0,autoscroll:!0,siteSpecific:!0};class h{constructor(){this.behaviors=[],this.loadedBehaviors=l.default.reduce(((t,e)=>(t[e.id]=e,t)),{}),this.mainBehavior=null,this.inited=!1,this.started=!1,(0,a.behaviorLog)("Loaded behaviors for: "+self.location.href)}init(t=c,e=!1,i=null){if((!this.inited||e)&&(this.inited=!0,this.opts=t,self.window)){if(this.timeout=t.timeout,void 0!==t.log){let e=t.log;"string"==typeof e&&(e=self[e]),"function"==typeof e?(0,a._setLogFunc)(e):!1===e&&(0,a._setLogFunc)(null)}if(this.autofetch=new o.AutoFetcher(!!t.autofetch,t.fetchHeaders,t.startEarly),t.autofetch&&((0,a.behaviorLog)("Using AutoFetcher"),this.behaviors.push(this.autofetch)),t.autoplay&&((0,a.behaviorLog)("Using Autoplay"),this.behaviors.push(new s.Autoplay(this.autofetch,t.startEarly))),this.isInTopFrame()&&i)for(const t of i)try{this.load(t)}catch(e){(0,a.behaviorLog)(`Failed to load custom behavior: ${e} ${t}`)}}}selectMainBehavior(){if(this.mainBehavior)return;const t=this.opts;let e=!1;if(t.siteSpecific)for(const i in this.loadedBehaviors){const o=this.loadedBehaviors[i];if(o.isMatch()){(0,a.behaviorLog)("Using Site-Specific Behavior: "+i),this.mainBehaviorClass=o;const s="object"==typeof t.siteSpecific&&t.siteSpecific[i]||{};try{this.mainBehavior=new r.BehaviorRunner(o,s)}catch(t){(0,a.behaviorLog)(t.toString(),"error")}e=!0;break}}return!e&&t.autoscroll&&((0,a.behaviorLog)("Using Autoscroll"),this.mainBehaviorClass=n.AutoScroll,this.mainBehavior=new n.AutoScroll(this.autofetch)),this.mainBehavior&&(this.behaviors.push(this.mainBehavior),this.mainBehavior instanceof r.BehaviorRunner)?this.mainBehavior.behaviorProps.id:""}load(t){if("function"!=typeof t)return void(0,a.behaviorLog)(`Must pass a class object, got ${t}`,"error");if("string"!=typeof t.id)return void(0,a.behaviorLog)('Behavior class must have a string string "id" property',"error");if("function"!=typeof t.isMatch||"function"!=typeof t.init)return void(0,a.behaviorLog)("Behavior class must have an is `isMatch()` and `init()` static methods","error");const e=t.id;(0,a.behaviorLog)(`Loading external class ${e}: ${t}`,"debug"),this.loadedBehaviors[e]=t}async resolve(t){const e=await i(75)(`${t}`);if(Array.isArray(e))for(const t of e)this.load(t);else this.load(e)}async awaitPageLoad(){this.selectMainBehavior(),this.mainBehavior?.awaitPageLoad&&await this.mainBehavior.awaitPageLoad()}async run(t=c,e=!1){if(e&&(this.started=!1),this.started)return void this.unpause();this.init(t,e),this.selectMainBehavior(),await(0,a.awaitLoad)(),this.behaviors.forEach((t=>{(0,a.behaviorLog)("Starting behavior: "+t.constructor.id||0),t.start()})),this.started=!0,await(0,a.sleep)(500);let i=Promise.allSettled(this.behaviors.map((t=>t.done())));this.timeout?((0,a.behaviorLog)(`Waiting for behaviors to finish or ${this.timeout}ms timeout`),await Promise.race([i,(0,a.sleep)(this.timeout)])):((0,a.behaviorLog)("Waiting for behaviors to finish"),await i),(0,a.behaviorLog)("All Behaviors Done for "+self.location.href),this.mainBehavior&&this.mainBehaviorClass.cleanup&&this.mainBehavior.cleanup()}async runOne(t,e={}){const i=l.default.find((e=>e.name===t));if(void 0===i)return void console.error(`No behavior of name ${t} found`);const o=new r.BehaviorRunner(i,e);o.start(),console.log(`Running behavior: ${t}`),await o.done(),console.log(`Behavior ${t} completed`)}pause(){(0,a.behaviorLog)("Pausing Main Behavior"+this.mainBehaviorClass.name),this.behaviors.forEach((t=>t.pause()))}unpause(){this.behaviors.forEach((t=>t.unpause()))}doAsyncFetch(t){return(0,a.behaviorLog)("Queueing Async Fetch Url: "+t),this.autofetch.queueUrl(t,!0)}isInTopFrame(){return self.window.top===self.window||window.__WB_replay_top===self.window}}(0,a._setBehaviorManager)(h),(0,a.installBehaviors)(self)},841:(t,e,i)=>{"use strict";i.r(e),i.d(e,{BackgroundBehavior:()=>s,Behavior:()=>n,BehaviorRunner:()=>a});var o=i(721);class s{debug(t){(0,o.behaviorLog)(t,"debug")}log(t){(0,o.behaviorLog)(t,"info")}}class n extends s{constructor(){super(),this._running=null,this.paused=null,this._unpause=null,this.state={},this.scrollOpts={behavior:"smooth",block:"center",inline:"center"}}start(){this._running=this.run()}done(){return this._running?this._running:Promise.resolve()}async run(){try{for await(const t of this)this.log(t),this.paused&&await this.paused;this.log(this.getState("done!"))}catch(t){this.log(this.getState(t))}}pause(){this.paused||(this.paused=new Promise((t=>{this._unpause=t})))}unpause(){this._unpause&&(this._unpause(),this.paused=null,this._unpause=null)}getState(t,e){return e&&(void 0===this.state[e]?this.state[e]=1:this.state[e]++),{state:this.state,msg:t}}cleanup(){}async awaitPageLoad(){}static load(){self.__bx_behaviors?self.__bx_behaviors.load(this):console.warn(`Could not load ${this.name} behavior: window.__bx_behaviors is not initialized`)}async*[Symbol.asyncIterator](){yield}}class a extends s{constructor(t,e={}){if(super(),this.behaviorProps=t,this.inst=new t,"function"!=typeof this.inst.run||"AsyncGeneratorFunction"!==this.inst.run.constructor.name)throw Error("Invalid behavior: missing `async run*` instance method");let{state:i,opts:s}=t.init();i=i||{},s=s?{...s,...e}:e;const n=o.behaviorLog;this.ctx={Lib:o,state:i,opts:s,log:n},this._running=null,this.paused=null,this._unpause=null}start(){this._running=this.run()}done(){return this._running?this._running:Promise.resolve()}async run(){try{for await(const t of this.inst.run(this.ctx))this.log(t),this.paused&&await this.paused;this.log((0,o.getState)(this.ctx,"done!"))}catch(t){this.log((0,o.getState)(this.ctx,t))}}pause(){this.paused||(this.paused=new Promise((t=>{this._unpause=t})))}unpause(){this._unpause&&(this._unpause(),this.paused=null,this._unpause=null)}cleanup(){}async awaitPageLoad(){this.inst.awaitPageLoad&&await this.inst.awaitPageLoad(this.ctx)}static load(){self.__bx_behaviors?self.__bx_behaviors.load(this):console.warn(`Could not load ${this.name} behavior: window.__bx_behaviors is not initialized`)}}},721:(t,e,i)=>{"use strict";i.r(e),i.d(e,{HistoryState:()=>y,RestoreState:()=>b,_setBehaviorManager:()=>p,_setLogFunc:()=>g,addLink:()=>f,awaitLoad:()=>d,behaviorLog:()=>w,getState:()=>N,installBehaviors:()=>v,isInViewport:()=>_,iterChildElem:()=>P,iterChildMatches:()=>E,openWindow:()=>m,scrollAndClick:()=>a,scrollIntoView:()=>T,scrollToOffset:()=>k,sleep:()=>l,waitUnit:()=>r,waitUntil:()=>c,waitUntilNode:()=>h,xpathNode:()=>S,xpathNodes:()=>x,xpathString:()=>L});let o=console.log,s=null;const n={behavior:"smooth",block:"center",inline:"center"};async function a(t,e=500,i=n){t.scrollIntoView(i),await l(e),t.click()}const r=200;function l(t){return new Promise((e=>setTimeout(e,t)))}async function c(t,e=r){for(;!t();)await l(e)}async function h(t,e=document,i=null,o=1e3,s=r){let n=null,a=!1;const l=c((()=>(n=S(t,e),a||n!==i&&null!==n)),s),h=new Promise((t=>setTimeout((()=>{a=!0,t("TIMEOUT")}),o)));return await Promise.race([l,h]),n}function d(){return new Promise((t=>{"complete"===document.readyState?t(null):window.addEventListener("load",t)}))}function u(t,e){try{t(e)}catch(i){t(JSON.stringify(e))}}function w(t,e="debug"){o&&u(o,{data:t,type:e})}function f(t){self.__bx_addLink&&self.__bx_addLink(t)}async function m(t){if(self.__bx_open){const e=new Promise((t=>self.__bx_openResolve=t));u(self.__bx_open,{url:t});let i=null;try{if(i=await e,i)return i}catch(t){console.warn(t)}finally{delete self.__bx_openResolve}}return window.open(t)}function g(t){o=t}function p(t){s=t}function v(t){t.__bx_behaviors=new s}class b{constructor(t,e){this.matchValue=L(t,e)}async restore(t,e){let i=null;for(;i=S(t),!i;)await l(100);return S(e.replace("$1",this.matchValue),i)}}class y{constructor(t){this.loc=window.location.href,t()}get changed(){return window.location.href!==this.loc}goBack(t){if(!this.changed)return Promise.resolve(!0);const e=S(t);return new Promise((t=>{window.addEventListener("popstate",(()=>{t(null)}),{once:!0}),e?e.click():window.history.back()}))}}function S(t,e){return e=e||document,document.evaluate(t,e,null,XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue}function*x(t,e){e=e||document;let i=document.evaluate(t,e,null,XPathResult.ORDERED_NODE_ITERATOR_TYPE),o=null;for(;null!==(o=i.iterateNext());)yield o}function L(t,e){return e=e||document,document.evaluate(t,e,null,XPathResult.STRING_TYPE).stringValue}async function*P(t,e,i){let o=t.firstElementChild;for(;o;)yield o,o.nextElementSibling||await Promise.race([c((()=>!!o.nextElementSibling),e),l(i)]),o=o.nextElementSibling}async function*E(t,e,i=r,o=5e3){let s=S(`.//${t}`,e);const n=e=>S(`./following-sibling::${t}`,e);for(;s;){yield s;let t=n(s);t?s=t:(await Promise.race([c((()=>(t=n(s),t)),i),l(o)]),s=t)}}function _(t){var e=t.getBoundingClientRect();return e.top>=0&&e.left>=0&&e.bottom<=(window.innerHeight||document.documentElement.clientHeight)&&e.right<=(window.innerWidth||document.documentElement.clientWidth)}function k(t,e=0){const i=t.getBoundingClientRect().top+window.pageYOffset-e;window.scrollTo({top:i,behavior:"smooth"})}function T(t,e={behavior:"smooth",block:"center",inline:"center"}){t.scrollIntoView(e)}function N(t,e,i){return void 0===typeof t.state&&(t.state={}),i&&(void 0===t.state[i]?t.state[i]=1:t.state[i]++),{state:t.state,msg:e}}},121:(t,e,i)=>{"use strict";i.r(e),i.d(e,{FacebookTimelineBehavior:()=>s});const o={feed:"//div[@role='feed']",article:".//div[@role='article']",pageletPostList:"//div[@data-pagelet='page']/div[@role='main']//div[@role='main']/div",pageletProfilePostList:"//div[@data-pagelet='page']//div[@data-pagelet='ProfileTimeline']",articleToPostList:"//div[@role='article']/../../../../div",photosOrVideos:`.//a[(contains(@href, '/photos/') or contains(@href, '/photo/?') or contains(@href, '/videos/')) and (starts-with(@href, '${window.location.origin}/') or starts-with(@href, '/'))]`,postQuery:".//a[contains(@href, '/posts/')]",extraLabel:"//*[starts-with(text(), '+')]",nextSlideQuery:"//div[@data-name='media-viewer-nav-container']/div[@data-visualcompletion][2]//div[@role='button']",nextSlide:"//div[@aria-hidden='false']//div[@role='button' and not(@aria-hidden) and @aria-label]",commentList:".//ul[(../h3) or (../h4)]",commentMoreReplies:"./div[2]/div[1]/div[2]/div[@role='button']",commentMoreComments:"./following-sibling::div/div/div[2][@role='button'][./span/span]",viewComments:".//h4/..//div[@role='button']",photoCommentList:"//ul[../h2]",firstPhotoThumbnail:"//div[@role='main']//div[3]//div[contains(@style, 'border-radius')]//div[contains(@style, 'max-width') and contains(@style, 'min-width')]//a[@role='link']",firstVideoThumbnail:"//div[@role='main']//div[contains(@style, 'z-index')]/following-sibling::div/div/div/div[last()]//a[contains(@href, '/videos/') and @aria-hidden!='true']",firstVideoSimple:"//div[@role='main']//a[contains(@href, '/videos/') and @aria-hidden!='true']",mainVideo:"//div[@data-pagelet='root']//div[@role='dialog']//div[@role='main']//video",nextVideo:"following::a[contains(@href, '/videos/') and @aria-hidden!='true']",isPhotoVideoPage:/^.*facebook\.com\/[^/]+\/(photos|videos)\/.+/,isPhotosPage:/^.*facebook\.com\/[^/]+\/photos\/?($|\?)/,isVideosPage:/^.*facebook\.com\/[^/]+\/videos\/?($|\?)/};class s{static isMatch(){return!!window.location.href.match(/https:\/\/(www\.)?facebook\.com\//)}static init(){return{state:{}}}constructor(){this.extraWindow=null,this.allowNewWindow=!1}async*iterPostFeeds(t){const{iterChildElem:e,waitUnit:i,waitUntil:s,xpathNode:n,xpathNodes:a}=t.Lib,r=Array.from(a(o.feed));if(r&&r.length)for(const a of r)for await(const r of e(a,i,10*s))yield*this.viewPost(t,n(o.article,r));else{const a=n(o.pageletPostList)||n(o.pageletProfilePostList)||n(o.articleToPostList);if(!a)return;for await(const r of e(a,i,10*s))yield*this.viewPost(t,n(o.article,r))}this.extraWindow&&this.extraWindow.close()}async*viewPost(t,e,i=2){const{getState:s,scrollIntoView:n,sleep:a,waitUnit:r,xpathNode:l}=t.Lib;if(!e)return;const c=l(o.postQuery,e);let h=null;c&&(h=new URL(c.href,window.location.href),h.search=""),yield s(t,"Viewing post "+(h||""),"posts"),n(e),await a(2*r),l(".//video",e)&&(yield s(t,"Playing inline video","videos"),await a(2*r));let d=l(o.commentList,e);if(!d){const t=l(o.viewComments,e);t&&(t.click(),await a(2*r)),d=l(o.commentList,e)}yield*this.iterComments(t,d,i),await a(5*r)}async*viewPhotosOrVideos(t,e){const{getState:i,sleep:s,waitUnit:n,xpathNode:a,xpathNodes:r}=t.Lib,l=Array.from(r(o.photosOrVideos,e)),c=new Set;let h=0;for(const e of l){const r=new URL(e.href,window.location.href);if(-1===e.href.indexOf("?fbid")&&(r.search=""),c.has(r.href))continue;const d=e.href.indexOf("/video")>=0?"videos":"photos";++h,c.add(r.href),yield i(t,`Viewing ${d} ${r.href}`,d),e.scrollIntoView(),await s(5*n),e.click(),await s(10*n),this.allowNewWindow&&await this.openNewWindow(t,r.href),h===l.length&&(yield*this.viewExtraObjects(t,e,d,this.allowNewWindow));const u=a(o.nextSlide);u&&(u.click(),await s(2*n))}}async*viewExtraObjects(t,e,i,s){const{getState:n,sleep:a,waitUnit:r,waitUntil:l,xpathNode:c}=t.Lib,h=c(o.extraLabel,e);if(!h)return;const d=Number(h.innerText.slice(1));if(isNaN(d))return;let u;for(let e=0;ewindow.location.href!==u),2*r),yield n(t,`Viewing extra ${i} ${window.location.href}`),s&&await this.openNewWindow(t,window.location.href))}}async openNewWindow(t,e){this.extraWindow?this.extraWindow.location.href=e:this.extraWindow=await t.Lib.openWindow(e)}async*iterComments(t,e,i=2){const{getState:s,scrollIntoView:n,sleep:a,waitUnit:r,xpathNode:l}=t.Lib;if(!e)return void await a(5*r);let c=e.firstElementChild,h=null,d=0;for(;c&&dwindow.location.href!==c),2*n);let h=null;for(;(h=r(o.nextSlideQuery))&&(c=window.location.href,await s(n),h.click(),await s(5*n),await Promise.race([a((()=>window.location.href!==c),2*n),s(3e3)]),window.location.href!==c);){yield e(t,`Viewing photo ${window.location.href}`,"photos");const i=r(o.photoCommentList);yield*this.iterComments(t,i,2),await s(5*n)}}async*iterAllVideos(t){const{getState:e,scrollIntoView:i,sleep:s,waitUnit:n,waitUntil:a,xpathNode:r,xpathNodes:l}=t.Lib,c=r("//video");c&&(i(c),await s(5*n));let h=r(o.firstVideoThumbnail)||r(o.firstVideoSimple);if(h)for(;h;){i(h);let c=window.location.href;h.click(),await a((()=>window.location.href!==c),2*n),yield e(t,"Viewing video: "+window.location.href,"videos"),await s(10*n),await Promise.race([a((()=>{for(const t of l("//video"))if(t.readyState>=3)return!0;return!1}),2*n),s(2e4)]),await s(10*n);const d=r(o.nextSlide);if(!d)break;c=window.location.href,d.click(),await a((()=>window.location.href!==c),2*n),h=r(o.nextVideo,h)}}async*run(t){const{getState:e,sleep:i,xpathNode:s}=t.Lib;if(yield e(t,"Starting..."),await i(2e3),o.isPhotosPage.exec(window.location.href))return t.state={photos:0,comments:0},void(yield*this.iterPhotoSlideShow(t));if(o.isVideosPage.exec(window.location.href))return t.state={videos:0,comments:0},void(yield*this.iterAllVideos(t));if(o.isPhotoVideoPage.exec(window.location.href)){t.state={comments:0};const e=s(o.photoCommentList);yield*this.iterComments(t,e,1e3)}else t.state={posts:0,comments:0,videos:0},yield*this.iterPostFeeds(t)}}s.id="Facebook"},954:(t,e,i)=>{"use strict";i.r(e),i.d(e,{default:()=>l});var o=i(121),s=i(741),n=i(667),a=i(739),r=i(714);const l=[s.InstagramPostsBehavior,a.TwitterTimelineBehavior,o.FacebookTimelineBehavior,n.TelegramBehavior,r.TikTokVideoBehavior,r.TikTokProfileBehavior]},741:(t,e,i)=>{"use strict";i.r(e),i.d(e,{InstagramPostsBehavior:()=>n});const o="//article[@role='presentation']//div[@role='presentation']/following-sibling::button",s={rootPath:"//main/div/div[2]/div",childMatchSelect:"string(.//a[starts-with(@href, '/')]/@href)",childMatch:"child::div[.//a[@href='$1']]",firstPostInRow:"div[1]/a",postCloseButton:"/html/body/div[last()]/div[1]/button[.//*[@aria-label]]",nextPost:"//button[.//*[local-name() = 'svg' and @aria-label='Next']]",postLoading:"//*[@aria-label='Loading...']",subpostNextOnlyChevron:o,subpostPrevNextChevron:o+"[2]",commentRoot:"//article[@role='presentation']/div[1]/div[2]//ul/div[last()]/div/div",viewReplies:"ul/li//button[span[not(count(*)) and contains(text(), '(')]]",loadMore:"//button[span[@aria-label]]",pageLoadWaitUntil:"//main"};class n{static isMatch(){return!!window.location.href.match(/https:\/\/(www\.)?instagram\.com\/\w[\w.-]+/)}static init(){return{state:{posts:0,slides:0,rows:0,comments:0}}}constructor(){this.maxCommentsTime=1e4,this.postOnlyWindow=null}cleanup(){this.postOnlyWindow&&(this.postOnlyWindow.close(),this.postOnlyWindow=null)}async waitForNext(t,e){return e?(await t.Lib.sleep(t.Lib.waitUnit),e.nextElementSibling?e.nextElementSibling:null):null}async*iterRow(t){const{RestoreState:e,sleep:i,waitUnit:o,xpathNode:n}=t.Lib;let a=n(s.rootPath);if(!a)return;let r=a.firstElementChild;if(r)for(;r;){await i(o);const n=new e(s.childMatchSelect,r);n.matchValue&&(yield r,r=await n.restore(s.rootPath,s.childMatch)),r=await this.waitForNext(t,r)}}async*viewStandalonePost(t,e){const{getState:i,sleep:o,waitUnit:n,waitUntil:a,xpathNode:r,xpathString:l}=t.Lib;let c=r(s.rootPath);if(!c||!c.firstElementChild)return;const h=l(s.childMatchSelect,c.firstElementChild);yield i(t,"Loading single post view for first post: "+h),window.history.replaceState({},"",h),window.dispatchEvent(new PopStateEvent("popstate",{state:{}}));let d=null,u=null;await o(5*n),await a((()=>(d=r(s.rootPath))!==c&&d),5*n),await o(5*n),window.history.replaceState({},"",e),window.dispatchEvent(new PopStateEvent("popstate",{state:{}})),await a((()=>(u=r(s.rootPath))!==d&&u),5*n)}async*iterSubposts(t){const{getState:e,sleep:i,waitUnit:o,xpathNode:n}=t.Lib;let a=n(s.subpostNextOnlyChevron),r=1;for(;a;)a.click(),await i(5*o),yield e(t,`Loading Slide ${++r} for ${window.location.href}`,"slides"),a=n(s.subpostPrevNextChevron);await i(5*o)}async iterComments(t){const{scrollIntoView:e,sleep:i,waitUnit:o,waitUntil:n,xpathNode:a}=t.Lib,r=a(s.commentRoot);if(!r)return;let l=r.firstElementChild,c=!1;const h=t=>a(s.viewReplies,t);for(;l;){e(l),c=!0;let r=h(l);for(;r;){const e=r.textContent;r.click(),t.state.comments++,await i(2.5*o),await n((()=>e!==r.textContent),o),r=h(l)}if(l.nextElementSibling&&"LI"===l.nextElementSibling.tagName&&!l.nextElementSibling.nextElementSibling){let e=a(s.loadMore,l.nextElementSibling);e&&(e.click(),t.state.comments++,await i(5*o))}l=l.nextElementSibling,await i(2.5*o)}return c}async*iterPosts(t,e){const{getState:i,sleep:o,waitUnit:n,xpathNode:a}=t.Lib;let r=0;for(;e&&++r<=3;)for(e.click(),await o(10*n),yield i(t,"Loading Post: "+window.location.href,"posts"),await fetch(window.location.href),yield*this.iterSubposts(t),yield i(t,"Loaded Comments","comments"),await Promise.race([this.iterComments(t),o(this.maxCommentsTime)]),e=a(s.nextPost);!e&&a(s.postLoading);)await o(2.5*n);await o(5*n)}async*run(t){const{getState:e,scrollIntoView:i,sleep:o,waitUnit:n,xpathNode:a}=t.Lib;for await(const r of this.iterRow(t)){i(r),await o(2.5*n),yield e(t,"Loading Row","rows");const l=a(s.firstPostInRow,r);yield*this.iterPosts(t,l);const c=a(s.postCloseButton);c&&c.click(),await o(5*n)}}async awaitPageLoad(t){const{Lib:e,log:i}=t,{waitUntilNode:o}=e;i("Waiting for Instagram to fully load","info"),await o(s.pageLoadWaitUntil,document,null,3e4)}}n.id="Instagram"},667:(t,e,i)=>{"use strict";i.r(e),i.d(e,{TelegramBehavior:()=>a});const o="//main//section[@class='tgme_channel_history js-message_history']",s="string(./div[@data-post]/@data-post)",n="string(.//a[@class='tgme_widget_message_link_preview' and @href]/@href)";class a{static isMatch(){return!!window.location.href.match(/https:\/\/t.me\/s\/\w[\w]+/)}static init(){return{state:{messages:0}}}async waitForPrev(t,e){return e?(await t.Lib.sleep(5*t.Lib.waitUnit),e.previousElementSibling?e.previousElementSibling:null):null}async*run(t){const{getState:e,scrollIntoView:i,sleep:a,waitUnit:r,xpathNode:l,xpathString:c}=t.Lib;let h=l(o);if(!h)return;let d=h.lastElementChild;for(;d;){i(d);const o=c(s,d)||"unknown",l=c(n,d);if(l&&l.endsWith(".jpg")||l.endsWith(".png")){yield e(t,"Loading External Image: "+l);const i=new Image;i.src=l,document.body.appendChild(i),await a(2.5*r),document.body.removeChild(i)}yield e(t,"Loading Message: "+o,"messages"),d=await this.waitForPrev(t,d)}}}a.id="Telegram"},714:(t,e,i)=>{"use strict";i.r(e),i.d(e,{BREADTH_ALL:()=>h,TikTokProfileBehavior:()=>u,TikTokVideoBehavior:()=>d});const o="//div[contains(@class, 'CommentListContainer')]",s="div[contains(@class, 'CommentItemContainer')]",n=".//p[contains(@class, 'ReplyActionText')]",a=".//p[starts-with(@data-e2e, 'view-more') and string-length(text()) > 0]",r="//div[starts-with(@data-e2e, 'user-post-item-list')]",l="div[contains(@class, 'DivItemContainerV2')]",c="button[contains(@class, 'StyledCloseIconContainer')]",h=Symbol("BREADTH_ALL");class d{static init(){return{state:{comments:0},opts:{breadth:h}}}static isMatch(){return!!window.location.href.match(/https:\/\/(www\.)?tiktok\.com\/@.+\/video\/\d+\/?.*/)}breadthComplete({opts:{breadth:t}},e){return t!==h&&t<=e}async*crawlThread(t,e,i=null,o=0){const{waitUntilNode:s,scrollAndClick:n,getState:r}=t.Lib,l=await s(a,e,i);l&&!this.breadthComplete(t,o)&&(await n(l,500),yield r(t,"View more replies","comments"),yield*this.crawlThread(t,e,l,o+1))}async*expandThread(t,e){const{xpathNode:i,scrollAndClick:o,getState:s}=t.Lib,a=i(n,e);a&&(await o(a,500),yield s(t,"View comment","comments"),yield*this.crawlThread(t,e,null,1))}async*run(t){const{xpathNode:e,iterChildMatches:i,scrollIntoView:n,getState:a}=t.Lib,r=e(o),l=i(s,r);for await(const e of l)n(e),yield a(t,"View comment","comments"),this.breadthComplete(t,0)||(yield*this.expandThread(t,e));yield a(t,"TikTok Video Behavior Complete")}}d.id="TikTokVideo";class u{static isMatch(){return!!window.location.href.match(/https:\/\/(www\.)?tiktok\.com\/@[a-zA-Z0-9]+(\/?$|\/\?.*)/)}static init(){return{state:{videos:0,comments:0},opts:{breadth:h}}}async*openVideo(t,e){const{HistoryState:i,xpathNode:o,sleep:s}=t.Lib,n=o(".//a",e);if(!n)return;const a=new i((()=>n.click()));if(await s(500),a.changed){const e=new d;yield*e.run(t),await s(500),await a.goBack(c)}}async*run(t){const{xpathNode:e,iterChildMatches:i,scrollIntoView:o,getState:s,sleep:n}=t.Lib,a=e(r),c=i(l,a);for await(const e of c)o(e),yield s(t,"View video","videos"),yield*this.openVideo(t,e),await n(500);yield s(t,"TikTok Profile Behavior Complete")}}u.id="TikTokProfile"},739:(t,e,i)=>{"use strict";i.r(e),i.d(e,{TwitterTimelineBehavior:()=>p});const o="//h1[@role='heading' and @aria-level='1']/following-sibling::div[@aria-label]//div[@style]",s=".//article",n="string(.//article//a[starts-with(@href, '/') and @aria-label]/@href)",a="child::div[.//a[@href='$1']]",r=".//div[@role='button' and not(@aria-haspopup) and not(@data-testid)]",l=".//div[@role='blockquote' and @aria-haspopup='false']",c=".//a[@role='link' and starts-with(@href, '/') and contains(@href, '/photo/')]",h="//div[@aria-roledescription='carousel']/div[2]/div[1]//div[@role='button']",d="//div[@aria-roledescription='carousel']/div[2]/div[2]//div[@role='button']",u="//div[@role='presentation']/div[@role='button' and @aria-label]",w="//div[@data-testid='titleContainer']//div[@role='button']",f=".//a[@href='/settings/content_you_see']/parent::div/parent::div/parent::div//div[@role='button']",m=".//*[@role='progressbar']",g=".//div[data-testid='placementTracking']";class p{static isMatch(){return!!window.location.href.match(/https:\/\/(www\.)?(x|twitter)\.com\//)}static init(){return{state:{tweets:0,images:0,videos:0},opts:{maxDepth:0}}}constructor(){this.seenTweets=new Set,this.seenMediaTweets=new Set}showingProgressBar(t,e){const{xpathNode:i}=t.Lib,o=i(m,e);return!!o&&o.clientHeight>10}async waitForNext(t,e){const{sleep:i,waitUnit:o}=t.Lib;if(!e)return null;if(await i(2*o),!e.nextElementSibling)return null;for(;this.showingProgressBar(t,e.nextElementSibling);)await i(o);return e.nextElementSibling}async expandMore(t,e){const{sleep:i,waitUnit:o,xpathNode:s}=t.Lib,n=s(r,e);if(!n)return e;const a=e.previousElementSibling;for(n.click(),await i(o);this.showingProgressBar(t,a.nextElementSibling);)await i(o);return e=a.nextElementSibling}async*infScroll(t){const{scrollIntoView:e,RestoreState:i,sleep:l,waitUnit:c,xpathNode:h}=t.Lib;let d=h(o);if(!d)return;let u=d.firstElementChild;if(u)for(;u;){let d=h(s,u);if(!d&&r&&(u=await this.expandMore(t,u),d=h(s,u)),u&&u.innerText&&e(u),u&&d){await l(c);const t=new i(n,u);yield d,t.matchValue&&(u=await t.restore(o,a))}u=await this.waitForNext(t,u)}}async*mediaPlaying(t,e){const{getState:i,sleep:o,xpathNode:s,xpathString:a}=t.Lib,r=s("(.//video | .//audio)",e);if(!r||r.paused)return;let l,c=null;try{c=new URL(a(n,e.parentElement),window.location.origin).href}catch(t){console.warn(t)}if(r.src.startsWith("https://")&&r.src.indexOf(".mp4")>0)return void(yield i(t,`Loading video for ${c||"unknown"}`,"videos"));if(c){if(this.seenMediaTweets.has(c))return;l=`Waiting for media playback for ${c} to finish`,this.seenMediaTweets.add(c)}else l="Loading video";yield i(t,l,"videos");const h=new Promise((t=>{r.addEventListener("ended",(()=>t(null))),r.addEventListener("abort",(()=>t(null))),r.addEventListener("error",(()=>t(null))),r.addEventListener("pause",(()=>t(null)))}));await Promise.race([h,o(6e4)])}async*clickImages(t,e){const{getState:i,HistoryState:o,sleep:s,waitUnit:n,xpathNode:a}=t.Lib,r=a(c,e);if(r){const e=new o((()=>r.click()));yield i(t,"Loading Image: "+window.location.href,"images"),await s(5*n);let l=a(h),c=window.location.href;for(;l;){if(l.click(),await s(2*n),window.location.href===c){await s(5*n);break}c=window.location.href,yield i(t,"Loading Image: "+window.location.href,"images"),await s(5*n),l=a(d)}await e.goBack(u)}}async*clickTweet(t,e,i){const{getState:o,HistoryState:s,sleep:n,waitUnit:a}=t.Lib,r=new s((()=>e.click()));if(await n(a),r.changed){yield o(t,"Capturing Tweet: "+window.location.href,"tweets");i{var o={".":607,"./":607,"./autofetcher":894,"./autofetcher.ts":894,"./autoplay":376,"./autoplay.ts":376,"./autoscroll":234,"./autoscroll.ts":234,"./index":607,"./index.ts":607,"./lib/behavior":841,"./lib/behavior.ts":841,"./lib/utils":721,"./lib/utils.ts":721,"./site":954,"./site/":954,"./site/facebook":121,"./site/facebook.ts":121,"./site/index":954,"./site/index.ts":954,"./site/instagram":741,"./site/instagram.ts":741,"./site/telegram":667,"./site/telegram.ts":667,"./site/tiktok":714,"./site/tiktok.ts":714,"./site/twitter":739,"./site/twitter.ts":739};function s(t){return Promise.resolve().then((()=>{if(!i.o(o,t)){var e=new Error("Cannot find module '"+t+"'");throw e.code="MODULE_NOT_FOUND",e}var s=o[t];return i(s)}))}s.keys=()=>Object.keys(o),s.id=75,t.exports=s}},e={};function i(o){var s=e[o];if(void 0!==s)return s.exports;var n=e[o]={exports:{}};return t[o](n,n.exports,i),n.exports}i.d=(t,e)=>{for(var o in e)i.o(e,o)&&!i.o(t,o)&&Object.defineProperty(t,o,{enumerable:!0,get:e[o]})},i.e=()=>Promise.resolve(),i.o=(t,e)=>Object.prototype.hasOwnProperty.call(t,e),i.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},(()=>{"use strict";i(607)})()})(); \ No newline at end of file +/*! behaviors.js is part of Webrecorder project. Copyright (C) 2021-2024, Webrecorder Software. Licensed under the Affero General Public License v3. */(()=>{var __webpack_modules__={"./node_modules/query-selector-shadow-dom/src/normalize.js": +/*!*****************************************************************!*\ + !*** ./node_modules/query-selector-shadow-dom/src/normalize.js ***! + \*****************************************************************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "normalizeSelector": () => (/* binding */ normalizeSelector)\n/* harmony export */ });\n/* istanbul ignore file */\n\n\n// normalize-selector-rev-02.js\n/*\n author: kyle simpson (@getify)\n original source: https://gist.github.com/getify/9679380\n\n modified for tests by david kaye (@dfkaye)\n 21 march 2014\n\n rev-02 incorporate kyle\'s changes 3/2/42014\n*/\n\nfunction normalizeSelector(sel) {\n // save unmatched text, if any\n function saveUnmatched() {\n if (unmatched) {\n // whitespace needed after combinator?\n if (tokens.length > 0 && /^[~+>]$/.test(tokens[tokens.length - 1])) {\n tokens.push(" ");\n }\n\n // save unmatched text\n tokens.push(unmatched);\n }\n }\n\n var tokens = [],\n match,\n unmatched,\n regex,\n state = [0],\n next_match_idx = 0,\n prev_match_idx,\n not_escaped_pattern = /(?:[^\\\\]|(?:^|[^\\\\])(?:\\\\\\\\)+)$/,\n whitespace_pattern = /^\\s+$/,\n state_patterns = [\n /\\s+|\\/\\*|["\'>~+[(]/g, // general\n /\\s+|\\/\\*|["\'[\\]()]/g, // [..] set\n /\\s+|\\/\\*|["\'[\\]()]/g, // (..) set\n null, // string literal (placeholder)\n /\\*\\//g, // comment\n ];\n sel = sel.trim();\n\n // eslint-disable-next-line no-constant-condition\n while (true) {\n unmatched = "";\n\n regex = state_patterns[state[state.length - 1]];\n\n regex.lastIndex = next_match_idx;\n match = regex.exec(sel);\n\n // matched text to process?\n if (match) {\n prev_match_idx = next_match_idx;\n next_match_idx = regex.lastIndex;\n\n // collect the previous string chunk not matched before this token\n if (prev_match_idx < next_match_idx - match[0].length) {\n unmatched = sel.substring(\n prev_match_idx,\n next_match_idx - match[0].length\n );\n }\n\n // general, [ ] pair, ( ) pair?\n if (state[state.length - 1] < 3) {\n saveUnmatched();\n\n // starting a [ ] pair?\n if (match[0] === "[") {\n state.push(1);\n }\n // starting a ( ) pair?\n else if (match[0] === "(") {\n state.push(2);\n }\n // starting a string literal?\n else if (/^["\']$/.test(match[0])) {\n state.push(3);\n state_patterns[3] = new RegExp(match[0], "g");\n }\n // starting a comment?\n else if (match[0] === "/*") {\n state.push(4);\n }\n // ending a [ ] or ( ) pair?\n else if (/^[\\])]$/.test(match[0]) && state.length > 0) {\n state.pop();\n }\n // handling whitespace or a combinator?\n else if (/^(?:\\s+|[~+>])$/.test(match[0])) {\n // need to insert whitespace before?\n if (\n tokens.length > 0 &&\n !whitespace_pattern.test(tokens[tokens.length - 1]) &&\n state[state.length - 1] === 0\n ) {\n // add normalized whitespace\n tokens.push(" ");\n }\n\n // case-insensitive attribute selector CSS L4\n if (\n state[state.length - 1] === 1 &&\n tokens.length === 5 &&\n tokens[2].charAt(tokens[2].length - 1) === "="\n ) {\n tokens[4] = " " + tokens[4];\n }\n\n // whitespace token we can skip?\n if (whitespace_pattern.test(match[0])) {\n continue;\n }\n }\n\n // save matched text\n tokens.push(match[0]);\n }\n // otherwise, string literal or comment\n else {\n // save unmatched text\n tokens[tokens.length - 1] += unmatched;\n\n // unescaped terminator to string literal or comment?\n if (not_escaped_pattern.test(tokens[tokens.length - 1])) {\n // comment terminator?\n if (state[state.length - 1] === 4) {\n // ok to drop comment?\n if (\n tokens.length < 2 ||\n whitespace_pattern.test(tokens[tokens.length - 2])\n ) {\n tokens.pop();\n }\n // otherwise, turn comment into whitespace\n else {\n tokens[tokens.length - 1] = " ";\n }\n\n // handled already\n match[0] = "";\n }\n\n state.pop();\n }\n\n // append matched text to existing token\n tokens[tokens.length - 1] += match[0];\n }\n }\n // otherwise, end of processing (no more matches)\n else {\n unmatched = sel.substr(next_match_idx);\n saveUnmatched();\n\n break;\n }\n }\n\n return tokens.join("").trim();\n}\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./node_modules/query-selector-shadow-dom/src/normalize.js?')},"./node_modules/query-selector-shadow-dom/src/querySelectorDeep.js": +/*!*************************************************************************!*\ + !*** ./node_modules/query-selector-shadow-dom/src/querySelectorDeep.js ***! + \*************************************************************************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ \"collectAllElementsDeep\": () => (/* binding */ collectAllElementsDeep),\n/* harmony export */ \"querySelectorAllDeep\": () => (/* binding */ querySelectorAllDeep),\n/* harmony export */ \"querySelectorDeep\": () => (/* binding */ querySelectorDeep)\n/* harmony export */ });\n/* harmony import */ var _normalize__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./normalize */ \"./node_modules/query-selector-shadow-dom/src/normalize.js\");\n\n\n/**\n* Finds first matching elements on the page that may be in a shadow root using a complex selector of n-depth\n*\n* Don't have to specify all shadow roots to button, tree is travered to find the correct element\n*\n* Example querySelectorAllDeep('downloads-item:nth-child(4) #remove');\n*\n* Example should work on chrome://downloads outputting the remove button inside of a download card component\n*\n* Example find first active download link element querySelectorDeep('#downloads-list .is-active a[href^=\"https://\"]');\n*\n* Another example querySelectorAllDeep('#downloads-list div#title-area + a');\ne.g.\n*/\nfunction querySelectorAllDeep(selector, root = document, allElements = null) {\n return _querySelectorDeep(selector, true, root, allElements);\n}\n\nfunction querySelectorDeep(selector, root = document, allElements = null) {\n return _querySelectorDeep(selector, false, root, allElements);\n}\n\nfunction _querySelectorDeep(selector, findMany, root, allElements = null) {\n selector = (0,_normalize__WEBPACK_IMPORTED_MODULE_0__.normalizeSelector)(selector);\n let lightElement = root.querySelector(selector);\n\n if (document.head.createShadowRoot || document.head.attachShadow) {\n // no need to do any special if selector matches something specific in light-dom\n if (!findMany && lightElement) {\n return lightElement;\n }\n\n // split on commas because those are a logical divide in the operation\n const selectionsToMake = splitByCharacterUnlessQuoted(selector, ',');\n\n return selectionsToMake.reduce((acc, minimalSelector) => {\n // if not finding many just reduce the first match\n if (!findMany && acc) {\n return acc;\n }\n // do best to support complex selectors and split the query\n const splitSelector = splitByCharacterUnlessQuoted(minimalSelector\n //remove white space at start of selector\n .replace(/^\\s+/g, '')\n .replace(/\\s*([>+~]+)\\s*/g, '$1'), ' ')\n // filter out entry white selectors\n .filter((entry) => !!entry)\n // convert \"a > b\" to [\"a\", \"b\"]\n .map((entry) => splitByCharacterUnlessQuoted(entry, '>'));\n\n const possibleElementsIndex = splitSelector.length - 1;\n const lastSplitPart = splitSelector[possibleElementsIndex][splitSelector[possibleElementsIndex].length - 1];\n const possibleElements = collectAllElementsDeep(lastSplitPart, root, allElements);\n const findElements = findMatchingElement(splitSelector, possibleElementsIndex, root);\n if (findMany) {\n acc = acc.concat(possibleElements.filter(findElements));\n return acc;\n } else {\n acc = possibleElements.find(findElements);\n return acc || null;\n }\n }, findMany ? [] : null);\n\n\n } else {\n if (!findMany) {\n return lightElement;\n } else {\n return root.querySelectorAll(selector);\n }\n }\n\n}\n\nfunction findMatchingElement(splitSelector, possibleElementsIndex, root) {\n return (element) => {\n let position = possibleElementsIndex;\n let parent = element;\n let foundElement = false;\n while (parent && !isDocumentNode(parent)) {\n let foundMatch = true;\n if (splitSelector[position].length === 1) {\n foundMatch = parent.matches(splitSelector[position]);\n } else {\n // selector is in the format \"a > b\"\n // make sure a few parents match in order\n const reversedParts = ([]).concat(splitSelector[position]).reverse();\n let newParent = parent;\n for (const part of reversedParts) {\n if (!newParent || !newParent.matches(part)) {\n foundMatch = false;\n break;\n }\n newParent = findParentOrHost(newParent, root);\n }\n }\n\n if (foundMatch && position === 0) {\n foundElement = true;\n break;\n }\n if (foundMatch) {\n position--;\n }\n parent = findParentOrHost(parent, root);\n }\n return foundElement;\n };\n\n}\n\nfunction splitByCharacterUnlessQuoted(selector, character) {\n return selector.match(/\\\\?.|^$/g).reduce((p, c) => {\n if (c === '\"' && !p.sQuote) {\n p.quote ^= 1;\n p.a[p.a.length - 1] += c;\n } else if (c === '\\'' && !p.quote) {\n p.sQuote ^= 1;\n p.a[p.a.length - 1] += c;\n\n } else if (!p.quote && !p.sQuote && c === character) {\n p.a.push('');\n } else {\n p.a[p.a.length - 1] += c;\n }\n return p;\n }, { a: [''] }).a;\n}\n\n/**\n * Checks if the node is a document node or not.\n * @param {Node} node\n * @returns {node is Document | DocumentFragment}\n */\nfunction isDocumentNode(node) {\n return node.nodeType === Node.DOCUMENT_FRAGMENT_NODE || node.nodeType === Node.DOCUMENT_NODE;\n}\n\nfunction findParentOrHost(element, root) {\n const parentNode = element.parentNode;\n return (parentNode && parentNode.host && parentNode.nodeType === 11) ? parentNode.host : parentNode === root ? null : parentNode;\n}\n\n/**\n * Finds all elements on the page, inclusive of those within shadow roots.\n * @param {string=} selector Simple selector to filter the elements by. e.g. 'a', 'div.main'\n * @return {!Array} List of anchor hrefs.\n * @author ebidel@ (Eric Bidelman)\n * License Apache-2.0\n */\nfunction collectAllElementsDeep(selector = null, root, cachedElements = null) {\n let allElements = [];\n\n if (cachedElements) {\n allElements = cachedElements;\n } else {\n const findAllElements = function(nodes) {\n for (let i = 0; i < nodes.length; i++) {\n const el = nodes[i];\n allElements.push(el);\n // If the element has a shadow root, dig deeper.\n if (el.shadowRoot) {\n findAllElements(el.shadowRoot.querySelectorAll('*'));\n }\n }\n };\n if(root.shadowRoot) {\n findAllElements(root.shadowRoot.querySelectorAll('*'));\n }\n findAllElements(root.querySelectorAll('*'));\n }\n\n return selector ? allElements.filter(el => el.matches(selector)) : allElements;\t}\n\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./node_modules/query-selector-shadow-dom/src/querySelectorDeep.js?")},"./index.ts": +/*!******************!*\ + !*** ./index.ts ***! + \******************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "BehaviorManager": () => (/* reexport safe */ _src__WEBPACK_IMPORTED_MODULE_0__.BehaviorManager)\n/* harmony export */ });\n/* harmony import */ var _src__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./src */ "./src/index.ts");\n\n\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./index.ts?')},"./src/autoclick.ts": +/*!**************************!*\ + !*** ./src/autoclick.ts ***! + \**************************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "AutoClick": () => (/* binding */ AutoClick)\n/* harmony export */ });\n/* harmony import */ var _lib_behavior__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./lib/behavior */ "./src/lib/behavior.ts");\n/* harmony import */ var _lib_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./lib/utils */ "./src/lib/utils.ts");\n\n\nclass AutoClick extends _lib_behavior__WEBPACK_IMPORTED_MODULE_0__.BackgroundBehavior {\n constructor(selector = "a") {\n super();\n this.seenElem = new WeakSet();\n this.selector = selector;\n this._donePromise = new Promise((resolve) => this._markDone = resolve);\n }\n beforeUnload(event) {\n event.preventDefault();\n return false;\n }\n nextSameOriginLink(origNoHash) {\n try {\n const allLinks = document.querySelectorAll(this.selector);\n for (const el of allLinks) {\n const elem = el;\n if (!elem.href || !elem.href.startsWith(self.location.origin) || elem.href.startsWith(origNoHash)) {\n continue;\n }\n if (!elem.isConnected) {\n continue;\n }\n if (this.seenElem.has(elem)) {\n continue;\n }\n this.seenElem.add(elem);\n return elem;\n }\n }\n catch (e) {\n this.debug(e.toString());\n }\n return null;\n }\n async start() {\n const origHref = self.location.href;\n const url = new URL(origHref);\n url.hash = "";\n const origNoHash = url.href + "#";\n window.addEventListener("beforeunload", (e) => this.beforeUnload(e));\n while (true) {\n const elem = this.nextSameOriginLink(origNoHash);\n if (!elem) {\n break;\n }\n await this.processElem(elem, origHref);\n }\n for (const el of document.querySelectorAll(this.selector)) {\n const elem = el;\n if (!elem.href || elem.href.startsWith(origNoHash)) {\n await this.processElem(elem, origHref);\n }\n }\n window.removeEventListener("beforeunload", (e) => this.beforeUnload(e));\n this._markDone();\n }\n async processElem(elem, origHref) {\n if (!elem.isConnected) {\n return;\n }\n try {\n if (!getEventListeners(elem).click) {\n return;\n }\n }\n catch (_e) {\n }\n const anySelf = self;\n if (elem.href) {\n if (anySelf.__bx_addSet && !await anySelf.__bx_addSet(elem.href)) {\n return;\n }\n this.debug("Clicking on link: " + elem.href);\n }\n else {\n this.debug("Click empty link");\n }\n elem.click();\n await (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.sleep)(250);\n if (self.location.href != origHref) {\n await new Promise((resolve) => {\n window.addEventListener("popstate", () => {\n resolve(null);\n }, { once: true });\n window.history.back();\n });\n }\n }\n catch(e) {\n this.debug(e.toString());\n }\n done() {\n return this._donePromise;\n }\n}\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./src/autoclick.ts?')},"./src/autofetcher.ts": +/*!****************************!*\ + !*** ./src/autofetcher.ts ***! + \****************************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "AutoFetcher": () => (/* binding */ AutoFetcher)\n/* harmony export */ });\n/* harmony import */ var query_selector_shadow_dom__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! query-selector-shadow-dom */ "./node_modules/query-selector-shadow-dom/src/querySelectorDeep.js");\n/* harmony import */ var _lib_behavior__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./lib/behavior */ "./src/lib/behavior.ts");\n/* harmony import */ var _lib_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./lib/utils */ "./src/lib/utils.ts");\n\n\n\nconst SRC_SET_SELECTOR = "img[srcset], img[data-srcset], img[data-src], noscript > img[src], img[loading=\'lazy\'], " +\n "video[srcset], video[data-srcset], video[data-src], audio[srcset], audio[data-srcset], audio[data-src], " +\n "picture > source[srcset], picture > source[data-srcset], picture > source[data-src], " +\n "video > source[srcset], video > source[data-srcset], video > source[data-src], " +\n "audio > source[srcset], audio > source[data-srcset], audio > source[data-src]";\nconst SRCSET_REGEX = /\\s*(\\S*\\s+[\\d.]+[wx]),|(?:\\s*,(?:\\s+|(?=https?:)))/;\nconst STYLE_REGEX = /(url\\s*\\(\\s*[\\\\"\']*)([^)\'"]+)([\\\\"\']*\\s*\\))/gi;\nconst IMPORT_REGEX = /(@import\\s*[\\\\"\']*)([^)\'";]+)([\\\\"\']*\\s*;?)/gi;\nconst MAX_CONCURRENT = 6;\nclass AutoFetcher extends _lib_behavior__WEBPACK_IMPORTED_MODULE_1__.BackgroundBehavior {\n constructor(active = false, headers = null, startEarly = false) {\n super();\n this.urlSet = new Set();\n this.pendingQueue = [];\n this.waitQueue = [];\n this.numPending = 0;\n this.numDone = 0;\n this.running = false;\n this.headers = headers || {};\n this._donePromise = new Promise((resolve) => this._markDone = resolve);\n this.active = active;\n if (this.active && startEarly) {\n document.addEventListener("DOMContentLoaded", () => this.initObserver());\n }\n }\n get numFetching() {\n return this.numDone + this.numPending + this.pendingQueue.length;\n }\n async start() {\n if (!this.active) {\n return;\n }\n this.initObserver();\n this.run();\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(500).then(() => {\n if (!this.pendingQueue.length && !this.numPending) {\n this._markDone(null);\n }\n });\n }\n done() {\n return this._donePromise;\n }\n async run() {\n this.running = true;\n for (const url of this.waitQueue) {\n this.doFetch(url);\n }\n this.waitQueue = [];\n this.extractSrcSrcSetAll(document);\n this.extractStyleSheets();\n this.extractDataAttributes(document);\n }\n isValidUrl(url) {\n return url && (url.startsWith("http:") || url.startsWith("https:"));\n }\n queueUrl(url, immediate = false) {\n try {\n url = new URL(url, document.baseURI).href;\n }\n catch (e) {\n return false;\n }\n if (!this.isValidUrl(url)) {\n return false;\n }\n if (this.urlSet.has(url)) {\n return false;\n }\n this.urlSet.add(url);\n if (this.running || immediate) {\n this.doFetch(url);\n }\n else {\n this.waitQueue.push(url);\n }\n return true;\n }\n async doFetchStream(url) {\n try {\n const resp = await fetch(url, { "credentials": "include", "referrerPolicy": "origin-when-cross-origin" });\n this.debug(`Autofetch: started ${url}`);\n const reader = resp.body.getReader();\n let res = null;\n while ((res = await reader.read()) && !res.done)\n ;\n this.debug(`Autofetch: finished ${url}`);\n return true;\n }\n catch (e) {\n this.debug(e);\n return false;\n }\n }\n async doFetchNonCors(url) {\n try {\n const abort = new AbortController();\n await fetch(url, {\n "mode": "no-cors",\n "credentials": "include",\n "referrerPolicy": "origin-when-cross-origin",\n "headers": this.headers,\n abort\n });\n abort.abort();\n this.debug(`Autofetch: started non-cors stream for ${url}`);\n }\n catch (e) {\n this.debug(`Autofetch: failed non-cors for ${url}`);\n }\n }\n async doFetch(url) {\n this.pendingQueue.push(url);\n if (this.numPending <= MAX_CONCURRENT) {\n while (this.pendingQueue.length > 0) {\n const url = this.pendingQueue.shift();\n this.numPending++;\n let success = false;\n if (!success) {\n await this.doFetchNonCors(url);\n }\n this.numPending--;\n this.numDone++;\n }\n if (!this.numPending) {\n this._markDone(null);\n }\n }\n }\n initObserver() {\n if (this.mutationObserver) {\n return;\n }\n this.mutationObserver = new MutationObserver((changes) => this.observeChange(changes));\n this.mutationObserver.observe(document.documentElement, {\n characterData: false,\n characterDataOldValue: false,\n attributes: true,\n attributeOldValue: true,\n subtree: true,\n childList: true,\n attributeFilter: ["srcset", "loading"]\n });\n }\n processChangedNode(target) {\n switch (target.nodeType) {\n case Node.ATTRIBUTE_NODE:\n if (target.nodeName === "srcset") {\n this.extractSrcSetAttr(target.nodeValue);\n }\n if (target.nodeName === "loading" && target.nodeValue === "lazy") {\n const elem = target.parentNode;\n if (elem.tagName === "IMG") {\n elem.setAttribute("loading", "eager");\n }\n }\n break;\n case Node.TEXT_NODE:\n if (target.parentNode && target.parentNode.tagName === "STYLE") {\n this.extractStyleText(target.nodeValue);\n }\n break;\n case Node.ELEMENT_NODE:\n if (target.sheet) {\n this.extractStyleSheet(target.sheet);\n }\n this.extractSrcSrcSet(target);\n setTimeout(() => this.extractSrcSrcSetAll(target), 1000);\n setTimeout(() => this.extractDataAttributes(target), 1000);\n break;\n }\n }\n observeChange(changes) {\n for (const change of changes) {\n this.processChangedNode(change.target);\n if (change.type === "childList") {\n for (const node of change.addedNodes) {\n this.processChangedNode(node);\n }\n }\n }\n }\n extractSrcSrcSetAll(root) {\n const elems = (0,query_selector_shadow_dom__WEBPACK_IMPORTED_MODULE_0__.querySelectorAllDeep)(SRC_SET_SELECTOR, root);\n for (const elem of elems) {\n this.extractSrcSrcSet(elem);\n }\n }\n extractSrcSrcSet(elem) {\n if (!elem || elem.nodeType !== Node.ELEMENT_NODE) {\n console.warn("No elem to extract from");\n return;\n }\n const data_src = elem.getAttribute("data-src");\n if (data_src) {\n this.queueUrl(data_src);\n }\n if (elem.getAttribute("loading") === "lazy") {\n elem.setAttribute("loading", "eager");\n }\n const srcset = elem.getAttribute("srcset");\n if (srcset) {\n this.extractSrcSetAttr(srcset);\n }\n const data_srcset = elem.getAttribute("data-srcset");\n if (data_srcset) {\n this.extractSrcSetAttr(data_srcset);\n }\n const src = elem.getAttribute("src");\n if (src && (srcset || data_srcset || elem.parentElement.tagName === "NOSCRIPT")) {\n this.queueUrl(src);\n }\n }\n extractSrcSetAttr(srcset) {\n for (const v of srcset.split(SRCSET_REGEX)) {\n if (v) {\n const parts = v.trim().split(" ");\n this.queueUrl(parts[0]);\n }\n }\n }\n extractStyleSheets(root) {\n root = root || document;\n for (const sheet of root.styleSheets) {\n this.extractStyleSheet(sheet);\n }\n }\n extractStyleSheet(sheet) {\n let rules;\n try {\n rules = sheet.cssRules || sheet.rules;\n }\n catch (e) {\n this.debug("Can\'t access stylesheet");\n return;\n }\n for (const rule of rules) {\n if (rule.type === CSSRule.MEDIA_RULE) {\n this.extractStyleText(rule.cssText);\n }\n }\n }\n extractStyleText(text) {\n const urlExtractor = (m, n1, n2, n3) => {\n this.queueUrl(n2);\n return n1 + n2 + n3;\n };\n text.replace(STYLE_REGEX, urlExtractor).replace(IMPORT_REGEX, urlExtractor);\n }\n extractDataAttributes(root) {\n const QUERY = "//@*[starts-with(name(), \'data-\') and " +\n "(starts-with(., \'http\') or starts-with(., \'/\') or starts-with(., \'./\') or starts-with(., \'../\'))]";\n for (const attr of (0,_lib_utils__WEBPACK_IMPORTED_MODULE_2__.xpathNodes)(QUERY, root)) {\n this.queueUrl(attr.value);\n }\n }\n}\nAutoFetcher.id = "AutoFetcher";\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./src/autofetcher.ts?')},"./src/autoplay.ts": +/*!*************************!*\ + !*** ./src/autoplay.ts ***! + \*************************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "Autoplay": () => (/* binding */ Autoplay)\n/* harmony export */ });\n/* harmony import */ var query_selector_shadow_dom__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! query-selector-shadow-dom */ "./node_modules/query-selector-shadow-dom/src/querySelectorDeep.js");\n/* harmony import */ var _lib_behavior__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./lib/behavior */ "./src/lib/behavior.ts");\n/* harmony import */ var _lib_utils__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./lib/utils */ "./src/lib/utils.ts");\n\n\n\nclass Autoplay extends _lib_behavior__WEBPACK_IMPORTED_MODULE_1__.BackgroundBehavior {\n constructor(autofetcher, startEarly = false) {\n super();\n this.running = false;\n this.polling = false;\n this.mediaSet = new Set();\n this.autofetcher = autofetcher;\n this.numPlaying = 0;\n this.promises = [];\n this._initDone = () => null;\n this.promises.push(new Promise((resolve) => this._initDone = resolve));\n if (startEarly) {\n document.addEventListener("DOMContentLoaded", () => this.pollAudioVideo());\n }\n }\n async start() {\n this.running = true;\n this.pollAudioVideo();\n this._initDone();\n }\n async pollAudioVideo() {\n const run = true;\n if (this.polling) {\n return;\n }\n this.polling = true;\n while (run) {\n for (const [, elem] of (0,query_selector_shadow_dom__WEBPACK_IMPORTED_MODULE_0__.querySelectorAllDeep)("video, audio, picture").entries()) {\n if (!elem["__bx_autoplay_found"]) {\n if (!this.running) {\n if (this.processFetchableUrl(elem)) {\n elem["__bx_autoplay_found"] = true;\n }\n continue;\n }\n await this.loadMedia(elem);\n elem["__bx_autoplay_found"] = true;\n }\n }\n await (0,_lib_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(500);\n }\n this.polling = false;\n }\n fetchSrcUrl(source) {\n const url = source.src || source.currentSrc;\n if (!url) {\n return false;\n }\n if (!url.startsWith("http:") && !url.startsWith("https:")) {\n return false;\n }\n if (this.mediaSet.has(url)) {\n return true;\n }\n this.debug("fetch media source URL: " + url);\n this.mediaSet.add(url);\n this.autofetcher.queueUrl(url);\n return true;\n }\n processFetchableUrl(media) {\n let found = this.fetchSrcUrl(media);\n const sources = media.querySelectorAll("source");\n for (const source of sources) {\n const foundSource = this.fetchSrcUrl(source);\n found = found || foundSource;\n }\n return found;\n }\n async loadMedia(media) {\n this.debug("processing media element: " + media.outerHTML);\n const found = this.processFetchableUrl(media);\n if (!media.play) {\n this.debug("media not playable, skipping");\n return;\n }\n if (found) {\n if (!media.paused) {\n media.pause();\n this.debug("media URL found, pausing playback");\n }\n return;\n }\n if (media.paused || media.currentTime) {\n if (media.paused) {\n this.debug("no src url found, attempting to click or play: " + media.outerHTML);\n }\n else {\n this.debug("media already playing, waiting for full playback to finish: " + media.outerHTML);\n }\n this.attemptMediaPlay(media).then(async (finished) => {\n let check = true;\n if (finished) {\n finished.then(() => check = false);\n }\n while (check) {\n if (this.processFetchableUrl(media)) {\n check = false;\n }\n this.debug("Waiting for fixed URL or media to finish: " + media.currentSrc);\n await (0,_lib_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1000);\n }\n });\n }\n else if (media.currentSrc) {\n this.debug("media playing from non-URL source: " + media.currentSrc);\n }\n }\n async attemptMediaPlay(media) {\n let resolveFinished;\n const finished = new Promise((res) => {\n resolveFinished = res;\n });\n let resolveStarted;\n const started = new Promise((res) => {\n resolveStarted = res;\n });\n started.then(() => this.promises.push(finished));\n if (!media.paused && media.currentTime > 0) {\n resolveStarted();\n }\n media.addEventListener("loadstart", () => { this.debug("media event: loadstart"); resolveStarted(true); });\n media.addEventListener("playing", () => { this.debug("media event: playing"); resolveStarted(true); });\n media.addEventListener("loadeddata", () => this.debug("media event: loadeddata"));\n media.addEventListener("ended", () => { this.debug("media event: ended"); resolveFinished(); });\n media.addEventListener("pause", () => { this.debug("media event: pause"); resolveFinished(); });\n media.addEventListener("abort", () => { this.debug("media event: abort"); resolveFinished(); });\n media.addEventListener("error", () => { this.debug("media event: error"); resolveFinished(); });\n media.addEventListener("stalled", () => { this.debug("media event: stalled"); resolveFinished(); });\n media.addEventListener("suspend", () => { this.debug("media event: suspend"); resolveFinished(); });\n media.muted = true;\n if (!media.paused && media.currentTime > 0) {\n return finished;\n }\n const hasA = media.closest("a");\n if (!hasA) {\n media.click();\n if (await Promise.race([started, (0,_lib_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1000)])) {\n this.debug("play started after media.click()");\n return finished;\n }\n }\n media.play();\n if (await Promise.race([started, (0,_lib_utils__WEBPACK_IMPORTED_MODULE_2__.sleep)(1000)])) {\n this.debug("play started after media.play()");\n }\n return finished;\n }\n done() {\n return Promise.allSettled(this.promises);\n }\n}\nAutoplay.id = "Autoplay";\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./src/autoplay.ts?')},"./src/autoscroll.ts": +/*!***************************!*\ + !*** ./src/autoscroll.ts ***! + \***************************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "AutoScroll": () => (/* binding */ AutoScroll)\n/* harmony export */ });\n/* harmony import */ var _lib_behavior__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./lib/behavior */ "./src/lib/behavior.ts");\n/* harmony import */ var _lib_utils__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./lib/utils */ "./src/lib/utils.ts");\n\n\nclass AutoScroll extends _lib_behavior__WEBPACK_IMPORTED_MODULE_0__.Behavior {\n constructor(autofetcher) {\n super();\n this.autoFetcher = autofetcher;\n this.showMoreQuery = "//*[contains(text(), \'show more\') or contains(text(), \'Show more\')]";\n this.state = {\n segments: 1\n };\n this.lastScrollPos = -1;\n this.samePosCount = 0;\n this.origPath = document.location.pathname;\n }\n currScrollPos() {\n return Math.round(self.scrollY + self.innerHeight);\n }\n canScrollMore() {\n const scrollElem = self.document.scrollingElement || self.document.body;\n return this.currScrollPos() < Math.max(scrollElem.clientHeight, scrollElem.scrollHeight);\n }\n hasScrollEL(obj) {\n try {\n return !!self["getEventListeners"](obj).scroll;\n }\n catch (e) {\n this.debug("getEventListeners() not available");\n return true;\n }\n }\n async shouldScroll() {\n if (!this.hasScrollEL(self.window) &&\n !this.hasScrollEL(self.document) &&\n !this.hasScrollEL(self.document.body)) {\n return false;\n }\n const lastScrollHeight = self.document.scrollingElement.scrollHeight;\n const numFetching = this.autoFetcher.numFetching;\n const scrollEnd = (document.scrollingElement.scrollHeight * 0.98) - self.innerHeight;\n window.scrollTo({ top: scrollEnd, left: 0, behavior: "smooth" });\n await (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.sleep)(500);\n if (lastScrollHeight !== self.document.scrollingElement.scrollHeight ||\n numFetching < this.autoFetcher.numFetching) {\n window.scrollTo({ top: 0, left: 0, behavior: "auto" });\n return true;\n }\n return false;\n }\n shouldScrollUp() {\n if (self.window.scrollY === 0) {\n return false;\n }\n if ((self.window.scrollY + self["scrollHeight"]) / self.document.scrollingElement.scrollHeight < 0.90) {\n return false;\n }\n return true;\n }\n async *[Symbol.asyncIterator]() {\n if (this.shouldScrollUp()) {\n yield* this.scrollUp();\n return;\n }\n if (await this.shouldScroll()) {\n yield* this.scrollDown();\n return;\n }\n yield this.getState("Skipping autoscroll, page seems to not be responsive to scrolling events");\n }\n async *scrollDown() {\n const scrollInc = Math.min(self.document.scrollingElement.clientHeight * 0.10, 30);\n const interval = 75;\n let elapsedWait = 0;\n let showMoreElem = null;\n let ignoreShowMoreElem = false;\n const scrollOpts = { top: scrollInc, left: 0, behavior: "auto" };\n let lastScrollHeight = self.document.scrollingElement.scrollHeight;\n while (this.canScrollMore()) {\n if (document.location.pathname !== this.origPath) {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.behaviorLog)("Location Changed, stopping scroll: " +\n `${document.location.pathname} != ${this.origPath}`, "info");\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.addLink)(document.location.href);\n return;\n }\n const scrollHeight = self.document.scrollingElement.scrollHeight;\n if (scrollHeight > lastScrollHeight) {\n this.state.segments++;\n lastScrollHeight = scrollHeight;\n }\n if (!showMoreElem && !ignoreShowMoreElem) {\n showMoreElem = (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.xpathNode)(this.showMoreQuery);\n }\n if (showMoreElem && (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.isInViewport)(showMoreElem)) {\n yield this.getState("Clicking \'Show More\', awaiting more content");\n showMoreElem["click"]();\n await (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.sleep)(_lib_utils__WEBPACK_IMPORTED_MODULE_1__.waitUnit);\n await Promise.race([\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.waitUntil)(() => self.document.scrollingElement.scrollHeight > scrollHeight, 500),\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.sleep)(30000)\n ]);\n if (self.document.scrollingElement.scrollHeight === scrollHeight) {\n ignoreShowMoreElem = true;\n }\n showMoreElem = null;\n }\n self.scrollBy(scrollOpts);\n await (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.sleep)(interval);\n if (this.state.segments === 1) {\n yield this.getState(`Scrolling down by ${scrollOpts.top} pixels every ${interval / 1000.0} seconds`);\n elapsedWait = 2.0;\n }\n else {\n const waitSecs = elapsedWait / (this.state.segments - 1);\n this.debug(`Waiting up to ${waitSecs} seconds for more scroll segments`);\n const startTime = Date.now();\n await Promise.race([\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.waitUntil)(() => this.canScrollMore(), interval),\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.sleep)(waitSecs)\n ]);\n elapsedWait += (Date.now() - startTime) * 2;\n }\n const currPos = this.currScrollPos();\n if (currPos === this.lastScrollPos) {\n if (++this.samePosCount >= 2) {\n break;\n }\n }\n else {\n this.samePosCount = 0;\n }\n this.lastScrollPos = currPos;\n }\n }\n async *scrollUp() {\n const scrollInc = Math.min(self.document.scrollingElement.clientHeight * 0.10, 30);\n const interval = 75;\n const scrollOpts = { top: -scrollInc, left: 0, behavior: "auto" };\n let lastScrollHeight = self.document.scrollingElement.scrollHeight;\n while (self.scrollY > 0) {\n const scrollHeight = self.document.scrollingElement.scrollHeight;\n if (scrollHeight > lastScrollHeight) {\n this.state.segments++;\n lastScrollHeight = scrollHeight;\n }\n self.scrollBy(scrollOpts);\n await (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.sleep)(interval);\n if (this.state.segments === 1) {\n yield this.getState(`Scrolling up by ${scrollOpts.top} pixels every ${interval / 1000.0} seconds`);\n }\n else {\n await Promise.race([\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.waitUntil)(() => self.scrollY > 0, interval),\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_1__.sleep)((this.state.segments - 1) * 2000)\n ]);\n }\n }\n }\n}\nAutoScroll.id = "Autoscroll";\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./src/autoscroll.ts?')},"./src/index.ts": +/*!**********************!*\ + !*** ./src/index.ts ***! + \**********************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "BehaviorManager": () => (/* binding */ BehaviorManager)\n/* harmony export */ });\n/* harmony import */ var _autofetcher__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./autofetcher */ "./src/autofetcher.ts");\n/* harmony import */ var _autoplay__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./autoplay */ "./src/autoplay.ts");\n/* harmony import */ var _autoscroll__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./autoscroll */ "./src/autoscroll.ts");\n/* harmony import */ var _autoclick__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./autoclick */ "./src/autoclick.ts");\n/* harmony import */ var _lib_utils__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./lib/utils */ "./src/lib/utils.ts");\n/* harmony import */ var _lib_behavior__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(/*! ./lib/behavior */ "./src/lib/behavior.ts");\n/* harmony import */ var _site__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(/*! ./site */ "./src/site/index.ts");\n\n\n\n\n\n\n\nconst DEFAULT_OPTS = { autofetch: true, autoplay: true, autoscroll: true, autoclick: true, siteSpecific: true };\nconst DEFAULT_SELECTOR = "a[href]";\nclass BehaviorManager {\n constructor() {\n this.behaviors = [];\n this.loadedBehaviors = _site__WEBPACK_IMPORTED_MODULE_6__["default"].reduce((behaviors, next) => {\n behaviors[next.id] = next;\n return behaviors;\n }, {});\n this.mainBehavior = null;\n this.inited = false;\n this.started = false;\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)("Loaded behaviors for: " + self.location.href);\n }\n init(opts = DEFAULT_OPTS, restart = false, customBehaviors = null) {\n if (this.inited && !restart) {\n return;\n }\n this.inited = true;\n this.opts = opts;\n if (!self.window) {\n return;\n }\n this.timeout = opts.timeout;\n if (opts.log !== undefined) {\n let logger = opts.log;\n if (typeof (logger) === "string") {\n logger = self[logger];\n }\n if (typeof (logger) === "function") {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__._setLogFunc)(logger);\n }\n else if (logger === false) {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__._setLogFunc)(null);\n }\n }\n this.autofetch = new _autofetcher__WEBPACK_IMPORTED_MODULE_0__.AutoFetcher(!!opts.autofetch, opts.fetchHeaders, opts.startEarly);\n if (opts.autofetch) {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)("Using AutoFetcher");\n this.behaviors.push(this.autofetch);\n }\n if (opts.autoplay) {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)("Using Autoplay");\n this.behaviors.push(new _autoplay__WEBPACK_IMPORTED_MODULE_1__.Autoplay(this.autofetch, opts.startEarly));\n }\n if (opts.autoclick) {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)("Using AutoClick");\n this.behaviors.push(new _autoclick__WEBPACK_IMPORTED_MODULE_3__.AutoClick(opts.clickSelector || DEFAULT_SELECTOR));\n }\n if (!this.isInTopFrame()) {\n return;\n }\n if (customBehaviors) {\n for (const behaviorClass of customBehaviors) {\n try {\n this.load(behaviorClass);\n }\n catch (e) {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)(`Failed to load custom behavior: ${e} ${behaviorClass}`);\n }\n }\n }\n }\n selectMainBehavior() {\n if (this.mainBehavior) {\n return;\n }\n const opts = this.opts;\n let siteMatch = false;\n if (opts.siteSpecific) {\n for (const name in this.loadedBehaviors) {\n const siteBehaviorClass = this.loadedBehaviors[name];\n if (siteBehaviorClass.isMatch()) {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)("Using Site-Specific Behavior: " + name);\n this.mainBehaviorClass = siteBehaviorClass;\n const siteSpecificOpts = typeof opts.siteSpecific === "object" ?\n (opts.siteSpecific[name] || {}) : {};\n try {\n this.mainBehavior = new _lib_behavior__WEBPACK_IMPORTED_MODULE_5__.BehaviorRunner(siteBehaviorClass, siteSpecificOpts);\n }\n catch (e) {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)(e.toString(), "error");\n }\n siteMatch = true;\n break;\n }\n }\n }\n if (!siteMatch && opts.autoscroll) {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)("Using Autoscroll");\n this.mainBehaviorClass = _autoscroll__WEBPACK_IMPORTED_MODULE_2__.AutoScroll;\n this.mainBehavior = new _autoscroll__WEBPACK_IMPORTED_MODULE_2__.AutoScroll(this.autofetch);\n }\n if (this.mainBehavior) {\n this.behaviors.push(this.mainBehavior);\n if (this.mainBehavior instanceof _lib_behavior__WEBPACK_IMPORTED_MODULE_5__.BehaviorRunner) {\n return this.mainBehavior.behaviorProps.id;\n }\n }\n return "";\n }\n load(behaviorClass) {\n if (typeof (behaviorClass) !== "function") {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)(`Must pass a class object, got ${behaviorClass}`, "error");\n return;\n }\n if (typeof (behaviorClass.id) !== "string") {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)("Behavior class must have a string string \\"id\\" property", "error");\n return;\n }\n if (typeof (behaviorClass.isMatch) !== "function" ||\n typeof (behaviorClass.init) !== "function") {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)("Behavior class must have an is `isMatch()` and `init()` static methods", "error");\n return;\n }\n const name = behaviorClass.id;\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)(`Loading external class ${name}: ${behaviorClass}`, "debug");\n this.loadedBehaviors[name] = behaviorClass;\n }\n async resolve(target) {\n const imported = await __webpack_require__("./src lazy recursive ^.*$")(`${target}`);\n if (Array.isArray(imported)) {\n for (const behavior of imported) {\n this.load(behavior);\n }\n }\n else {\n this.load(imported);\n }\n }\n async awaitPageLoad() {\n this.selectMainBehavior();\n if (this.mainBehavior?.awaitPageLoad) {\n await this.mainBehavior.awaitPageLoad();\n }\n }\n async run(opts = DEFAULT_OPTS, restart = false) {\n if (restart) {\n this.started = false;\n }\n if (this.started) {\n this.unpause();\n return;\n }\n this.init(opts, restart);\n this.selectMainBehavior();\n await (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.awaitLoad)();\n this.behaviors.forEach(x => {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)("Starting behavior: " + x.constructor.id || 0);\n x.start();\n });\n this.started = true;\n await (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.sleep)(500);\n let allBehaviors = Promise.allSettled(this.behaviors.map(x => x.done()));\n if (this.timeout) {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)(`Waiting for behaviors to finish or ${this.timeout}ms timeout`);\n await Promise.race([allBehaviors, (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.sleep)(this.timeout)]);\n }\n else {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)("Waiting for behaviors to finish");\n await allBehaviors;\n }\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)("All Behaviors Done for " + self.location.href);\n if (this.mainBehavior && this.mainBehaviorClass.cleanup) {\n this.mainBehavior.cleanup();\n }\n }\n async runOne(name, behaviorOpts = {}) {\n const siteBehaviorClass = _site__WEBPACK_IMPORTED_MODULE_6__["default"].find(b => b.name === name);\n if (typeof siteBehaviorClass === "undefined") {\n console.error(`No behavior of name ${name} found`);\n return;\n }\n const behavior = new _lib_behavior__WEBPACK_IMPORTED_MODULE_5__.BehaviorRunner(siteBehaviorClass, behaviorOpts);\n behavior.start();\n console.log(`Running behavior: ${name}`);\n await behavior.done();\n console.log(`Behavior ${name} completed`);\n }\n pause() {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)("Pausing Main Behavior" + this.mainBehaviorClass.name);\n this.behaviors.forEach(x => x.pause());\n }\n unpause() {\n this.behaviors.forEach(x => x.unpause());\n }\n doAsyncFetch(url) {\n (0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.behaviorLog)("Queueing Async Fetch Url: " + url);\n return this.autofetch.queueUrl(url, true);\n }\n isInTopFrame() {\n return self.window.top === self.window || window["__WB_replay_top"] === self.window;\n }\n}\n(0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__._setBehaviorManager)(BehaviorManager);\n(0,_lib_utils__WEBPACK_IMPORTED_MODULE_4__.installBehaviors)(self);\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./src/index.ts?')},"./src/lib/behavior.ts": +/*!*****************************!*\ + !*** ./src/lib/behavior.ts ***! + \*****************************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "BackgroundBehavior": () => (/* binding */ BackgroundBehavior),\n/* harmony export */ "Behavior": () => (/* binding */ Behavior),\n/* harmony export */ "BehaviorRunner": () => (/* binding */ BehaviorRunner)\n/* harmony export */ });\n/* harmony import */ var _utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./utils */ "./src/lib/utils.ts");\n\n\nclass BackgroundBehavior {\n debug(msg) {\n (0,_utils__WEBPACK_IMPORTED_MODULE_0__.behaviorLog)(msg, "debug");\n }\n log(msg) {\n (0,_utils__WEBPACK_IMPORTED_MODULE_0__.behaviorLog)(msg, "info");\n }\n}\nclass Behavior extends BackgroundBehavior {\n constructor() {\n super();\n this._running = null;\n this.paused = null;\n this._unpause = null;\n this.state = {};\n this.scrollOpts = { behavior: "smooth", block: "center", inline: "center" };\n }\n start() {\n this._running = this.run();\n }\n done() {\n return this._running ? this._running : Promise.resolve();\n }\n async run() {\n try {\n for await (const step of this) {\n this.log(step);\n if (this.paused) {\n await this.paused;\n }\n }\n this.log(this.getState("done!"));\n }\n catch (e) {\n this.log(this.getState(e));\n }\n }\n pause() {\n if (this.paused) {\n return;\n }\n this.paused = new Promise((resolve) => {\n this._unpause = resolve;\n });\n }\n unpause() {\n if (this._unpause) {\n this._unpause();\n this.paused = null;\n this._unpause = null;\n }\n }\n getState(msg, incrValue) {\n if (incrValue) {\n if (this.state[incrValue] === undefined) {\n this.state[incrValue] = 1;\n }\n else {\n this.state[incrValue]++;\n }\n }\n return { state: this.state, msg };\n }\n cleanup() {\n }\n async awaitPageLoad() {\n }\n static load() {\n if (self["__bx_behaviors"]) {\n self["__bx_behaviors"].load(this);\n }\n else {\n console.warn(`Could not load ${this.name} behavior: window.__bx_behaviors is not initialized`);\n }\n }\n async *[Symbol.asyncIterator]() {\n yield;\n }\n}\nclass AbstractBehaviorInst {\n}\nclass BehaviorRunner extends BackgroundBehavior {\n constructor(behavior, mainOpts = {}) {\n super();\n this.behaviorProps = behavior;\n this.inst = new behavior;\n if (typeof this.inst.run !== "function" ||\n this.inst.run.constructor.name !== "AsyncGeneratorFunction") {\n throw Error("Invalid behavior: missing `async run*` instance method");\n }\n let { state, opts } = behavior.init();\n state = state || {};\n opts = opts ? { ...opts, ...mainOpts } : mainOpts;\n const log = _utils__WEBPACK_IMPORTED_MODULE_0__.behaviorLog;\n this.ctx = { Lib: _utils__WEBPACK_IMPORTED_MODULE_0__, state, opts, log };\n this._running = null;\n this.paused = null;\n this._unpause = null;\n }\n start() {\n this._running = this.run();\n }\n done() {\n return this._running ? this._running : Promise.resolve();\n }\n async run() {\n try {\n for await (const step of this.inst.run(this.ctx)) {\n this.log(step);\n if (this.paused) {\n await this.paused;\n }\n }\n this.log((0,_utils__WEBPACK_IMPORTED_MODULE_0__.getState)(this.ctx, "done!"));\n }\n catch (e) {\n this.log((0,_utils__WEBPACK_IMPORTED_MODULE_0__.getState)(this.ctx, e));\n }\n }\n pause() {\n if (this.paused) {\n return;\n }\n this.paused = new Promise((resolve) => {\n this._unpause = resolve;\n });\n }\n unpause() {\n if (this._unpause) {\n this._unpause();\n this.paused = null;\n this._unpause = null;\n }\n }\n cleanup() {\n }\n async awaitPageLoad() {\n if (this.inst.awaitPageLoad) {\n await this.inst.awaitPageLoad(this.ctx);\n }\n }\n static load() {\n if (self["__bx_behaviors"]) {\n self["__bx_behaviors"].load(this);\n }\n else {\n console.warn(`Could not load ${this.name} behavior: window.__bx_behaviors is not initialized`);\n }\n }\n}\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./src/lib/behavior.ts?')},"./src/lib/utils.ts": +/*!**************************!*\ + !*** ./src/lib/utils.ts ***! + \**************************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "HistoryState": () => (/* binding */ HistoryState),\n/* harmony export */ "RestoreState": () => (/* binding */ RestoreState),\n/* harmony export */ "_setBehaviorManager": () => (/* binding */ _setBehaviorManager),\n/* harmony export */ "_setLogFunc": () => (/* binding */ _setLogFunc),\n/* harmony export */ "addLink": () => (/* binding */ addLink),\n/* harmony export */ "awaitLoad": () => (/* binding */ awaitLoad),\n/* harmony export */ "behaviorLog": () => (/* binding */ behaviorLog),\n/* harmony export */ "getState": () => (/* binding */ getState),\n/* harmony export */ "installBehaviors": () => (/* binding */ installBehaviors),\n/* harmony export */ "isInViewport": () => (/* binding */ isInViewport),\n/* harmony export */ "iterChildElem": () => (/* binding */ iterChildElem),\n/* harmony export */ "iterChildMatches": () => (/* binding */ iterChildMatches),\n/* harmony export */ "openWindow": () => (/* binding */ openWindow),\n/* harmony export */ "scrollAndClick": () => (/* binding */ scrollAndClick),\n/* harmony export */ "scrollIntoView": () => (/* binding */ scrollIntoView),\n/* harmony export */ "scrollToOffset": () => (/* binding */ scrollToOffset),\n/* harmony export */ "sleep": () => (/* binding */ sleep),\n/* harmony export */ "waitUnit": () => (/* binding */ waitUnit),\n/* harmony export */ "waitUntil": () => (/* binding */ waitUntil),\n/* harmony export */ "waitUntilNode": () => (/* binding */ waitUntilNode),\n/* harmony export */ "xpathNode": () => (/* binding */ xpathNode),\n/* harmony export */ "xpathNodes": () => (/* binding */ xpathNodes),\n/* harmony export */ "xpathString": () => (/* binding */ xpathString)\n/* harmony export */ });\nlet _logFunc = console.log;\nlet _behaviorMgrClass = null;\nconst scrollOpts = { behavior: "smooth", block: "center", inline: "center" };\nasync function scrollAndClick(node, interval = 500, opts = scrollOpts) {\n node.scrollIntoView(opts);\n await sleep(interval);\n node.click();\n}\nconst waitUnit = 200;\nfunction sleep(timeout) {\n return new Promise((resolve) => setTimeout(resolve, timeout));\n}\nasync function waitUntil(pred, interval = waitUnit) {\n while (!pred()) {\n await sleep(interval);\n }\n}\nasync function waitUntilNode(path, root = document, old = null, timeout = 1000, interval = waitUnit) {\n let node = null;\n let stop = false;\n const waitP = waitUntil(() => {\n node = xpathNode(path, root);\n return stop || (node !== old && node !== null);\n }, interval);\n const timeoutP = new Promise((r) => setTimeout(() => { stop = true; r("TIMEOUT"); }, timeout));\n await Promise.race([waitP, timeoutP]);\n return node;\n}\nfunction awaitLoad() {\n return new Promise((resolve) => {\n if (document.readyState === "complete") {\n resolve(null);\n }\n else {\n window.addEventListener("load", resolve);\n }\n });\n}\nfunction callBinding(binding, obj) {\n try {\n binding(obj);\n }\n catch (e) {\n binding(JSON.stringify(obj));\n }\n}\nfunction behaviorLog(data, type = "debug") {\n if (_logFunc) {\n callBinding(_logFunc, { data, type });\n }\n}\nfunction addLink(url) {\n if (self["__bx_addLink"]) {\n self["__bx_addLink"](url);\n }\n}\nasync function openWindow(url) {\n if (self["__bx_open"]) {\n const p = new Promise((resolve) => self["__bx_openResolve"] = resolve);\n callBinding(self["__bx_open"], { url });\n let win = null;\n try {\n win = await p;\n if (win) {\n return win;\n }\n }\n catch (e) {\n console.warn(e);\n }\n finally {\n delete self["__bx_openResolve"];\n }\n }\n return window.open(url);\n}\nfunction _setLogFunc(func) {\n _logFunc = func;\n}\nfunction _setBehaviorManager(cls) {\n _behaviorMgrClass = cls;\n}\nfunction installBehaviors(obj) {\n obj.__bx_behaviors = new _behaviorMgrClass();\n}\nclass RestoreState {\n constructor(childMatchSelect, child) {\n this.matchValue = xpathString(childMatchSelect, child);\n }\n async restore(rootPath, childMatch) {\n let root = null;\n while (root = xpathNode(rootPath), !root) {\n await sleep(100);\n }\n return xpathNode(childMatch.replace("$1", this.matchValue), root);\n }\n}\nclass HistoryState {\n constructor(op) {\n this.loc = window.location.href;\n op();\n }\n get changed() {\n return window.location.href !== this.loc;\n }\n goBack(backButtonQuery) {\n if (!this.changed) {\n return Promise.resolve(true);\n }\n const backButton = xpathNode(backButtonQuery);\n return new Promise((resolve) => {\n window.addEventListener("popstate", () => {\n resolve(null);\n }, { once: true });\n if (backButton) {\n backButton["click"]();\n }\n else {\n window.history.back();\n }\n });\n }\n}\nfunction xpathNode(path, root) {\n root = root || document;\n return document.evaluate(path, root, null, XPathResult.FIRST_ORDERED_NODE_TYPE).singleNodeValue;\n}\nfunction* xpathNodes(path, root) {\n root = root || document;\n let iter = document.evaluate(path, root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE);\n let result = null;\n while ((result = iter.iterateNext()) !== null) {\n yield result;\n }\n}\nfunction xpathString(path, root) {\n root = root || document;\n return document.evaluate(path, root, null, XPathResult.STRING_TYPE).stringValue;\n}\nasync function* iterChildElem(root, timeout, totalTimeout) {\n let child = root.firstElementChild;\n while (child) {\n yield child;\n if (!child.nextElementSibling) {\n await Promise.race([\n waitUntil(() => !!child.nextElementSibling, timeout),\n sleep(totalTimeout)\n ]);\n }\n child = child.nextElementSibling;\n }\n}\nasync function* iterChildMatches(path, root, interval = waitUnit, timeout = 5000) {\n let node = xpathNode(`.//${path}`, root);\n const getMatch = (node) => xpathNode(`./following-sibling::${path}`, node);\n while (node) {\n yield node;\n let next = getMatch(node);\n if (next) {\n node = next;\n continue;\n }\n await Promise.race([\n waitUntil(() => {\n next = getMatch(node);\n return next;\n }, interval),\n sleep(timeout)\n ]);\n node = next;\n }\n}\nfunction isInViewport(elem) {\n var bounding = elem.getBoundingClientRect();\n return (bounding.top >= 0 &&\n bounding.left >= 0 &&\n bounding.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&\n bounding.right <= (window.innerWidth || document.documentElement.clientWidth));\n}\nfunction scrollToOffset(element, offset = 0) {\n const elPosition = element.getBoundingClientRect().top;\n const topPosition = elPosition + window.pageYOffset - offset;\n window.scrollTo({ top: topPosition, behavior: "smooth" });\n}\nfunction scrollIntoView(element, opts = {\n behavior: "smooth", block: "center", inline: "center"\n}) {\n element.scrollIntoView(opts);\n}\nfunction getState(ctx, msg, incrValue) {\n if (typeof ctx.state === undefined) {\n ctx.state = {};\n }\n if (incrValue) {\n if (ctx.state[incrValue] === undefined) {\n ctx.state[incrValue] = 1;\n }\n else {\n ctx.state[incrValue]++;\n }\n }\n return { state: ctx.state, msg };\n}\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./src/lib/utils.ts?')},"./src/site/facebook.ts": +/*!******************************!*\ + !*** ./src/site/facebook.ts ***! + \******************************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "FacebookTimelineBehavior": () => (/* binding */ FacebookTimelineBehavior)\n/* harmony export */ });\nconst Q = {\n feed: "//div[@role=\'main\']/div[4]/div[2]/div/div[2]/div[2]",\n article: ".//div[@role=\'article\']",\n pageletPostList: "//div[@data-pagelet=\'page\']/div[@role=\'main\']//div[@role=\'main\']/div",\n pageletProfilePostList: "//div[@data-pagelet=\'page\']//div[@data-pagelet=\'ProfileTimeline\']",\n articleToPostList: "//div[@role=\'article\']/../../../../div",\n photosOrVideos: `.//a[(contains(@href, \'/photos/\') or contains(@href, \'/photo/?\') or contains(@href, \'/videos/\')) and (starts-with(@href, \'${window.location.origin}/\') or starts-with(@href, \'/\'))]`,\n postQuery: ".//a[contains(@href, \'/posts/\')]",\n extraLabel: "//*[starts-with(text(), \'+\')]",\n nextSlideQuery: "//div[@data-name=\'media-viewer-nav-container\']/div[@data-visualcompletion][2]//div[@role=\'button\']",\n nextSlide: "//div[@aria-hidden=\'false\']//div[@role=\'button\' and not(@aria-hidden) and @aria-label]",\n commentList: ".//ul[(../h3) or (../h4)]",\n commentMoreReplies: "./div[2]/div[1]/div[2]/div[@role=\'button\']",\n commentMoreComments: "./following-sibling::div/div/div[2][@role=\'button\'][./span/span]",\n viewComments: ".//h4/..//div[@role=\'button\']",\n photoCommentList: "//ul[../h2]",\n firstPhotoThumbnail: "//div[@role=\'main\']//div[3]//div[contains(@style, \'border-radius\')]//div[contains(@style, \'max-width\') and contains(@style, \'min-width\')]//a[@role=\'link\']",\n firstVideoThumbnail: "//div[@role=\'main\']//div[contains(@style, \'z-index\')]/following-sibling::div/div/div/div[last()]//a[contains(@href, \'/videos/\') and @aria-hidden!=\'true\']",\n firstVideoSimple: "//div[@role=\'main\']//a[contains(@href, \'/videos/\') and @aria-hidden!=\'true\']",\n mainVideo: "//div[@data-pagelet=\'root\']//div[@role=\'dialog\']//div[@role=\'main\']//video",\n nextVideo: "following::a[contains(@href, \'/videos/\') and @aria-hidden!=\'true\']",\n isPhotoVideoPage: /^.*facebook\\.com\\/[^/]+\\/(photos|videos)\\/.+/,\n isPhotosPage: /^.*facebook\\.com\\/[^/]+\\/photos\\/?($|\\?)/,\n isVideosPage: /^.*facebook\\.com\\/[^/]+\\/videos\\/?($|\\?)/,\n};\nclass FacebookTimelineBehavior {\n static isMatch() {\n return !!window.location.href.match(/https:\\/\\/(www\\.)?facebook\\.com\\//);\n }\n static init() {\n return {\n state: {}\n };\n }\n constructor() {\n this.extraWindow = null;\n this.allowNewWindow = false;\n }\n async *iterPostFeeds(ctx) {\n const { iterChildElem, waitUnit, waitUntil, xpathNode, xpathNodes } = ctx.Lib;\n const feeds = Array.from(xpathNodes(Q.feed));\n if (feeds && feeds.length) {\n for (const feed of feeds) {\n for await (const post of iterChildElem(feed, waitUnit, waitUntil * 10)) {\n yield* this.viewPost(ctx, xpathNode(Q.article, post));\n }\n }\n }\n else {\n const feed = xpathNode(Q.pageletPostList) ||\n xpathNode(Q.pageletProfilePostList) ||\n xpathNode(Q.articleToPostList);\n if (!feed) {\n return;\n }\n for await (const post of iterChildElem(feed, waitUnit, waitUntil * 10)) {\n yield* this.viewPost(ctx, xpathNode(Q.article, post));\n }\n }\n if (this.extraWindow) {\n this.extraWindow.close();\n }\n }\n async *viewPost(ctx, post, maxExpands = 2) {\n const { getState, scrollIntoView, sleep, waitUnit, xpathNode } = ctx.Lib;\n if (!post) {\n return;\n }\n const postLink = xpathNode(Q.postQuery, post);\n let url = null;\n if (postLink) {\n url = new URL(postLink.href, window.location.href);\n url.search = "";\n }\n yield getState(ctx, "Viewing post " + (url || ""), "posts");\n scrollIntoView(post);\n await sleep(waitUnit * 2);\n if (xpathNode(".//video", post)) {\n yield getState(ctx, "Playing inline video", "videos");\n await sleep(waitUnit * 2);\n }\n let commentRootUL = xpathNode(Q.commentList, post);\n if (!commentRootUL) {\n const viewCommentsButton = xpathNode(Q.viewComments, post);\n if (viewCommentsButton) {\n viewCommentsButton.click();\n await sleep(waitUnit * 2);\n }\n commentRootUL = xpathNode(Q.commentList, post);\n }\n yield* this.iterComments(ctx, commentRootUL, maxExpands);\n await sleep(waitUnit * 5);\n }\n async *viewPhotosOrVideos(ctx, post) {\n const { getState, sleep, waitUnit, xpathNode, xpathNodes } = ctx.Lib;\n const objects = Array.from(xpathNodes(Q.photosOrVideos, post));\n const objHrefs = new Set();\n let count = 0;\n for (const obj of objects) {\n const url = new URL(obj.href, window.location.href);\n if (obj.href.indexOf("?fbid") === -1) {\n url.search = "";\n }\n if (objHrefs.has(url.href)) {\n continue;\n }\n const type = obj.href.indexOf("/video") >= 0 ? "videos" : "photos";\n ++count;\n objHrefs.add(url.href);\n yield getState(ctx, `Viewing ${type} ${url.href}`, type);\n obj.scrollIntoView();\n await sleep(waitUnit * 5);\n obj.click();\n await sleep(waitUnit * 10);\n if (this.allowNewWindow) {\n await this.openNewWindow(ctx, url.href);\n }\n if (count === objects.length) {\n yield* this.viewExtraObjects(ctx, obj, type, this.allowNewWindow);\n }\n const close = xpathNode(Q.nextSlide);\n if (close) {\n close.click();\n await sleep(waitUnit * 2);\n }\n }\n }\n async *viewExtraObjects(ctx, obj, type, openNew) {\n const { getState, sleep, waitUnit, waitUntil, xpathNode } = ctx.Lib;\n const extraLabel = xpathNode(Q.extraLabel, obj);\n if (!extraLabel) {\n return;\n }\n const num = Number(extraLabel.innerText.slice(1));\n if (isNaN(num)) {\n return;\n }\n let lastHref;\n for (let i = 0; i < num; i++) {\n const nextSlideButton = xpathNode(Q.nextSlideQuery);\n if (!nextSlideButton) {\n continue;\n }\n lastHref = window.location.href;\n nextSlideButton.click();\n await sleep(waitUnit * 5);\n await waitUntil(() => window.location.href !== lastHref, waitUnit * 2);\n yield getState(ctx, `Viewing extra ${type} ${window.location.href}`);\n if (openNew) {\n await this.openNewWindow(ctx, window.location.href);\n }\n }\n }\n async openNewWindow(ctx, url) {\n if (!this.extraWindow) {\n this.extraWindow = await ctx.Lib.openWindow(url);\n }\n else {\n this.extraWindow.location.href = url;\n }\n }\n async *iterComments(ctx, commentRootUL, maxExpands = 2) {\n const { getState, scrollIntoView, sleep, waitUnit, xpathNode } = ctx.Lib;\n if (!commentRootUL) {\n await sleep(waitUnit * 5);\n return;\n }\n let commentBlock = commentRootUL.firstElementChild;\n let lastBlock = null;\n let count = 0;\n while (commentBlock && count < maxExpands) {\n while (commentBlock && count < maxExpands) {\n yield getState(ctx, "Loading comments", "comments");\n scrollIntoView(commentBlock);\n await sleep(waitUnit * 2);\n const moreReplies = xpathNode(Q.commentMoreReplies, commentBlock);\n if (moreReplies) {\n moreReplies.click();\n await sleep(waitUnit * 5);\n }\n lastBlock = commentBlock;\n commentBlock = lastBlock.nextElementSibling;\n count++;\n }\n if (count === maxExpands) {\n break;\n }\n let moreButton = xpathNode(Q.commentMoreComments, commentRootUL);\n if (moreButton) {\n scrollIntoView(moreButton);\n moreButton.click();\n await sleep(waitUnit * 5);\n if (lastBlock) {\n commentBlock = lastBlock.nextElementSibling;\n await sleep(waitUnit * 5);\n }\n }\n }\n await sleep(waitUnit * 2);\n }\n async *iterPhotoSlideShow(ctx) {\n const { getState, scrollIntoView, sleep, waitUnit, waitUntil, xpathNode } = ctx.Lib;\n const firstPhoto = xpathNode(Q.firstPhotoThumbnail);\n if (!firstPhoto) {\n return;\n }\n let lastHref = window.location.href;\n scrollIntoView(firstPhoto);\n firstPhoto.click();\n await sleep(waitUnit * 5);\n await waitUntil(() => window.location.href !== lastHref, waitUnit * 2);\n let nextSlideButton = null;\n while ((nextSlideButton = xpathNode(Q.nextSlideQuery))) {\n lastHref = window.location.href;\n await sleep(waitUnit);\n nextSlideButton.click();\n await sleep(waitUnit * 5);\n await Promise.race([\n waitUntil(() => window.location.href !== lastHref, waitUnit * 2),\n sleep(3000)\n ]);\n if (window.location.href === lastHref) {\n break;\n }\n yield getState(ctx, `Viewing photo ${window.location.href}`, "photos");\n const root = xpathNode(Q.photoCommentList);\n yield* this.iterComments(ctx, root, 2);\n await sleep(waitUnit * 5);\n }\n }\n async *iterAllVideos(ctx) {\n const { getState, scrollIntoView, sleep, waitUnit, waitUntil, xpathNode, xpathNodes } = ctx.Lib;\n const firstInlineVideo = xpathNode("//video");\n if (firstInlineVideo) {\n scrollIntoView(firstInlineVideo);\n await sleep(waitUnit * 5);\n }\n let videoLink = xpathNode(Q.firstVideoThumbnail) || xpathNode(Q.firstVideoSimple);\n if (!videoLink) {\n return;\n }\n while (videoLink) {\n scrollIntoView(videoLink);\n let lastHref = window.location.href;\n videoLink.click();\n await waitUntil(() => window.location.href !== lastHref, waitUnit * 2);\n yield getState(ctx, "Viewing video: " + window.location.href, "videos");\n await sleep(waitUnit * 10);\n await Promise.race([\n waitUntil(() => {\n for (const video of xpathNodes("//video")) {\n if (video.readyState >= 3) {\n return true;\n }\n }\n return false;\n }, waitUnit * 2),\n sleep(20000)\n ]);\n await sleep(waitUnit * 10);\n const close = xpathNode(Q.nextSlide);\n if (!close) {\n break;\n }\n lastHref = window.location.href;\n close.click();\n await waitUntil(() => window.location.href !== lastHref, waitUnit * 2);\n videoLink = xpathNode(Q.nextVideo, videoLink);\n }\n }\n async *run(ctx) {\n const { getState, sleep, xpathNode } = ctx.Lib;\n yield getState(ctx, "Starting...");\n await sleep(2000);\n if (Q.isPhotosPage.exec(window.location.href)) {\n ctx.state = { "photos": 0, "comments": 0 };\n yield* this.iterPhotoSlideShow(ctx);\n return;\n }\n if (Q.isVideosPage.exec(window.location.href)) {\n ctx.state = { "videos": 0, "comments": 0 };\n yield* this.iterAllVideos(ctx);\n return;\n }\n if (Q.isPhotoVideoPage.exec(window.location.href)) {\n ctx.state = { "comments": 0 };\n const root = xpathNode(Q.photoCommentList);\n yield* this.iterComments(ctx, root, 1000);\n return;\n }\n ctx.state = { "posts": 0, "comments": 0, "videos": 0 };\n yield* this.iterPostFeeds(ctx);\n }\n}\nFacebookTimelineBehavior.id = "Facebook";\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./src/site/facebook.ts?')},"./src/site/index.ts": +/*!***************************!*\ + !*** ./src/site/index.ts ***! + \***************************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "default": () => (__WEBPACK_DEFAULT_EXPORT__)\n/* harmony export */ });\n/* harmony import */ var _facebook__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./facebook */ "./src/site/facebook.ts");\n/* harmony import */ var _instagram__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! ./instagram */ "./src/site/instagram.ts");\n/* harmony import */ var _telegram__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! ./telegram */ "./src/site/telegram.ts");\n/* harmony import */ var _twitter__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! ./twitter */ "./src/site/twitter.ts");\n/* harmony import */ var _tiktok__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(/*! ./tiktok */ "./src/site/tiktok.ts");\n\n\n\n\n\nconst siteBehaviors = [\n _instagram__WEBPACK_IMPORTED_MODULE_1__.InstagramPostsBehavior,\n _twitter__WEBPACK_IMPORTED_MODULE_3__.TwitterTimelineBehavior,\n _facebook__WEBPACK_IMPORTED_MODULE_0__.FacebookTimelineBehavior,\n _telegram__WEBPACK_IMPORTED_MODULE_2__.TelegramBehavior,\n _tiktok__WEBPACK_IMPORTED_MODULE_4__.TikTokVideoBehavior,\n _tiktok__WEBPACK_IMPORTED_MODULE_4__.TikTokProfileBehavior\n];\n/* harmony default export */ const __WEBPACK_DEFAULT_EXPORT__ = (siteBehaviors);\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./src/site/index.ts?')},"./src/site/instagram.ts": +/*!*******************************!*\ + !*** ./src/site/instagram.ts ***! + \*******************************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "InstagramPostsBehavior": () => (/* binding */ InstagramPostsBehavior)\n/* harmony export */ });\nconst subpostNextOnlyChevron = "//article[@role=\'presentation\']//div[@role=\'presentation\']/following-sibling::button";\nconst Q = {\n rootPath: "//main/div/div[2]/div",\n childMatchSelect: "string(.//a[starts-with(@href, \'/\')]/@href)",\n childMatch: "child::div[.//a[@href=\'$1\']]",\n firstPostInRow: "div[1]/a",\n postCloseButton: "/html/body/div[last()]/div[1]/button[.//*[@aria-label]]",\n nextPost: "//button[.//*[local-name() = \'svg\' and @aria-label=\'Next\']]",\n postLoading: "//*[@aria-label=\'Loading...\']",\n subpostNextOnlyChevron,\n subpostPrevNextChevron: subpostNextOnlyChevron + "[2]",\n commentRoot: "//article[@role=\'presentation\']/div[1]/div[2]//ul/div[last()]/div/div",\n viewReplies: "ul/li//button[span[not(count(*)) and contains(text(), \'(\')]]",\n loadMore: "//button[span[@aria-label]]",\n pageLoadWaitUntil: "//main"\n};\nclass InstagramPostsBehavior {\n static isMatch() {\n return !!window.location.href.match(/https:\\/\\/(www\\.)?instagram\\.com\\/\\w[\\w.-]+/);\n }\n static init() {\n return {\n state: {\n posts: 0,\n slides: 0,\n rows: 0,\n comments: 0,\n }\n };\n }\n constructor() {\n this.maxCommentsTime = 10000;\n this.postOnlyWindow = null;\n }\n cleanup() {\n if (this.postOnlyWindow) {\n this.postOnlyWindow.close();\n this.postOnlyWindow = null;\n }\n }\n async waitForNext(ctx, child) {\n if (!child) {\n return null;\n }\n await ctx.Lib.sleep(ctx.Lib.waitUnit);\n if (!child.nextElementSibling) {\n return null;\n }\n return child.nextElementSibling;\n }\n async *iterRow(ctx) {\n const { RestoreState, sleep, waitUnit, xpathNode } = ctx.Lib;\n let root = xpathNode(Q.rootPath);\n if (!root) {\n return;\n }\n let child = root.firstElementChild;\n if (!child) {\n return;\n }\n while (child) {\n await sleep(waitUnit);\n const restorer = new RestoreState(Q.childMatchSelect, child);\n if (restorer.matchValue) {\n yield child;\n child = await restorer.restore(Q.rootPath, Q.childMatch);\n }\n child = await this.waitForNext(ctx, child);\n }\n }\n async *viewStandalonePost(ctx, origLoc) {\n const { getState, sleep, waitUnit, waitUntil, xpathNode, xpathString } = ctx.Lib;\n let root = xpathNode(Q.rootPath);\n if (!root || !root.firstElementChild) {\n return;\n }\n const firstPostHref = xpathString(Q.childMatchSelect, root.firstElementChild);\n yield getState(ctx, "Loading single post view for first post: " + firstPostHref);\n window.history.replaceState({}, "", firstPostHref);\n window.dispatchEvent(new PopStateEvent("popstate", { state: {} }));\n let root2 = null;\n let root3 = null;\n await sleep(waitUnit * 5);\n await waitUntil(() => (root2 = xpathNode(Q.rootPath)) !== root && root2, waitUnit * 5);\n await sleep(waitUnit * 5);\n window.history.replaceState({}, "", origLoc);\n window.dispatchEvent(new PopStateEvent("popstate", { state: {} }));\n await waitUntil(() => (root3 = xpathNode(Q.rootPath)) !== root2 && root3, waitUnit * 5);\n }\n async *iterSubposts(ctx) {\n const { getState, sleep, waitUnit, xpathNode } = ctx.Lib;\n let next = xpathNode(Q.subpostNextOnlyChevron);\n let count = 1;\n while (next) {\n next.click();\n await sleep(waitUnit * 5);\n yield getState(ctx, `Loading Slide ${++count} for ${window.location.href}`, "slides");\n next = xpathNode(Q.subpostPrevNextChevron);\n }\n await sleep(waitUnit * 5);\n }\n async iterComments(ctx) {\n const { scrollIntoView, sleep, waitUnit, waitUntil, xpathNode } = ctx.Lib;\n const root = xpathNode(Q.commentRoot);\n if (!root) {\n return;\n }\n let child = root.firstElementChild;\n let commentsLoaded = false;\n const getViewRepliesButton = (child) => {\n return xpathNode(Q.viewReplies, child);\n };\n while (child) {\n scrollIntoView(child);\n commentsLoaded = true;\n let viewReplies = getViewRepliesButton(child);\n while (viewReplies) {\n const orig = viewReplies.textContent;\n viewReplies.click();\n ctx.state.comments++;\n await sleep(waitUnit * 2.5);\n await waitUntil(() => orig !== viewReplies.textContent, waitUnit);\n viewReplies = getViewRepliesButton(child);\n }\n if (child.nextElementSibling && child.nextElementSibling.tagName === "LI" && !child.nextElementSibling.nextElementSibling) {\n let loadMore = xpathNode(Q.loadMore, child.nextElementSibling);\n if (loadMore) {\n loadMore.click();\n ctx.state.comments++;\n await sleep(waitUnit * 5);\n }\n }\n child = child.nextElementSibling;\n await sleep(waitUnit * 2.5);\n }\n return commentsLoaded;\n }\n async *iterPosts(ctx, next) {\n const { getState, sleep, waitUnit, xpathNode } = ctx.Lib;\n let count = 0;\n while (next && ++count <= 3) {\n next.click();\n await sleep(waitUnit * 10);\n yield getState(ctx, "Loading Post: " + window.location.href, "posts");\n await fetch(window.location.href);\n yield* this.iterSubposts(ctx);\n yield getState(ctx, "Loaded Comments", "comments");\n await Promise.race([\n this.iterComments(ctx),\n sleep(this.maxCommentsTime)\n ]);\n next = xpathNode(Q.nextPost);\n while (!next && xpathNode(Q.postLoading)) {\n await sleep(waitUnit * 2.5);\n }\n }\n await sleep(waitUnit * 5);\n }\n async *run(ctx) {\n const { getState, scrollIntoView, sleep, waitUnit, xpathNode } = ctx.Lib;\n for await (const row of this.iterRow(ctx)) {\n scrollIntoView(row);\n await sleep(waitUnit * 2.5);\n yield getState(ctx, "Loading Row", "rows");\n const first = xpathNode(Q.firstPostInRow, row);\n yield* this.iterPosts(ctx, first);\n const close = xpathNode(Q.postCloseButton);\n if (close) {\n close.click();\n }\n await sleep(waitUnit * 5);\n }\n }\n async awaitPageLoad(ctx) {\n const { Lib, log } = ctx;\n const { waitUntilNode } = Lib;\n log("Waiting for Instagram to fully load", "info");\n await waitUntilNode(Q.pageLoadWaitUntil, document, null, 30000);\n }\n}\nInstagramPostsBehavior.id = "Instagram";\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./src/site/instagram.ts?')},"./src/site/telegram.ts": +/*!******************************!*\ + !*** ./src/site/telegram.ts ***! + \******************************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "TelegramBehavior": () => (/* binding */ TelegramBehavior)\n/* harmony export */ });\nconst Q = {\n telegramContainer: "//main//section[@class=\'tgme_channel_history js-message_history\']",\n postId: "string(./div[@data-post]/@data-post)",\n linkExternal: "string(.//a[@class=\'tgme_widget_message_link_preview\' and @href]/@href)",\n};\nclass TelegramBehavior {\n static isMatch() {\n return !!window.location.href.match(/https:\\/\\/t.me\\/s\\/\\w[\\w]+/);\n }\n static init() {\n return {\n state: { messages: 0 }\n };\n }\n async waitForPrev(ctx, child) {\n if (!child) {\n return null;\n }\n await ctx.Lib.sleep(ctx.Lib.waitUnit * 5);\n if (!child.previousElementSibling) {\n return null;\n }\n return child.previousElementSibling;\n }\n async *run(ctx) {\n const { getState, scrollIntoView, sleep, waitUnit, xpathNode, xpathString } = ctx.Lib;\n let root = xpathNode(Q.telegramContainer);\n if (!root) {\n return;\n }\n let child = root.lastElementChild;\n while (child) {\n scrollIntoView(child);\n const postId = xpathString(Q.postId, child) || "unknown";\n const linkUrl = xpathString(Q.linkExternal, child);\n if (linkUrl && linkUrl.endsWith(".jpg") || linkUrl.endsWith(".png")) {\n yield getState(ctx, "Loading External Image: " + linkUrl);\n const image = new Image();\n image.src = linkUrl;\n document.body.appendChild(image);\n await sleep(waitUnit * 2.5);\n document.body.removeChild(image);\n }\n yield getState(ctx, "Loading Message: " + postId, "messages");\n child = await this.waitForPrev(ctx, child);\n }\n }\n}\nTelegramBehavior.id = "Telegram";\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./src/site/telegram.ts?')},"./src/site/tiktok.ts": +/*!****************************!*\ + !*** ./src/site/tiktok.ts ***! + \****************************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "BREADTH_ALL": () => (/* binding */ BREADTH_ALL),\n/* harmony export */ "TikTokProfileBehavior": () => (/* binding */ TikTokProfileBehavior),\n/* harmony export */ "TikTokVideoBehavior": () => (/* binding */ TikTokVideoBehavior)\n/* harmony export */ });\nconst Q = {\n commentList: "//div[contains(@class, \'CommentListContainer\')]",\n commentItem: "div[contains(@class, \'CommentItemContainer\')]",\n viewMoreReplies: ".//p[contains(@class, \'ReplyActionText\')]",\n viewMoreThread: ".//p[starts-with(@data-e2e, \'view-more\') and string-length(text()) > 0]",\n profileVideoList: "//div[starts-with(@data-e2e, \'user-post-item-list\')]",\n profileVideoItem: "div[contains(@class, \'DivItemContainerV2\')]",\n backButton: "button[contains(@class, \'StyledCloseIconContainer\')]"\n};\nconst BREADTH_ALL = Symbol("BREADTH_ALL");\nclass TikTokVideoBehavior {\n static init() {\n return {\n state: { comments: 0 },\n opts: { breadth: BREADTH_ALL }\n };\n }\n static isMatch() {\n const pathRegex = /https:\\/\\/(www\\.)?tiktok\\.com\\/@.+\\/video\\/\\d+\\/?.*/;\n return !!window.location.href.match(pathRegex);\n }\n breadthComplete({ opts: { breadth } }, iter) {\n return breadth !== BREADTH_ALL && breadth <= iter;\n }\n async *crawlThread(ctx, parentNode, prev = null, iter = 0) {\n const { waitUntilNode, scrollAndClick, getState } = ctx.Lib;\n const next = await waitUntilNode(Q.viewMoreThread, parentNode, prev);\n if (!next || this.breadthComplete(ctx, iter))\n return;\n await scrollAndClick(next, 500);\n yield getState(ctx, "View more replies", "comments");\n yield* this.crawlThread(ctx, parentNode, next, iter + 1);\n }\n async *expandThread(ctx, item) {\n const { xpathNode, scrollAndClick, getState } = ctx.Lib;\n const viewMore = xpathNode(Q.viewMoreReplies, item);\n if (!viewMore)\n return;\n await scrollAndClick(viewMore, 500);\n yield getState(ctx, "View comment", "comments");\n yield* this.crawlThread(ctx, item, null, 1);\n }\n async *run(ctx) {\n const { xpathNode, iterChildMatches, scrollIntoView, getState } = ctx.Lib;\n const commentList = xpathNode(Q.commentList);\n const commentItems = iterChildMatches(Q.commentItem, commentList);\n for await (const item of commentItems) {\n scrollIntoView(item);\n yield getState(ctx, "View comment", "comments");\n if (this.breadthComplete(ctx, 0))\n continue;\n yield* this.expandThread(ctx, item);\n }\n yield getState(ctx, "TikTok Video Behavior Complete");\n }\n}\nTikTokVideoBehavior.id = "TikTokVideo";\nclass TikTokProfileBehavior {\n static isMatch() {\n const pathRegex = /https:\\/\\/(www\\.)?tiktok\\.com\\/@[a-zA-Z0-9]+(\\/?$|\\/\\?.*)/;\n return !!window.location.href.match(pathRegex);\n }\n static init() {\n return {\n state: { videos: 0, comments: 0 },\n opts: { breadth: BREADTH_ALL }\n };\n }\n async *openVideo(ctx, item) {\n const { HistoryState, xpathNode, sleep } = ctx.Lib;\n const link = xpathNode(".//a", item);\n if (!link)\n return;\n const viewState = new HistoryState(() => link.click());\n await sleep(500);\n if (viewState.changed) {\n const videoBehavior = new TikTokVideoBehavior();\n yield* videoBehavior.run(ctx);\n await sleep(500);\n await viewState.goBack(Q.backButton);\n }\n }\n async *run(ctx) {\n const { xpathNode, iterChildMatches, scrollIntoView, getState, sleep } = ctx.Lib;\n const profileVideoList = xpathNode(Q.profileVideoList);\n const profileVideos = iterChildMatches(Q.profileVideoItem, profileVideoList);\n for await (const item of profileVideos) {\n scrollIntoView(item);\n yield getState(ctx, "View video", "videos");\n yield* this.openVideo(ctx, item);\n await sleep(500);\n }\n yield getState(ctx, "TikTok Profile Behavior Complete");\n }\n}\nTikTokProfileBehavior.id = "TikTokProfile";\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./src/site/tiktok.ts?')},"./src/site/twitter.ts": +/*!*****************************!*\ + !*** ./src/site/twitter.ts ***! + \*****************************/(__unused_webpack_module,__webpack_exports__,__webpack_require__)=>{"use strict";eval('__webpack_require__.r(__webpack_exports__);\n/* harmony export */ __webpack_require__.d(__webpack_exports__, {\n/* harmony export */ "TwitterTimelineBehavior": () => (/* binding */ TwitterTimelineBehavior)\n/* harmony export */ });\nconst Q = {\n rootPath: "//h1[@role=\'heading\' and @aria-level=\'1\']/following-sibling::div[@aria-label]//div[@style]",\n anchor: ".//article",\n childMatchSelect: "string(.//article//a[starts-with(@href, \'/\') and @aria-label]/@href)",\n childMatch: "child::div[.//a[@href=\'$1\']]",\n expand: ".//div[@role=\'button\' and not(@aria-haspopup) and not(@data-testid)]",\n quote: ".//div[@role=\'blockquote\' and @aria-haspopup=\'false\']",\n image: ".//a[@role=\'link\' and starts-with(@href, \'/\') and contains(@href, \'/photo/\')]",\n imageFirstNext: "//div[@aria-roledescription=\'carousel\']/div[2]/div[1]//div[@role=\'button\']",\n imageNext: "//div[@aria-roledescription=\'carousel\']/div[2]/div[2]//div[@role=\'button\']",\n imageClose: "//div[@role=\'presentation\']/div[@role=\'button\' and @aria-label]",\n backButton: "//div[@data-testid=\'titleContainer\']//div[@role=\'button\']",\n viewSensitive: ".//a[@href=\'/settings/content_you_see\']/parent::div/parent::div/parent::div//div[@role=\'button\']",\n progress: ".//*[@role=\'progressbar\']",\n promoted: ".//div[data-testid=\'placementTracking\']",\n};\nclass TwitterTimelineBehavior {\n static isMatch() {\n return !!window.location.href.match(/https:\\/\\/(www\\.)?(x|twitter)\\.com\\//);\n }\n static init() {\n return {\n state: {\n tweets: 0,\n images: 0,\n videos: 0\n },\n opts: {\n maxDepth: 0\n }\n };\n }\n constructor() {\n this.seenTweets = new Set();\n this.seenMediaTweets = new Set();\n }\n showingProgressBar(ctx, root) {\n const { xpathNode } = ctx.Lib;\n const node = xpathNode(Q.progress, root);\n if (!node) {\n return false;\n }\n return node.clientHeight > 10;\n }\n async waitForNext(ctx, child) {\n const { sleep, waitUnit } = ctx.Lib;\n if (!child) {\n return null;\n }\n await sleep(waitUnit * 2);\n if (!child.nextElementSibling) {\n return null;\n }\n while (this.showingProgressBar(ctx, child.nextElementSibling)) {\n await sleep(waitUnit);\n }\n return child.nextElementSibling;\n }\n async expandMore(ctx, child) {\n const { sleep, waitUnit, xpathNode } = ctx.Lib;\n const expandElem = xpathNode(Q.expand, child);\n if (!expandElem) {\n return child;\n }\n const prev = child.previousElementSibling;\n expandElem.click();\n await sleep(waitUnit);\n while (this.showingProgressBar(ctx, prev.nextElementSibling)) {\n await sleep(waitUnit);\n }\n child = prev.nextElementSibling;\n return child;\n }\n async *infScroll(ctx) {\n const { scrollIntoView, RestoreState, sleep, waitUnit, xpathNode } = ctx.Lib;\n let root = xpathNode(Q.rootPath);\n if (!root) {\n return;\n }\n let child = root.firstElementChild;\n if (!child) {\n return;\n }\n while (child) {\n let anchorElem = xpathNode(Q.anchor, child);\n if (!anchorElem && Q.expand) {\n child = await this.expandMore(ctx, child);\n anchorElem = xpathNode(Q.anchor, child);\n }\n if (child && child.innerText) {\n scrollIntoView(child);\n }\n if (child && anchorElem) {\n await sleep(waitUnit);\n const restorer = new RestoreState(Q.childMatchSelect, child);\n yield anchorElem;\n if (restorer.matchValue) {\n child = await restorer.restore(Q.rootPath, Q.childMatch);\n }\n }\n child = await this.waitForNext(ctx, child);\n }\n }\n async *mediaPlaying(ctx, tweet) {\n const { getState, sleep, xpathNode, xpathString } = ctx.Lib;\n const media = xpathNode("(.//video | .//audio)", tweet);\n if (!media || media.paused) {\n return;\n }\n let mediaTweetUrl = null;\n try {\n mediaTweetUrl = new URL(xpathString(Q.childMatchSelect, tweet.parentElement), window.location.origin).href;\n }\n catch (e) {\n console.warn(e);\n }\n if (media.src.startsWith("https://") && media.src.indexOf(".mp4") > 0) {\n yield getState(ctx, `Loading video for ${mediaTweetUrl || "unknown"}`, "videos");\n return;\n }\n let msg;\n if (mediaTweetUrl) {\n if (this.seenMediaTweets.has(mediaTweetUrl)) {\n return;\n }\n msg = `Waiting for media playback for ${mediaTweetUrl} to finish`;\n this.seenMediaTweets.add(mediaTweetUrl);\n }\n else {\n msg = "Loading video";\n }\n yield getState(ctx, msg, "videos");\n const p = new Promise((resolve) => {\n media.addEventListener("ended", () => resolve(null));\n media.addEventListener("abort", () => resolve(null));\n media.addEventListener("error", () => resolve(null));\n media.addEventListener("pause", () => resolve(null));\n });\n await Promise.race([p, sleep(60000)]);\n }\n async *clickImages(ctx, tweet) {\n const { getState, HistoryState, sleep, waitUnit, xpathNode } = ctx.Lib;\n const imagePopup = xpathNode(Q.image, tweet);\n if (imagePopup) {\n const imageState = new HistoryState(() => imagePopup.click());\n yield getState(ctx, "Loading Image: " + window.location.href, "images");\n await sleep(waitUnit * 5);\n let nextImage = xpathNode(Q.imageFirstNext);\n let prevLocation = window.location.href;\n while (nextImage) {\n nextImage.click();\n await sleep(waitUnit * 2);\n if (window.location.href === prevLocation) {\n await sleep(waitUnit * 5);\n break;\n }\n prevLocation = window.location.href;\n yield getState(ctx, "Loading Image: " + window.location.href, "images");\n await sleep(waitUnit * 5);\n nextImage = xpathNode(Q.imageNext);\n }\n await imageState.goBack(Q.imageClose);\n }\n }\n async *clickTweet(ctx, tweet, depth) {\n const { getState, HistoryState, sleep, waitUnit } = ctx.Lib;\n const tweetState = new HistoryState(() => tweet.click());\n await sleep(waitUnit);\n if (tweetState.changed) {\n yield getState(ctx, "Capturing Tweet: " + window.location.href, "tweets");\n const maxDepth = ctx.opts.maxDepth;\n if (depth < maxDepth && !this.seenTweets.has(window.location.href)) {\n yield* this.iterTimeline(ctx, depth + 1);\n }\n this.seenTweets.add(window.location.href);\n await sleep(waitUnit * 2);\n await tweetState.goBack(Q.backButton);\n await sleep(waitUnit);\n }\n }\n async *iterTimeline(ctx, depth = 0) {\n const { getState, sleep, waitUnit, xpathNode } = ctx.Lib;\n if (this.seenTweets.has(window.location.href)) {\n return;\n }\n yield getState(ctx, "Capturing thread: " + window.location.href, "threads");\n for await (const tweet of this.infScroll(ctx)) {\n if (xpathNode(Q.promoted, tweet)) {\n continue;\n }\n await sleep(waitUnit * 2.5);\n const viewButton = xpathNode(Q.viewSensitive, tweet);\n if (viewButton) {\n viewButton.click();\n await sleep(waitUnit * 2.5);\n }\n yield* this.clickImages(ctx, tweet);\n const quoteTweet = xpathNode(Q.quote, tweet);\n if (quoteTweet) {\n yield* this.clickTweet(ctx, quoteTweet, 1000);\n }\n yield* this.mediaPlaying(ctx, tweet);\n yield* this.clickTweet(ctx, tweet, depth);\n await sleep(waitUnit * 5);\n }\n }\n async *run(ctx) {\n yield* this.iterTimeline(ctx, 0);\n }\n}\nTwitterTimelineBehavior.id = "Twitter";\n\n\n//# sourceURL=webpack://browsertrix-behaviors/./src/site/twitter.ts?')},"./src lazy recursive ^.*$": +/*!*****************************************!*\ + !*** ./src/ lazy ^.*$ namespace object ***! + \*****************************************/(module,__unused_webpack_exports,__webpack_require__)=>{eval('var map = {\n\t".": "./src/index.ts",\n\t"./": "./src/index.ts",\n\t"./autoclick": "./src/autoclick.ts",\n\t"./autoclick.ts": "./src/autoclick.ts",\n\t"./autofetcher": "./src/autofetcher.ts",\n\t"./autofetcher.ts": "./src/autofetcher.ts",\n\t"./autoplay": "./src/autoplay.ts",\n\t"./autoplay.ts": "./src/autoplay.ts",\n\t"./autoscroll": "./src/autoscroll.ts",\n\t"./autoscroll.ts": "./src/autoscroll.ts",\n\t"./index": "./src/index.ts",\n\t"./index.ts": "./src/index.ts",\n\t"./lib/behavior": "./src/lib/behavior.ts",\n\t"./lib/behavior.ts": "./src/lib/behavior.ts",\n\t"./lib/utils": "./src/lib/utils.ts",\n\t"./lib/utils.ts": "./src/lib/utils.ts",\n\t"./site": "./src/site/index.ts",\n\t"./site/": "./src/site/index.ts",\n\t"./site/facebook": "./src/site/facebook.ts",\n\t"./site/facebook.ts": "./src/site/facebook.ts",\n\t"./site/index": "./src/site/index.ts",\n\t"./site/index.ts": "./src/site/index.ts",\n\t"./site/instagram": "./src/site/instagram.ts",\n\t"./site/instagram.ts": "./src/site/instagram.ts",\n\t"./site/telegram": "./src/site/telegram.ts",\n\t"./site/telegram.ts": "./src/site/telegram.ts",\n\t"./site/tiktok": "./src/site/tiktok.ts",\n\t"./site/tiktok.ts": "./src/site/tiktok.ts",\n\t"./site/twitter": "./src/site/twitter.ts",\n\t"./site/twitter.ts": "./src/site/twitter.ts"\n};\n\nfunction webpackAsyncContext(req) {\n\treturn Promise.resolve().then(() => {\n\t\tif(!__webpack_require__.o(map, req)) {\n\t\t\tvar e = new Error("Cannot find module \'" + req + "\'");\n\t\t\te.code = \'MODULE_NOT_FOUND\';\n\t\t\tthrow e;\n\t\t}\n\n\t\tvar id = map[req];\n\t\treturn __webpack_require__(id);\n\t});\n}\nwebpackAsyncContext.keys = () => (Object.keys(map));\nwebpackAsyncContext.id = "./src lazy recursive ^.*$";\nmodule.exports = webpackAsyncContext;\n\n//# sourceURL=webpack://browsertrix-behaviors/./src/_lazy_^.*$_namespace_object?')}},__webpack_module_cache__={};function __webpack_require__(e){var n=__webpack_module_cache__[e];if(void 0!==n)return n.exports;var t=__webpack_module_cache__[e]={exports:{}};return __webpack_modules__[e](t,t.exports,__webpack_require__),t.exports}__webpack_require__.d=(e,n)=>{for(var t in n)__webpack_require__.o(n,t)&&!__webpack_require__.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:n[t]})},__webpack_require__.e=()=>Promise.resolve(),__webpack_require__.o=(e,n)=>Object.prototype.hasOwnProperty.call(e,n),__webpack_require__.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var __webpack_exports__=__webpack_require__("./index.ts")})(); \ No newline at end of file diff --git a/src/autoclick.ts b/src/autoclick.ts new file mode 100644 index 0000000..bc90d92 --- /dev/null +++ b/src/autoclick.ts @@ -0,0 +1,133 @@ +import { BackgroundBehavior } from "./lib/behavior"; +import { sleep } from "./lib/utils"; + +declare let getEventListeners: any; + +export class AutoClick extends BackgroundBehavior +{ + _donePromise: Promise; + _markDone: () => void; + selector: string; + seenElem = new WeakSet(); + + constructor(selector = "a") { + super(); + this.selector = selector; + this._donePromise = new Promise((resolve) => this._markDone = resolve); + } + + beforeUnload(event) { + event.preventDefault(); + return false; + } + + nextSameOriginLink(origNoHash: string) : HTMLAnchorElement | null { + try { + const allLinks = document.querySelectorAll(this.selector); + for (const el of allLinks) { + const elem = el as HTMLAnchorElement; + + // skip URLs to same page OR outside current origin + if (!elem.href || !elem.href.startsWith(self.location.origin) || elem.href.startsWith(origNoHash)) { + continue; + } + if (!elem.isConnected) { + continue; + } + if (this.seenElem.has(elem)) { + continue; + } + this.seenElem.add(elem); + return elem; + } + } catch (e) { + this.debug(e.toString()); + } + + return null; + } + + async start() { + const origHref = self.location.href; + + const url = new URL(origHref); + url.hash = ""; + const origNoHash = url.href + "#"; + + // process all links (except hash links) which could result in attempted navigation + window.addEventListener("beforeunload", (e) => this.beforeUnload(e)); + + // process external links on current origin + + // eslint-disable-next-line no-constant-condition + while (true) { + const elem = this.nextSameOriginLink(origNoHash); + + if (!elem) { + break; + } + + await this.processElem(elem, origHref); + } + + // process hashlinks on same page + for (const el of document.querySelectorAll(this.selector)) { + const elem = el as HTMLAnchorElement; + if (!elem.href || elem.href.startsWith(origNoHash)) { + await this.processElem(elem, origHref); + } + } + + window.removeEventListener("beforeunload", (e) => this.beforeUnload(e)); + + this._markDone(); + } + + async processElem(elem: HTMLAnchorElement, origHref: string) { + if (!elem.isConnected) { + return; + } + + // if successfully called getEventListeners and no click handler, we can skip + try { + if (!getEventListeners(elem).click) { + return; + } + } catch (_e) { + // getEventListeners not available, need to actually click + } + + const anySelf = self as any; + + if (elem.href) { + // skip if already clicked this URL, tracked in external state + if (anySelf.__bx_addSet && !await anySelf.__bx_addSet(elem.href)) { + return; + } + + this.debug("Clicking on link: " + elem.href); + } else { + this.debug("Click empty link"); + } + + elem.click(); + + await sleep(250); + + if (self.location.href != origHref) { + await new Promise((resolve) => { + window.addEventListener("popstate", () => { + resolve(null); + }, { once: true }); + + window.history.back(); + }); + } + } catch (e) { + this.debug(e.toString()); + } + + done() { + return this._donePromise; + } +} diff --git a/src/index.ts b/src/index.ts index 3854a49..0877ae0 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,7 @@ import { AutoFetcher } from "./autofetcher"; import { Autoplay } from "./autoplay"; import { AutoScroll } from "./autoscroll"; +import { AutoClick } from "./autoclick"; import { awaitLoad, sleep, behaviorLog, _setLogFunc, _setBehaviorManager, installBehaviors } from "./lib/utils"; import { Behavior, BehaviorRunner } from "./lib/behavior"; @@ -15,14 +16,18 @@ interface BehaviorManagerOpts { autofetch?: boolean; autoplay?: boolean; autoscroll?: boolean; + autoclick?: boolean; log?: ((...message: string[]) => void) | string | false; siteSpecific?: boolean | object; timeout?: number; fetchHeaders?: object | null; startEarly?: boolean | null; + clickSelector?: string; } -const DEFAULT_OPTS: BehaviorManagerOpts = {autofetch: true, autoplay: true, autoscroll: true, siteSpecific: true}; +const DEFAULT_OPTS: BehaviorManagerOpts = {autofetch: true, autoplay: true, autoscroll: true, autoclick: true, siteSpecific: true}; + +const DEFAULT_SELECTOR = "a[href]"; export class BehaviorManager { autofetch: AutoFetcher; @@ -89,6 +94,11 @@ export class BehaviorManager { this.behaviors.push(new Autoplay(this.autofetch, opts.startEarly)); } + if (opts.autoclick) { + behaviorLog("Using AutoClick"); + this.behaviors.push(new AutoClick(opts.clickSelector || DEFAULT_SELECTOR)); + } + if (!this.isInTopFrame()) { return; }