美文网首页
七夕将至,20行js代码给女友做个卡通P图微信机器人

七夕将至,20行js代码给女友做个卡通P图微信机器人

作者: 小K前端 | 来源:发表于2021-08-13 10:51 被阅读0次

    七夕将至,又到了各位程序猿给女友,老婆送礼物的节日。今年老婆规定了,不能花费太多钱,还禁止买淘宝直男礼物。真的太难了😿,想破头皮也不知道送啥好,头发却已经掉了一缕又一缕,什么代码绽放烟花,照片墙,哄老婆的机器人都做过了。这次怎么办呢,又不让花钱,又要有想法,看来只能祭起我的大杀器,码代码过七夕了。看到老婆之前喜欢玩抖音,P照片。还经常会用到人脸卡通化,人脸年龄变化,人脸性别变化的特效。那我就想,何不做一个微信机器人,你发照片我帮你自动生成特效,不用任何APP就能实现,还能让老婆拉闺蜜建个微信群一块玩。

    想好了就开干,之前写过一个《三步教你用Node做一个微信哄女友(基友)神器》,所以这次再写一个机器人也不算太难,只是要提前找好相应的图片生成接口才行,经过一番资料查找,发现腾讯云有个人脸变换的功能,经过测试后,发现就是我想要的功能,而且效果还不错,关键是每个月有 1000 次的免费额度,这就很香了。三种转换模式就是 3000 次,白嫖不香么 😏,白嫖腾讯这就更香了,哈哈

    功能介绍

    本次实现的主要功能是发送照片,根据选择生成对应的特效。微信机器人的主要实现用的还是Wechaty,协议是基于免费版web协议的,所以不用担心没有Wechaty的付费token,如果说你的微信没法登陆网页版微信,没关系wechaty-puppet-wechat协议是基于 UOS 桌面版的,新账号也可以用。

    已实现功能:

    私聊和群内都可以实现照片特效实现

    • 多轮交互式对话实现
      • 人脸照片动漫化
      • 人脸年龄变化
      • 人脸性别转换

    效果展示

    picall.png

    提前准备腾讯云账号

    开通照片转换功能

    登录腾讯云账号,没有就直接 QQ 登录,直接点击管理控制台开通即可,不用付费,也不用选资源包,开通后自动有每个月 1000 次的免费额度,如果自己和朋友玩完全足够了。如果你是想活跃社群或者土豪,就随便充值了

    tencent.png

    获取腾讯的 secretid 和 secretkey

    访问此页面https://console.cloud.tencent.com/cam/capi获取你的secretidsecretkey,配置插件的时候需要用的到

    使用步骤

    1、初始化项目

    node环境需要自己配置一下,node>=14,。新建一个文件夹face-carton,在文件夹内部执行npm init,一路回车即可

    2、安装头像转化插件和 Wechaty

    这里说明一下,头像转化插件wechaty-face-carton就是我这次做的主要功能,已经开源在github,由于已经发布到npm,所以这里你只需要安装就可以使用了,对于不关心代码的童鞋,直接安装使用就行了。如果想知道代码怎么实现的,可以到github仓库查看一下源码。对于源码的实现,文后我也会放一部分核心代码进行说明。

    配置 npm 源为淘宝源(重要,因为需要安装 chromium,不配置的话下载会失败或者速度很慢,因为这个玩意 140M 左右)

    npm config set registry https://registry.npm.taobao.org
    npm config set disturl https://npm.taobao.org/dist
    npm config set puppeteer_download_host https://npm.taobao.org/mirrors
    
    npm install wechaty wechaty-face-carton wechaty-puppet-wechat --save
    

    如果安装出现问题,建议删除node_modules后多试几次,对于其他环境问题可以参考:
    常见问题处理wechaty官网

    3、主要代码(不超过20行)

    目录下新建文件index.js

    const { Wechaty } = require('wechaty')
    const WechatyFaceCartonPlugin = require('wechaty-face-carton')
    const name = 'wechat-carton'
    const bot = new Wechaty({ name, puppet: 'wechaty-puppet-wechat' })
    bot
      .use(
        WechatyFaceCartonPlugin({
          maxuser: 20, // 支持最多多少人进行对话,建议不要设置太多,否则占用内存会增加
          secretId: '腾讯secretId', // 腾讯secretId
          secretKey: '腾讯secretKey', // 腾讯secretKey
          allowUser: ['Leo_chen'], // 允许哪些好友使用人像漫画化功能,为空[]代表所有人开启
          allowRoom: ['测试1'], // 允许哪些群使用人像漫画化功能,为空[]代表不开启任何一个群
          quickModel: true, // 快速体验模式 默认关闭 开启后可直接生成二维码扫描体验,如果自己代码有登录逻辑可以不配置此项
          tipsword: '卡通', // 私聊发送消息,触发照片卡通化提示 如果直接发送图片,默认进入图片卡通化功能,不填则当用户初次发送文字消息时不做任何处理
        })
      )
      .start()
      .catch((e) => console.error(e))
    

    参数说明

    参数名 必填 默认值 说明
    maxuser 20 支持最多多少人进行对话,建议不要设置太多,否则占用内存会增加
    secretId: '' 腾讯 secretId
    secretKey '' 腾讯 secretKey
    allowUser [] 允许哪些好友使用人像漫画化功能,为空[]代表所有人开启
    allowRoom [] 允许哪些群使用人像漫画化功能,为空[]代表不开启任何一个群
    quickModel false 快速体验模式 默认关闭 开启后可直接生成二维码扫描体验,如果自己代码有登录逻辑可以不配置此项,如果是单独使用此插件,建议开启
    tipsword '卡通' 私聊发送消息,触发照片卡通化提示。如果直接发送图片,默认进入图片卡通化功能,不填则当用户初次发送文字消息时不做任何处理,建议填写触发关键词

    4、运行项目

    node index.js
    

    扫码登录后,给小助手发送图片,即可转化图片,对于不能转化的图片,小助手会给出原因

    docker运行

    1、新建Dockerfile

    如果遇到过多的环境问题让你非常苦恼,你也可以在以上第三步完成后,根目录新建一个Dockerfile文件,里面填入内容,对!就一行就行!

    FROM wechaty/onbuild
    

    2、build镜像

    完成后就可以直接build镜像

    docker build -t wechaty-carton .
    

    3、运行镜像

    build完成后就可以直接run后扫码了

    docker run wechaty-carton
    

    插件核心代码解析

    插件源码地址:https://github.com/leochen-g/wechaty-face-carton,如果能帮你哄女朋友开心,麻烦给个star,小心心❤送给你 😏

    代码结构

    插件主入口为index.jsservice/tencent.js为调用腾讯云服务的主要方法,service/multiReply.js是多轮对话实现的核心,util/index.js为一些公共的处理方法,包括群发消息,私聊消息的公共方法抽取。

    image.png

    消息监听

    消息监听很简单,Wechaty暴露出message事件,只要根据消息类型进行过滤即可,对于本插件而言,图片消息是触发转化的关键

    
    const { contactSay, roomSay, delay } = require('./util/index')
    const { BotManage } = require('./service/multiReply')
    const Qrterminal = require('qrcode-terminal')
    let config = {}
    let BotRes = ''
    
    /**
     * 根据消息类型过滤私聊消息事件
     * @param {*} that bot实例
     * @param {*} msg 消息主体
     */
    async function dispatchFriendFilterByMsgType(that, msg) {
      try {
        const type = msg.type()
        const contact = msg.talker() // 发消息人
        const name = await contact.name()
        const isOfficial = contact.type() === that.Contact.Type.Official
        const id = await contact.id
        switch (type) {
           // 文字消息处理
          case that.Message.Type.Text:
            content = msg.text()
            if (!isOfficial) {
              console.log(`发消息人${name}:${content}`)
              if (content.trim()) {
                const multiReply = await BotRes.run(id, { type: 1, content })
                let replys = multiReply.replys
                let replyIndex = multiReply.replys_index
                await delay(1000)
                await contactSay(contact, replys[replyIndex])
              }
            }
            break
           // 图片消息处理 
          case that.Message.Type.Image:
            console.log(`发消息人${name}:发了一张图片`)
            // 判断是否配置了指定人开启转换
            if (!config.allowUser.length || config.allowUser.includes(name)) {
              const file = await msg.toFileBox()
              const base = await file.toDataURL()
              const multiReply = await BotRes.run(id, { type: 3, url: base })
              let replys = multiReply.replys
              let replyIndex = multiReply.replys_index
              await delay(1000)
              await contactSay(contact, replys[replyIndex])
            } else {
              console.log(`没有开启 ${name} 的人脸漫画化功能, 或者检查是否已经配置此人微信昵称`)
            }
            break
          default:
            break
        }
      } catch (error) {
        console.log('监听消息错误', error)
      }
    }
    
    /**
     * 根据消息类型过滤群消息事件
     * @param {*} that bot实例
     * @param {*} room room对象
     * @param {*} msg 消息主体
     */
    async function dispatchRoomFilterByMsgType(that, room, msg) {
      const contact = msg.talker() // 发消息人
      const contactName = contact.name()
      const roomName = await room.topic()
      const type = msg.type()
      const userName = await contact.name()
      const userSelfName = that.userSelf().name()
      const id = await contact.id
      switch (type) {
         // 文字消息处理
        case that.Message.Type.Text:
          content = msg.text()
          console.log(`群名: ${roomName} 发消息人: ${contactName} 内容: ${content}`)
          // 判断是否配置了指定群开启转换
          if (config.allowRoom.includes(roomName)) {
            const mentionSelf = content.includes(`@${userSelfName}`)
            if (mentionSelf) {
              content = content.replace(/@[^,,::\s@]+/g, '').trim()
              if (content) {
                const multiReply = await BotRes.run(id, { type: 1, content })
                let replys = multiReply.replys
                let replyIndex = multiReply.replys_index
                await delay(1000)
                await roomSay(room, contact, replys[replyIndex])
              }
            }
          }
          break
         // 图片消息处理 
        case that.Message.Type.Image:
          console.log(`群名: ${roomName} 发消息人: ${contactName} 发了一张图片`)
          // 判断是否配置了指定群开启转换
          if (config.allowRoom.includes(roomName)) {
            console.log(`匹配到群:${roomName}的人脸漫画化功能已开启,正在生成中...`)
            const file = await msg.toFileBox()
            const base = await file.toDataURL()
            const multiReply = await BotRes.run(id, { type: 3, url: base })
            let replys = multiReply.replys
            let replyIndex = multiReply.replys_index
            await delay(1000)
            await roomSay(room, contact, replys[replyIndex])
          } else {
            console.log('没有开通此群人脸漫画化功能')
          }
          break
        default:
          break
      }
    }
    
    /**
     * 消息事件监听
     * @param {*} msg
     * @returns
     */
    async function onMessage(msg) {
      try {
          
        if (!BotRes) {
          BotRes = new BotManage(config.maxuser, this, config)
        }
        const room = msg.room() // 是否为群消息
        const msgSelf = msg.self() // 是否自己发给自己的消息
        if (msgSelf) return
        // 根据不同消息类型进行消息的派发处理
        if (room) {
          dispatchRoomFilterByMsgType(this, room, msg)
        } else {
          dispatchFriendFilterByMsgType(this, msg)
        }
      } catch (e) {
        console.log('reply error', e)
      }
    }
    
    .....
    

    多轮对话核心代码

    对于多轮对话的实现,我是参考大佬@kevinfu1717的python版Wechaty的代码,把他python代码中的多轮对话的核心代码转换成了js版,具体实现逻辑呢,我就引用他的解释,一些对应js中的方法名我进行了修改。如果有对python实现有兴趣的可以访问https://github.com/kevinfu1717/multimediaChatbot

    service/multiReply.js文件

    1. multiReply中的MultiReply使用类似“简易工厂模式”。(熟悉工厂模式的筒子可以忽略本段)。每一个触发聊天的用户都会生成一个user_bot,用户的输入就好像工厂里面的原材料,经过BotManage分配到各个工序的工人(各个技能模块,如:卡通人脸生成、人脸年龄变化、人脸性别变化等)进行处理,最终组装好的产品给到用户。不同用户的输入就像不同的原材料,不断送进工厂处理,流水的bot铁打不变的BotManage,而每个user_bot装载的是整个聊天过程中的所有对话。以上纯属个人胡扯,工厂模式正规解释具体见:https://juejin.cn/post/6844903653774458888
    const { generateCarton } = require('./tencent')
    
    class MultiReply {
      constructor() {
        this.userName = ''
        this.startTime = 0 // 开始时间
        this.queryList = [] // 用户说的话
        this.replys = [] // 每次回复,回复用户的内容(列表)
        this.reply_index = 0 // 回复用户的话回复到第几部分
        this.step = 0 // 当前step
        this.stepRecord = [] // 经历过的step
        this.lastReply = {} // 最后回复的内容
        this.imageData = '' // 用户发送的图片
        this.model = 1 // 默认选择漫画模式
        this.age = 60 // 用户选择的年龄
        this.gender = 0 // 用户性别转换的模式
      }
      paramsInit() {
        this.startTime = 0 // 开始时间
        this.queryList = [] // 用户说的话
        this.replys = [] // 每次回复,回复用户的内容(列表)
        this.reply_index = 0 // 回复用户的话回复到第几部分
        this.step = 0 // 当前step
        this.stepRecord = [] // 经历过的step
        this.lastReply = {} // 最后回复的内容
        this.imageData = '' // 用户发送的图片
        this.model = 1 // 默认选择漫画模式
        this.age = 60 // 用户选择的年龄
        this.gender = 0 // 用户性别转换的模式
      }
    }
    
    class BotManage {
      constructor(maxuser, that, config) {
        this.Bot = that
        this.config = config
        this.userBotDict = {} // 存放所有对话的用户
        this.userTimeDict = {}
        this.maxuser = maxuser // 最大同时处理的用户数
        this.loopLimit = 4
        this.replyList = [
          { type: 1, content: '请选择你要转换的模式(发送序号):\n\n[1]、卡通化照片\n\n[2]、变换年龄\n\n[3]、变换性别\n\n' },
          { type: 1, content: '请输入你想要转换的年龄:请输入10~80的任意数字' },
          { type: 1, content: '请输入你想转换的性别(发送序号):\n\n[0]、男变女\n\n[1]、女变男\n\n' },
          { type: 1, content: '你输入的序号有误,请输入正确的序号' },
          { type: 1, content: '你输入的年龄有误,请输入10~80的任意数字' },
          { type: 1, content: '你选择的序号有误,请输入你想转换的性别(发送序号):\n\n[0]、男变女\n\n[1]、女变男\n\n' },
        ]
      }
      async creatBot(username, content) {
        console.log('bot process create')
        this.userBotDict[username] = new MultiReply()
        this.userBotDict[username].userName = username
        this.userBotDict[username].imageData = content.url
        return await this.updateBot(username, content)
      }
      // 更新对话
      async updateBot(username, content) {
        console.log(`更新{${username}}对话`)
        this.userTimeDict[username] = new Date().getTime()
        this.userBotDict[username].queryList.push(content)
        return await this.talk(username, content)
      }
      async talk(username, content) {
        // 防止进入死循环
        if (this.userBotDict[username].stepRecord.length >= this.loopLimit) {
          const arr = this.userBotDict[username].stepRecord.slice(-1 * this.loopLimit)
          console.log('ini', arr, this.userBotDict[username].stepRecord)
          console.log(
            'arr.reduce((x, y) => x * y) ',
            arr.reduce((x, y) => x * y)
          )
          console.log(
            'arr.reduce((x, y) => x * y) ',
            arr.reduce((x, y) => x * y)
          )
          const lastIndex = this.userBotDict[username].stepRecord.length - 1
          console.log('limit last', this.userBotDict[username].stepRecord.length, this.loopLimit)
          console.log('limit', this.userBotDict[username].stepRecord[this.userBotDict[username].stepRecord.length - 1] ** this.loopLimit)
          if (arr.reduce((x, y) => x * y) === this.userBotDict[username].stepRecord[this.userBotDict[username].stepRecord.length - 1] ** this.loopLimit) {
            this.userBotDict[username].step = 100
          }
        }
        // 对话结束
        if (this.userBotDict[username].step == 100) {
          this.userBotDict[username].paramsInit()
          this.userBotDict[username] = this.addReply(username, { type: 1, content: '你已经输入太多错误指令了,小图已经不知道怎么回答了,还是重新发送照片吧' })
          return this.userBotDict[username]
        }
        // 图片处理完毕后
        if (this.userBotDict[username].step == 101) {
          this.userBotDict[username].paramsInit()
          this.userBotDict[username] = this.addReply(username, { type: 1, content: '你的图片已经生成了,如果还想体验的话,请重新发送照片' })
          return this.userBotDict[username]
        }
        if (this.userBotDict[username].step == 0) {
          console.log('第一轮对话,让用户选择转换的内容')
          this.userBotDict[username].stepRecord.push(0)
          if (content.type === 3) {
            this.userBotDict[username].step += 1
            this.userBotDict[username] = this.addReply(username, this.replyList[0])
            return this.userBotDict[username]
          } else {
            if (this.config.tipsword && content.content.includes(this.config.tipsword)) {
              // 如果没有发图片,直接发文字,触发关键词
              return {
                replys: [{ type: 1, content: '想要体验人脸卡通化功能,请先发送带人脸的照片给我' }],
                replys_index: 0,
              }
            } else {
              // 如果没有发图片,直接发文字,没有触发关键词
              this.removeBot(username)
              return {
                replys: [{ type: 1, content: '' }],
                replys_index: 0,
              }
            }
          }
        } else if (this.userBotDict[username].step == 1) {
          console.log('第二轮对话,用户选择需要转换的模式')
          this.userBotDict[username].stepRecord.push(1)
          if (content.type === 1) {
            if (parseInt(content.content) === 1) {
              // 用户选择了漫画模式
              this.userBotDict[username].step = 101
              this.userBotDict[username].model = 1
              return await this.generateImage(username)
            } else if (parseInt(content.content) === 2) {
              // 用户选择了变换年龄模式
              this.userBotDict[username].step += 1
              this.userBotDict[username].model = 2
              this.userBotDict[username] = this.addReply(username, this.replyList[1])
              return this.userBotDict[username]
            } else if (parseInt(content.content) === 3) {
              // 用户选择了变换性别模式
              this.userBotDict[username].step += 1
              this.userBotDict[username].model = 3
              this.userBotDict[username] = this.addReply(username, this.replyList[2])
              return this.userBotDict[username]
            } else {
              // 输入模式错误提示
              this.userBotDict[username].step = 1
              this.userBotDict[username] = this.addReply(username, this.replyList[3])
              return this.userBotDict[username]
            }
          }
        } else if (this.userBotDict[username].step == 2) {
          console.log('第三轮对话,用户输入指定模式所需要的配置')
          this.userBotDict[username].stepRecord.push(2)
          if (content.type === 1) {
            if (this.userBotDict[username].model === 2) {
              // 用户选择了年龄变换模式
              if (parseInt(content.content) >= 10 && parseInt(content.content) <= 80) {
                this.userBotDict[username].step = 101
                this.userBotDict[username].age = content.content
                return await this.generateImage(username)
              } else {
                this.userBotDict[username].step = 2
                this.userBotDict[username] = this.addReply(username, this.replyList[4])
                return this.userBotDict[username]
              }
            } else if (this.userBotDict[username].model === 3) {
              // 用户选择了性别变换模式
              if (parseInt(content.content) === 0 || parseInt(content.content) === 1) {
                this.userBotDict[username].step = 101
                this.userBotDict[username].gender = parseInt(content.content)
                return await this.generateImage(username)
              } else {
                this.userBotDict[username].step = 2
                this.userBotDict[username] = this.addReply(username, this.replyList[5])
                return this.userBotDict[username]
              }
            }
          }
        }
      }
      addReply(username, replys) {
        this.userBotDict[username].replys.push(replys)
        this.userBotDict[username].replys_index = this.userBotDict[username].replys.length - 1
        return this.userBotDict[username]
      }
      removeBot(dictKey) {
        console.log('bot process remove', dictKey)
        delete this.userTimeDict[dictKey]
        delete this.userBotDict[dictKey]
      }
      getBotList() {
        return this.userBotDict
      }
      /**
       * 生成图片
       * @param {*} username 用户名
       * @returns
       */
      async generateImage(username) {
        const image = await generateCarton(this.config, this.userBotDict[username].imageData, { model: this.userBotDict[username].model, gender: this.userBotDict[username].gender, age: this.userBotDict[username].age })
        this.userBotDict[username] = this.addReply(username, image)
        return this.userBotDict[username]
      }
      getImage(username, content, step) {
        this.userBotDict[username].paramsInit()
        this.userBotDict[username].step = step
        if (content.type === 3) {
          this.userBotDict[username].imageData = content.url
        }
        let replys = { type: 1, content: '请选择你要转换的模式(发送序号):\n\n [1]、卡通化照片\n\n[2]、变换年龄\n\n[3]、变换性别\n\n' }
        this.userBotDict[username] = this.addReply(username, replys)
        return this.userBotDict[username]
      }
      // 对话入口
      async run(username, content) {
        if (content.type === 1) {
          if (!Object.keys(this.userTimeDict).includes(username)) {
            if (this.config.tipsword && content.content.includes(this.config.tipsword)) {
              // 如果没有发图片,直接发文字,触发关键词
              return {
                replys: [{ type: 1, content: '想要体验人脸卡通化功能,请先发送带人脸的照片给我' }],
                replys_index: 0,
              }
            } else {
              // 如果没有发图片,直接发文字,没有触发关键词
              return {
                replys: [{ type: 1, content: '' }],
                replys_index: 0,
              }
            }
          } else {
            // 如果对话环境中已存在,则更新对话内容
            console.log(`${username}用户正在对话环境中`)
            return this.updateBot(username, content)
          }
        } else if (content.type === 3) {
          if (Object.keys(this.userTimeDict).includes(username)) {
            console.log(`${username}用户正在对话环境中`)
            return this.getImage(username, content, 1)
          } else {
            if (this.userBotDict.length > this.maxuser) {
              const minNum = Math.min(...Object.values(this.userTimeDict))
              const earlyIndex = arr.indexOf(minNum)
              const earlyKey = Object.keys(this.userTimeDict)[earlyIndex]
              this.removeBot(earlyKey)
            }
            return await this.creatBot(username, content)
          }
        }
      }
    }
    
    module.exports = {
      BotManage,
    }
    

    util/index.js文件

    roomSay和contactSay会把multiReply中返回的对话内容,“翻译”成真正发给用户的内容。例如:是文本的直接发送,是图片的包装一下发送给用户。

    const { FileBox, UrlLink, MiniProgram } = require('wechaty')
    
    /**
     * 延时函数
     * @param {*} ms 毫秒
     */
    async function delay(ms) {
      return new Promise((resolve) => setTimeout(resolve, ms))
    }
    
    /**
     * 群回复
     * @param {*} contact
     * @param {*} msg
     * @param {*} isRoom
     * type 1 文字 2 图片url 3 图片base64 4 url链接 5 小程序  6 名片
     */
    async function roomSay(room, contact, msg) {
      try {
        if (msg.type === 1 && msg.content) {
          // 文字
          console.log('回复内容', msg.content)
          contact ? await room.say(msg.content, contact) : await room.say(msg.content)
        } else if (msg.type === 2 && msg.url) {
          // url文件
          let obj = FileBox.fromUrl(msg.url)
          console.log('回复内容', obj)
          contact ? await room.say('', contact) : ''
          await delay(500)
          await room.say(obj)
        } else if (msg.type === 3 && msg.url) {
          // bse64文件
          let obj = FileBox.fromDataURL(msg.url, 'room-avatar.jpg')
          contact ? await room.say('', contact) : ''
          await delay(500)
          await room.say(obj)
        } else if (msg.type === 4 && msg.url && msg.title && msg.description) {
          console.log('in url')
          let url = new UrlLink({
            description: msg.description,
            thumbnailUrl: msg.thumbUrl,
            title: msg.title,
            url: msg.url,
          })
          console.log(url)
          await room.say(url)
        } else if (msg.type === 5 && msg.appid && msg.title && msg.pagePath && msg.description && msg.thumbUrl && msg.thumbKey) {
          let miniProgram = new MiniProgram({
            appid: msg.appid,
            title: msg.title,
            pagePath: msg.pagePath,
            description: msg.description,
            thumbUrl: msg.thumbUrl,
            thumbKey: msg.thumbKey,
          })
          await room.say(miniProgram)
        }
      } catch (e) {
        console.log('群回复错误', e)
      }
    }
    
    /**
     * 私聊发送消息
     * @param contact
     * @param msg
     * @param isRoom
     *  type 1 文字 2 图片url 3 图片base64 4 url链接 5 小程序  6 名片
     */
    async function contactSay(contact, msg, isRoom = false) {
      try {
        if (msg.type === 1 && msg.content) {
          // 文字
          console.log('回复内容', msg.content)
          await contact.say(msg.content)
        } else if (msg.type === 2 && msg.url) {
          // url文件
          let obj = FileBox.fromUrl(msg.url)
          console.log('回复内容', obj)
          if (isRoom) {
            await contact.say(`@${contact.name()}`)
            await delay(500)
          }
          await contact.say(obj)
        } else if (msg.type === 3 && msg.url) {
          // bse64文件
          let obj = FileBox.fromDataURL(msg.url, 'user-avatar.jpg')
          await contact.say(obj)
        } else if (msg.type === 4 && msg.url && msg.title && msg.description && msg.thumbUrl) {
          let url = new UrlLink({
            description: msg.description,
            thumbnailUrl: msg.thumbUrl,
            title: msg.title,
            url: msg.url,
          })
          await contact.say(url)
        } else if (msg.type === 5 && msg.appid && msg.title && msg.pagePath && msg.description && msg.thumbUrl && msg.thumbKey) {
          let miniProgram = new MiniProgram({
            appid: msg.appid,
            title: msg.title,
            pagePath: msg.pagePath,
            description: msg.description,
            thumbUrl: msg.thumbUrl,
            thumbKey: msg.thumbKey,
          })
          await contact.say(miniProgram)
        }
      } catch (e) {
        console.log('私聊发送消息失败', msg, e)
      }
    }
    
    module.exports = {
      contactSay,
      roomSay,
      delay,
    }
    

    注意

    要注意一下,不要把额度用超了,用超了就只能下个月才能玩了。

    历史文章

    相关文章

      网友评论

          本文标题:七夕将至,20行js代码给女友做个卡通P图微信机器人

          本文链接:https://www.haomeiwen.com/subject/oyedbltx.html