1. 基础 API 教程概述

在使用 wechaty 相关 api 之前请大家先学习 wechaty 基础篇!!!

博主有话要说!!!

为什么要选择看博主的博文?因为 Wechaty 官方文档已经太久没有更新了,所以同学们学习 WeChaty 请使用博主的博客作为教程文档,博主都是自己研究测试整理出来的,所有的案例测试都是最新版。

基础篇:最新 2023 如何使用 wechaty 框架开发微信机器人详细教程(基础篇)

在这里插入图片描述

2. 图片二维码扫码登陆

配置微信机器人启动快捷脚本 npm run start

在 package.json 文件中,配置 "start": "node bot.js"

在这里插入图片描述

在 terminal 终端进行执行

PS C:\Users\Administrator\Desktop\HackerWaking\Wechaty-Project> npm run start

> wechaty-project@1.0.0 start
> node index.js

图片二维码扫码登陆

const { WechatyBuilder } = require('wechaty');
const qrcode = require('qrcode-terminal');

const bot = WechatyBuilder.build()

bot.on('scan', (code, status) => {
    qrcode.generate(code, { small: true });
})

3. 判断微信群消息

判断 message.room() 对象进行判断是否是群消息

bot.on('message', async message => {
    const room = message.room()
    if (room) {
        console.log(room)
        const talker = message.talker();
        talker.say("hello, i got your message")
    }
});

查询 message.room() 群名 topic 回复群消息 hello

bot.on('message', async message => {
    const room = message.room()
    if (room && await room.topic() == "testgroup") {
        room.say("hello")
    }
});

4. 接收图片消息保存

机器人 bot 监听消息判断是否是图片消息?

const fs = require('fs');

bot.on('message', async message => {
	if (message.type() == bot.Message.Type.Image) {
	    const fileBox = await message.toFileBox()
	    console.log(fileBox)
	    console.log("Message.Type.Image")
	    const ws = fs.createWriteStream(fileBox._name);  
	    ws.write(fileBox.buffer);  
	}
});

图片类型消息 fileBox 数据结构

FileBox {
	version: '1.5.5',
	_type: 4,
	_size: 115413,
	md5: undefined,
	mimeType: 'application/unknown',
	_mediaType: 'image/jpeg',
	_name: '6373249138066348739.jpg',
	_metadata: undefined,
	base64: undefined,
	remoteUrl: undefined,
	qrCode: undefined,
	uuid: undefined,
	buffer: <Buffer ff d8 ff e0 00 10 4a 46 49 46 00 01 01 00 00 01 00 01 00 00 ff db 00 43 00 0a 07 07 08 07 06 0a 08 08 08 0b 0a 0a 0b 0e 18 10 0e 0d 0d 0e 1d 15 16 11 ... 115363 more bytes>,
	localPath: undefined,
	stream: undefined,
	headers: undefined
}

bot.Message.Type 消息类型

MessageType {
	Unknown = 0,
	
	Attachment  = 1,    ### Attach(6),
	Audio       = 2,    ### Audio(1), Voice(34)
	Contact     = 3,    ### ShareCard(42)
	ChatHistory = 4,    ### ChatHistory(19)
	Emoticon    = 5,    ### Sticker: Emoticon(15), Emoticon(47)
	Image       = 6,    ### Img(2), Image(3)
	Text        = 7,    ### Text(1)
	Location    = 8,    ### Location(48)
	MiniProgram = 9,    ### MiniProgram(33)
	GroupNote   = 10,   ### GroupNote(53)
	Transfer    = 11,   ### Transfers(2000)
	RedEnvelope = 12,   ### RedEnvelopes(2001)
	Recalled    = 13,   ### Recalled(10002)
	Url         = 14,   ### Url(5)
	Video       = 15,   ### Video(4), Video(43)
	Post        = 16,   ### Moment, Channel, Tweet, etc
}

5. 微信群发送图片

官方 Wechaty 发送案例展示

import { FileBox }  from 'wechaty'

const fileBox = FileBox.fromFile('C:\Users\Administrator\Desktop\image.png')
await bot.say(fileBox)

亲测存在报错!uncaughtException TypeError: Cannot read properties of undefined (reading ‘fromFile’)

