|
@@ -198,17 +198,31 @@ export let sendPost = (segmentId: string, receiver: IChat): void => {
|
|
|
throw Error();
|
|
|
};
|
|
|
|
|
|
-type IgGraphQLTimelineMediaNode = {
|
|
|
- id: string,
|
|
|
- display_url: string,
|
|
|
+type IgGraphQLTimelineMediaNode = IgGraphQLTimelineBaseNode & {
|
|
|
+ edge_media_to_caption: {
|
|
|
+ edges: {node: {text: string}}[],
|
|
|
+ },
|
|
|
owner: {
|
|
|
id: string,
|
|
|
username?: string,
|
|
|
},
|
|
|
+ shortcode: string,
|
|
|
+ taken_at_timestamp: number,
|
|
|
+ accessibility_caption: string,
|
|
|
+};
|
|
|
+
|
|
|
+type IgGraphQLTimelineBaseNode = {
|
|
|
+ id: string,
|
|
|
+ display_url: string,
|
|
|
+ display_resources: {
|
|
|
+ src: string,
|
|
|
+ config_width: number,
|
|
|
+ config_height: number,
|
|
|
+ }[],
|
|
|
} & (
|
|
|
{__typename: 'GraphImage'} |
|
|
|
{__typename: 'GraphSidecar', edge_sidecar_to_children: {
|
|
|
- edges: {node: (IgGraphQLTimelineMediaNode & {__typename: 'GraphImage'})}[],
|
|
|
+ edges: {node: (IgGraphQLTimelineBaseNode & {__typename: 'GraphImage' | 'GraphVideo'})}[],
|
|
|
}} |
|
|
|
{__typename: 'GraphVideo', video_url: string, product_type?: 'igtv'}
|
|
|
);
|
|
@@ -229,6 +243,28 @@ export type IgGraphQLUser = {
|
|
|
},
|
|
|
};
|
|
|
|
|
|
+const graphNodeToMediaItem = (node: IgGraphQLTimelineMediaNode) => ({
|
|
|
+ taken_at: node.taken_at_timestamp,
|
|
|
+ code: node.shortcode,
|
|
|
+ caption: (caption => (caption ? caption.edges[0].node : {}) as MediaItem['caption'])(node.edge_media_to_caption),
|
|
|
+ user: {
|
|
|
+ pk: node.owner.id,
|
|
|
+ username: node.owner.username,
|
|
|
+ } as unknown as MediaItem['user'],
|
|
|
+ ...graphBaseNodeToCarouselMediaItem(node)
|
|
|
+}) as MediaItem;
|
|
|
+
|
|
|
+const graphBaseNodeToCarouselMediaItem = (node: IgGraphQLTimelineBaseNode) => ({
|
|
|
+ pk: node.id,
|
|
|
+ ...(node.__typename === 'GraphImage') && {image_versions2: {candidates: node.display_resources.map(
|
|
|
+ ({src, config_width, config_height}) => ({url: src, width: config_width, height: config_height})
|
|
|
+ )}},
|
|
|
+ ...(node.__typename === 'GraphVideo' && {video_versions: [{url: node.video_url, height: 0, width: 0}]}),
|
|
|
+ ...(node.__typename === 'GraphSidecar' && {carousel_media: node.edge_sidecar_to_children.edges.map(
|
|
|
+ ({node}) => graphBaseNodeToCarouselMediaItem(node)
|
|
|
+ )})
|
|
|
+})
|
|
|
+
|
|
|
export type MediaItem = MediaInfoResponseItemsItem & UserFeedResponseItemsItem;
|
|
|
|
|
|
export type LazyMediaItem = {
|
|
@@ -412,6 +448,7 @@ export default class {
|
|
|
username.includes(':') ? Promise.resolve(username) : this.queryUser(username)
|
|
|
).then(userNameId => doOnNewPage(newPage => {
|
|
|
const [userName, userId] = userNameId.split(':');
|
|
|
+ let fullName: string;
|
|
|
url = graphqlLinkBuilder({userId});
|
|
|
logger.debug(`pulling ${targetId !== '0' ? `feed ${url} up to ${targetId}` : `top of feed ${url}`}...`);
|
|
|
page = newPage;
|
|
@@ -422,7 +459,7 @@ export default class {
|
|
|
return page.context().addCookies(this.webshotCookies)
|
|
|
.then(() => page.goto(url, {waitUntil: 'load', timeout: getTimeout()}))
|
|
|
.then(response => {
|
|
|
- const itemIds: string[] = [];
|
|
|
+ const nodes: IgGraphQLTimelineMediaNode[] = [];
|
|
|
const redirectionHandler = () =>
|
|
|
acceptCookieConsent(page)
|
|
|
.then(() => browserLogin(page))
|
|
@@ -453,20 +490,26 @@ export default class {
|
|
|
return json;
|
|
|
});
|
|
|
};
|
|
|
- const jsonHandler = ({user}: {user: IgGraphQLUser}): string[] | Promise<string[]> => {
|
|
|
+ const jsonHandler = (
|
|
|
+ {user}: {user: IgGraphQLUser}
|
|
|
+ ): IgGraphQLTimelineMediaNode[] | Promise<IgGraphQLTimelineMediaNode[]> => {
|
|
|
+ if (user.full_name) fullName = user.full_name;
|
|
|
const pageInfo = user.edge_owner_to_timeline_media.page_info;
|
|
|
for (const {node} of user.edge_owner_to_timeline_media.edges) {
|
|
|
+ if (!fullName && node.accessibility_caption) {
|
|
|
+ fullName = node.accessibility_caption.replace(/^Photo by (.+) on \w+ \d+, \d+\..*/, '$1');
|
|
|
+ }
|
|
|
// exclude IGTV
|
|
|
if (node.__typename === 'GraphVideo' && node.product_type === 'igtv') continue;
|
|
|
// add post if ID is greater than target
|
|
|
- if (node.id && BigNumOps.compare(node.id, targetId) > 0) itemIds.push(node.id);
|
|
|
+ if (node.id && BigNumOps.compare(node.id, targetId) > 0) nodes.push(node);
|
|
|
// return of ID is equal to or smaller than target
|
|
|
- else return itemIds;
|
|
|
+ else return nodes;
|
|
|
// return after first addition if newly subscribed or restarted with resuming disabled
|
|
|
- if (Number(targetId) < 1) return itemIds;
|
|
|
+ if (Number(targetId) < 1) return nodes;
|
|
|
}
|
|
|
// return if all IDs are greater than target but end of feed is reached
|
|
|
- if (!pageInfo?.has_next_page) return itemIds;
|
|
|
+ if (!pageInfo?.has_next_page) return nodes;
|
|
|
// else, fetch next page using end_cursor
|
|
|
logger.info('unable to find a smaller id than target, trying on next page...');
|
|
|
url = graphqlLinkBuilder({userId, after: pageInfo.end_cursor});
|
|
@@ -485,9 +528,21 @@ export default class {
|
|
|
} else if (err.name === 'TimeoutError') {
|
|
|
logger.warn(`navigation timed out at ${getTimerTime()} ms`);
|
|
|
} else throw err;
|
|
|
- return [] as string[];
|
|
|
- }).then(itemIds => promisify(setTimeout)(getTimeout()).then(() =>
|
|
|
- itemIds.map(id => this.lazyGetMediaById(id))
|
|
|
+ return [] as IgGraphQLTimelineMediaNode[];
|
|
|
+ }).then(nodes =>
|
|
|
+ (!fullName && nodes.length ?
|
|
|
+ this.client.user.searchExact(nodes[0].owner.username) :
|
|
|
+ Promise.resolve({full_name: fullName})
|
|
|
+ ).then(({full_name}) => promisify(setTimeout)(getTimeout()).then(() =>
|
|
|
+ nodes.map(node => {
|
|
|
+ const item = graphNodeToMediaItem(node);
|
|
|
+ item.user.full_name = full_name;
|
|
|
+ return {
|
|
|
+ pk: item.pk,
|
|
|
+ item: () => Promise.resolve(item),
|
|
|
+ };
|
|
|
+ })
|
|
|
+ )
|
|
|
));
|
|
|
})).finally(() => { page.close(); });
|
|
|
});
|