Skip to content

Commit 9d674ca

Browse files
author
funcodingdev
committed
fix: 群聊消息、消息推送节点支持表单输入和JSON输入两种方式
1 parent f79fb69 commit 9d674ca

7 files changed

Lines changed: 565 additions & 71 deletions

File tree

nodes/WeCom/resources/appChat/execute.ts

Lines changed: 197 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import type { IExecuteFunctions, INodeExecutionData, IDataObject } from 'n8n-workflow';
2+
import { NodeOperationError } from 'n8n-workflow';
23
import { weComApiRequest } from '../../shared/transport';
34

45
export async function executeAppChat(
@@ -7,6 +8,47 @@ export async function executeAppChat(
78
items: INodeExecutionData[],
89
): Promise<INodeExecutionData[]> {
910
const returnData: INodeExecutionData[] = [];
11+
const parseOptionalJsonParameter = (
12+
value: unknown,
13+
parameterName: string,
14+
itemIndex: number,
15+
): IDataObject | IDataObject[] | undefined => {
16+
if (value === undefined || value === null) {
17+
return undefined;
18+
}
19+
if (typeof value === 'string') {
20+
const trimmed = value.trim();
21+
if (!trimmed || trimmed === '{}' || trimmed === '[]') {
22+
return undefined;
23+
}
24+
try {
25+
return JSON.parse(trimmed) as IDataObject | IDataObject[];
26+
} catch (error) {
27+
throw new NodeOperationError(
28+
this.getNode(),
29+
`${parameterName} 必须是有效的 JSON: ${(error as Error).message}`,
30+
{ itemIndex },
31+
);
32+
}
33+
}
34+
return value as IDataObject | IDataObject[];
35+
};
36+
const resolveSafeValue = (value: unknown): number | undefined => {
37+
if (value === undefined || value === null) {
38+
return undefined;
39+
}
40+
if (typeof value === 'boolean') {
41+
return value ? 1 : 0;
42+
}
43+
if (typeof value === 'number') {
44+
return value;
45+
}
46+
if (typeof value === 'string') {
47+
const parsed = Number.parseInt(value, 10);
48+
return Number.isNaN(parsed) ? undefined : parsed;
49+
}
50+
return undefined;
51+
};
1052

1153
for (let i = 0; i < items.length; i++) {
1254
try {
@@ -164,18 +206,69 @@ export async function executeAppChat(
164206
},
165207
};
166208
} else if (operation === 'sendNews') {
209+
const newsInputMode = this.getNodeParameter('news_input_mode', i, 'form') as string;
210+
const newsJson = newsInputMode === 'json'
211+
? parseOptionalJsonParameter(
212+
this.getNodeParameter('news_json', i, '[]') as string,
213+
'news_json',
214+
i,
215+
)
216+
: undefined;
167217
const articles = this.getNodeParameter('articles', i, {}) as IDataObject;
168-
const articleList = (articles.article as IDataObject[]) || [];
169218
const safe = this.getNodeParameter('safe', i, false) as boolean;
170219

220+
let articleList: IDataObject[] = [];
221+
let safeValue: number | undefined = safe ? 1 : 0;
222+
223+
if (newsInputMode === 'json') {
224+
if (!newsJson) {
225+
throw new NodeOperationError(
226+
this.getNode(),
227+
'请选择 JSON 输入并提供 news_json',
228+
{ itemIndex: i },
229+
);
230+
}
231+
if (Array.isArray(newsJson)) {
232+
articleList = newsJson as IDataObject[];
233+
} else if ((newsJson as IDataObject).news) {
234+
const newsPayload = (newsJson as IDataObject).news as IDataObject;
235+
if (!Array.isArray(newsPayload.articles)) {
236+
throw new NodeOperationError(
237+
this.getNode(),
238+
'news_json.news 必须包含 articles 数组',
239+
{ itemIndex: i },
240+
);
241+
}
242+
articleList = newsPayload.articles as IDataObject[];
243+
} else if (Array.isArray((newsJson as IDataObject).articles)) {
244+
articleList = (newsJson as IDataObject).articles as IDataObject[];
245+
} else {
246+
throw new NodeOperationError(
247+
this.getNode(),
248+
'news_json 必须是图文数组或包含 articles 的对象',
249+
{ itemIndex: i },
250+
);
251+
}
252+
253+
const safeFromJson = resolveSafeValue((newsJson as IDataObject).safe);
254+
if (safeFromJson !== undefined) {
255+
safeValue = safeFromJson;
256+
}
257+
} else {
258+
articleList = (articles.article as IDataObject[]) || [];
259+
}
260+
171261
body = {
172262
...body,
173263
msgtype: 'news',
174264
news: {
175265
articles: articleList,
176266
},
177-
safe: safe ? 1 : 0,
178267
};
268+
269+
if (safeValue !== undefined) {
270+
body.safe = safeValue;
271+
}
179272
} else if (operation === 'sendVoice') {
180273
const mediaId = this.getNodeParameter('media_ID', i) as string;
181274

@@ -211,36 +304,130 @@ export async function executeAppChat(
211304
safe: safe ? 1 : 0,
212305
};
213306
} else if (operation === 'sendTextCard') {
307+
const textcardInputMode = this.getNodeParameter(
308+
'textcard_input_mode',
309+
i,
310+
'form',
311+
) as string;
312+
const textcardJson = textcardInputMode === 'json'
313+
? parseOptionalJsonParameter(
314+
this.getNodeParameter('textcard_json', i, '{}') as string,
315+
'textcard_json',
316+
i,
317+
)
318+
: undefined;
214319
const title = this.getNodeParameter('title', i) as string;
215320
const description = this.getNodeParameter('description', i) as string;
216321
const url = this.getNodeParameter('url', i) as string;
217322
const btntxt = this.getNodeParameter('btntxt', i, '详情') as string;
218323
const safe = this.getNodeParameter('safe', i, false) as boolean;
219324

220-
body = {
221-
...body,
222-
msgtype: 'textcard',
223-
textcard: {
325+
let textcard: IDataObject;
326+
let safeValue: number | undefined = safe ? 1 : 0;
327+
328+
if (textcardInputMode === 'json') {
329+
if (!textcardJson) {
330+
throw new NodeOperationError(
331+
this.getNode(),
332+
'请选择 JSON 输入并提供 textcard_json',
333+
{ itemIndex: i },
334+
);
335+
}
336+
if (Array.isArray(textcardJson)) {
337+
throw new NodeOperationError(
338+
this.getNode(),
339+
'textcard_json 必须是对象',
340+
{ itemIndex: i },
341+
);
342+
}
343+
const textcardPayload = (textcardJson as IDataObject).textcard as IDataObject | undefined;
344+
textcard = textcardPayload ?? (textcardJson as IDataObject);
345+
const safeFromJson = resolveSafeValue((textcardJson as IDataObject).safe);
346+
if (safeFromJson !== undefined) {
347+
safeValue = safeFromJson;
348+
}
349+
} else {
350+
textcard = {
224351
title,
225352
description,
226353
url,
227354
btntxt,
228-
},
229-
safe: safe ? 1 : 0,
355+
};
356+
}
357+
358+
body = {
359+
...body,
360+
msgtype: 'textcard',
361+
textcard,
230362
};
363+
364+
if (safeValue !== undefined) {
365+
body.safe = safeValue;
366+
}
231367
} else if (operation === 'sendMpNews') {
368+
const mpnewsInputMode = this.getNodeParameter('mpnews_input_mode', i, 'form') as string;
369+
const mpnewsJson = mpnewsInputMode === 'json'
370+
? parseOptionalJsonParameter(
371+
this.getNodeParameter('mpnews_json', i, '[]') as string,
372+
'mpnews_json',
373+
i,
374+
)
375+
: undefined;
232376
const articles = this.getNodeParameter('articles', i, {}) as IDataObject;
233-
const articleList = (articles.article as IDataObject[]) || [];
234377
const safe = this.getNodeParameter('safe', i, false) as boolean;
235378

379+
let articleList: IDataObject[] = [];
380+
let safeValue: number | undefined = safe ? 1 : 0;
381+
382+
if (mpnewsInputMode === 'json') {
383+
if (!mpnewsJson) {
384+
throw new NodeOperationError(
385+
this.getNode(),
386+
'请选择 JSON 输入并提供 mpnews_json',
387+
{ itemIndex: i },
388+
);
389+
}
390+
if (Array.isArray(mpnewsJson)) {
391+
articleList = mpnewsJson as IDataObject[];
392+
} else if ((mpnewsJson as IDataObject).mpnews) {
393+
const mpnewsPayload = (mpnewsJson as IDataObject).mpnews as IDataObject;
394+
if (!Array.isArray(mpnewsPayload.articles)) {
395+
throw new NodeOperationError(
396+
this.getNode(),
397+
'mpnews_json.mpnews 必须包含 articles 数组',
398+
{ itemIndex: i },
399+
);
400+
}
401+
articleList = mpnewsPayload.articles as IDataObject[];
402+
} else if (Array.isArray((mpnewsJson as IDataObject).articles)) {
403+
articleList = (mpnewsJson as IDataObject).articles as IDataObject[];
404+
} else {
405+
throw new NodeOperationError(
406+
this.getNode(),
407+
'mpnews_json 必须是图文数组或包含 articles 的对象',
408+
{ itemIndex: i },
409+
);
410+
}
411+
412+
const safeFromJson = resolveSafeValue((mpnewsJson as IDataObject).safe);
413+
if (safeFromJson !== undefined) {
414+
safeValue = safeFromJson;
415+
}
416+
} else {
417+
articleList = (articles.article as IDataObject[]) || [];
418+
}
419+
236420
body = {
237421
...body,
238422
msgtype: 'mpnews',
239423
mpnews: {
240424
articles: articleList,
241425
},
242-
safe: safe ? 1 : 0,
243426
};
427+
428+
if (safeValue !== undefined) {
429+
body.safe = safeValue;
430+
}
244431
}
245432

246433
const response = await weComApiRequest.call(this, 'POST', '/cgi-bin/appchat/send', body);
@@ -266,4 +453,3 @@ export async function executeAppChat(
266453

267454
return returnData;
268455
}
269-

nodes/WeCom/resources/appChat/sendMpNews.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,39 @@ export const sendMpNewsDescription: INodeProperties[] = [
2020
'群聊的唯一标识。<a href="https://developer.work.weixin.qq.com/document/path/90248" target="_blank">官方文档</a>',
2121
hint: '必填。群聊的唯一标识,必须是该应用所创建的群',
2222
},
23+
{
24+
displayName: '输入方式',
25+
name: 'mpnews_input_mode',
26+
type: 'options',
27+
options: [
28+
{ name: '表单输入', value: 'form' },
29+
{ name: 'JSON输入', value: 'json' },
30+
],
31+
default: 'form',
32+
displayOptions: {
33+
show: showOnlyForSendMpNews,
34+
},
35+
description: '选择图文消息的输入方式',
36+
},
37+
{
38+
displayName: '图文列表(JSON)',
39+
name: 'mpnews_json',
40+
type: 'json',
41+
typeOptions: {
42+
rows: 4,
43+
},
44+
default: '[]',
45+
placeholder: '[{"title":"标题","content":"<p>内容</p>","thumb_media_id":"MEDIA_ID"}]',
46+
displayOptions: {
47+
show: {
48+
...showOnlyForSendMpNews,
49+
mpnews_input_mode: ['json'],
50+
},
51+
},
52+
hint: 'JSON输入模式下仅展示此字段',
53+
description:
54+
'可选。使用JSON直接输入图文列表(数组)或对象(包含articles)。<a href="https://developer.work.weixin.qq.com/document/path/90248" target="_blank">官方文档</a>',
55+
},
2356
{
2457
displayName: '图文列表',
2558
name: 'articles',
@@ -28,7 +61,10 @@ export const sendMpNewsDescription: INodeProperties[] = [
2861
multipleValues: true,
2962
},
3063
displayOptions: {
31-
show: showOnlyForSendMpNews,
64+
show: {
65+
...showOnlyForSendMpNews,
66+
mpnews_input_mode: ['form'],
67+
},
3268
},
3369
default: {},
3470
placeholder: '添加图文',
@@ -117,7 +153,10 @@ export const sendMpNewsDescription: INodeProperties[] = [
117153
name: 'safe',
118154
type: 'boolean',
119155
displayOptions: {
120-
show: showOnlyForSendMpNews,
156+
show: {
157+
...showOnlyForSendMpNews,
158+
mpnews_input_mode: ['form'],
159+
},
121160
},
122161
default: false,
123162
description:

nodes/WeCom/resources/appChat/sendNews.ts

Lines changed: 41 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,39 @@ export const sendNewsDescription: INodeProperties[] = [
2020
'群聊的唯一标识。<a href="https://developer.work.weixin.qq.com/document/path/90248" target="_blank">官方文档</a>',
2121
hint: '必填。群聊的唯一标识,必须是该应用所创建的群',
2222
},
23+
{
24+
displayName: '输入方式',
25+
name: 'news_input_mode',
26+
type: 'options',
27+
options: [
28+
{ name: '表单输入', value: 'form' },
29+
{ name: 'JSON输入', value: 'json' },
30+
],
31+
default: 'form',
32+
displayOptions: {
33+
show: showOnlyForSendNews,
34+
},
35+
description: '选择图文消息的输入方式',
36+
},
37+
{
38+
displayName: '图文列表(JSON)',
39+
name: 'news_json',
40+
type: 'json',
41+
typeOptions: {
42+
rows: 4,
43+
},
44+
default: '[]',
45+
placeholder: '[{"title":"标题","description":"摘要","url":"https://example.com","picurl":"https://example.com/image.jpg"}]',
46+
displayOptions: {
47+
show: {
48+
...showOnlyForSendNews,
49+
news_input_mode: ['json'],
50+
},
51+
},
52+
hint: 'JSON输入模式下仅展示此字段',
53+
description:
54+
'可选。使用JSON直接输入图文列表(数组)或对象(包含articles)。<a href="https://developer.work.weixin.qq.com/document/path/90248" target="_blank">官方文档</a>',
55+
},
2356
{
2457
displayName: '图文列表',
2558
name: 'articles',
@@ -28,7 +61,10 @@ export const sendNewsDescription: INodeProperties[] = [
2861
multipleValues: true,
2962
},
3063
displayOptions: {
31-
show: showOnlyForSendNews,
64+
show: {
65+
...showOnlyForSendNews,
66+
news_input_mode: ['form'],
67+
},
3268
},
3369
default: {},
3470
placeholder: '添加图文',
@@ -93,7 +129,10 @@ export const sendNewsDescription: INodeProperties[] = [
93129
name: 'safe',
94130
type: 'boolean',
95131
displayOptions: {
96-
show: showOnlyForSendNews,
132+
show: {
133+
...showOnlyForSendNews,
134+
news_input_mode: ['form'],
135+
},
97136
},
98137
default: false,
99138
description:

0 commit comments

Comments
 (0)