七夕將至,20行js代碼給女朋友作個卡通P圖微信機器人

七夕將至,又到了各位程序猿給女朋友,老婆送禮物的節日。今年老婆規定了,不能花費太多錢,還禁止買淘寶直男禮物。真的太難了😿,想破頭皮也不知道送啥好,頭髮卻已經掉了一縷又一縷,什麼代碼綻開煙花,照片牆,哄老婆的機器人都作過了。此次怎麼辦呢,又不讓花錢,又要有想法,看來只能祭起個人大殺器,碼代碼過七夕了。看到老婆以前喜歡玩抖音,P照片。還常常會用到人臉卡通化,人臉年齡變化,人臉性別變化的特效。那我就想,何不作一個微信機器人,你發照片我幫你自動生成特效,不用任何APP就能實現,還能讓老婆拉閨蜜建個微信羣一塊玩。javascript

想好了就開幹,以前寫過一個《三步教你用Node作一個微信哄女朋友(基友)神器》,因此此次再寫一個機器人也不算太難,只是要提早找好相應的圖片生成接口才行,通過一番資料查找,發現騰訊雲有我的臉變換的功能,通過測試後,發現就是我想要的功能,並且效果還不錯,關鍵是每月有 1000 次的免費額度,這就很香了。三種轉換模式就是 3000 次,白嫖不香麼 😏,白嫖騰訊這就更香了,哈哈java

功能介紹

本次實現的主要功能是發送照片,根據選擇生成對應的特效。微信機器人的主要實現用的仍是Wechaty,協議是基於免費版web協議的,因此不用擔憂沒有Wechaty的付費token,若是說你的微信無法登錄網頁版微信,不要緊wechaty-puppet-wechat協議是基於 UOS 桌面版的,新帳號也能夠用。node

已實現功能:python

私聊和羣內均可以實現照片特效實現git

  • 多輪交互式對話實現
    • 人臉照片動漫化
    • 人臉年齡變化
    • 人臉性別轉換

效果展現

picall.png

提早準備騰訊雲帳號

開通照片轉換功能

登陸騰訊雲帳號,沒有就直接 QQ 登陸,直接點擊管理控制檯開通便可,不用付費,也不用選資源包,開通後自動有每月 1000 次的免費額度,若是本身和朋友玩徹底足夠了。若是你是想活躍社羣或者土豪,就隨便充值了github

tencent.png

獲取騰訊的 secretid 和 secretkey

訪問此頁面https://console.cloud.tencent.com/cam/capi獲取你的secretidsecretkey,配置插件的時候須要用的到web

使用步驟

一、初始化項目

node環境須要本身配置一下,node>=14,。新建一個文件夾face-carton,在文件夾內部執行npm init,一路回車便可docker

二、安裝頭像轉化插件和 Wechaty

這裏說明一下,頭像轉化插件wechaty-face-carton就是我此次作的主要功能,已經開源在github,因爲已經發布到npm,因此這裏你只須要安裝就可使用了,對於不關心代碼的童鞋,直接安裝使用就好了。若是想知道代碼怎麼實現的,能夠到github倉庫查看一下源碼。對於源碼的實現,文後我也會放一部分核心代碼進行說明。npm

配置 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官網

三、主要代碼(不超過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 '卡通' 私聊發送消息,觸發照片卡通化提示。若是直接發送圖片,默認進入圖片卡通化功能,不填則當用戶初次發送文字消息時不作任何處理,建議填寫觸發關鍵詞

四、運行項目

node index.js

掃碼登陸後,給小助手發送圖片,便可轉化圖片,對於不能轉化的圖片,小助手會給出緣由

docker運行

一、新建Dockerfile

若是遇到過多的環境問題讓你很是苦惱,你也能夠在以上第三步完成後,根目錄新建一個Dockerfile文件,裏面填入內容,對!就一行就行!

FROM wechaty/onbuild

二、build鏡像

完成後就能夠直接build鏡像

docker build -t wechaty-carton .

三、運行鏡像

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

注意

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

問題與交流

若有使用問題能夠直接加小助手,回覆卡通,進微信羣交流,若是

歷史文章

本文由博客一文多發平臺 OpenWrite 發佈!

相關文章
相關標籤/搜索