Browse Source

handle temp chats, friend requests; update deps

Mike L 4 years ago
parent
commit
3a02ccc37c
9 changed files with 184 additions and 46 deletions
  1. 1 0
      README.md
  2. 9 0
      dist/command.js
  3. 66 21
      dist/mirai.js
  4. 1 0
      dist/webshot.js
  5. 2 2
      package.json
  6. 9 0
      src/command.ts
  7. 81 20
      src/mirai.ts
  8. 14 3
      src/model.d.ts
  9. 1 0
      src/webshot.ts

+ 1 - 0
README.md

@@ -14,6 +14,7 @@
 - 视频使用 [gifski](https://github.com/ImageOptim/gifski) 压缩为 GIF(请务必下载并放到 `PATH` 下,推荐[这里](https://github.com/CL-Jeremy/gifski/releases/tag/1.0.1-unofficial)的最新修改版,注意从包管理器安装依赖)
 - 机器人的 QQ 号码必须手动填写
 - Puppeteer 不再自动启动,请手动开启并监听本地 9222 端口(这种方式可以使用 Chrome 或是远程 WebSocket 代理服务器)
+- 自动处理<u>来自群友的好友请求</u>和<u>来自好友的加群邀请</u>
 
 ## 配置
 

+ 9 - 0
dist/command.js

@@ -38,6 +38,9 @@ function linkFinder(checkedMatch, chat, lock) {
     return [link, index];
 }
 function sub(chat, args, reply, lock, lockfile) {
+    if (chat.chatType === "temp" /* Temp */) {
+        return reply('请先添加机器人为好友。');
+    }
     if (args.length === 0) {
         return reply('找不到要订阅的链接。');
     }
@@ -94,6 +97,9 @@ https://twitter.com/TomoyoKurosawa/status/1294613494860361729`);
 }
 exports.sub = sub;
 function unsub(chat, args, reply, lock, lockfile) {
+    if (chat.chatType === "temp" /* Temp */) {
+        return reply('请先添加机器人为好友。');
+    }
     if (args.length === 0) {
         return reply('找不到要退订的链接。');
     }
@@ -113,6 +119,9 @@ function unsub(chat, args, reply, lock, lockfile) {
 }
 exports.unsub = unsub;
 function list(chat, _, reply, lock) {
+    if (chat.chatType === "temp" /* Temp */) {
+        return reply('请先添加机器人为好友。');
+    }
     const links = [];
     Object.keys(lock.threads).forEach(key => {
         if (lock.threads[key].subscribers.find(({ chatID, chatType }) => chat.chatID === chatID && chat.chatType === chatType))

+ 66 - 21
dist/mirai.js

@@ -19,28 +19,56 @@ const command_1 = require("./command");
 const helper_1 = require("./helper");
 const loggers_1 = require("./loggers");
 const logger = loggers_1.getLogger('qqbot');
-const ChatTypeMap = {
-    GroupMessage: "group" /* Group */,
-    FriendMessage: "private" /* Private */,
-    TempMessage: "temp" /* Temp */,
-};
 exports.Message = message_1.default;
 class default_1 {
     constructor(opt) {
+        this.getChat = (msg) => __awaiter(this, void 0, void 0, function* () {
+            switch (msg.type) {
+                case 'FriendMessage':
+                    return {
+                        chatID: msg.sender.id,
+                        chatType: "private" /* Private */,
+                    };
+                case 'GroupMessage':
+                    return {
+                        chatID: msg.sender.group.id,
+                        chatType: "group" /* Group */,
+                    };
+                case 'TempMessage':
+                    const friendList = yield this.bot.api.friendList();
+                    // already befriended
+                    if (friendList.some(friendItem => friendItem.id = msg.sender.id)) {
+                        return {
+                            chatID: msg.sender.id,
+                            chatType: "private" /* Private */,
+                        };
+                    }
+                    return {
+                        chatID: {
+                            qq: msg.sender.id,
+                            group: msg.sender.group.id,
+                        },
+                        chatType: "temp" /* Temp */,
+                    };
+            }
+        });
         this.sendTo = (subscriber, msg) => (() => {
             switch (subscriber.chatType) {
                 case 'group':
                     return this.bot.api.sendGroupMessage(msg, subscriber.chatID);
                 case 'private':
                     return this.bot.api.sendFriendMessage(msg, subscriber.chatID);
+                // currently disabled
+                case 'temp':
+                    return this.bot.api.sendTempMessage(msg, subscriber.chatID.qq, subscriber.chatID.group);
             }
         })()
             .then(response => {
-            logger.info(`pushing data to ${subscriber.chatID} was successful, response:`);
+            logger.info(`pushing data to ${JSON.stringify(subscriber.chatID)} was successful, response:`);
             logger.info(response);
         })
             .catch(reason => {
-            logger.error(`error pushing data to ${subscriber.chatID}, reason: ${reason}`);
+            logger.error(`error pushing data to ${JSON.stringify(subscriber.chatID)}, reason: ${reason}`);
             throw Error(reason);
         });
         this.uploadPic = (img, timeout = -1) => {
@@ -98,17 +126,32 @@ class default_1 {
                 port: this.botInfo.port,
             });
             this.bot.axios.defaults.maxContentLength = Infinity;
-            this.bot.on('message', (msg) => {
-                const chat = {
-                    chatType: ChatTypeMap[msg.type],
-                    chatID: 0,
-                };
-                if (msg.type === 'FriendMessage') {
-                    chat.chatID = msg.sender.id;
-                }
-                else if (msg.type === 'GroupMessage') {
-                    chat.chatID = msg.sender.group.id;
-                }
+            this.bot.on('NewFriendRequestEvent', evt => {
+                logger.debug(`detected new friend request event: ${JSON.stringify(evt)}`);
+                this.bot.api.groupList()
+                    .then((groupList) => {
+                    if (groupList.some(groupItem => groupItem.id === evt.groupId)) {
+                        evt.respond('allow');
+                        return logger.info(`accepted friend request from ${evt.fromId} (from group ${evt.groupId})`);
+                    }
+                    logger.warn(`received friend request from ${evt.fromId} (from group ${evt.groupId})`);
+                    logger.warn('please manually accept this friend request');
+                });
+            });
+            this.bot.on('BotInvitedJoinGroupRequestEvent', evt => {
+                logger.debug(`detected group invitation event: ${JSON.stringify(evt)}`);
+                this.bot.api.friendList()
+                    .then((friendList) => {
+                    if (friendList.some(friendItem => friendItem.id = evt.fromId)) {
+                        evt.respond('allow');
+                        return logger.info(`accepted group invitation from ${evt.fromId} (friend)`);
+                    }
+                    logger.warn(`received group invitation from ${evt.fromId} (unknown)`);
+                    logger.warn('please manually accept this group invitation');
+                });
+            });
+            this.bot.on('message', (msg) => __awaiter(this, void 0, void 0, function* () {
+                const chat = yield this.getChat(msg);
                 const cmdObj = helper_1.default(msg.plain);
                 switch (cmdObj.cmd) {
                     case 'twitter_view':
@@ -132,9 +175,11 @@ class default_1 {
 /twitter - 查询当前聊天中的订阅
 /twitter_subscribe [链接] - 订阅 Twitter 搬运
 /twitter_unsubscribe [链接] - 退订 Twitter 搬运
-/twitter_view [链接] - 查看推文`);
+/twitter_view [链接] - 查看推文
+${chat.chatType === "temp" /* Temp */ &&
+                            '(当前游客模式下无法使用订阅功能,请先添加本账号为好友。)'}`);
                 }
-            });
+            }));
         };
         // TODO doesn't work if connection is dropped after connection
         this.listen = (logMsg) => {
@@ -156,7 +201,7 @@ class default_1 {
         };
         this.login = (logMsg) => __awaiter(this, void 0, void 0, function* () {
             logger.warn(logMsg !== null && logMsg !== void 0 ? logMsg : 'Logging in...');
-            yield this.bot.login(this.botInfo.bot_id)
+            yield this.bot.link(this.botInfo.bot_id)
                 .then(() => logger.warn(`Logged in as ${this.botInfo.bot_id}`))
                 .catch(() => {
                 logger.error(`Cannot log in. Do you have a bot logged in as ${this.botInfo.bot_id}?`);

+ 1 - 0
dist/webshot.js

@@ -246,6 +246,7 @@ class Webshot extends CallableInstance {
                     method: 'get',
                     url,
                     responseType: 'arraybuffer',
+                    timeout: 150000,
                 }).then(res => {
                     if (res.status === 200) {
                         logger.info(`successfully fetched ${url}`);

+ 2 - 2
package.json

@@ -34,7 +34,7 @@
     "command-line-usage": "^5.0.5",
     "html-entities": "^1.3.1",
     "log4js": "^6.3.0",
-    "mirai-ts": "^0.4.6",
+    "mirai-ts": "github:CL-Jeremy/mirai-ts#built",
     "pngjs": "^5.0.0",
     "puppeteer": "^2.1.0",
     "read-all-stream": "^3.1.0",
@@ -42,7 +42,7 @@
     "sharp": "^0.25.4",
     "temp": "^0.9.1",
     "twitter": "^1.7.1",
-    "typescript": "^3.9.7"
+    "typescript": "^4.0.2"
   },
   "devDependencies": {
     "@types/node": "^10.17.27",

+ 9 - 0
src/command.ts

@@ -43,6 +43,9 @@ function linkFinder(checkedMatch: string[], chat: IChat, lock: ILock): [string,
 function sub(chat: IChat, args: string[], reply: (msg: string) => any,
   lock: ILock, lockfile: string
 ): void {
+  if (chat.chatType === ChatType.Temp) {
+    return reply('请先添加机器人为好友。');
+  }
   if (args.length === 0) {
     return reply('找不到要订阅的链接。');
   }
@@ -98,6 +101,9 @@ https://twitter.com/TomoyoKurosawa/status/1294613494860361729`);
 function unsub(chat: IChat, args: string[], reply: (msg: string) => any,
   lock: ILock, lockfile: string
 ): void {
+  if (chat.chatType === ChatType.Temp) {
+    return reply('请先添加机器人为好友。');
+  }
   if (args.length === 0) {
     return reply('找不到要退订的链接。');
   }
@@ -116,6 +122,9 @@ function unsub(chat: IChat, args: string[], reply: (msg: string) => any,
 }
 
 function list(chat: IChat, _: string[], reply: (msg: string) => any, lock: ILock): void {
+  if (chat.chatType === ChatType.Temp) {
+    return reply('请先添加机器人为好友。');
+  }
   const links = [];
   Object.keys(lock.threads).forEach(key => {
     if (lock.threads[key].subscribers.find(({chatID, chatType}) => 

+ 81 - 20
src/mirai.ts

@@ -20,12 +20,6 @@ interface IQQProps {
   unsub(chat: IChat, args: string[], replyfn: (msg: string) => any): void;
 }
 
-const ChatTypeMap: Record<MessageType.ChatMessageType, ChatType> = {
-  GroupMessage: ChatType.Group,
-  FriendMessage: ChatType.Private,
-  TempMessage: ChatType.Temp,
-};
-
 export type MessageChain = MessageType.MessageChain;
 export const Message = MiraiMessage;
 
@@ -34,6 +28,41 @@ export default class {
   private botInfo: IQQProps;
   public bot: Mirai;
 
+  private getChat = async (msg: MessageType.ChatMessage): Promise<IChat> => {
+    switch (msg.type) {
+      case 'FriendMessage':
+        return {
+          chatID: msg.sender.id,
+          chatType: ChatType.Private,
+        };
+      case 'GroupMessage':
+        return {
+          chatID: msg.sender.group.id,
+          chatType: ChatType.Group,
+        };
+      case 'TempMessage':
+        const friendList: [{
+          id: number,
+          nickname: string,
+          remark: string,
+        }] = await this.bot.api.friendList();
+        // already befriended
+        if (friendList.some(friendItem => friendItem.id = msg.sender.id)) {
+          return {
+            chatID: msg.sender.id,
+            chatType: ChatType.Private,
+          };
+        }
+        return {
+          chatID: {
+            qq: msg.sender.id,
+            group: msg.sender.group.id,
+          },
+          chatType: ChatType.Temp,
+        };
+    }
+  }
+
   public sendTo = (subscriber: IChat, msg: string | MessageChain) =>
     (() => {
       switch (subscriber.chatType) {
@@ -41,14 +70,17 @@ export default class {
           return this.bot.api.sendGroupMessage(msg, subscriber.chatID);
         case 'private':
           return this.bot.api.sendFriendMessage(msg, subscriber.chatID);
+        // currently disabled
+        case 'temp':
+          return this.bot.api.sendTempMessage(msg, subscriber.chatID.qq, subscriber.chatID.group);
       }
     })()
     .then(response => {
-      logger.info(`pushing data to ${subscriber.chatID} was successful, response:`);
+      logger.info(`pushing data to ${JSON.stringify(subscriber.chatID)} was successful, response:`);
       logger.info(response);
     })
     .catch(reason => {
-      logger.error(`error pushing data to ${subscriber.chatID}, reason: ${reason}`);
+      logger.error(`error pushing data to ${JSON.stringify(subscriber.chatID)}, reason: ${reason}`);
       throw Error(reason);
     })
 
@@ -108,16 +140,42 @@ export default class {
 
     this.bot.axios.defaults.maxContentLength = Infinity;
 
-    this.bot.on('message', (msg) => {
-      const chat: IChat = {
-        chatType: ChatTypeMap[msg.type],
-        chatID: 0,
-      };
-      if (msg.type === 'FriendMessage') {
-          chat.chatID = msg.sender.id;
-      } else if (msg.type === 'GroupMessage') {
-          chat.chatID = msg.sender.group.id;
-      }
+    this.bot.on('NewFriendRequestEvent', evt => {
+      logger.debug(`detected new friend request event: ${JSON.stringify(evt)}`);
+      this.bot.api.groupList()
+      .then((groupList: [{
+        id: number,
+        name: string,
+        permission: 'OWNER' | 'ADMINISTRATOR' | 'MEMBER',
+      }]) => {
+        if (groupList.some(groupItem => groupItem.id === evt.groupId)) {
+          evt.respond('allow');
+          return logger.info(`accepted friend request from ${evt.fromId} (from group ${evt.groupId})`);
+        }
+        logger.warn(`received friend request from ${evt.fromId} (from group ${evt.groupId})`);
+        logger.warn('please manually accept this friend request');
+      });
+    });
+
+    this.bot.on('BotInvitedJoinGroupRequestEvent', evt => {
+      logger.debug(`detected group invitation event: ${JSON.stringify(evt)}`);
+      this.bot.api.friendList()
+      .then((friendList: [{
+        id: number,
+        nickname: string,
+        remark: string,
+      }]) => {
+        if (friendList.some(friendItem => friendItem.id = evt.fromId)) {
+          evt.respond('allow');
+          return logger.info(`accepted group invitation from ${evt.fromId} (friend)`);
+        }
+        logger.warn(`received group invitation from ${evt.fromId} (unknown)`);
+        logger.warn('please manually accept this group invitation');
+      });
+    });
+
+    this.bot.on('message', async msg => {
+      const chat = await this.getChat(msg);
       const cmdObj = command(msg.plain);
       switch (cmdObj.cmd) {
         case 'twitter_view':
@@ -141,7 +199,10 @@ export default class {
 /twitter - 查询当前聊天中的订阅
 /twitter_subscribe [链接] - 订阅 Twitter 搬运
 /twitter_unsubscribe [链接] - 退订 Twitter 搬运
-/twitter_view [链接] - 查看推文`);
+/twitter_view [链接] - 查看推文
+${chat.chatType === ChatType.Temp &&
+  '(当前游客模式下无法使用订阅功能,请先添加本账号为好友。)'
+}`);
       }
     });
 }
@@ -167,7 +228,7 @@ export default class {
 
   private login = async (logMsg?: string) => {
     logger.warn(logMsg ?? 'Logging in...');
-    await this.bot.login(this.botInfo.bot_id)
+    await this.bot.link(this.botInfo.bot_id)
     .then(() => logger.warn(`Logged in as ${this.botInfo.bot_id}`))
     .catch(() => {
       logger.error(`Cannot log in. Do you have a bot logged in as ${this.botInfo.bot_id}?`);

+ 14 - 3
src/model.d.ts

@@ -1,15 +1,26 @@
 declare const enum ChatType {
   Private = 'private',
   Group = 'group',
-  Discuss = 'discuss',
   Temp = 'temp'
 }
 
-interface IChat {
+interface IPrivateChat {
   chatID: number,
-  chatType: ChatType,
+  chatType: ChatType.Private,
 }
 
+interface IGroupChat {
+  chatID: number,
+  chatType: ChatType.Group,
+}
+
+interface ITempChat {
+  chatID: {qq: number, group: number},
+  chatType: ChatType.Temp,
+}
+
+type IChat = IPrivateChat | IGroupChat | ITempChat;
+
 interface ILock {
   workon: number,
   feed: string[],

+ 1 - 0
src/webshot.ts

@@ -262,6 +262,7 @@ extends CallableInstance<
         method: 'get',
         url,
         responseType: 'arraybuffer',
+        timeout: 150000,
       }).then(res => {
         if (res.status === 200) {
             logger.info(`successfully fetched ${url}`);