diff --git a/.gitignore b/.gitignore index 86acfb2..7a69683 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,8 @@ # 目标代码 dist/ +# 依赖包 +node_modules/ + # 编辑器 .vscode/ diff --git a/credentials/GwezzChangdunovelApi.credentials.ts b/credentials/GwezzChangdunovelApi.credentials.ts new file mode 100644 index 0000000..b3dbca1 --- /dev/null +++ b/credentials/GwezzChangdunovelApi.credentials.ts @@ -0,0 +1,37 @@ +import type { ICredentialTestRequest, ICredentialType, INodeProperties, Icon } from 'n8n-workflow'; + +export class GwezzChangdunovelApi implements ICredentialType { + name = 'gwezzChangdunovelApi'; + + displayName = 'Gwezz 常读分销 API'; + + icon = { light: 'file:../icons/gwezz.svg', dark: 'file:../icons/gwezz.dark.svg' } as Icon; + + documentationUrl = 'https://github.com/org/-gwezz-changdunovel?tab=readme-ov-file#credentials'; + + properties: INodeProperties[] = [ + { + displayName: 'distributor_id', + name: 'distributor_id', + type: 'string', + required: true, + default: '', + description: '分销商 ID,可与常读商务同学沟通获取', + }, + { + displayName: 'secret_key', + name: 'secret_key', + type: 'string', + typeOptions: { password: true }, + required: true, + default: '', + description: '签名密钥,可与常读商务同学沟通获取', + }, + ]; + + test: ICredentialTestRequest = { + request: { + url: 'https://www.changdunovel.com/novelsale/openap', + }, + }; +} diff --git a/nodes/GwezzChangdunovel/gwezz.dark.svg b/icons/gwezz.dark.svg similarity index 100% rename from nodes/GwezzChangdunovel/gwezz.dark.svg rename to icons/gwezz.dark.svg diff --git a/nodes/GwezzChangdunovel/gwezz.svg b/icons/gwezz.svg similarity index 100% rename from nodes/GwezzChangdunovel/gwezz.svg rename to icons/gwezz.svg diff --git a/node_modules/.package-lock.json b/node_modules/.package-lock.json index 73856c9..e7c61b2 100644 --- a/node_modules/.package-lock.json +++ b/node_modules/.package-lock.json @@ -1286,6 +1286,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", + "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, "node_modules/@types/parse-path": { "version": "7.0.3", "resolved": "https://registry.npmmirror.com/@types/parse-path/-/parse-path-7.0.3.tgz", @@ -1800,7 +1810,6 @@ "x64" ], "dev": true, - "ideallyInert": true, "license": "MIT", "optional": true, "os": [ @@ -6822,6 +6831,13 @@ "node": ">=18.17" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, "node_modules/universal-user-agent": { "version": "7.0.3", "resolved": "https://registry.npmmirror.com/universal-user-agent/-/universal-user-agent-7.0.3.tgz", diff --git a/nodes/GwezzChangdunovel/GwezzChangdunovel.node.ts b/nodes/GwezzChangdunovel/GwezzChangdunovel.node.ts index f26123c..9fa2a85 100644 --- a/nodes/GwezzChangdunovel/GwezzChangdunovel.node.ts +++ b/nodes/GwezzChangdunovel/GwezzChangdunovel.node.ts @@ -1,11 +1,45 @@ -import { type INodeType, type INodeTypeDescription } from 'n8n-workflow'; +import { createHash } from 'crypto'; +import { + type INodeType, + type INodeTypeDescription, + IExecuteFunctions, + INodeExecutionData, +} from 'n8n-workflow'; import { distributionDescription } from './resources/distribution'; +/** + * 生成 MD5 签名 + * @param params 参数数组 + * @returns MD5 签名字符串 + */ +function getSign(params: (string | number)[]): string { + const paramStr = params.join(''); + return createHash('md5').update(paramStr).digest('hex'); +} + +/** + * 创建认证参数 + * @param distributorId 分销商 ID + * @param secretKey 签名密钥 + * @returns + */ +function createAuthParams( + distributorId: string, + secretKey: string, +): Record { + const timeStamp = Math.floor(Date.now() / 1000); + return { + distributor_id: distributorId, + ts: timeStamp, + sign: getSign([distributorId, secretKey, timeStamp]), + }; +} + export class GwezzChangdunovel implements INodeType { description: INodeTypeDescription = { displayName: 'Gwezz 常读分销', name: 'gwezzChangdunovel', - icon: { light: 'file:gwezz.svg', dark: 'file:gwezz.dark.svg' }, + icon: { light: 'file:../../icons/gwezz.svg', dark: 'file:../../icons/gwezz.dark.svg' }, group: ['transform'], version: 1, subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}', @@ -16,7 +50,7 @@ export class GwezzChangdunovel implements INodeType { usableAsTool: true, inputs: ['main'], outputs: ['main'], - credentials: [], + credentials: [{ name: 'gwezzChangdunovelApi', required: true }], requestDefaults: { baseURL: 'https://www.changdunovel.com/novelsale/openapi', headers: { @@ -41,4 +75,53 @@ export class GwezzChangdunovel implements INodeType { ...distributionDescription, ], }; + + async execute(this: IExecuteFunctions): Promise { + const items = this.getInputData(); + const returnData: INodeExecutionData[] = []; + + const resource = this.getNodeParameter('resource', 0); + const operation = this.getNodeParameter('operation', 0); + + const credentials = await this.getCredentials('gwezzChangdunovelApi'); + const distributorId = credentials.distributor_id as string; + const secretKey = credentials.secret_key as string; + + let responseData; + for (let i = 0; i < items.length; i++) { + try { + if (resource === 'distribution') { + if (operation === 'contentBookMetaV1') { + const bookId = this.getNodeParameter('book_id', i) as string; + + const authParams = createAuthParams(distributorId, secretKey); + const qs = { + ...authParams, + book_id: bookId, + }; + + responseData = await this.helpers.httpRequest({ + method: 'GET', + url: '/v1/content/book/meta', + qs, + }); + } + } + returnData.push({ json: responseData, itemIndex: i }); + } catch (error) { + if (this.continueOnFail()) { + returnData.push({ + json: { + error: (error as Error).message, + }, + itemIndex: i, + }); + continue; + } + throw error; + } + } + + return [this.helpers.returnJsonArray(returnData)]; + } } diff --git a/nodes/GwezzChangdunovel/resources/distribution/contentBooMetaV1.ts b/nodes/GwezzChangdunovel/resources/distribution/contentBooMetaV1.ts index cfe7485..26a0663 100644 --- a/nodes/GwezzChangdunovel/resources/distribution/contentBooMetaV1.ts +++ b/nodes/GwezzChangdunovel/resources/distribution/contentBooMetaV1.ts @@ -8,7 +8,7 @@ const showOnlyForDistributionContentBookMetaV1 = { export const contentBookMetaV1Description: INodeProperties[] = [ { displayName: '书籍 ID', - name: 'bookId', + name: 'book_id', type: 'string', required: true, default: '', diff --git a/nodes/GwezzChangdunovel/resources/distribution/index.ts b/nodes/GwezzChangdunovel/resources/distribution/index.ts index 24007fb..b2fe11e 100644 --- a/nodes/GwezzChangdunovel/resources/distribution/index.ts +++ b/nodes/GwezzChangdunovel/resources/distribution/index.ts @@ -18,7 +18,7 @@ export const distributionDescription: INodeProperties[] = [ { name: '获取书籍信息', value: 'contentBookMetaV1', - action: 'Get content book metadata', + action: '获取书籍信息', routing: { request: { method: 'GET', diff --git a/package-lock.json b/package-lock.json index 0ff8c67..c5d34d0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "devDependencies": { "@n8n/node-cli": "*", + "@types/node": "^24.9.2", "eslint": "9.32.0", "prettier": "3.6.2", "release-it": "^19.0.4", @@ -1296,6 +1297,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "24.9.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.9.2.tgz", + "integrity": "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, "node_modules/@types/parse-path": { "version": "7.0.3", "resolved": "https://registry.npmmirror.com/@types/parse-path/-/parse-path-7.0.3.tgz", @@ -6814,6 +6825,13 @@ "node": ">=18.17" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "dev": true, + "license": "MIT" + }, "node_modules/universal-user-agent": { "version": "7.0.3", "resolved": "https://registry.npmmirror.com/universal-user-agent/-/universal-user-agent-7.0.3.tgz", diff --git a/package.json b/package.json index eeeb1b5..c992187 100644 --- a/package.json +++ b/package.json @@ -30,13 +30,16 @@ "n8n": { "n8nNodesApiVersion": 1, "strict": true, - "credentials": [], + "credentials": [ + "dist/credentials/GwezzChangdunovelApi.credentials.js" + ], "nodes": [ "dist/nodes/GwezzChangdunovel/GwezzChangdunovel.node.js" ] }, "devDependencies": { "@n8n/node-cli": "*", + "@types/node": "^24.9.2", "eslint": "9.32.0", "prettier": "3.6.2", "release-it": "^19.0.4", @@ -45,4 +48,4 @@ "peerDependencies": { "n8n-workflow": "*" } -} \ No newline at end of file +}