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

index cache by user ID to cope with renamed users

Mike L 3 роки тому
батько
коміт
b8939ef634
2 змінених файлів з 77 додано та 80 видалено
  1. 36 37
      dist/twitter.js
  2. 41 43
      src/twitter.ts

+ 36 - 37
dist/twitter.js

@@ -193,8 +193,9 @@ class default_1 {
         };
         this.queryUser = (rawUserName) => {
             const username = ScreenNameNormalizer.normalize(rawUserName).split(':')[0];
-            if (username in this.cache) {
-                return Promise.resolve(`${username}:${this.cache[username].user.pk}`);
+            for (const { user } of Object.values(this.cache)) {
+                if (user.username === username)
+                    return Promise.resolve(`${username}:${user.pk}`);
             }
             return this.client.user.searchExact(username)
                 .catch((error) => {
@@ -207,7 +208,7 @@ class default_1 {
             })
                 .then(user => {
                 logger.info(`initialized cache item for user ${user.full_name} (@${username})`);
-                this.cache[user.username] = { user, stories: {}, pullOrder: 0 };
+                this.cache[user.pk] = { user, stories: {}, pullOrder: 0 };
                 return `${user.username}:${user.pk}`;
             });
         };
@@ -231,38 +232,34 @@ class default_1 {
             if (this.isInactiveTime)
                 return;
             logger.debug(`current cache: ${JSON.stringify(this.cache)}`);
-            utils_1.chainPromises(Object.entries(this.lock.threads).map(([feed, thread]) => {
+            utils_1.chainPromises(Object.entries(this.lock.threads).map(([feed, thread]) => () => {
                 const id = thread.id;
                 const userName = parseLink(feed).userName;
                 logger.debug(`preparing to add user @${userName} to next pull task...`);
-                return (map = {}) => {
-                    if (userName in this.cache) {
-                        const item = this.cache[userName];
-                        if (item.pullOrder === 0)
-                            item.pullOrder = -1;
-                        return Promise.resolve(Object.assign(map, { [id]: item.user }));
-                    }
-                    return util_1.promisify(setTimeout)((Math.random() * 2 + 1) * 5000).then(() => this.client.user.info(id).then(user => {
-                        logger.info(`initialized cache item for user ${user.full_name} (@${userName})`);
-                        this.cache[userName] = { user, stories: {}, pullOrder: -1 };
-                        return Object.assign(map, { [id]: user });
-                    }));
-                };
+                if (id in this.cache) {
+                    const item = this.cache[id];
+                    if (item.pullOrder === 0)
+                        item.pullOrder = -1;
+                    return Promise.resolve();
+                }
+                return util_1.promisify(setTimeout)((Math.random() * 2 + 1) * 5000).then(() => this.client.user.info(id).then(user => {
+                    logger.info(`initialized cache item for user ${user.full_name} (@${user.username})`);
+                    this.cache[id] = { user, stories: {}, pullOrder: -1 };
+                }));
             }))
-                .then(idToUserMap => {
+                .then(() => {
                 const userIdCache = Object.values(this.cache).some(item => item.pullOrder < 0) ?
-                    this.pullOrders = utils_1.Arr.shuffle(Object.keys(idToUserMap)).map(Number) :
+                    this.pullOrders = utils_1.Arr.shuffle(Object.keys(this.cache)).map(Number) :
                     this.pullOrders;
                 return utils_1.chainPromises(utils_1.Arr.chunk(userIdCache, 20).map(userIds => () => {
-                    const itemToUserName = (item) => idToUserMap[item.user.pk].username;
-                    logger.info(`pulling stories from users:${userIds.map(id => ` @${idToUserMap[id].username}`)}`);
+                    logger.info(`pulling stories from users:${userIds.map(id => ` @${this.cache[id].user.username}`)}`);
                     return this.client.feed.reelsMedia({ userIds }).items()
                         .then(storyItems => Promise.all(storyItems
-                        .filter(item => !(item.pk in this.cache[itemToUserName(item)].stories))
-                        .map(item => this.webshot([Object.assign(Object.assign({}, item), { user: this.cache[itemToUserName(item)].user })], (msgs, text, author) => this.cache[itemToUserName(item)].stories[item.pk] = { pk: item.pk, msgs, text, author, original: item }, this.webshotDelay))))
+                        .filter(item => !(item.pk in this.cache[item.user.pk].stories))
+                        .map(item => this.webshot([Object.assign(Object.assign({}, item), { user: this.cache[item.user.pk].user })], (msgs, text, author) => this.cache[item.user.pk].stories[item.pk] = { pk: item.pk, msgs, text, author, original: item }, this.webshotDelay))))
                         .finally(() => Object.values(this.lock.threads).forEach(thread => {
                         if (userIds.includes(thread.id)) {
-                            thread.updatedAt = (this.cache[idToUserMap[thread.id].username].updated = new Date()).toString();
+                            thread.updatedAt = (this.cache[thread.id].updated = new Date()).toString();
                         }
                     }));
                 }), (lp1, lp2) => () => lp1().then(() => util_1.promisify(setTimeout)(this.workInterval * 1000 / this.lock.feed.length).then(lp2)));
@@ -395,15 +392,15 @@ class default_1 {
             return this.queryUser(rawUserName)
                 .then(userNameId => {
                 var _a, _b;
-                const [userName, userId] = userNameId.split(':');
-                if (Date.now() - ((_b = (_a = this.cache[userName]) === null || _a === void 0 ? void 0 : _a.updated) === null || _b === void 0 ? void 0 : _b.getTime()) > this.workInterval * 1000 &&
-                    Object.keys(this.cache[userName].stories).length > 0) {
-                    return userName;
+                const userId = userNameId.split(':')[1];
+                if (Date.now() - ((_b = (_a = this.cache[userId]) === null || _a === void 0 ? void 0 : _a.updated) === null || _b === void 0 ? void 0 : _b.getTime()) > this.workInterval * 1000 &&
+                    Object.keys(this.cache[userId].stories).length > 0) {
+                    return userId;
                 }
                 return this.client.feed.reelsMedia({ userIds: [userId] }).items()
                     .then(storyItems => Promise.all(storyItems
-                    .filter(item => !(item.pk in this.cache[userName].stories))
-                    .map(item => this.webshot([Object.assign(Object.assign({}, item), { user: this.cache[userName].user })], (msgs, text, author) => this.cache[userName].stories[item.pk] = { pk: item.pk, msgs, text, author, original: item }, this.webshotDelay))).then(() => userName).finally(() => this.cache[userName].updated = new Date()));
+                    .filter(item => !(item.pk in this.cache[userId].stories))
+                    .map(item => this.webshot([Object.assign(Object.assign({}, item), { user: this.cache[userId].user })], (msgs, text, author) => this.cache[userId].stories[item.pk] = { pk: item.pk, msgs, text, author, original: item }, this.webshotDelay))).then(() => userId).finally(() => this.cache[userId].updated = new Date()));
             })
                 .then(action)
                 .catch((error) => {
@@ -434,8 +431,9 @@ class default_1 {
             const reply = msg => this.bot.sendTo(receiver, msg);
             workNow({
                 rawUserName,
-                action: userName => {
-                    const storyItems = Object.values(this.cache[userName].stories)
+                action: userId => {
+                    const userName = this.cache[userId].user.username;
+                    const storyItems = Object.values(this.cache[userId].stories)
                         .sort((i1, i2) => -utils_1.BigNumOps.compare(i2.pk, i1.pk));
                     if (storyItems.length === 0)
                         return reply(`当前用户 (@${userName}) 没有可用的 Instagram 限时动态。`);
@@ -453,10 +451,10 @@ class default_1 {
             const sender = this.sendStories(`instagram stories for ${rawUserName}`, receiver);
             workNow({
                 rawUserName,
-                action: userName => {
-                    if (!(storyId in this.cache[userName].stories))
+                action: userId => {
+                    if (!(storyId in this.cache[userId].stories))
                         return reply('此动态不存在或已过期。');
-                    return this.workOnMedia([this.cache[userName].stories[storyId]], sender);
+                    return this.workOnMedia([this.cache[userId].stories[storyId]], sender);
                 },
                 reply,
                 retryAction: () => exports.sendStory(rawUserName, storyId, receiver),
@@ -471,8 +469,9 @@ class default_1 {
             const sender = this.sendStories(`instagram stories for ${rawUserName}`, receiver);
             workNow({
                 rawUserName,
-                action: userName => {
-                    const storyItems = Object.values(this.cache[userName].stories)
+                action: userId => {
+                    const userName = this.cache[userId].user.username;
+                    const storyItems = Object.values(this.cache[userId].stories)
                         .sort((i1, i2) => -utils_1.BigNumOps.compare(i2.pk, i1.pk));
                     if (storyItems.length === 0)
                         return reply(`当前用户 (@${userName}) 没有可用的 Instagram 限时动态。`);

+ 41 - 43
src/twitter.ts

@@ -254,28 +254,28 @@ export default class {
 
     const workNow = (config: {
       rawUserName: string,
-      action: (userName: string) => void,
+      action: (userId: number | string) => void,
       retryAction: () => void,
       reply: (msg: string) => void
     }) => {
       const {action, retryAction, reply, rawUserName} = config;
       return this.queryUser(rawUserName)
         .then(userNameId => {
-          const [userName, userId] = userNameId.split(':');
-          if (Date.now() - this.cache[userName]?.updated?.getTime() > this.workInterval * 1000 &&
-            Object.keys(this.cache[userName].stories).length > 0) {
-            return userName;
+          const userId = userNameId.split(':')[1];
+          if (Date.now() - this.cache[userId]?.updated?.getTime() > this.workInterval * 1000 &&
+            Object.keys(this.cache[userId].stories).length > 0) {
+            return userId;
           }
           return this.client.feed.reelsMedia({userIds: [userId]}).items()
             .then(storyItems => Promise.all(storyItems
-              .filter(item => !(item.pk in this.cache[userName].stories))
+              .filter(item => !(item.pk in this.cache[userId].stories))
               .map(item => this.webshot(
-                [{...item, user: this.cache[userName].user}],
+                [{...item, user: this.cache[userId].user}],
                 (msgs: string, text: string, author: string) =>
-                  this.cache[userName].stories[item.pk] = {pk: item.pk, msgs, text, author, original: item},
+                  this.cache[userId].stories[item.pk] = {pk: item.pk, msgs, text, author, original: item},
                 this.webshotDelay
               ))
-            ).then(() => userName).finally(() => this.cache[userName].updated = new Date()));
+            ).then(() => userId).finally(() => this.cache[userId].updated = new Date()));
         })
         .then(action)
         .catch((error: IgClientError & Partial<RequestError>) => {
@@ -305,8 +305,9 @@ export default class {
       const reply = msg => this.bot.sendTo(receiver, msg);
       workNow({
         rawUserName,
-        action: userName => {
-          const storyItems = Object.values(this.cache[userName].stories)
+        action: userId => {
+          const userName = this.cache[userId].user.username;
+          const storyItems = Object.values(this.cache[userId].stories)
             .sort((i1, i2) => -BigNumOps.compare(i2.pk, i1.pk)); // ascending!
           if (storyItems.length === 0) return reply(`当前用户 (@${userName}) 没有可用的 Instagram 限时动态。`);
           return reply('#. 编号:发送时间\n' + storyItems.map(({original}, index) =>
@@ -324,9 +325,9 @@ export default class {
       const sender = this.sendStories(`instagram stories for ${rawUserName}`, receiver);
       workNow({
         rawUserName,
-        action: userName => {
-          if (!(storyId in this.cache[userName].stories)) return reply('此动态不存在或已过期。');
-          return this.workOnMedia([this.cache[userName].stories[storyId]], sender);
+        action: userId => {
+          if (!(storyId in this.cache[userId].stories)) return reply('此动态不存在或已过期。');
+          return this.workOnMedia([this.cache[userId].stories[storyId]], sender);
         },
         reply,
         retryAction: () => sendStory(rawUserName, storyId, receiver),
@@ -339,8 +340,9 @@ export default class {
       const sender = this.sendStories(`instagram stories for ${rawUserName}`, receiver);
       workNow({
         rawUserName,
-        action: userName => {
-          const storyItems = Object.values(this.cache[userName].stories)
+        action: userId => {
+          const userName = this.cache[userId].user.username;
+          const storyItems = Object.values(this.cache[userId].stories)
             .sort((i1, i2) => -BigNumOps.compare(i2.pk, i1.pk)); // ascending!
           if (storyItems.length === 0) return reply(`当前用户 (@${userName}) 没有可用的 Instagram 限时动态。`);
           if (startIndex + 1 > storyItems.length) return reply('跳过数量到达或超过当前用户可用的限时动态数量。');
@@ -372,8 +374,8 @@ export default class {
 
   public queryUser = (rawUserName: string) => {
     const username = ScreenNameNormalizer.normalize(rawUserName).split(':')[0];
-    if (username in this.cache) {
-      return Promise.resolve(`${username}:${this.cache[username].user.pk}`);
+    for (const {user} of Object.values(this.cache)) {
+      if (user.username === username) return Promise.resolve(`${username}:${user.pk}`);
     }
     return this.client.user.searchExact(username)
       .catch((error: IgClientError) => {
@@ -384,7 +386,7 @@ export default class {
       })
       .then(user => {
         logger.info(`initialized cache item for user ${user.full_name} (@${username})`);
-        this.cache[user.username] = {user, stories: {}, pullOrder: 0};
+        this.cache[user.pk] = {user, stories: {}, pullOrder: 0};
         return `${user.username}:${user.pk}`;
       });
   };
@@ -411,7 +413,7 @@ export default class {
   };
 
   private cache: {
-    [userName: string]: {
+    [userId: string]: {
       user: UserFeedResponseUser & ReelsMediaFeedResponseItem['user'],
       stories: {[storyId: string]: CachedMediaItem},
       pullOrder: number, // one-based; -1: subscribed, awaiting shuffle; 0: not subscribed
@@ -432,46 +434,42 @@ export default class {
   private workForAll = () => {
     if (this.isInactiveTime) return;
     logger.debug(`current cache: ${JSON.stringify(this.cache)}`);
-    chainPromises(Object.entries(this.lock.threads).map(([feed, thread]) => {
+    chainPromises(Object.entries(this.lock.threads).map(([feed, thread]) => () => {
       const id = thread.id;
       const userName = parseLink(feed).userName;
       logger.debug(`preparing to add user @${userName} to next pull task...`);
-      return (map: {[key: number]: UserFeedResponseUser} = {}) => {
-        if (userName in this.cache) {
-          const item = this.cache[userName];
-          if (item.pullOrder === 0) item.pullOrder = -1;
-          return Promise.resolve(Object.assign(map, {[id]: item.user}));
-        }
-        return promisify(setTimeout)((Math.random() * 2 + 1) * 5000).then(() =>
-          this.client.user.info(id).then(user => {
-            logger.info(`initialized cache item for user ${user.full_name} (@${userName})`);
-            this.cache[userName] = {user, stories: {}, pullOrder: -1};
-            return Object.assign(map, {[id]: user});
-          })
-        );
-      };
+      if (id in this.cache) {
+        const item = this.cache[id];
+        if (item.pullOrder === 0) item.pullOrder = -1;
+        return Promise.resolve();
+      }
+      return promisify(setTimeout)((Math.random() * 2 + 1) * 5000).then(() =>
+        this.client.user.info(id).then(user => {
+          logger.info(`initialized cache item for user ${user.full_name} (@${user.username})`);
+          this.cache[id] = {user, stories: {}, pullOrder: -1};
+        })
+      );
     }))
-      .then(idToUserMap => {
+      .then(() => {
         const userIdCache = Object.values(this.cache).some(item => item.pullOrder < 0) ?
-          this.pullOrders = Arr.shuffle(Object.keys(idToUserMap)).map(Number) :
+          this.pullOrders = Arr.shuffle(Object.keys(this.cache)).map(Number) :
           this.pullOrders;
         return chainPromises(
           Arr.chunk(userIdCache, 20).map(userIds => () => {
-            const itemToUserName = (item: MediaItem) => idToUserMap[item.user.pk].username;
-            logger.info(`pulling stories from users:${userIds.map(id => ` @${idToUserMap[id].username}`)}`);
+            logger.info(`pulling stories from users:${userIds.map(id => ` @${this.cache[id].user.username}`)}`);
             return this.client.feed.reelsMedia({userIds}).items()
               .then(storyItems => Promise.all(storyItems
-                .filter(item => !(item.pk in this.cache[itemToUserName(item)].stories))
+                .filter(item => !(item.pk in this.cache[item.user.pk].stories))
                 .map(item => this.webshot(
-                  [{...item, user: this.cache[itemToUserName(item)].user}],
+                  [{...item, user: this.cache[item.user.pk].user}],
                   (msgs: string, text: string, author: string) =>
-                    this.cache[itemToUserName(item)].stories[item.pk] = {pk: item.pk, msgs, text, author, original: item},
+                    this.cache[item.user.pk].stories[item.pk] = {pk: item.pk, msgs, text, author, original: item},
                   this.webshotDelay
                 ))
               ))
               .finally(() => Object.values(this.lock.threads).forEach(thread => {
                 if (userIds.includes(thread.id)) {
-                  thread.updatedAt = (this.cache[idToUserMap[thread.id].username].updated = new Date()).toString();
+                  thread.updatedAt = (this.cache[thread.id].updated = new Date()).toString();
                 }
               })) as unknown as Promise<void>;
           }),