|
@@ -106,7 +106,7 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
|
|
|
.then(() => page.goto(url, {waitUntil: 'load', timeout: getTimeout()}))
|
|
|
// hide header, "more options" button, like and retweet count
|
|
|
.then(() => page.addStyleTag({
|
|
|
- content: 'header,#layers{display:none!important}' +
|
|
|
+ content: 'header,#layers{display:none!important}article{background-color:transparent!important}' +
|
|
|
'[data-testid="caret"],[role="group"],[data-testid="tweet"]+*>[class*=" "]+div:nth-last-child(2){display:none}',
|
|
|
}))
|
|
|
.then(() => page.addStyleTag({
|
|
@@ -123,17 +123,42 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
|
|
|
});
|
|
|
}, 250);
|
|
|
}))
|
|
|
- .then(() => page.waitForSelector('article', {timeout: getTimeout()}))
|
|
|
- .catch((err: Error): Promise<puppeteer.ElementHandle<Element> | null> => {
|
|
|
+ // find main tweet
|
|
|
+ .then(() => page.waitForSelector('xpath=//section/*/*/div[.//article[not(.//time)]]', {timeout: getTimeout()}))
|
|
|
+ // toggle visibility of sensitive tweets
|
|
|
+ .then(handle => handle.$$('xpath=..//a[contains(@href,"content_you_see")]/../../..//*[@role="button"]')
|
|
|
+ .then(sensitiveToggles => {
|
|
|
+ const count = sensitiveToggles.length;
|
|
|
+ if (count) logger.info(`found ${count} sensitive ${count === 1 ? 'tweet' : 'tweets'} on page, uncollapsing...`);
|
|
|
+ return chainPromises(sensitiveToggles.filter(toggle => toggle.isVisible()).map(toggle => () => toggle.click()));
|
|
|
+ })
|
|
|
+ .then(() => handle)
|
|
|
+ )
|
|
|
+ .catch((err: Error): Promise<puppeteer.ElementHandle<HTMLDivElement> | null> => {
|
|
|
if (err.name !== 'TimeoutError') throw err;
|
|
|
- logger.warn(`navigation timed out at ${getTimerTime()} seconds`);
|
|
|
+ logger.warn(`navigation timed out at ${getTimerTime()} ms`);
|
|
|
return null;
|
|
|
})
|
|
|
- .then(handle => {
|
|
|
+ // scroll to last tweet by owner in thread, if any, or top of thread
|
|
|
+ .then((handle: puppeteer.ElementHandle<HTMLDivElement>) => {
|
|
|
if (handle === null) throw new puppeteer.errors.TimeoutError();
|
|
|
+ return handle.evaluate(div => {
|
|
|
+ try {
|
|
|
+ const selector = '[data-testid="tweet"]>:nth-child(2)>:first-child a';
|
|
|
+ const getProfileUrl = () => (div.querySelector<HTMLAnchorElement>(selector) || {href: ''}).href;
|
|
|
+ const ownerProfileUrl = getProfileUrl();
|
|
|
+ // eslint-disable-next-line no-cond-assign
|
|
|
+ while (div = div.previousElementSibling as HTMLDivElement) {
|
|
|
+ if (getProfileUrl() !== ownerProfileUrl) continue;
|
|
|
+ return document.documentElement.scrollTop = window.scrollY + div.getBoundingClientRect().top;
|
|
|
+ }
|
|
|
+ } catch {/* handle errors like none-found cases */}
|
|
|
+ document.documentElement.scrollTop = 0;
|
|
|
+ }).then(() => handle);
|
|
|
})
|
|
|
- .then(() => page.evaluate(() => {
|
|
|
- const cardImg = document.querySelector('div[data-testid^="card.layout"][data-testid$=".media"] img');
|
|
|
+ // scrape card image from main tweet
|
|
|
+ .then(handle => handle.evaluate(div => {
|
|
|
+ const cardImg = div.querySelector('div[data-testid^="card.layout"][data-testid$=".media"] img');
|
|
|
if (typeof cardImg?.getAttribute('src') === 'string') {
|
|
|
const match = /^(.*\/card_img\/(\d+)\/.+\?format=.*)&name=/.exec(cardImg?.getAttribute('src'));
|
|
|
if (match) {
|
|
@@ -156,9 +181,6 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
|
|
|
.then(cardImg => {
|
|
|
if (cardImg) this.extendEntity(cardImg);
|
|
|
})
|
|
|
- .then(() => page.addScriptTag({
|
|
|
- content: 'document.documentElement.scrollTop=0;',
|
|
|
- }))
|
|
|
.then(() => chainPromises(morePostProcessings.map(func => () => func(page))))
|
|
|
.then(() => promisify(setTimeout)(getTimeout()))
|
|
|
.then(() => page.screenshot())
|