|
@@ -70,17 +70,9 @@ class Webshot extends CallableInstance<[Tweet[], (...args) => void, number], Pro
|
|
.then(() => this.connect(onready));
|
|
.then(() => this.connect(onready));
|
|
};
|
|
};
|
|
|
|
|
|
- private extendEntity = (media: MediaEntity) => {
|
|
|
|
- logger.info('not working on a tweet');
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
- private truncateLongThread = (atId: string) => {
|
|
|
|
- logger.info('not working on a tweet');
|
|
|
|
- };
|
|
|
|
-
|
|
|
|
private renderWebshot = (
|
|
private renderWebshot = (
|
|
url: string, height: number, webshotDelay: number,
|
|
url: string, height: number, webshotDelay: number,
|
|
- ...morePostProcessings: ((page: puppeteer.Page) => Promise<any>)[]
|
|
|
|
|
|
+ ...morePostProcessings: ((page?: puppeteer.Page, handle?: puppeteer.ElementHandle) => Promise<any>)[]
|
|
): Promise<string> => {
|
|
): Promise<string> => {
|
|
temp.track();
|
|
temp.track();
|
|
const jpeg = (data: Readable) => data.pipe(sharp()).jpeg({quality: 90, trellisQuantisation: true});
|
|
const jpeg = (data: Readable) => data.pipe(sharp()).jpeg({quality: 90, trellisQuantisation: true});
|
|
@@ -155,7 +147,7 @@ class Webshot extends CallableInstance<[Tweet[], (...args) => void, number], Pro
|
|
throw err;
|
|
throw err;
|
|
logger.warn(`${err} (${getTimerTime()} ms)`);
|
|
logger.warn(`${err} (${getTimerTime()} ms)`);
|
|
return page.evaluate(() => document.documentElement.outerHTML).then(html => {
|
|
return page.evaluate(() => document.documentElement.outerHTML).then(html => {
|
|
- const path = temp.path({ suffix: '.html' });
|
|
|
|
|
|
+ const path = temp.path({suffix: '.html'});
|
|
writeFileSync(path, html);
|
|
writeFileSync(path, html);
|
|
logger.warn(`saved debug html to ${path}`);
|
|
logger.warn(`saved debug html to ${path}`);
|
|
}).then(() => page.screenshot()).then(screenshot => {
|
|
}).then(() => page.screenshot()).then(screenshot => {
|
|
@@ -164,52 +156,10 @@ class Webshot extends CallableInstance<[Tweet[], (...args) => void, number], Pro
|
|
});
|
|
});
|
|
}).then(() => null);
|
|
}).then(() => null);
|
|
})
|
|
})
|
|
- // scroll back at least 2 tweets revealing 2nd last tweet by owner in thread, or top of thread, if any
|
|
|
|
- .then((handle: puppeteer.ElementHandle<HTMLDivElement>) => {
|
|
|
|
|
|
+ .then(handle => {
|
|
if (handle === null) throw new puppeteer.errors.TimeoutError();
|
|
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();
|
|
|
|
- const bottom = div;
|
|
|
|
- // eslint-disable-next-line no-cond-assign
|
|
|
|
- while (div = div.previousElementSibling as HTMLDivElement) {
|
|
|
|
- if (getProfileUrl() !== ownerProfileUrl || div === bottom.previousElementSibling) continue;
|
|
|
|
- const top = document.documentElement.scrollTop = window.scrollY + div.getBoundingClientRect().top;
|
|
|
|
- if (top > 10)
|
|
|
|
- return div.querySelector<HTMLAnchorElement>('article a[aria-label]').href.replace(/.*\/status\//, '');
|
|
|
|
- }
|
|
|
|
- } catch {/* handle errors like none-found cases */}
|
|
|
|
- document.documentElement.scrollTop = 0;
|
|
|
|
- }).then(this.truncateLongThread).then(() => handle);
|
|
|
|
- })
|
|
|
|
- // 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) {
|
|
|
|
- // tslint:disable-next-line: variable-name
|
|
|
|
- const [media_url_https, id_str] = match.slice(1);
|
|
|
|
- return {
|
|
|
|
- media_url: media_url_https.replace(/^https/, 'http'),
|
|
|
|
- media_url_https,
|
|
|
|
- url: '',
|
|
|
|
- display_url: '',
|
|
|
|
- expanded_url: '',
|
|
|
|
- type: 'photo',
|
|
|
|
- id: Number(id_str),
|
|
|
|
- id_str,
|
|
|
|
- sizes: undefined,
|
|
|
|
- };
|
|
|
|
- }
|
|
|
|
- }
|
|
|
|
- }))
|
|
|
|
- .then(cardImg => {
|
|
|
|
- if (cardImg) this.extendEntity(cardImg);
|
|
|
|
|
|
+ return chainPromises(morePostProcessings.map(func => () => func(page, handle)));
|
|
})
|
|
})
|
|
- .then(() => chainPromises(morePostProcessings.map(func => () => func(page))))
|
|
|
|
.then(() => promisify(setTimeout)(getTimeout()))
|
|
.then(() => promisify(setTimeout)(getTimeout()))
|
|
// hide highlight of retweet header
|
|
// hide highlight of retweet header
|
|
.then(() => page.evaluate(() => (document.activeElement as unknown as HTMLOrSVGElement).blur()))
|
|
.then(() => page.evaluate(() => (document.activeElement as unknown as HTMLOrSVGElement).blur()))
|
|
@@ -345,8 +295,10 @@ class Webshot extends CallableInstance<[Tweet[], (...args) => void, number], Pro
|
|
// invoke webshot
|
|
// invoke webshot
|
|
if (this.mode === 0) {
|
|
if (this.mode === 0) {
|
|
const url = `https://mobile.twitter.com/${twi.user.screen_name}/status/${twi.id_str}`;
|
|
const url = `https://mobile.twitter.com/${twi.user.screen_name}/status/${twi.id_str}`;
|
|
- promise = promise.then(() => {
|
|
|
|
- this.extendEntity = (cardImg: MediaEntity) => {
|
|
|
|
|
|
+ promise = promise.then(() => this.renderWebshot(url, 1920, webshotDelay, (
|
|
|
|
+ _, handle: puppeteer.ElementHandle<HTMLDivElement>
|
|
|
|
+ ) => {
|
|
|
|
+ const extendEntity = (cardImg: MediaEntity) => {
|
|
originTwi.extended_entities = {
|
|
originTwi.extended_entities = {
|
|
...originTwi.extended_entities,
|
|
...originTwi.extended_entities,
|
|
media: [
|
|
media: [
|
|
@@ -355,13 +307,51 @@ class Webshot extends CallableInstance<[Tweet[], (...args) => void, number], Pro
|
|
],
|
|
],
|
|
};
|
|
};
|
|
};
|
|
};
|
|
- this.truncateLongThread = (atId: string) => {
|
|
|
|
|
|
+ const truncateLongThread = (atId: string) => {
|
|
if (!atId) return;
|
|
if (!atId) return;
|
|
logger.info(`thread too long, truncating at tweet ${atId}...`);
|
|
logger.info(`thread too long, truncating at tweet ${atId}...`);
|
|
truncatedAt = atId;
|
|
truncatedAt = atId;
|
|
};
|
|
};
|
|
- return this.renderWebshot(url, 1920, webshotDelay);
|
|
|
|
- })
|
|
|
|
|
|
+ // scroll back at least 2 tweets revealing 2nd last tweet by owner in thread, or top of thread
|
|
|
|
+ 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();
|
|
|
|
+ const bottom = div;
|
|
|
|
+ // eslint-disable-next-line no-cond-assign
|
|
|
|
+ while (div = div.previousElementSibling as HTMLDivElement) {
|
|
|
|
+ if (getProfileUrl() !== ownerProfileUrl || div === bottom.previousElementSibling) continue;
|
|
|
|
+ const top = document.documentElement.scrollTop = window.scrollY + div.getBoundingClientRect().top;
|
|
|
|
+ if (top > 10)
|
|
|
|
+ return div.querySelector<HTMLAnchorElement>('article a[aria-label]').href.replace(/.*\/status\//, '');
|
|
|
|
+ }
|
|
|
|
+ } catch {/* handle errors like none-found cases */}
|
|
|
|
+ document.documentElement.scrollTop = 0;
|
|
|
|
+ }).then(truncateLongThread)
|
|
|
|
+ // scrape card image from main tweet
|
|
|
|
+ .then(() => 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) {
|
|
|
|
+ // tslint:disable-next-line: variable-name
|
|
|
|
+ const [media_url_https, id_str] = match.slice(1);
|
|
|
|
+ return {
|
|
|
|
+ media_url: media_url_https.replace(/^https/, 'http'),
|
|
|
|
+ media_url_https,
|
|
|
|
+ url: '',
|
|
|
|
+ display_url: '',
|
|
|
|
+ expanded_url: '',
|
|
|
|
+ type: 'photo',
|
|
|
|
+ id: Number(id_str),
|
|
|
|
+ id_str,
|
|
|
|
+ sizes: undefined,
|
|
|
|
+ };
|
|
|
|
+ }
|
|
|
|
+ }
|
|
|
|
+ })).then(cardImg => { if (cardImg) extendEntity(cardImg); });
|
|
|
|
+ }))
|
|
.then(fileurl => {
|
|
.then(fileurl => {
|
|
if (fileurl) return Message.Image(fileurl);
|
|
if (fileurl) return Message.Image(fileurl);
|
|
return '[截图不可用] ' + author + text;
|
|
return '[截图不可用] ' + author + text;
|