Przeglądaj źródła

ditch lazy media api for routine work

Mike L 3 lat temu
rodzic
commit
1eed197dae
2 zmienionych plików z 94 dodań i 19 usunięć
  1. 26 6
      dist/twitter.js
  2. 68 13
      src/twitter.ts

+ 26 - 6
dist/twitter.js

@@ -164,6 +164,11 @@ let sendPost = (segmentId, receiver) => {
     throw Error();
 };
 exports.sendPost = sendPost;
+const graphNodeToMediaItem = (node) => (Object.assign({ taken_at: node.taken_at_timestamp, code: node.shortcode, caption: (caption => (caption ? caption.edges[0].node : {}))(node.edge_media_to_caption), user: {
+        pk: node.owner.id,
+        username: node.owner.username,
+    } }, graphBaseNodeToCarouselMediaItem(node)));
+const graphBaseNodeToCarouselMediaItem = (node) => (Object.assign(Object.assign(Object.assign({ 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)) })));
 const logger = (0, loggers_1.getLogger)('instagram');
 const maxTrials = 3;
 const retryInterval = 1500;
@@ -200,6 +205,7 @@ class default_1 {
                     let url;
                     return (username.includes(':') ? Promise.resolve(username) : this.queryUser(username)).then(userNameId => doOnNewPage(newPage => {
                         const [userName, userId] = userNameId.split(':');
+                        let fullName;
                         url = graphqlLinkBuilder({ userId });
                         logger.debug(`pulling ${targetId !== '0' ? `feed ${url} up to ${targetId}` : `top of feed ${url}`}...`);
                         page = newPage;
@@ -210,7 +216,7 @@ class default_1 {
                         return page.context().addCookies(this.webshotCookies)
                             .then(() => page.goto(url, { waitUntil: 'load', timeout: getTimeout() }))
                             .then(response => {
-                            const itemIds = [];
+                            const nodes = [];
                             const redirectionHandler = () => acceptCookieConsent(page)
                                 .then(() => browserLogin(page))
                                 .catch((err) => {
@@ -240,19 +246,24 @@ class default_1 {
                                 });
                             };
                             const jsonHandler = ({ user }) => {
+                                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');
+                                    }
                                     if (node.__typename === 'GraphVideo' && node.product_type === 'igtv')
                                         continue;
                                     if (node.id && utils_1.BigNumOps.compare(node.id, targetId) > 0)
-                                        itemIds.push(node.id);
+                                        nodes.push(node);
                                     else
-                                        return itemIds;
+                                        return nodes;
                                     if (Number(targetId) < 1)
-                                        return itemIds;
+                                        return nodes;
                                 }
                                 if (!(pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.has_next_page))
-                                    return itemIds;
+                                    return nodes;
                                 logger.info('unable to find a smaller id than target, trying on next page...');
                                 url = graphqlLinkBuilder({ userId, after: pageInfo.end_cursor });
                                 const nextPageDelay = this.webshotDelay * (0.4 + Math.random() * 0.1);
@@ -274,7 +285,16 @@ class default_1 {
                             else
                                 throw err;
                             return [];
-                        }).then(itemIds => (0, util_1.promisify)(setTimeout)(getTimeout()).then(() => itemIds.map(id => this.lazyGetMediaById(id))));
+                        }).then(nodes => (!fullName && nodes.length ?
+                            this.client.user.searchExact(nodes[0].owner.username) :
+                            Promise.resolve({ full_name: fullName })).then(({ full_name }) => (0, util_1.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(); });
                 });
                 setTimeout(this.work, this.workInterval * 1000 / this.lock.feed.length);

+ 68 - 13
src/twitter.ts

@@ -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(); });
         });