可能是 wechaty 版本问题 >>> 解决方案:单独安装 file-box 模块包调用

npm i file-box

具体解决方案实现

const { FileBox } = require('file-box') 

const fileBox1 = FileBox.fromFile('C:\Users\Administrator\Desktop\image.png')
await bot.say(fileBox1)

const fileBox2 = FileBox.fromUrl('https://wechaty.js.org/img/icon.png')
await bot.say(fileBox2)
函数说明
FileBox.fromFile选择本地图片
FileBox.fromUrl选择网络图片

例如:检测关键词回复图片

const { WechatyBuilder } = require('wechaty');
const { FileBox } = require('file-box') 
const qrcode = require('qrcode-terminal');
const path = require('path')
const bot = WechatyBuilder.build()

bot.on('scan', (code, status) => { qrcode.generate(code, { small: true }); })
bot.on('login', user => console.log(`User ${user} logged in`))

bot.on('message', async message => {
    const room = message.room()
	if (room && message.payload.type != 7) {
	    return;
    } else {
        let msg = message.text()
        if (msg == "chicken") {
            await room.say("Chicken is too beautiful.")
        }
        if (msg == "ikun") {
            const fileBox = FileBox.fromFile(path.join(process.cwd(), "image.png"))
            await room.say(fileBox)
        }
    }
});

bot.start();

真机演示展示

在这里插入图片描述

6. 音频处理工具

在这里插入图片描述

下载地址:https://github.com/BtbN/FFmpeg-Builds/releases

ffmpeg 工具 bin 目录

在这里插入图片描述

配置 ffmpeg/bin 系统环境变量

在这里插入图片描述

7. 接收语音消息保存

机器人 bot 监听消息判断是否是语音消息?

const fs = require('fs');
bot.on('message', async message => {
	if (message.type() == bot.Message.Type.Audio) {
	    const fileBox = await message.toFileBox()
	    console.log(fileBox)
	    console.log("Message.Type.Audio")
	    const ws = fs.createWriteStream(fileBox._name);  
	    ws.write(fileBox.stream);  
	}
});

语音类型消息 fileBox 数据结构

FileBox {
	version: '1.5.5',
	_type: 6,
	_size: undefined,
	md5: undefined,
	mimeType: 'application/unknown',
	_mediaType: 'audio/silk',
	_name: 'message-6741646316917598047-audio.sil',
	_metadata: { voiceLength: 1231 },
	base64: undefined,
	remoteUrl: undefined,
	qrCode: undefined,
	uuid: undefined,
	buffer: undefined,
	localPath: undefined,
	stream: <Buffer ff f3 38 c4 00 0c 30 02 11 bd 41 18 00 00 38 44 2c 29 1a c0 7c 1f a8 13 07 cf a7 52 81 00 cf 83 fc 06 7d ff 97 3f fc bb df ff 58 3e 08 1c cb ff ac fe ... 2539 more bytes>,
	headers: undefined
}

接收保存类型:sil 文件

message-5316276040531231762-audio.sil

借助 ffmpeg 转换音频格式

const { exec } = require('child_process');

const silFilePath = 'message-5316276040531231762-audio.sil';
const mp3FilePath = 'message-5316276040531231762-audio.mp3';

const command = `ffmpeg -i ${silFilePath} ${mp3FilePath}`;

exec(command, (error, stdout, stderr) => {
    if (error) {
        console.error(`Error: ${error.message}`);
        return;
    }
    console.log('Conversion successful!');
});

8. 查询联系人信息

事件 ready 是指微信机器人登陆完成启动预备状态

bot.on('ready', () => {
	let contactName = ""
    bot.Contact.find(contactName).then(contact => {
        console.log(contact)
    }).catch(err => {
        console.log()
    })
})

联系人信息结构体

