Bläddra i källkod

fix critical bug with timeout (infinite loop)

Mike L 4 år sedan
förälder
incheckning
9026cf2e24
4 ändrade filer med 67 tillägg och 30 borttagningar
  1. 38 20
      dist/mirai.js
  2. 2 2
      dist/twitter.js
  3. 25 6
      src/mirai.ts
  4. 2 2
      src/twitter.ts

+ 38 - 20
dist/mirai.js

@@ -10,6 +10,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
 };
 Object.defineProperty(exports, "__esModule", { value: true });
 const axios_1 = require("axios");
+const crypto = require("crypto");
 const mirai_ts_1 = require("mirai-ts");
 const message_1 = require("mirai-ts/dist/message");
 const helper_1 = require("./helper");
@@ -23,27 +24,44 @@ const ChatTypeMap = {
 exports.MiraiMessage = message_1.default;
 class default_1 {
     constructor(opt) {
-        this.sendTo = (subscriber, msg, timeout) => new Promise((resolve, reject) => {
-            if (timeout === 0 || timeout < -1)
-                reject('Error: timeout must be greater than 0 ms');
-            (() => {
-                switch (subscriber.chatType) {
-                    case 'group':
-                        return this.bot.api.sendGroupMessage(msg, subscriber.chatID);
-                    case 'private':
-                        return this.bot.api.sendFriendMessage(msg, subscriber.chatID);
+        this.revokeList = new Set();
+        this.sendTo = (subscriber, msg, timeout) => {
+            const msgId = `${new Date().getTime()}${crypto.randomBytes(8).toString('hex')}`;
+            timeout = Math.floor(timeout);
+            return new Promise((resolve, reject) => {
+                if (timeout === 0 || timeout < -1 || timeout > 0xFFFFFFFF) {
+                    reject(`Error: timeout must be between 1 - ${0xFFFFFFFF} ms`);
                 }
-            })().then(resolve).catch(reject);
-            setTimeout(() => reject('Error: request timed out'), timeout);
-        })
-            .then(response => {
-            logger.info(`pushing data to ${subscriber.chatID} was successful, response:`);
-            logger.info(response);
-        })
-            .catch(reason => {
-            logger.error(`error pushing data to ${subscriber.chatID}, reason: ${reason}`);
-            throw Error(reason);
-        });
+                (() => {
+                    switch (subscriber.chatType) {
+                        case 'group':
+                            return this.bot.api.sendGroupMessage(msg, subscriber.chatID);
+                        case 'private':
+                            return this.bot.api.sendFriendMessage(msg, subscriber.chatID);
+                    }
+                })().then(response => {
+                    if (this.revokeList.has(msgId)) {
+                        this.bot.api.recall(response.messageId)
+                            .then(() => logger.info(`overdue message to ${subscriber.chatID} recalled`))
+                            .catch(() => logger.info(`error recalling overdue message to ${subscriber.chatID}`))
+                            .finally(() => this.revokeList.delete(msgId));
+                    }
+                    resolve(response);
+                }).catch(reject);
+                setTimeout(() => {
+                    this.revokeList.add(msgId);
+                    reject('Error: timed out, requesting termination');
+                }, timeout);
+            })
+                .then(response => {
+                logger.info(`pushing data to ${subscriber.chatID} was successful, response:`);
+                logger.info(response);
+            })
+                .catch(reason => {
+                logger.error(`error pushing data to ${subscriber.chatID}, reason: ${reason}`);
+                throw Error(reason);
+            });
+        };
         this.initBot = () => {
             this.bot = new mirai_ts_1.default({
                 authKey: this.botInfo.access_token,

+ 2 - 2
dist/twitter.js

@@ -94,7 +94,7 @@ class default_1 {
                 if (lock.threads[lock.feed[lock.workon]].offset === 0)
                     tweets.splice(1);
                 const maxCount = 3;
-                let sendTimeout = 5000;
+                let sendTimeout = 10000;
                 const retryTimeout = 1500;
                 const ordinal = (n) => {
                     switch ((~~(n / 10) % 10 === 1) ? 0 : n % 10) {
@@ -113,7 +113,7 @@ class default_1 {
                         logger.info(`pushing data of thread ${lock.feed[lock.workon]} to ${JSON.stringify(subscriber)}`);
                         const retry = (reason, count) => {
                             if (count <= maxCount)
-                                sendTimeout *= count / (count - 1);
+                                sendTimeout *= (count + 2) / (count + 1);
                             setTimeout(() => {
                                 msg.forEach((message, pos) => {
                                     if (count > maxCount && message.type === 'Image') {

+ 25 - 6
src/mirai.ts

@@ -1,4 +1,5 @@
 import axios from 'axios';
+import * as crypto from 'crypto';
 import Mirai, { Api, MessageType } from 'mirai-ts';
 import Message from 'mirai-ts/dist/message';
 
@@ -31,9 +32,15 @@ export default class {
   private botInfo: IQQProps;
   public bot: Mirai;
 
-  public sendTo = (subscriber: IChat, msg: string | MessageChain, timeout?: number) =>
-    new Promise<Api.Response.sendMessage>((resolve, reject) => {
-      if (timeout === 0 || timeout < -1) reject('Error: timeout must be greater than 0 ms');
+  private revokeList: Set<string> = new Set();
+
+  public sendTo = (subscriber: IChat, msg: string | MessageChain, timeout?: number) => {
+    const msgId = `${new Date().getTime()}${crypto.randomBytes(8).toString('hex')}`;
+    timeout = Math.floor(timeout);
+    return new Promise<Api.Response.sendMessage>((resolve, reject) => {
+      if (timeout === 0 || timeout < -1 || timeout > 0xFFFFFFFF) {
+        reject(`Error: timeout must be between 1 - ${0xFFFFFFFF} ms`);
+      }
       (() => {
         switch (subscriber.chatType) {
           case 'group':
@@ -41,8 +48,19 @@ export default class {
           case 'private':
             return this.bot.api.sendFriendMessage(msg, subscriber.chatID);
         }
-      })().then(resolve).catch(reject);
-      setTimeout(() => reject('Error: request timed out'), timeout);
+      })().then(response => {
+        if (this.revokeList.has(msgId)) {
+          this.bot.api.recall(response.messageId)
+          .then(() => logger.info(`overdue message to ${subscriber.chatID} recalled`))
+          .catch(() => logger.info(`error recalling overdue message to ${subscriber.chatID}`))
+          .finally(() => this.revokeList.delete(msgId));
+        }
+        resolve(response);
+      }).catch(reject);
+      setTimeout(() => {
+        this.revokeList.add(msgId);
+        reject('Error: timed out, requesting termination');
+      }, timeout);
     })
     .then(response => {
       logger.info(`pushing data to ${subscriber.chatID} was successful, response:`);
@@ -51,7 +69,8 @@ export default class {
     .catch(reason => {
       logger.error(`error pushing data to ${subscriber.chatID}, reason: ${reason}`);
       throw Error(reason);
-    })
+    });
+  }
 
   private initBot = () => {
     this.bot = new Mirai({

+ 2 - 2
src/twitter.ts

@@ -137,7 +137,7 @@ export default class {
       if (lock.threads[lock.feed[lock.workon]].offset === 0) tweets.splice(1);
 
       const maxCount = 3;
-      let sendTimeout = 5000;
+      let sendTimeout = 10000;
       const retryTimeout = 1500;
       const ordinal = (n: number) => {
         switch ((~~(n / 10) % 10 === 1) ? 0 : n % 10) {
@@ -155,7 +155,7 @@ export default class {
         lock.threads[lock.feed[lock.workon]].subscribers.forEach(subscriber => {
           logger.info(`pushing data of thread ${lock.feed[lock.workon]} to ${JSON.stringify(subscriber)}`);
           const retry = (reason, count: number) => { // workaround for https://github.com/mamoe/mirai/issues/194
-            if (count <= maxCount) sendTimeout *= count / (count - 1);
+            if (count <= maxCount) sendTimeout *= (count + 2) / (count + 1);
             setTimeout(() => {
               (msg as MessageChain).forEach((message, pos) => {
                 if (count > maxCount && message.type === 'Image') {