import { Article, PlatformBasicConfig, PlatformTarget } from './types';
import { PublishSdk } from './sdk';
import { isSupportContentType } from './utils';
import { HttpException } from '@nestjs/common';
interface WechatPublishData {
media_id: string;
}
export interface WeChatPlatformConfig extends PlatformBasicConfig {
APPID: string;
APPSECRET: string;
/** 暂时的权宜之计 */
ThumbMediaId: string;
}
interface WeChatAddDraftArticle {
/** 标题 */
title: string;
/** 作者 */
author?: string;
/** 图文消息的摘要,仅有单图文消息才有摘要,多图文此处为空。如果本字段为没有填写,则默认抓取正文前54个字 */
digest?: string;
/** 图文消息的具体内容,支持HTML标签,必须少于2万字符,小于1M,且此处会去除JS,涉及图片url必须来源 "上传图文消息内的图片获取URL"接口获取。外部图片url将被过滤。 */
content: string;
/** 图文消息的原文地址,即点击“阅读原文”后的URL */
content_source_url?: string;
/** 图文消息的封面图片素材id(必须是永久MediaID) */
thumb_media_id: string;
/** Uint32 是否打开评论,0不打开(默认),1打开 */
need_open_comment?: 0 | 1;
/** Uint32 是否粉丝才可评论,0所有人可评论(默认),1粉丝才可评论 */
only_fans_can_comment?: 0 | 1;
}
interface WeChatAddDraftPayload {
articles: WeChatAddDraftArticle[];
}
interface WechatArticle extends Article {
content_source_url?: string;
thumb_media_id: string;
need_open_comment: 0 | 1;
only_fans_can_comment: 0 | 1;
}
interface WechatApiErrorRes {
errcode?: number;
errmsg?: string;
}
interface GetAccessTokenRes extends WechatApiErrorRes {
access_token?: string;
expires_in?: number;
}
interface Freepublish extends WechatApiErrorRes {
publish_id?: string;
msg_data_id?: string;
}
export interface WechatImageMaterial extends WechatApiErrorRes {
total_count?: number;
item_count?: number;
item?: {
media_id: string;
name: string;
update_time: number;
url: string;
}[];
}
export class WechatPublishSdk extends PublishSdk<
WeChatPlatformConfig,
WechatPublishData
> {
name = PlatformTarget.WeChat;
constructor(protected platformConfig: WeChatPlatformConfig) {
super();
}
/**
* 获取AccessToken
* @returns
*/
async getAccessToken(): Promise<string | undefined> {
const url = `https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=${this.platformConfig.APPID}&secret=${this.platformConfig.APPSECRET}`;
const res = await fetch(url);
if (!res.ok) {
throw new HttpException('can not fetch wechat api', -1);
}
const resJson = (await res.json()) as GetAccessTokenRes;
if (!resJson?.access_token) {
throw new HttpException(resJson?.errmsg, resJson?.errcode);
}
return resJson?.access_token;
}
/**
* 新建草稿
* @param accessToken 访问token
* @param newPostPayload 草稿内容
* @returns 草稿id
*/
async addDraft(
accessToken: string,
newPostPayload: WeChatAddDraftPayload,
): Promise<string | undefined> {
const url = `https://api.weixin.qq.com/cgi-bin/draft/add?access_token=${accessToken}`;
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify(newPostPayload),
});
if (!res.ok) {
throw new HttpException('can not fetch wechat api', -1);
}
const resJson = (await res.json()) as {
errcode: number;
media_id: string;
errmsg;
};
if (!resJson?.media_id) {
throw new HttpException(resJson?.errmsg, resJson?.errcode);
}
return resJson?.media_id;
}
async publish(
payload: WechatPublishData,
articles: WechatArticle[],
): Promise<any> {
const accessToken = await this.getAccessToken();
if (!accessToken) {
throw new Error('Cannot get wechat access token');
}
const newPostPayload = {
articles: articles.map(
({
title,
digest,
content,
thumb_media_id,
content_source_url = '',
need_open_comment = 0,
only_fans_can_comment = 0,
}) => ({
title,
digest,
content,
author: 'cherry-markdown',
content_source_url,
need_open_comment,
only_fans_can_comment,
thumb_media_id: thumb_media_id ?? this.platformConfig.ThumbMediaId,
}),
),
};
const draftId = await this.addDraft(accessToken, newPostPayload);
const publishUrl = `https://api.weixin.qq.com/cgi-bin/freepublish/submit?access_token=${accessToken}`;
const res = await fetch(publishUrl, {
method: 'POST',
body: JSON.stringify({
media_id: draftId,
}),
});
if (!res.ok) {
throw new HttpException('can not fetch wechat api', -1);
}
const resJson = (await res.json()) as Freepublish;
if (resJson.errmsg) {
throw new HttpException(resJson?.errmsg, resJson?.errcode);
}
return {
publishId: resJson?.publish_id,
msgDataId: resJson?.msg_data_id,
};
}
async checkConfig(): Promise<boolean> {
if (!this.platformConfig?.APPID) {
throw new Error('The WeChat Platform Config must have APPID');
}
if (!this.platformConfig?.APPSECRET) {
throw new Error('The WeChat Platform Config must have APPSECRET');
}
if (!this.platformConfig.ContentType) {
throw new Error('Any platform must have ContentType');
}
if (!this.platformConfig.ThumbMediaId) {
console.warn('WARN: The WeChat Platform Config must have ThumbMediaId');
}
if (!isSupportContentType(this.platformConfig.ContentType)) {
throw new Error(
`The WeChat Platform does not support this ContentType: ${this.platformConfig.ContentType}`,
);
}
return true;
}
/**
* 获取微信公众号的图片素材
* @param offset Offset
* @param count Count
* @returns 图片素材列表
*/
async getAllWechatImageMaterial(offset = 0, count = 20) {
const accessToken = await this.getAccessToken();
const url = `https://api.weixin.qq.com/cgi-bin/material/batchget_material?access_token=${accessToken}`;
console.log('2222');
const res = await fetch(url, {
method: 'POST',
body: JSON.stringify({
type: 'image',
offset,
count,
}),
});
if (!res.ok) {
throw new HttpException('can not fetch wechat api', -1);
}
const resJson = (await res.json()) as WechatImageMaterial;
if (resJson.errmsg) {
throw new HttpException(resJson?.errmsg, resJson?.errcode);
}
return resJson;
}
}