乐趣区

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

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

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

性能介绍

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

已实现性能:

私聊和群内都能够实现照片特效实现

  • 多轮交互式对话实现

    • 人脸照片动漫化
    • 人脸年龄变动
    • 人脸性别转换

成果展现

提前准备腾讯云账号

开明照片转换性能

登录腾讯云账号,没有就间接 QQ 登录,间接点击治理控制台开明即可,不必付费,也不必选资源包,开明后主动有每个月 1000 次的收费额度,如果本人和敌人玩齐全足够了。如果你是想沉闷社群或者土豪,就轻易充值了

获取腾讯的 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为一些公共的解决办法,包含群发音讯,私聊音讯的公共办法抽取。

音讯监听

音讯监听很简略,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,
}

留神

要留神一下,不要把额度用超了,用超了就只能下个月能力玩了。

问题与交换

如有应用问题能够间接加小助手,回复 卡通,进微信群交换,如果

历史文章

  • Wechaty-web-panel 可视化插件
  • 三步教你用 Node 做一个微信脱单神器,小白可上手
  • 应用 UOS 微信桌面版协定登录,wechaty 免费版 web 协定又能够用了
退出移动版