WechatifiedContactSelfImpl {
    _events: [Object: null prototype] {},
    _eventsCount: 0,
    _maxListeners: undefined,
    id: '@80d60ed0fb7633cb0069de96f78718df80a0148618f1981b7fe3869f145a82d5',
    payload: {
	    address: undefined,
	    alias: '',
	    avatar: '/cgi-bin/mmwebwx-bin/webwxgeticon?seq=1945142728&username=@80d60ed0fb7633cb0069de96f78718df80a0148618f1981b7fe3869f145a82d5&skey=@crypt_13d84e54_07d2f691525dadf043c467d1a3f4b23f',
	    city: undefined,
	    friend: false,
	    gender: 1,
	    id: '@80d60ed0fb7633cb0069de96f78718df80a0148618f1981b7fe3869f145a82d5',
	    name: '一语中的',
	    phone: [],
	    province: undefined,
	    signature: '唤醒手腕',
	    star: false,
	    weixin: undefined,
	    type: 1
    },
    [Symbol(kCapture)]: false
}

查询联系人并且发送消息

bot.on('ready', () => {
    let contactName = ""
    bot.Contact.find(contactName).then(contact => {
    	contact.say("hello")
    }).catch(err => {
        console.log()
    })
})

9. ChatGPT3.5 应用

特别注意:调用 chatGPT 3.5 需要注册获取 OPENAI_API_KEY

环境变量配置文件 .env 存储 OPENAI_API_KEY

OPENAI_API_KEY=【你的OPENAI_API_KEY

配置 Openai 接口调用工具 openai.js

const dotenv = require('dotenv')
const OpenAI = require('openai')

dotenv.config()

const openai = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY
});

const getOpenAIResponse = async (input) => {
    const chatCompletion = await openai.chat.completions.create({
        messages: [{ role: 'user', content: input }],
        model: 'gpt-3.5-turbo',
    });
    return chatCompletion.choices[0].message.content
}

module.exports = {
    getOpenAIResponse
}

配置需要开通服务的群列表 config.json

{ "group": [ "testgroup", "helloworld" ] }

微信机器人启动 bot.js

const { WechatyBuilder } = require('wechaty');
const qrcode = require('qrcode-terminal');
const { getOpenAIResponse } = require('./openai.js')
const fs = require('fs');

const data = fs.readFileSync('config.json', 'utf8');
const config = JSON.parse(data);

const bot = WechatyBuilder.build()

bot.on('scan', (code, status) => {
    qrcode.generate(code, { small: true });
})

bot.on('login', user => console.log(`User ${user} logged in`))

bot.on('message', async message => {
    const room = message.room()
    if (room && config.group.includes(await room.topic())) {
        if (message.payload.type != 7) {
            room.say("Sorry, please input text.");
            return;
        } else {
            room.say(await getOpenAIResponse(message.text()))
        }
    }
});

bot.start();

package.json 依赖模块版本

"dependencies": {
    "openai": "^4.16.1",
    "qrcode-terminal": "^0.12.0",
    "wechaty": "^1.20.2"
}

案例视频展示

在这里插入图片描述

10. 开通 Pad-local 协议

默认免费 WEB 协议是不支持发送语音权限(只具有接收语音消息的权限)IPAD 协议支持

在这里插入图片描述

推荐开通 pad-local 协议(链接地址):http://pad-local.com/

TOKEN 购买完成

在这里插入图片描述

11. 创建 puppet 登陆

PuppetPadlocal 创建机器人

const { PuppetPadlocal } = require("wechaty-puppet-padlocal");
const { WechatyBuilder } = require('wechaty');

const puppet = new PuppetPadlocal({
    token: "puppet_padlocal_xxxxxxxxxxxx"
});

const bot = WechatyBuilder.build({
    name: "wristwaking",
    puppet
})

详细官方案例教程文档介绍:https://github.com/wechaty/puppet-padlocal/wiki/API-%E4%BD%BF%E7%94%A8%E6%96%87%E6%A1%A3-(TypeScript-JavaScript)

手机端使用微信扫描登陆显示(登陆设备 iPad)
在这里插入图片描述
Node 控制台登陆扫描输出

Welcome to Wechaty PadLocal puppet!
puppet-padlocal version: 1.20.1
adlocal-ts-client version: 0.5.3
13:26:41 INFO [PuppetPadlocal] start login with type: AutoLogin
User Contact<一语中的> logged in

注意:通过 PadLocal 协议登陆具有自动登陆功能(扫码完二次登陆无需进行扫码)

INFO TestBot started 问题

如果运行 npm run bot.js 后,正常情况会提示扫码登录。如果一直卡在 INFO TestBot started,二维码没有出现。解决方案:检测本地网络是否能够正常上网?检测是否开启了全局的 VPN?检测是否设置了网络防火墙?

