Переглянути джерело

fix 302, pagination, new sub; allow viewing url only

Mike L 3 роки тому
батько
коміт
8cdcc2187e
6 змінених файлів з 62 додано та 50 видалено
  1. 1 1
      dist/command.js
  2. 1 1
      dist/koishi.js
  3. 29 23
      dist/twitter.js
  4. 2 2
      src/command.ts
  5. 1 1
      src/koishi.ts
  6. 28 22
      src/twitter.ts

+ 1 - 1
dist/command.js

@@ -148,7 +148,7 @@ function view(chat, args, reply) {
     if (args.length === 0) {
         return reply('找不到要查看的链接。');
     }
-    const match = twitter_1.isValidUrlSegment(args[0]) && args[0] || ((_a = twitter_1.parseLink(args[0])) === null || _a === void 0 ? void 0 : _a.postUrlSegment);
+    const match = (_a = twitter_1.parseLink(args[0])) === null || _a === void 0 ? void 0 : _a.postUrlSegment;
     if (!match) {
         return reply('链接格式有误。');
     }

+ 1 - 1
dist/koishi.js

@@ -199,7 +199,7 @@ class default_1 {
 /instagram - 查询当前聊天中的 Instagram 动态订阅
 /instagram_sub[scribe]〈链接|用户名〉- 订阅 Instagram 媒体搬运
 /instagram_unsub[scribe]〈链接|用户名〉- 退订 Instagram 媒体搬运
-/instagram_view〈链接|用户名〉- 查看媒体\
+/instagram_view〈链接〉- 查看媒体\
 ${chat.chatType === "temp" ?
                                 '\n(当前游客模式下无法使用订阅功能,请先添加本账号为好友。)' : ''}`);
                         }

+ 29 - 23
dist/twitter.js

@@ -9,7 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
     });
 };
 Object.defineProperty(exports, "__esModule", { value: true });
-exports.sendPost = exports.getPostOwner = exports.WebshotHelpers = exports.ScreenNameNormalizer = exports.SessionManager = exports.urlSegmentToId = exports.idToUrlSegment = exports.isValidUrlSegment = exports.parseLink = exports.linkBuilder = exports.graphqlLinkBuilder = void 0;
+exports.sendPost = exports.getPostOwner = exports.WebshotHelpers = exports.ScreenNameNormalizer = exports.SessionManager = exports.urlSegmentToId = exports.idToUrlSegment = exports.parseLink = exports.linkBuilder = exports.graphqlLinkBuilder = void 0;
 const crypto = require("crypto");
 const fs = require("fs");
 const http = require("http");
@@ -35,8 +35,6 @@ const parseLink = (link) => {
     return;
 };
 exports.parseLink = parseLink;
-const isValidUrlSegment = (input) => /^[A-Za-z0-9\-_]+$/.test(input);
-exports.isValidUrlSegment = isValidUrlSegment;
 const linkBuilder = (config) => {
     if (config.userName)
         return `https://www.instagram.com/${config.userName}/`;
@@ -210,32 +208,40 @@ class default_1 {
                         return page.context().addCookies(this.webshotCookies)
                             .then(() => page.goto(url, { waitUntil: 'load', timeout: getTimeout() }))
                             .then(response => {
-                            const responseHandler = (res) => {
-                                if (res.status() === 302) {
-                                    return acceptCookieConsent(page)
-                                        .then(() => browserLogin(page))
-                                        .catch((err) => {
-                                        if (err.name === 'TimeoutError') {
-                                            logger.warn('navigation timed out, assuming login has failed');
-                                            isWaitingForLogin = false;
-                                        }
-                                        throw err;
-                                    })
-                                        .then(() => browserSaveCookies(page))
-                                        .then(() => page.goto(url, { waitUntil: 'load', timeout: getTimeout() }))
-                                        .then(responseHandler);
+                            const itemIds = [];
+                            const redirectionHandler = () => acceptCookieConsent(page)
+                                .then(() => browserLogin(page))
+                                .catch((err) => {
+                                if (err.name === 'TimeoutError') {
+                                    logger.warn('navigation timed out, assuming login has failed');
+                                    isWaitingForLogin = false;
                                 }
+                                throw err;
+                            })
+                                .then(() => browserSaveCookies(page))
+                                .then(() => page.goto(url, { waitUntil: 'load', timeout: getTimeout() }))
+                                .then(responseHandler);
+                            const responseHandler = (res) => {
                                 if (res.status() !== 200) {
                                     const err = new Error(`error navigating to user page, error was: ${res.status()} ${res.statusText()}`);
                                     throw Object.defineProperty(err, 'name', {
                                         value: 'ResponseError',
                                     });
                                 }
-                                return res.json();
+                                return res.json()
+                                    .catch(redirectionHandler)
+                                    .then((json) => {
+                                    var _a;
+                                    if (!json || !((_a = (json.graphql || json.data)) === null || _a === void 0 ? void 0 : _a.user)) {
+                                        logger.warn('error parsing graphql response, returning empty object...');
+                                        const data = { user: { edge_owner_to_timeline_media: { edges: [] } } };
+                                        return { graphql: data, data };
+                                    }
+                                    return json;
+                                });
                             };
                             const jsonHandler = ({ user }) => {
                                 const pageInfo = user.edge_owner_to_timeline_media.page_info;
-                                const itemIds = [];
                                 for (const { node } of user.edge_owner_to_timeline_media.edges) {
                                     if (node.__typename === 'GraphVideo' && node.product_type === 'igtv')
                                         continue;
@@ -243,8 +249,10 @@ class default_1 {
                                         itemIds.push(node.id);
                                     else
                                         return itemIds;
+                                    if (Number(targetId) < 1)
+                                        return itemIds;
                                 }
-                                if (!pageInfo.has_next_page)
+                                if (!(pageInfo === null || pageInfo === void 0 ? void 0 : pageInfo.has_next_page))
                                     return itemIds;
                                 logger.info('unable to find a smaller id than target, trying on next page...');
                                 url = graphqlLinkBuilder({ userId: user.id, after: pageInfo.end_cursor });
@@ -258,7 +266,7 @@ class default_1 {
                             if (err.name !== 'TimeoutError' && err.name !== 'ResponseError')
                                 throw err;
                             if (err.name === 'ResponseError') {
-                                logger.warn(`error while fetching tweets for ${userName}: ${err.message}`);
+                                logger.warn(`error while fetching posts by @${userName}: ${err.message}`);
                             }
                             else
                                 logger.warn(`navigation timed out at ${getTimerTime()} ms`);
@@ -348,8 +356,6 @@ class default_1 {
                         updateOffset();
                         return;
                     }
-                    if (currentThread.offset === '0')
-                        mediaItems.splice(1);
                     return this.workOnMedia(mediaItems, this.sendMedia(`thread ${currentFeed}`, ...currentThread.subscribers))
                         .then(updateDate).then(updateOffset);
                 }).then(() => {

+ 2 - 2
src/command.ts

@@ -8,7 +8,7 @@ import { relativeDate } from './datetime';
 import { getLogger } from './loggers';
 import {
   getPostOwner, sendPost, ScreenNameNormalizer as normalizer,
-  isValidUrlSegment, linkBuilder, parseLink, urlSegmentToId
+  linkBuilder, parseLink, urlSegmentToId
 } from './twitter';
 import { BigNumOps } from './utils';
 
@@ -153,7 +153,7 @@ function view(chat: IChat, args: string[], reply: (msg: string) => any): void {
   if (args.length === 0) {
     return reply('找不到要查看的链接。');
   }
-  const match = isValidUrlSegment(args[0]) && args[0] || parseLink(args[0])?.postUrlSegment;
+  const match = parseLink(args[0])?.postUrlSegment;
   if (!match) {
     return reply('链接格式有误。');
   }

+ 1 - 1
src/koishi.ts

@@ -218,7 +218,7 @@ export default class {
 /instagram - 查询当前聊天中的 Instagram 动态订阅
 /instagram_sub[scribe]〈链接|用户名〉- 订阅 Instagram 媒体搬运
 /instagram_unsub[scribe]〈链接|用户名〉- 退订 Instagram 媒体搬运
-/instagram_view〈链接|用户名〉- 查看媒体\
+/instagram_view〈链接〉- 查看媒体\
 ${chat.chatType === ChatType.Temp ?
     '\n(当前游客模式下无法使用订阅功能,请先添加本账号为好友。)' : ''
 }`);

+ 28 - 22
src/twitter.ts

@@ -32,8 +32,6 @@ const parseLink = (link: string): { userName?: string, postUrlSegment?: string }
   return;
 };
 
-const isValidUrlSegment = (input: string) => /^[A-Za-z0-9\-_]+$/.test(input);
-
 const linkBuilder = (config: ReturnType<typeof parseLink>): string => {
   if (config.userName) return `https://www.instagram.com/${config.userName}/`;
   if (config.postUrlSegment) return `https://www.instagram.com/p/${config.postUrlSegment}/`;
@@ -46,7 +44,7 @@ const graphqlLinkBuilder = ({userId, first = '12', after}: {userId: string, firs
 const urlSegmentToId = (urlSegment: string) => urlSegment.length <= 28 ?
   pubUrlSegmentToId(urlSegment) : pubUrlSegmentToId(urlSegment.slice(0, -28));
 
-export { graphqlLinkBuilder, linkBuilder, parseLink, isValidUrlSegment, idToUrlSegment, urlSegmentToId };
+export { graphqlLinkBuilder, linkBuilder, parseLink, idToUrlSegment, urlSegmentToId };
 
 interface IWorkerOption {
   sessionLockfile: string;
@@ -397,21 +395,21 @@ export default class {
             return page.context().addCookies(this.webshotCookies)
               .then(() => page.goto(url, {waitUntil: 'load', timeout: getTimeout()}))
               .then(response => {
+                const itemIds: string[] = [];
+                const redirectionHandler = () =>
+                  acceptCookieConsent(page)
+                    .then(() => browserLogin(page))
+                    .catch((err: Error) => {
+                      if (err.name === 'TimeoutError') {
+                        logger.warn('navigation timed out, assuming login has failed');
+                        isWaitingForLogin = false;
+                      }
+                      throw err;
+                    })
+                    .then(() => browserSaveCookies(page))
+                    .then(() => page.goto(url, {waitUntil: 'load', timeout: getTimeout()}))
+                    .then(responseHandler);
                 const responseHandler = (res: typeof response): ReturnType<typeof response.json> => {
-                  if (res.status() === 302) {
-                    return acceptCookieConsent(page)
-                      .then(() => browserLogin(page))
-                      .catch((err: Error) => {
-                        if (err.name === 'TimeoutError') {
-                          logger.warn('navigation timed out, assuming login has failed');
-                          isWaitingForLogin = false;
-                        }
-                        throw err;
-                      })
-                      .then(() => browserSaveCookies(page))
-                      .then(() => page.goto(url, {waitUntil: 'load', timeout: getTimeout()}))
-                      .then(responseHandler);
-                  }
                   if (res.status() !== 200) {
                     const err = new Error(
                       `error navigating to user page, error was: ${res.status()} ${res.statusText()}`
@@ -420,17 +418,26 @@ export default class {
                       value: 'ResponseError',
                     });
                   }
-                  return res.json();
+                  return res.json()
+                    .catch(redirectionHandler)
+                    .then((json: {[key: string]: {user: IgGraphQLUser}}) => {
+                      if (!json || !(json.graphql || json.data)?.user) {
+                        logger.warn('error parsing graphql response, returning empty object...');
+                        const data = {user: {edge_owner_to_timeline_media: {edges: []}} as IgGraphQLUser};
+                        return {graphql: data, data};
+                      }
+                      return json;
+                    });
                 };
                 const jsonHandler = ({user}: {user: IgGraphQLUser}): string[] | Promise<string[]> => {
                   const pageInfo = user.edge_owner_to_timeline_media.page_info;
-                  const itemIds: string[] = [];
                   for (const {node} of user.edge_owner_to_timeline_media.edges) {
                     if (node.__typename === 'GraphVideo' && node.product_type === 'igtv') continue;
                     if (node.id && BigNumOps.compare(node.id, targetId) > 0) itemIds.push(node.id);
                     else return itemIds;
+                    if (Number(targetId) < 1) return itemIds;
                   }
-                  if (!pageInfo.has_next_page) return itemIds;
+                  if (!pageInfo?.has_next_page) return itemIds;
                   logger.info('unable to find a smaller id than target, trying on next page...');
                   url = graphqlLinkBuilder({userId: user.id, after: pageInfo.end_cursor});
                   return page.goto(url, {waitUntil: 'load', timeout: getTimeout()})
@@ -442,7 +449,7 @@ export default class {
               }).catch((err: Error) => {
                 if (err.name !== 'TimeoutError' && err.name !== 'ResponseError') throw err;
                 if (err.name === 'ResponseError') {
-                  logger.warn(`error while fetching tweets for ${userName}: ${err.message}`);
+                  logger.warn(`error while fetching posts by @${userName}: ${err.message}`);
                 } else logger.warn(`navigation timed out at ${getTimerTime()} ms`);
                 return [] as string[];
               }).then(itemIds => promisify(setTimeout)(getTimeout()).then(() =>
@@ -554,7 +561,6 @@ export default class {
           const updateOffset = () => currentThread.offset = topOfFeed;
 
           if (currentThread.offset === '-1') { updateOffset(); return; }
-          if (currentThread.offset === '0') mediaItems.splice(1);
 
           return this.workOnMedia(mediaItems, this.sendMedia(`thread ${currentFeed}`, ...currentThread.subscribers))
             .then(updateDate).then(updateOffset);