apptise-core
Version:
Core library for Apptise unified notification system
177 lines • 6.78 kB
JavaScript
import { NotificationPlugin } from '../base/plugin.js';
import { HttpClient } from '../utils/http-client.js';
/**
* Google Chat 通知插件
* 支持通过 Webhook 向 Google Chat 发送消息
*/
export class GoogleChatPlugin extends NotificationPlugin {
registration = {
serviceId: 'google-chat',
protocols: ['gchat'],
name: 'Google Chat',
description: 'Send notifications to Google Chat via webhooks',
version: '1.0.0',
};
/**
* 解析 Google Chat URL
* 支持格式:
* 1. gchat://workspace/key/token
* 2. gchat://workspace/path?key=xxx&token=xxx
* @param url - Google Chat webhook URL
* @returns 解析后的插件配置
*/
parseUrl(url) {
const parsedUrl = this.parseUrlBase(url);
if (!this.registration.protocols.includes(parsedUrl.protocol)) {
throw this.createError('INVALID_URL', `Unsupported protocol: ${parsedUrl.protocol}. Expected one of: ${this.registration.protocols.join(', ')}`);
}
let workspace;
let key;
let token;
// 解析路径部分
const pathParts = parsedUrl.pathname?.split('/').filter(Boolean) || [];
// 检查是否有查询参数中的 key 和 token
const queryKey = parsedUrl.searchParams.get('key');
const queryToken = parsedUrl.searchParams.get('token');
if (queryKey && queryToken) {
// 格式: gchat://workspace/path?key=xxx&token=xxx
workspace = parsedUrl.hostname || '';
if (!workspace) {
throw this.createError('INVALID_URL', 'Missing workspace in URL');
}
key = queryKey;
token = queryToken;
}
else if (pathParts.length >= 3) {
// 格式: gchat://workspace/key/token
[workspace, key, token] = pathParts;
}
else {
throw this.createError('INVALID_URL', 'Invalid Google Chat URL format. Expected: gchat://workspace/key/token or gchat://workspace/path?key=xxx&token=xxx');
}
if (!workspace || !key || !token) {
throw this.createError('INVALID_URL', 'Missing required parameters: workspace, key, or token');
}
// 构建 webhook URL (不包含 key 和 token 参数)
const webhookUrl = `https://chat.googleapis.com/v1/spaces/${workspace}/messages`;
// 解析其他查询参数
const thread = parsedUrl.searchParams.get('thread');
const format = parsedUrl.searchParams.get('format') || 'text';
const avatar = parsedUrl.searchParams.get('avatar');
const mode = parsedUrl.searchParams.get('mode') || 'sync';
return {
serviceId: this.registration.serviceId,
url,
config: {
workspace,
key,
token,
webhookUrl,
thread,
format,
avatar,
mode,
},
};
}
/**
* 发送通知到 Google Chat
* @param config - 插件配置
* @param message - 通知消息
* @returns 发送结果
*/
async send(config, message) {
if (!this.validateConfig(config)) {
return this.createErrorResult('Invalid plugin configuration');
}
if (!this.validateMessage(message)) {
return this.createErrorResult('Invalid notification message');
}
try {
const { result, duration } = await this.measureTime(async () => {
return await this.sendMessage(config, message);
});
return this.createSuccessResult(result, duration);
}
catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Unknown error occurred';
return this.createErrorResult(errorMessage, undefined, undefined);
}
}
/**
* 发送消息到 Google Chat
* @private
*/
async sendMessage(config, message) {
const { webhookUrl, thread, format, avatar, key, token } = config.config;
// 构建请求 URL,添加 key 和 token 参数(按照Python版本的顺序)
const url = new URL(webhookUrl);
url.searchParams.set('token', token);
url.searchParams.set('key', key);
if (thread) {
url.searchParams.set('threadKey', thread);
}
const requestUrl = url.toString();
// 构建消息体
const payload = this.buildPayload(message, format, avatar);
// 构建请求头
const headers = {
'content-type': 'application/json; charset=utf-8',
'User-Agent': 'Apptise/1.0.0 (+https://github.com/apptise/apptise)',
};
const requestData = JSON.stringify(payload);
// Log HTTP request details for equivalence testing
console.log(`[APPTISE_HTTP_REQUEST] Method: POST`);
console.log(`[APPTISE_HTTP_REQUEST] URL: ${requestUrl}`);
console.log(`[APPTISE_HTTP_REQUEST] Headers: ${JSON.stringify(headers)}`);
console.log(`[APPTISE_HTTP_REQUEST] Data: ${requestData}`);
console.log(`[APPTISE_HTTP_REQUEST] Timeout: 30000`);
// 发送请求
const response = await HttpClient.post(requestUrl, requestData, {
headers,
timeout: 30000,
});
// Log HTTP response details for equivalence testing
console.log(`[APPTISE_HTTP_RESPONSE] Status: ${response.status}`);
console.log(`[APPTISE_HTTP_RESPONSE] Headers: ${JSON.stringify(response.headers)}`);
console.log(`[APPTISE_HTTP_RESPONSE] Content: ${JSON.stringify(response.data)}`);
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
return response.data;
}
/**
* 构建消息载荷
* @private
*/
buildPayload(message, format, avatar) {
const payload = {};
if (format === 'markdown') {
// Markdown 格式
let text = `**${message.title}**`;
if (message.body) {
text += `\n${message.body}`;
}
payload.text = text;
}
else {
// 纯文本格式
let text = message.title;
if (message.body) {
text += `\r\n${message.body}`;
}
payload.text = text;
}
// 添加头像
if (avatar) {
payload.sender = {
displayName: 'Apptise',
avatarUrl: avatar,
};
}
return payload;
}
}
// 导出插件实例
export const googleChatPlugin = new GoogleChatPlugin();
//# sourceMappingURL=google-chat.js.map