网络测试:测试和 PadLocal 服务器之间网络是否通畅?

ping gateway.pad-local.com

网络测试:测试和微信服务器之间网络是否通畅?

$ ping long.weixin.qq.com
$ ping short.weixin.qq.com

12. ffmpeg 音频工具

在这里插入图片描述

下载地址:https://github.com/BtbN/FFmpeg-Builds/releases

ffmpeg 工具 bin 目录

在这里插入图片描述

配置 ffmpeg/bin 系统环境变量

在这里插入图片描述

13. 被动回复语音消息

借助 vhan 开源接口创建 MP3 语音音频

const fs = require('fs')
const axios = require('axios')
const path = require('path')

let content = "唤醒手腕我喜欢你"

axios({
    method: 'get',
    url: `https://api.vvhan.com/api/song?txt=${content}`,
    responseType: 'stream',
})
.then(async res => {
    await res.data.pipe(fs.createWriteStream(path.join(__dirname, 'content.mp3')))
})
.catch(err => {
    console.log(err);
});

借助 ffmpeg 工具 和 silk_v3_encoder 工具 进行音频转换处理 sil 语音

在这里插入图片描述

GitHub silk-v3-decoder 工具资源地址:https://github.com/kn007/silk-v3-decoder

const { execSync } = require('child_process');

const mp3FilePath = 'content.mp3';
const silFilePath = 'output.sil';

execSync(`ffmpeg -i "${mp3FilePath}" -f s16le -ar 24000 -ac 1 -acodec pcm_s16le "temp.pcm"`);
execSync(`silk_v3_encoder "temp.pcm" "${silFilePath}" -tencent`)

语音文件为 silk 格式。silk 是 skype 开源的一款语音编解码器,被微信的语音文件所采用。注意:文件后缀必须是 sil

bot.on('message', async message => {
    if (message.type() == bot.Message.Type.Audio) {
        const voiceFilePath = "output.sil"
        const voiceLength = 2000;
        const fileBox = FileBox.fromFile(voiceFilePath);
        fileBox.metadata = {
            voiceLength
        };
        await message.say(fileBox)
    }
});

特别注意:被动回复语音消息 WEB 协议不支持(推荐采用 iPad 协议实现)

运行演示视频

在这里插入图片描述

14. 发送动态链接

bot.on('message', async message => {
    if (message.text() == "发送链接") {
        const urlLink = new bot.UrlLink({
            title: "Hello World! 你好世界!",
            description: "This is description",
            thumbnailUrl: "http://q1.qlogo.cn/g?b=qq&nk=1620444902&s=100",
            url: "https://www.baidu.com",
        });
        await message.say(urlLink)
    }
});

动态链接配置说明

parmas说明案例
title标题Hello World! 你好世界!
description详细描述This is description
thumbnailUrl预览图http://q1.qlogo.cn/g?b=qq&nk=1620444902&s=100 (图片链接地址)
url链接地址https://www.baidu.com(链接)

发送动态链接测试结果:

在这里插入图片描述

15. 自动通过加好友请求

bot.on("friendship", async friendship => {
	console.log(friendship)
    if (friendship.type() === FriendshipType.Receive) {
        await friendship.accept();
    }
});

WechatifiedFriendshipImpl 数据结构

WechatifiedFriendshipImpl {
	_events: [Object: null prototype] {},
	_eventsCount: 0,
	_maxListeners: undefined,
	id: '6163475632946471959',
	payload: {
		contactId: 'wxid_njqktrcgap7822',
		hello: '我是子夜吴歌',
		id: '6163475632946471959',
		scene: 15,
		shareCardContactId: '',
		shareCardNickName: '',
		sourceContactId: '',
		sourceNickName: '',
		stranger: 'v3_020b3826fd0301000000000095b7279086b4b4000000501ea9a3dba12f95f6b60a0536a1adb6bca1de246eb2c79828d52b3f6f6fdbb0dcdf144ece9d5ef453bc87350909b183e109f57083da037301a1a492156c38faaa1792d953a18405e5034f1876@stranger',       
		ticket: 'v4_000b708f0b0400000100000000003ce338ab64bfba5b2eab48d683651000000050ded0b020927e3c97896a09d47e6e9e44ac1aeaa3585259ed6edc2f83825b3d166d50e27fb7b44161a4427e52d47e03cb9920bf7ad004fed00c4754150d75b4969a5ffefc1e5e6ebe3612f3f52a147c7ffc2634859836c1586dcf74d0bdeac97c5b6b9224a6cee2acf97a3705a88ababfb21dfb2f0bddbc75@stranger',
		timestamp: 1703138888,
		type: 2
	},
	[Symbol(kCapture)]: false
}

