|
@@ -61,6 +61,20 @@ class Webshot extends CallableInstance {
|
|
|
logger.info(`shooting ${width}*${height} webshot for ${url}`);
|
|
|
this.browser.newPage()
|
|
|
.then(page => {
|
|
|
+ const startTime = new Date().getTime();
|
|
|
+ const getTimerTime = () => new Date().getTime() - startTime;
|
|
|
+ const getTimeout = () => Math.max(1000, webshotDelay - getTimerTime());
|
|
|
+ let idle = false;
|
|
|
+ const awaitIdle = page.waitForNavigation({ waitUntil: 'networkidle0', timeout: getTimeout() });
|
|
|
+ const waitUntilIdle = () => {
|
|
|
+ if (idle)
|
|
|
+ return Promise.resolve();
|
|
|
+ return awaitIdle.then(() => { idle = true; });
|
|
|
+ };
|
|
|
+ const waitForSelectorUntilIdle = (selector) => Promise.race([
|
|
|
+ waitUntilIdle().then(() => Promise.reject(new puppeteer.errors.TimeoutError())),
|
|
|
+ page.waitForSelector(selector, { timeout: getTimeout() }),
|
|
|
+ ]);
|
|
|
const article = page.setUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.99 Safari/537.36')
|
|
|
.then(() => page.setViewport({
|
|
|
width: width / zoomFactor,
|
|
@@ -69,15 +83,18 @@ class Webshot extends CallableInstance {
|
|
|
deviceScaleFactor: zoomFactor,
|
|
|
}))
|
|
|
.then(() => page.setBypassCSP(true))
|
|
|
- .then(() => page.goto(url, { waitUntil: 'networkidle0', timeout: webshotDelay }))
|
|
|
- .catch(() => {
|
|
|
- logger.warn(`navigation timed out at ${webshotDelay} seconds`);
|
|
|
- })
|
|
|
+ .then(() => page.goto(url, { waitUntil: 'load', timeout: getTimeout() }))
|
|
|
// hide header, "more options" button, like and retweet count
|
|
|
.then(() => page.addStyleTag({
|
|
|
content: 'header{display:none!important}path[d=\'M20.207 7.043a1 1 0 0 0-1.414 0L12 13.836 5.207 7.043a1 1 0 0 0-1.414 1.414l7.5 7.5a.996.996 0 0 0 1.414 0l7.5-7.5a1 1 0 0 0 0-1.414z\'],div[role=\'button\']{display: none;}',
|
|
|
}))
|
|
|
- .then(() => page.$('article'));
|
|
|
+ .then(() => waitForSelectorUntilIdle('article'))
|
|
|
+ .catch((err) => {
|
|
|
+ if (err.name !== 'TimeoutError')
|
|
|
+ throw err;
|
|
|
+ logger.warn(`navigation timed out at ${getTimerTime()} seconds`);
|
|
|
+ return Promise.resolve(null);
|
|
|
+ });
|
|
|
const captureLoadedPage = () => page.addScriptTag({
|
|
|
content: 'document.documentElement.scrollTop=0;',
|
|
|
})
|
|
@@ -165,10 +182,30 @@ class Webshot extends CallableInstance {
|
|
|
article.then(elementHandle => {
|
|
|
if (elementHandle === null) {
|
|
|
logger.error(`error shooting webshot for ${url}, could not load web page of tweet`);
|
|
|
+ page.close();
|
|
|
resolve({ base64: '', boundary: 0 });
|
|
|
}
|
|
|
else {
|
|
|
- captureLoadedPage();
|
|
|
+ const coverSelector = page.$x('//article//div[@role="button"]/div/img/..');
|
|
|
+ const badgeSelector = page.$x('//article//div[@role="button"]/div/img/../../..//span/..');
|
|
|
+ const getFirst = (arraySelector) => arraySelector.then(candidatesHandle => {
|
|
|
+ if (candidatesHandle.length) {
|
|
|
+ return candidatesHandle[0];
|
|
|
+ }
|
|
|
+ });
|
|
|
+ const prepend = (e1, e2) => e1.parentElement.prepend(e2);
|
|
|
+ waitForSelectorUntilIdle('video')
|
|
|
+ .then(videoHandle => {
|
|
|
+ logger.info('found video, replacing it with cover...');
|
|
|
+ return getFirst(badgeSelector).then(badgeHandle => page.evaluate(prepend, videoHandle, badgeHandle))
|
|
|
+ .then(() => getFirst(coverSelector).then(coverHandle => page.evaluate(prepend, videoHandle, coverHandle)))
|
|
|
+ .then(() => page.evaluate((e) => e.remove(), videoHandle));
|
|
|
+ })
|
|
|
+ .catch((err) => {
|
|
|
+ if (err.name !== 'TimeoutError')
|
|
|
+ throw err;
|
|
|
+ })
|
|
|
+ .then(captureLoadedPage);
|
|
|
}
|
|
|
});
|
|
|
})
|
|
@@ -182,42 +219,52 @@ class Webshot extends CallableInstance {
|
|
|
}).catch(error => new Promise(resolve => this.reconnect(error, resolve))
|
|
|
.then(() => this.renderWebshot(url, height, webshotDelay)));
|
|
|
};
|
|
|
- this.fetchMedia = (url) => new Promise((resolve, reject) => {
|
|
|
- logger.info(`fetching ${url}`);
|
|
|
- axios_1.default({
|
|
|
- method: 'get',
|
|
|
- url,
|
|
|
- responseType: 'arraybuffer',
|
|
|
- }).then(res => {
|
|
|
- if (res.status === 200) {
|
|
|
- logger.info(`successfully fetched ${url}`);
|
|
|
- resolve(res.data);
|
|
|
- }
|
|
|
- else {
|
|
|
- logger.error(`failed to fetch ${url}: ${res.status}`);
|
|
|
- reject();
|
|
|
- }
|
|
|
- }).catch(err => {
|
|
|
- logger.error(`failed to fetch ${url}: ${err.message}`);
|
|
|
- reject();
|
|
|
- });
|
|
|
- }).then(data => ((ext) => __awaiter(this, void 0, void 0, function* () {
|
|
|
- switch (ext) {
|
|
|
- case 'jpg':
|
|
|
- return { mimetype: 'image/jpeg', data };
|
|
|
- case 'png':
|
|
|
- return { mimetype: 'image/png', data };
|
|
|
- case 'mp4':
|
|
|
- const [width, height] = url.match(/\/(\d+)x(\d+)\//).slice(1).map(Number);
|
|
|
+ this.fetchMedia = (url) => {
|
|
|
+ const gif = (data) => {
|
|
|
+ const matchDims = url.match(/\/(\d+)x(\d+)\//);
|
|
|
+ if (matchDims) {
|
|
|
+ const [width, height] = matchDims.slice(1).map(Number);
|
|
|
const factor = width + height > 1600 ? 0.375 : 0.5;
|
|
|
- try {
|
|
|
- return { mimetype: 'image/gif', data: yield gifski_1.default(data, width * factor) };
|
|
|
+ return gifski_1.default(data, width * factor);
|
|
|
+ }
|
|
|
+ return gifski_1.default(data);
|
|
|
+ };
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ logger.info(`fetching ${url}`);
|
|
|
+ axios_1.default({
|
|
|
+ method: 'get',
|
|
|
+ url,
|
|
|
+ responseType: 'arraybuffer',
|
|
|
+ }).then(res => {
|
|
|
+ if (res.status === 200) {
|
|
|
+ logger.info(`successfully fetched ${url}`);
|
|
|
+ resolve(res.data);
|
|
|
}
|
|
|
- catch (err) {
|
|
|
- throw Error(err);
|
|
|
+ else {
|
|
|
+ logger.error(`failed to fetch ${url}: ${res.status}`);
|
|
|
+ reject();
|
|
|
}
|
|
|
- }
|
|
|
- }))(url.split('/').slice(-1)[0].match(/\.([^:?&]+)/)[1])).then(typedData => `data:${typedData.mimetype};base64,${Buffer.from(typedData.data).toString('base64')}`);
|
|
|
+ }).catch(err => {
|
|
|
+ logger.error(`failed to fetch ${url}: ${err.message}`);
|
|
|
+ reject();
|
|
|
+ });
|
|
|
+ }).then(data => ((ext) => __awaiter(this, void 0, void 0, function* () {
|
|
|
+ switch (ext) {
|
|
|
+ case 'jpg':
|
|
|
+ return { mimetype: 'image/jpeg', data };
|
|
|
+ case 'png':
|
|
|
+ return { mimetype: 'image/png', data };
|
|
|
+ case 'mp4':
|
|
|
+ try {
|
|
|
+ return { mimetype: 'image/gif', data: yield gif(data) };
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ logger.error(err);
|
|
|
+ throw Error(err);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }))(url.split('/').slice(-1)[0].match(/\.([^:?&]+)/)[1])).then(typedData => `data:${typedData.mimetype};base64,${Buffer.from(typedData.data).toString('base64')}`);
|
|
|
+ };
|
|
|
// tslint:disable-next-line: no-conditional-assignment
|
|
|
if (this.mode = mode) {
|
|
|
onready();
|