Files
2026-01-12 18:01:04 +08:00

287 lines
10 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 企业微信 API 客户端
*/
import { WeChatError } from './common/wechat-error.js';
/**
* 企业微信API客户端
*/
export class WeChatClient {
config;
accessToken;
tokenExpireTime;
constructor(config) {
this.config = {
baseUrl: process.env.WEIXIN_CORP_URL ?? 'https://qyapi.weixin.qq.com',
...config,
};
}
/**
* 获取access_token
* @returns access_token
*/
async getAccessToken() {
// 检查token是否有效
if (this.accessToken && this.tokenExpireTime && Date.now() < this.tokenExpireTime) {
return this.accessToken;
}
const url = new URL('/cgi-bin/gettoken', this.config.baseUrl);
url.searchParams.set('corpid', this.config.corpId);
url.searchParams.set('corpsecret', this.config.corpSecret);
const response = await fetch(url);
const data = await response.json();
if (data.errcode !== 0 || !data.access_token) {
throw new Error(`获取access_token失败: ${data.errmsg}`);
}
this.accessToken = data.access_token;
// 提前5分钟过期,避免临界点问题
this.tokenExpireTime = Date.now() + (data.expires_in || 7200) * 1000 - 5 * 60 * 1000;
return this.accessToken;
}
/**
* 获取客户详情
* @param externalUserId 外部联系人的userid
* @param cursor 上次请求返回的next_cursor,用于分页
* @returns 客户详情
*/
async getExternalContact(externalUserId, cursor) {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/get', this.config.baseUrl);
url.searchParams.set('access_token', token);
url.searchParams.set('external_userid', externalUserId);
if (cursor) {
url.searchParams.set('cursor', cursor);
}
const response = await fetch(url);
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 获取客户详情(支持分页,自动获取所有跟进人)
* @param externalUserId 外部联系人的userid
* @returns 客户详情(包含所有跟进人)
*/
async getExternalContactFull(externalUserId) {
let result = null;
let cursor;
do {
const data = await this.getExternalContact(externalUserId, cursor);
if (!result) {
// 第一次请求,直接使用返回的数据
result = data;
}
else {
// 后续请求,合并follow_user列表
result.follow_user.push(...data.follow_user);
// 更新next_cursor
if (data.next_cursor !== undefined) {
result.next_cursor = data.next_cursor;
}
}
cursor = data.next_cursor;
} while (cursor);
return result;
}
/**
* 获取配置了客户联系功能的成员列表
* @returns 配置了客户联系功能的成员userid列表
*/
async getFollowUserList() {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/get_follow_user_list', this.config.baseUrl);
url.searchParams.set('access_token', token);
const response = await fetch(url);
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 获取客户列表
* @param userId 企业成员的userid
* @returns 外部联系人的userid列表
*/
async getExternalContactList(userId) {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/list', this.config.baseUrl);
url.searchParams.set('access_token', token);
url.searchParams.set('userid', userId);
const response = await fetch(url);
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 批量获取客户详情
* @param userId 企业成员的userid
* @param cursor 上次请求返回的next_cursor,用于分页
* @param limit 返回的最大记录数,整型,最大值1000,默认值为100
* @returns 客户详情列表
*/
async batchGetExternalContact(userId, cursor, limit = 100) {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/batch/get_by_user', this.config.baseUrl);
url.searchParams.set('access_token', token);
url.searchParams.set('userid', userId);
if (cursor) {
url.searchParams.set('cursor', cursor);
}
url.searchParams.set('limit', limit.toString());
const response = await fetch(url);
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 批量获取客户详情(自动分页,获取所有客户)
* @param userId 企业成员的userid
* @returns 所有客户详情列表
*/
async batchGetExternalContactFull(userId) {
const allContacts = [];
let cursor;
do {
const data = await this.batchGetExternalContact(userId, cursor);
if (data.external_contact_list) {
allContacts.push(...data.external_contact_list);
}
cursor = data.next_cursor;
} while (cursor);
return allContacts;
}
/**
* 添加「联系我」方式
* @param params 联系方式配置参数
* @returns 联系方式配置信息(包含config_id和qr_code)
*/
async addContactWay(params) {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/add_contact_way', this.config.baseUrl);
url.searchParams.set('access_token', token);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 获取「联系我」方式
* @param params 查询参数(包含config_id)
* @returns 联系方式配置信息
*/
async getContactWay(params) {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/get_contact_way', this.config.baseUrl);
url.searchParams.set('access_token', token);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 更新「联系我」方式
* @param params 更新参数(包含config_id和要更新的字段)
* @returns 更新结果
*/
async updateContactWay(params) {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/update_contact_way', this.config.baseUrl);
url.searchParams.set('access_token', token);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 删除「联系我」方式
* @param params 删除参数(包含config_id)
* @returns 删除结果
*/
async deleteContactWay(params) {
const token = await this.getAccessToken();
const url = new URL('/cgi-bin/externalcontact/del_contact_way', this.config.baseUrl);
url.searchParams.set('access_token', token);
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(params),
});
const data = await response.json();
// 检查API错误
if (data.errcode !== 0) {
const error = new WeChatError(data.errcode, data.errmsg);
error.errcode = data.errcode;
error.errmsg = data.errmsg;
throw error;
}
return data;
}
/**
* 重置access_token(强制重新获取)
*/
resetAccessToken() {
this.accessToken = undefined;
this.tokenExpireTime = undefined;
}
}
//# sourceMappingURL=wechat-client.js.map