监听自动通过好友结果:

在这里插入图片描述
控制台输出结果

自动通过验证

16. 读取全部群信息

我们可以通过 bot.Room.findAll() 函数,进行获取当前账号所有群组信息。

bot.on('ready', async () => {
    const roomList = await bot.Room.findAll();
    console.log(`Total ${roomList.length} rooms found`);
})

测试结果:

Total 29 rooms found

特别注意:如果我们想要全部群信息,必须要等到微信机器人准备完成之后才能进行读取,否则结果可能为空。就是您必须在 ready 事件触发时候进行读取,在 login / scan 事件读取的话,群组数据将读取失败。

Room Impl 接口实现

WechatifiedRoomImpl {
	_events: [Object: null prototype] {},
	_eventsCount: 0,
	_maxListeners: undefined,
	id: '@@af95d96d3c31bc8999708594b48c5f4d79660cc48285a17706e85be3b92e7b2a',
	payload: {
		adminIdList: [],
		avatar: '/cgi-bin/mmwebwx-bin/webwxgetheadimg?seq=0&username=@@af95d96d3c31bc8999708594b48c5f4d79660cc48285a17706e85be3b92e7b2a&skey=@crypt_13d84e54_8d83375971336cd46842d5667ee67649',
		id: '@@af95d96d3c31bc8999708594b48c5f4d79660cc48285a17706e85be3b92e7b2a',
		memberIdList: [
			'@a01b4c748882b1f35b685428cded4ea6386437d974e348240d644c61c883f855',
			'@0209e75ea4788f781e241a1a2ac7936563a90d11f6c0dcc3f067035cb6d5dd91'
		],
		topic: '雨落晴天, 一语中的'
	},
	[Symbol(kCapture)]: false
}

通过 Room 对象发送群消息

Promise <void> say(textOrContactOrFileOrUrl, …mentionList) 在群内发消息,如果设置了 …mentionList 参数,机器人在群内发送消息的时候还会@这些联系人。

room.say("hello, good night!")

艾特群主进行发送消息

注意:一般来说群主是群成员列表中的第一个(通常不是最准确的方法)要注意艾特功能(即“@”某人)在微信的 Web 和 API 版本中可能有一些限制和特定的实现方式。

bot.on('ready', async () => {
    console.log("机器人开始运行!!!")
    let roomList = await bot.Room.findAll()
    for (let index = 0; index < roomList.length; index++) {
        let room = roomList[index];
        let members = await room.memberAll()
        let groupTopic = await room.topic()
        console.log("群名称:", groupTopic, "群主名称:", members[0].payload.name)
        if (groupTopic == "唤醒手腕测试群") {
            room.say(`@${members[0].payload.name} 你在吗?`)
        }
    }})
})

17. 保存联系人头像

想要保存指定联系人头像的信息,首先根据联系人的昵称进行查找联系人对象。bot.Contact.find 方法返回一个 Promise,该 Promise 解析为一个 Contact 对象(如果找到)或 null(如果未找到)。

注意:bot.Contact.find 方法返回的是单个联系人对象。如果你想要根据某些条件查找多个联系人,你可能需要使用 bot.Contact.findAll 方法,它返回的是一个联系人对象的数组。

bot.on('ready', async () => {
    console.log("机器人开始运行!!!")
    const contact = await bot.Contact.find({ name: "番茄土豆" });
    console.log(contact)

    fileBox = await contact.avatar()
    const filePath = './番茄土豆的头像.jpg';
    fs.writeFile(filePath, fileBox.buffer, 'binary', (err) => {
        if (err) {
            console.error(err);
        } else {
            console.log("save success!!!");
        }
    });
    await contact.say("我已经保存了您的头像!!!")
})
Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