|
@@ -74,6 +74,10 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
|
|
|
logger.info('not working on a tweet');
|
|
|
};
|
|
|
|
|
|
+ private truncateLongThread = (atId: string) => {
|
|
|
+ logger.info('not working on a tweet');
|
|
|
+ };
|
|
|
+
|
|
|
private renderWebshot = (
|
|
|
url: string, height: number, webshotDelay: number,
|
|
|
...morePostProcessings: ((page: puppeteer.Page) => Promise<any>)[]
|
|
@@ -149,7 +153,7 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
|
|
|
logger.warn(`navigation timed out at ${getTimerTime()} seconds`);
|
|
|
return null;
|
|
|
})
|
|
|
- // scroll to 2nd last tweet by owner in thread, if any, or top of thread
|
|
|
+ // 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>) => {
|
|
|
if (handle === null) throw new puppeteer.errors.TimeoutError();
|
|
|
return handle.evaluate(div => {
|
|
@@ -157,14 +161,17 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
|
|
|
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) continue;
|
|
|
- return document.documentElement.scrollTop = window.scrollY + div.getBoundingClientRect().top;
|
|
|
+ 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(() => handle);
|
|
|
+ }).then(this.truncateLongThread).then(() => handle);
|
|
|
})
|
|
|
// scrape card image from main tweet
|
|
|
.then(handle => handle.evaluate(div => {
|
|
@@ -304,6 +311,7 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
|
|
|
});
|
|
|
const originTwi = twi.retweeted_status || twi;
|
|
|
let messageChain = '';
|
|
|
+ let truncatedAt: string;
|
|
|
|
|
|
// text processing
|
|
|
let author = `${twi.user.name} (@${twi.user.screen_name}):\n`;
|
|
@@ -337,6 +345,11 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
|
|
|
],
|
|
|
};
|
|
|
};
|
|
|
+ this.truncateLongThread = (atId: string) => {
|
|
|
+ if (!atId) return;
|
|
|
+ logger.info(`thread too long, truncating at tweet ${atId}...`);
|
|
|
+ truncatedAt = atId;
|
|
|
+ };
|
|
|
promise = promise.then(() => this.renderWebshot(url, 1920, webshotDelay))
|
|
|
.then(fileurl => {
|
|
|
if (fileurl) return Message.Image(fileurl);
|
|
@@ -384,6 +397,12 @@ class Webshot extends CallableInstance<[Tweets, (...args) => void, number], Prom
|
|
|
});
|
|
|
}
|
|
|
}
|
|
|
+ // refer to earlier tweets if thread is truncated
|
|
|
+ promise = promise.then(() => {
|
|
|
+ if (truncatedAt) {
|
|
|
+ messageChain += `\n回复此命令查看对话串中更早的推文:\n/twitter_view ${truncatedAt}`;
|
|
|
+ }
|
|
|
+ });
|
|
|
// refer to quoted tweet, if any
|
|
|
if (originTwi.is_quote_status) {
|
|
|
promise = promise.then(() => {
|