Skip to main content
Glama
api-client.ts6.97 kB
import axios, { AxiosInstance } from 'axios'; import FormData from 'form-data'; import { AuthManager } from '../auth/auth-manager.js'; import { logger } from '../utils/logger.js'; /** * 微信公众号 API 客户端 * 封装微信公众号 API 调用 */ export class WechatApiClient { private authManager: AuthManager; private httpClient: AxiosInstance; constructor(authManager: AuthManager) { this.authManager = authManager; this.httpClient = axios.create({ baseURL: 'https://api.weixin.qq.com', timeout: 30000, }); // 请求拦截器:自动添加 access_token this.httpClient.interceptors.request.use(async (config) => { if (config.url && !config.url.includes('access_token=')) { const tokenInfo = await this.authManager.getAccessToken(); const separator = config.url.includes('?') ? '&' : '?'; config.url += `${separator}access_token=${tokenInfo.accessToken}`; } return config; }); // 响应拦截器:处理错误 this.httpClient.interceptors.response.use( (response) => response, (error) => { const status = error?.response?.status; logger.error('Wechat API request failed:', status ? String(status) : error?.message); throw error; } ); } getAuthManager(): AuthManager { return this.authManager; } /** * 上传临时素材 */ async uploadMedia(params: { type: 'image' | 'voice' | 'video' | 'thumb'; media: Buffer; fileName: string; title?: string; introduction?: string; }): Promise<{ mediaId: string; type: string; createdAt: number; url?: string }> { try { const formData = new FormData(); formData.append('media', params.media, params.fileName); if (params.type === 'video') { const description = { title: params.title || 'Video', introduction: params.introduction || '', }; formData.append('description', JSON.stringify(description)); } const response = await this.httpClient.post( `/cgi-bin/media/upload?type=${params.type}`, formData, { headers: { ...formData.getHeaders(), }, } ); if (response.data.errcode) { throw new Error(`Upload failed: ${response.data.errmsg} (${response.data.errcode})`); } return { mediaId: response.data.media_id, type: response.data.type, createdAt: response.data.created_at * 1000, url: response.data.url, }; } catch (error) { logger.error('Failed to upload media:', (error as any)?.message ?? String(error)); throw error; } } /** * 获取临时素材 */ async getMedia(mediaId: string): Promise<Buffer> { try { const response = await this.httpClient.get( `/cgi-bin/media/get?media_id=${mediaId}`, { responseType: 'arraybuffer', } ); return Buffer.from(response.data); } catch (error) { logger.error('Failed to get media:', (error as any)?.message ?? String(error)); throw error; } } /** * 新增永久图文素材 */ async addNews(articles: Array<{ title: string; author?: string; digest?: string; content: string; contentSourceUrl?: string; thumbMediaId: string; showCoverPic?: number; needOpenComment?: number; onlyFansCanComment?: number; }>): Promise<{ mediaId: string }> { try { const response = await this.httpClient.post('/cgi-bin/material/add_news', { articles, }); if (response.data.errcode) { throw new Error(`Add news failed: ${response.data.errmsg} (${response.data.errcode})`); } return { mediaId: response.data.media_id, }; } catch (error) { logger.error('Failed to add news:', (error as any)?.message ?? String(error)); throw error; } } /** * 新增草稿 */ async addDraft(articles: Array<{ title: string; author?: string; digest?: string; content: string; contentSourceUrl?: string; thumbMediaId: string; showCoverPic?: number; needOpenComment?: number; onlyFansCanComment?: number; }>): Promise<{ mediaId: string }> { try { const response = await this.httpClient.post('/cgi-bin/draft/add', { articles, }); if (response.data.errcode) { throw new Error(`Add draft failed: ${response.data.errmsg} (${response.data.errcode})`); } return { mediaId: response.data.media_id, }; } catch (error) { logger.error('Failed to add draft:', (error as any)?.message ?? String(error)); throw error; } } /** * 发布接口 */ async publishDraft(mediaId: string): Promise<{ publishId: string; msgDataId: string }> { try { const response = await this.httpClient.post('/cgi-bin/freepublish/submit', { media_id: mediaId, }); if (response.data.errcode) { throw new Error(`Publish failed: ${response.data.errmsg} (${response.data.errcode})`); } return { publishId: response.data.publish_id, msgDataId: response.data.msg_data_id, }; } catch (error) { logger.error('Failed to publish draft:', (error as any)?.message ?? String(error)); throw error; } } /** * 上传图文消息图片 */ async uploadImg(formData: FormData): Promise<{ url: string; errcode?: number; errmsg?: string }> { try { const response = await this.httpClient.post( '/cgi-bin/media/uploadimg', formData, { headers: { ...formData.getHeaders(), }, } ); return response.data; } catch (error) { logger.error('Failed to upload image:', (error as any)?.message ?? String(error)); throw error; } } /** * 通用 GET 请求 */ async get(path: string, params?: Record<string, unknown>): Promise<unknown> { try { const response = await this.httpClient.get(path, { params }); if (response.data.errcode && response.data.errcode !== 0) { throw new Error(`API Error: ${response.data.errmsg} (${response.data.errcode})`); } return response.data; } catch (error) { logger.error(`GET ${path} failed:`, (error as any)?.message ?? String(error)); throw error; } } /** * 通用 POST 请求 */ async post(path: string, data?: unknown): Promise<unknown> { try { const response = await this.httpClient.post(path, data); if (response.data.errcode && response.data.errcode !== 0) { throw new Error(`API Error: ${response.data.errmsg} (${response.data.errcode})`); } return response.data; } catch (error) { logger.error(`POST ${path} failed:`, (error as any)?.message ?? String(error)); throw error; } } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/xwang152-jack/wechat-official-account-mcp'

If you have feedback or need assistance with the MCP directory API, please join our Discord server