1
头图

Qixi Festival is approaching, and it's the festival when programmers give gifts to their girlfriends and wives. This year, the wife stipulated that she should not spend too much money, and it is also forbidden to buy gifts from Taobao straight men. It’s really hard 😿, I don’t know what to give if I want to get a scalp, but my hair has fallen out one after another, what kind of code is blooming with fireworks, the photo wall, and the robot coaxing my wife have done it. What should I do this time? I don't want to spend money and I have to have ideas. It seems that I can only sacrifice my big killer, and the code is over Qixi Festival. Before I saw my wife, I liked to play Douyin and P photos. It also often uses special effects such as face cartoonization, face age change, and face gender change. Then I thought, why not make a WeChat robot, you can send photos and I will automatically generate special effects for you, which can be achieved without any APP, and let my wife and girlfriends build a WeChat group to play together.

written a 16115e044243bd "Three Steps to Use Node to Make a WeChat Girlfriend (Friends) Artifact" , so it’s not too difficult to write another robot this time, just find it in advance. The corresponding image generation interface is good. After searching for information, I found that Tencent Cloud has the function of personal face transformation. After testing, I found that it is the function I want, and the effect is not bad. The key is that there are 1000 times a month. Free quota, which is very fragrant. The three conversion modes are 3000 times. Isn’t white prostitutes fragrant?, white prostitution is even more fragrant, haha

Features

The main function implemented this time is to send photos and generate corresponding special effects according to the selection. The main implementation of the robot is still 16115e0442441a Wechaty . The protocol is based on the free version of the web protocol, so don’t worry about not having Wechaty’s paid token. If your WeChat can’t log on to the web version of WeChat, it doesn’t matter that the wechaty-puppet-wechat protocol is based on the UOS desktop version. Yes, the new account can also be used.

Realized functions:

Photo effects can be realized in private chat and in groups

  • Multi-round interactive dialogue implementation

    • Anime face photo
    • Face age change
    • Face gender conversion

Show results

picall.png

Prepare Tencent Cloud account in advance

Open photo conversion function

Log in to the Tencent Cloud account. If you don’t have one, you can log in directly with QQ, and you can just click on the management console to activate. There is no need to pay or select resource packs. After activation, you will automatically have a free quota of 1000 times per month. If you play completely with your friends enough. If you want to activate the community or local tyrants, just recharge

tencent.png

Get Tencent's secretid and secretkey

Visit this page https://console.cloud.tencent.com/cam/capi get your secretid and secretkey , you need to configure the plug-in

Steps for usage

1. Initialize the project

The node environment needs to be configured by yourself, node>=14. Create a new folder face-carton , execute npm init inside the folder, and press Enter all the way

2. Install avatar conversion plug-in and Wechaty

Here to explain, the avatar conversion plug-in wechaty-face-carton is the main function I made this time. It has been open sourced at github . Since it has been released to npm, you only need to install it here. For children's shoes who don’t care about the code, install it directly. Just use it. If you want to know how the code is implemented, you can check the source code in the github repository. For the realization of the source code, I will also put a part of the core code for explanation after the article.

Configure the npm as Taobao source (important, because you need to install chromium , if you don’t configure it, the download will fail or the speed will be very slow, because this thing is about 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

If there is a problem with the installation, it is recommended to delete node_modules and try several times. For other environmental problems, you can refer to:
FAQ and wechaty official website

3. Main code (no more than 20 lines)

Create a new file 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))

Parameter Description

parameter nameRequiredDefaultsinstruction
maxuserno20Supports the maximum number of people to have a conversation, it is recommended not to set too many, otherwise the memory usage will increase
secretId:Yes''Tencent secretId
secretKeyYes''Tencent secretKey
allowUserno[]Which friends are allowed to use the portrait caricature function, leave it blank [] on behalf of everyone
allowRoomno[]Which groups are allowed to use the portrait caricature function, blank [] means not to open any group
quickModelnofalseAfter the quick experience mode is turned off by default, you can directly generate a QR code scanning experience. If your code has login logic, you don’t need to configure this option. If you use this plug-in alone, it is recommended to turn it on
tipswordno'Cartoon'Send a message in private chat, trigger the photo cartoon prompt. If you send the picture directly, the picture cartoonization function is entered by default. If you don’t fill it, no processing will be done when the user sends a text message for the first time. It is recommended to fill in the trigger keyword

4. Run the project

node index.js

After scanning the code to log in, send the picture to the assistant to convert the picture. For the picture that cannot be converted, the assistant will give the reason

docker run

1. Create a new Dockerfile

If you encounter too many environmental problems that make you very distressed, you can also create a Dockerfile root directory after the third step above is completed, and fill in the content, yes! Just one line!

FROM wechaty/onbuild

2. Build mirror

After completion, you can build the image directly

docker build -t wechaty-carton .

3. Run the mirror

After the build is completed, you can scan the code directly after running

docker run wechaty-carton

Plug-in core code analysis

Plug-in source code address: https://github.com/leochen-g/wechaty-face-carton , if it can help you make your girlfriend happy, please give a star, be careful ❤ give it to you😏

Code structure

The main entrance of the plug-in is index.js , service/tencent.js is the main method for invoking Tencent cloud services, service/multiReply.js is the core of the realization of multi-round dialogue, util/index.js is some public processing methods, including group message, private chat message extraction of public methods.

image.png

Message monitoring

Message monitoring is very simple. Wechaty exposes the message event. Just filter according to the message type. For this plugin, image messages are the key to triggering the conversion.


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)
  }
}

.....

Multi-round dialogue core code

For the realization of multiple rounds of dialogue, I refer to @kevinfu1717 , and convert the core code of the multiple rounds of dialogue in his python code to the js version. For the specific implementation logic, I will quote him For the explanation, some corresponding method names in js have been modified by me. If you are interested in python implementation, you can visit https://github.com/kevinfu1717/multimediaChatbot

service/multiReply.js file

  1. MultiReply in multiReply uses a similar "simple factory mode". (Those who are familiar with the factory model can ignore this paragraph). Each user who triggers the chat will generate a user_bot. The user's input is like raw materials in the factory, which are assigned to workers in various processes through BotManage (various skill modules, such as: cartoon face generation, face age change, face gender change Etc.) for processing, and the final assembled product is given to the user. The input of different users is like different raw materials, which are continuously sent to the factory for processing. The flowing bot irons the unchanging BotManage, and each user_bot loads all the conversations in the entire chat process. The above is purely personal nonsense. For a formal explanation of the factory model, refer to: 16115e04424a85 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 file

roomSay and contactSay will "translate" the conversation content returned in the multiReply into the content that is actually sent to the user. For example: It is the direct sending of the text, the package of the picture is sent to the user.

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,
}

Notice

Be careful not to overuse the quota. If you overuse it, you can only play next month.

Questions and exchanges

If you have any questions, you can directly add a small assistant, reply to the cartoon, and enter the WeChat group to communicate, if

Historical articles

kiven
33 声望3 粉丝

用跑步来释放自己的天性,跑起来,动起来,爱上跑步是我今年最大的收获。前端开发是目前的工作,下一步往着全栈工程师进发!