diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md index 98385543..dffcf011 100644 --- a/.claude/CLAUDE.md +++ b/.claude/CLAUDE.md @@ -269,6 +269,10 @@ bun test - 必须包含:`config.ts`, `src/index.ts`, `index.ts` - 可选包含:`test/index.test.ts`, `DESIGN.md` +### 4. 认证处理规范 +- 优先使用传入的 `accessToken` +- 未提供时,通过 `appId` 和 `appSecret` 自动获取 +- 使用 `lib/auth.js` 中封装的认证函数 ## 最佳实践 ### 1. 代码兼容性 diff --git a/bun.lock b/bun.lock index 6cb03ec3..e9b80ef1 100644 --- a/bun.lock +++ b/bun.lock @@ -8,6 +8,10 @@ "@ts-rest/core": "3.52.1", "@ts-rest/express": "3.52.1", "@ts-rest/open-api": "3.52.1", + "@types/cheerio": "^0.22.35", + "@types/marked": "^5.0.2", + "cheerio": "^1.1.0", + "marked": "^17.0.1", }, "devDependencies": { "@eslint/js": "^9.24.0", @@ -94,7 +98,7 @@ }, "sdk": { "name": "@fastgpt-sdk/plugin", - "version": "0.2.13", + "version": "0.2.16", "dependencies": { "@fortaine/fetch-event-source": "^3.0.6", "@ts-rest/core": "3.52.1", @@ -434,6 +438,8 @@ "@types/bun": ["@types/bun@1.3.1", "", { "dependencies": { "bun-types": "1.3.1" } }, "sha512-4jNMk2/K9YJtfqwoAa28c8wK+T7nvJFOjxI4h/7sORWcypRNxBpr+TPNaCfVWq70tLCJsqoFwcf0oI0JU/fvMQ=="], + "@types/cheerio": ["@types/cheerio@0.22.35", "", { "dependencies": { "@types/node": "*" } }, "sha512-yD57BchKRvTV+JD53UZ6PD8KWY5g5rvvMLRnZR3EQBCZXiDT/HR+pKpMzFGlWNhFrXlo7VPZXtKvIEwZkAWOIA=="], + "@types/connect": ["@types/connect@3.4.38", "", { "dependencies": { "@types/node": "*" } }, "sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug=="], "@types/estree": ["@types/estree@1.0.8", "", {}, "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w=="], @@ -450,6 +456,8 @@ "@types/jsonfile": ["@types/jsonfile@6.1.4", "", { "dependencies": { "@types/node": "*" } }, "sha512-D5qGUYwjvnNNextdU59/+fI+spnwtTFmyQP0h+PfIOSkNfpU6AOICUOkm4i0OnSk+NyjdPJrxCDro0sJsWlRpQ=="], + "@types/marked": ["@types/marked@5.0.2", "", {}, "sha512-OucS4KMHhFzhz27KxmWg7J+kIYqyqoW5kdIEI319hqARQQUTqhao3M/F+uFnDXD0Rg72iDDZxZNxq5gvctmLlg=="], + "@types/mime": ["@types/mime@1.3.5", "", {}, "sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w=="], "@types/node": ["@types/node@24.9.2", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-uWN8YqxXxqFMX2RqGOrumsKeti4LlmIMIyV0lgut4jx7KQBcBiW6vkDtIBvHnHIquwNfJhk8v2OtmO8zXWHfPA=="], @@ -548,6 +556,8 @@ "body-parser": ["body-parser@2.2.0", "", { "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", "debug": "^4.4.0", "http-errors": "^2.0.0", "iconv-lite": "^0.6.3", "on-finished": "^2.4.1", "qs": "^6.14.0", "raw-body": "^3.0.0", "type-is": "^2.0.0" } }, "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg=="], + "boolbase": ["boolbase@1.0.0", "", {}, "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww=="], + "brace-expansion": ["brace-expansion@1.1.12", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg=="], "braces": ["braces@3.0.3", "", { "dependencies": { "fill-range": "^7.1.1" } }, "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA=="], @@ -586,6 +596,10 @@ "check-error": ["check-error@2.1.1", "", {}, "sha512-OAlb+T7V4Op9OwdkjmguYRqncdlx5JiofwOAUkmTF+jNdHwzTaTs4sRAGpzLF3oOz5xAyDGrPgeIDFQmDOTiJw=="], + "cheerio": ["cheerio@1.1.2", "", { "dependencies": { "cheerio-select": "^2.1.0", "dom-serializer": "^2.0.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "encoding-sniffer": "^0.2.1", "htmlparser2": "^10.0.0", "parse5": "^7.3.0", "parse5-htmlparser2-tree-adapter": "^7.1.0", "parse5-parser-stream": "^7.1.2", "undici": "^7.12.0", "whatwg-mimetype": "^4.0.0" } }, "sha512-IkxPpb5rS/d1IiLbHMgfPuS0FgiWTtFIm/Nj+2woXDLTZ7fOT2eqzgYbdMlLweqlHbsZjxEChoVK+7iph7jyQg=="], + + "cheerio-select": ["cheerio-select@2.1.0", "", { "dependencies": { "boolbase": "^1.0.0", "css-select": "^5.1.0", "css-what": "^6.1.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.0.1" } }, "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g=="], + "cjs-module-lexer": ["cjs-module-lexer@1.4.3", "", {}, "sha512-9z8TZaGM1pfswYeXrUpzPrkx8UnWYdhJclsiYMm6x/w5+nN+8Tf/LnAgfLGQCm59qAOxU8WwHEq2vNwF6i4j+Q=="], "cli-cursor": ["cli-cursor@4.0.0", "", { "dependencies": { "restore-cursor": "^4.0.0" } }, "sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg=="], @@ -626,6 +640,10 @@ "cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="], + "css-select": ["css-select@5.2.2", "", { "dependencies": { "boolbase": "^1.0.0", "css-what": "^6.1.0", "domhandler": "^5.0.2", "domutils": "^3.0.1", "nth-check": "^2.0.1" } }, "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw=="], + + "css-what": ["css-what@6.2.2", "", {}, "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA=="], + "csstype": ["csstype@3.1.3", "", {}, "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="], "date-fns": ["date-fns@4.1.0", "", {}, "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg=="], @@ -646,6 +664,14 @@ "depd": ["depd@2.0.0", "", {}, "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw=="], + "dom-serializer": ["dom-serializer@2.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.2", "entities": "^4.2.0" } }, "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg=="], + + "domelementtype": ["domelementtype@2.3.0", "", {}, "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="], + + "domhandler": ["domhandler@5.0.3", "", { "dependencies": { "domelementtype": "^2.3.0" } }, "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w=="], + + "domutils": ["domutils@3.2.2", "", { "dependencies": { "dom-serializer": "^2.0.0", "domelementtype": "^2.3.0", "domhandler": "^5.0.3" } }, "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw=="], + "dts-bundle-generator": ["dts-bundle-generator@9.5.1", "", { "dependencies": { "typescript": ">=5.0.2", "yargs": "^17.6.0" }, "bin": { "dts-bundle-generator": "dist/bin/dts-bundle-generator.js" } }, "sha512-DxpJOb2FNnEyOzMkG11sxO2dmxPjthoVWxfKqWYJ/bI/rT1rvTMktF5EKjAYrRZu6Z6t3NhOUZ0sZ5ZXevOfbA=="], "dunder-proto": ["dunder-proto@1.0.1", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" } }, "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A=="], @@ -660,6 +686,10 @@ "encodeurl": ["encodeurl@2.0.0", "", {}, "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg=="], + "encoding-sniffer": ["encoding-sniffer@0.2.1", "", { "dependencies": { "iconv-lite": "^0.6.3", "whatwg-encoding": "^3.1.1" } }, "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw=="], + + "entities": ["entities@4.5.0", "", {}, "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw=="], + "es-define-property": ["es-define-property@1.0.1", "", {}, "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g=="], "es-errors": ["es-errors@1.3.0", "", {}, "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw=="], @@ -790,6 +820,8 @@ "html-escaper": ["html-escaper@2.0.2", "", {}, "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg=="], + "htmlparser2": ["htmlparser2@10.0.0", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.1", "entities": "^6.0.0" } }, "sha512-TwAZM+zE5Tq3lrEHvOlvwgj1XLWQCtaaibSN11Q+gGBAS7Y1uZSWwXXRe4iF6OXnaq1riyQAPFOBtYc77Mxq0g=="], + "http-errors": ["http-errors@2.0.0", "", { "dependencies": { "depd": "2.0.0", "inherits": "2.0.4", "setprototypeof": "1.2.0", "statuses": "2.0.1", "toidentifier": "1.0.1" } }, "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ=="], "http-proxy-agent": ["http-proxy-agent@7.0.2", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="], @@ -912,6 +944,8 @@ "make-dir": ["make-dir@4.0.0", "", { "dependencies": { "semver": "^7.5.3" } }, "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw=="], + "marked": ["marked@17.0.1", "", { "bin": { "marked": "bin/marked.js" } }, "sha512-boeBdiS0ghpWcSwoNm/jJBwdpFaMnZWRzjA6SkUMYb40SVaN1x7mmfGKp0jvexGcx+7y2La5zRZsYFZI6Qpypg=="], + "math-intrinsics": ["math-intrinsics@1.1.0", "", {}, "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g=="], "media-typer": ["media-typer@1.1.0", "", {}, "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw=="], @@ -964,6 +998,8 @@ "npm-run-path": ["npm-run-path@5.3.0", "", { "dependencies": { "path-key": "^4.0.0" } }, "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ=="], + "nth-check": ["nth-check@2.1.1", "", { "dependencies": { "boolbase": "^1.0.0" } }, "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w=="], + "object-inspect": ["object-inspect@1.13.4", "", {}, "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew=="], "on-finished": ["on-finished@2.4.1", "", { "dependencies": { "ee-first": "1.1.1" } }, "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg=="], @@ -986,6 +1022,12 @@ "parent-module": ["parent-module@1.0.1", "", { "dependencies": { "callsites": "^3.0.0" } }, "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g=="], + "parse5": ["parse5@7.3.0", "", { "dependencies": { "entities": "^6.0.0" } }, "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw=="], + + "parse5-htmlparser2-tree-adapter": ["parse5-htmlparser2-tree-adapter@7.1.0", "", { "dependencies": { "domhandler": "^5.0.3", "parse5": "^7.0.0" } }, "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g=="], + + "parse5-parser-stream": ["parse5-parser-stream@7.1.2", "", { "dependencies": { "parse5": "^7.0.0" } }, "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow=="], + "parseurl": ["parseurl@1.3.3", "", {}, "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ=="], "path-exists": ["path-exists@4.0.0", "", {}, "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="], @@ -1214,6 +1256,10 @@ "webidl-conversions": ["webidl-conversions@7.0.0", "", {}, "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g=="], + "whatwg-encoding": ["whatwg-encoding@3.1.1", "", { "dependencies": { "iconv-lite": "0.6.3" } }, "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ=="], + + "whatwg-mimetype": ["whatwg-mimetype@4.0.0", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="], + "whatwg-url": ["whatwg-url@14.2.0", "", { "dependencies": { "tr46": "^5.1.0", "webidl-conversions": "^7.0.0" } }, "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw=="], "which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="], @@ -1348,6 +1394,8 @@ "glob/minimatch": ["minimatch@9.0.5", "", { "dependencies": { "brace-expansion": "^2.0.1" } }, "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow=="], + "htmlparser2/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "http-errors/statuses": ["statuses@2.0.1", "", {}, "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ=="], "lint-staged/chalk": ["chalk@5.3.0", "", {}, "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w=="], @@ -1358,6 +1406,8 @@ "openapi3-ts/yaml": ["yaml@2.8.1", "", { "bin": { "yaml": "bin.mjs" } }, "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw=="], + "parse5/entities": ["entities@6.0.1", "", {}, "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g=="], + "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "proxy-addr/ipaddr.js": ["ipaddr.js@1.9.1", "", {}, "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="], diff --git a/lib/worker/loadTool.ts b/lib/worker/loadTool.ts index b15aebff..99e1d2be 100644 --- a/lib/worker/loadTool.ts +++ b/lib/worker/loadTool.ts @@ -70,7 +70,6 @@ const LoadToolsDev = async (filename: string): Promise => { versionList: [], version: toolSetVersion }); - tools.push(...children); } else { // is not toolset @@ -80,7 +79,6 @@ const LoadToolsDev = async (filename: string): Promise => { const toolVersion = (rootMod as any).versionList ? generateToolVersion((rootMod as any).versionList) : generateToolVersion([]); - tools.push({ ...(rootMod as ToolType), tags: rootMod.tags || [ToolTagEnum.enum.other], diff --git a/modules/tool/api/upload/install.ts b/modules/tool/api/upload/install.ts index 8d015518..cb0d5171 100644 --- a/modules/tool/api/upload/install.ts +++ b/modules/tool/api/upload/install.ts @@ -47,7 +47,7 @@ export default s.route(contract.tool.upload.install, async ({ body }) => { ); await refreshVersionKey(SystemCacheKeyEnum.systemTool); - addLog.debug(`Success installed tools: ${toolIds}`); + addLog.info(`Success installed tools: ${toolIds}`); return { status: 200, diff --git a/modules/tool/loadToolProd.ts b/modules/tool/loadToolProd.ts index 4492effe..adc3c644 100644 --- a/modules/tool/loadToolProd.ts +++ b/modules/tool/loadToolProd.ts @@ -3,10 +3,31 @@ import type { ToolSetType, ToolType } from './type'; import { addLog } from '@/utils/log'; import { join } from 'path'; import { parseMod } from './parseMod'; +import { createHash } from 'crypto'; +import { readFile } from 'fs/promises'; // Load tool or toolset and its children export const LoadToolsByFilename = async (filename: string): Promise => { - const rootMod = (await import(join(toolsDir, filename))).default as ToolType | ToolSetType; + const filePath = join(toolsDir, filename); + + // Calculate file content hash for cache key + // Same content = same hash = reuse cache, reducing memory usage + const fileContent = await readFile(filePath); + const contentHash = createHash('md5').update(fileContent).digest('hex').slice(0, 8); + + // Clear module cache in Node.js to prevent memory leaks + // @ts-ignore - require.cache only exists in Node.js, not in Bun + if (typeof require !== 'undefined' && require.cache) { + // Try to delete cache entries (works for CJS modules) + delete require.cache[filePath]; + delete require.cache[require.resolve?.(filePath) || filePath]; + } + + // Use content hash as cache buster (works for ESM in both Node.js and Bun) + // This ensures same content reuses the same cached module + const modulePath = `${filePath}?v=${contentHash}`; + + const rootMod = (await import(modulePath)).default as ToolType | ToolSetType; if (!rootMod.toolId) { addLog.error(`Can not parse toolId, filename: ${filename}`); diff --git a/modules/tool/packages/seedream/bun.lock b/modules/tool/packages/seedream/bun.lock index ced69009..34d48ec3 100644 --- a/modules/tool/packages/seedream/bun.lock +++ b/modules/tool/packages/seedream/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "@fastgpt-plugins/tool-seedream", diff --git a/modules/tool/packages/wechatOfficialAccount/README.md b/modules/tool/packages/wechatOfficialAccount/README.md index 6ca71e4e..5d3e6093 100644 --- a/modules/tool/packages/wechatOfficialAccount/README.md +++ b/modules/tool/packages/wechatOfficialAccount/README.md @@ -1,4 +1,5 @@ # 获取密钥 +在[微信开发者平台](https://developers.weixin.qq.com) 登陆,选择“公众号” 按照如图所示的方式获取密钥 ![](./assets/get-secrets.jpg) diff --git a/modules/tool/packages/wechatOfficialAccount/assets/get-secrets.jpg b/modules/tool/packages/wechatOfficialAccount/assets/get-secrets.jpg index 6bf4c2ac..1227ada4 100644 Binary files a/modules/tool/packages/wechatOfficialAccount/assets/get-secrets.jpg and b/modules/tool/packages/wechatOfficialAccount/assets/get-secrets.jpg differ diff --git a/modules/tool/packages/wechatOfficialAccount/bun.lock b/modules/tool/packages/wechatOfficialAccount/bun.lock index 0663dd4e..90565526 100644 --- a/modules/tool/packages/wechatOfficialAccount/bun.lock +++ b/modules/tool/packages/wechatOfficialAccount/bun.lock @@ -1,5 +1,6 @@ { "lockfileVersion": 1, + "configVersion": 0, "workspaces": { "": { "name": "@fastgpt-plugins/tool-wechat-ofi-account", diff --git a/modules/tool/packages/wechatOfficialAccount/children/getDraftList/config.ts b/modules/tool/packages/wechatOfficialAccount/children/getDraftList/config.ts new file mode 100644 index 00000000..b8e30fb9 --- /dev/null +++ b/modules/tool/packages/wechatOfficialAccount/children/getDraftList/config.ts @@ -0,0 +1,100 @@ +import { defineTool } from '@tool/type'; +import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; + +export default defineTool({ + name: { + 'zh-CN': '获取草稿箱文章列表', + en: 'Get Draft List' + }, + description: { + 'zh-CN': '获取微信公众号草稿箱中的文章列表,支持分页查询', + en: 'Get the list of draft articles in WeChat Official Account draft box with pagination support' + }, + toolDescription: + '获取微信公众号草稿箱中的文章列表。支持分页查询,可设置偏移量和每页数量。返回的草稿信息包括标题、作者、摘要、封面图等基本信息,可选择是否返回完整的文章内容。', + versionList: [ + { + value: '0.1.0', + description: 'Default version', + inputs: [ + { + key: 'accessToken', + label: '访问令牌', + description: '微信公众号 API 访问令牌(可选,与 appId/secret 二选一)', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + required: false + }, + { + key: 'appId', + label: 'AppID', + description: '微信公众号 AppID(与 secret 配合使用,或使用 accessToken)', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + required: false + }, + { + key: 'secret', + label: 'AppSecret', + description: '微信公众号 AppSecret(与 appId 配合使用,或使用 accessToken)', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + required: false + }, + { + key: 'offset', + label: '偏移量', + description: '从全部素材的该偏移位置开始返回,0 表示从第一个素材返回,默认为 0', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.number, + required: false, + toolDescription: 'offset for pagination, 0 means start from the first item' + }, + { + key: 'count', + label: '返回数量', + description: '返回素材的数量,取值范围在 1 到 20 之间,默认为 20', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.number, + required: false, + toolDescription: 'number of items to return, between 1 and 20' + }, + { + key: 'noContent', + label: '不返回内容', + description: '是否不返回文章内容字段,1 表示不返回,0 表示返回,默认为 0', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.number, + required: false, + toolDescription: '1 means no content field returned, 0 means content field returned' + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.number, + key: 'total_count', + label: '草稿总数', + description: '草稿箱中的草稿总数量' + }, + { + valueType: WorkflowIOValueTypeEnum.number, + key: 'item_count', + label: '本次返回数量', + description: '本次返回的草稿数量' + }, + { + valueType: WorkflowIOValueTypeEnum.object, + key: 'item', + label: '草稿列表', + description: '草稿文章列表数组,每个元素包含 media_id 和文章信息' + }, + { + valueType: WorkflowIOValueTypeEnum.string, + key: 'error_message', + label: '错误信息', + description: '处理过程中的错误信息' + } + ] + } + ] +}); diff --git a/modules/tool/packages/wechatOfficialAccount/children/getDraftList/index.ts b/modules/tool/packages/wechatOfficialAccount/children/getDraftList/index.ts new file mode 100644 index 00000000..d698ed48 --- /dev/null +++ b/modules/tool/packages/wechatOfficialAccount/children/getDraftList/index.ts @@ -0,0 +1,10 @@ +import config from './config'; +import { InputType, OutputType, tool as toolCb } from './src'; +import { exportTool } from '@tool/utils/tool'; + +export default exportTool({ + toolCb, + InputType, + OutputType, + config +}); diff --git a/modules/tool/packages/wechatOfficialAccount/children/getDraftList/src/index.ts b/modules/tool/packages/wechatOfficialAccount/children/getDraftList/src/index.ts new file mode 100644 index 00000000..1874ebff --- /dev/null +++ b/modules/tool/packages/wechatOfficialAccount/children/getDraftList/src/index.ts @@ -0,0 +1,84 @@ +import { z } from 'zod'; +import { handleGetAuthToken, handleBatchGetDraft } from '../../../lib/handler'; + +export const InputType = z + .object({ + // 认证参数(二选一) + accessToken: z.string().optional(), + appId: z.string().optional(), + secret: z.string().optional(), + + // 查询参数 + offset: z.number().int().min(0).optional().default(0), + count: z.number().int().min(1).max(20).optional().default(20), + noContent: z.number().int().min(0).max(1).optional() + }) + .refine( + (data) => { + // 验证认证参数:要么提供 accessToken,要么同时提供 appId 和 secret + return data.accessToken || (data.appId && data.secret); + }, + { + message: '必须提供 accessToken,或者同时提供 appId 和 secret', + path: ['认证参数'] + } + ); + +export const OutputType = z.object({ + total_count: z.number().optional(), + item_count: z.number().optional(), + item: z.array(z.any()).optional(), + error_message: z.string().optional() +}); + +export async function tool({ + accessToken, + appId, + secret, + offset = 0, + count = 20, + noContent +}: z.infer): Promise> { + // 1. 获取 access_token + let token = accessToken; + if (!token) { + const result = await handleGetAuthToken({ + grant_type: 'client_credential', + appid: appId!, + secret: secret! + }); + + if ('access_token' in result && result.access_token) { + token = result.access_token; + } else { + const errorMsg = (result as any).errmsg || '未知错误'; + return { + error_message: `获取 access_token 失败: ${errorMsg} (错误码: ${(result as any).errcode})` + }; + } + } + + // 2. 获取草稿列表 + const params: { + access_token: string; + offset: number; + count: number; + no_content?: number; + } = { + access_token: token, + offset, + count + }; + + if (noContent !== undefined) { + params.no_content = noContent; + } + + const result = await handleBatchGetDraft(params); + + return { + total_count: result.total_count, + item_count: result.item_count, + item: result.item + }; +} diff --git a/modules/tool/packages/wechatOfficialAccount/children/uploadMarkdownToDraft/config.ts b/modules/tool/packages/wechatOfficialAccount/children/uploadMarkdownToDraft/config.ts index e5698103..fced63e9 100644 --- a/modules/tool/packages/wechatOfficialAccount/children/uploadMarkdownToDraft/config.ts +++ b/modules/tool/packages/wechatOfficialAccount/children/uploadMarkdownToDraft/config.ts @@ -1,6 +1,242 @@ import { defineTool } from '@tool/type'; import { FlowNodeInputTypeEnum, WorkflowIOValueTypeEnum } from '@tool/type/fastgpt'; +const v2 = { + inputs: [ + { + key: 'accessToken', + label: '访问令牌', + description: '微信公众号 API 访问令牌(可选,与 appId/appSecret 二选一)', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + required: false + }, + { + key: 'markdownContent', + label: 'Markdown 内容', + description: '要转换的 Markdown 格式文章内容,支持单个字符串或字符串数组(多篇文档)', + renderTypeList: [ + FlowNodeInputTypeEnum.reference, + FlowNodeInputTypeEnum.textarea, + FlowNodeInputTypeEnum.JSONEditor + ], + valueType: WorkflowIOValueTypeEnum.string, + toolDescription: 'markdown format content or array of markdown contents', + required: true + }, + { + key: 'coverImage', + label: '封面图', + description: + '封面图片 URL 或 media_id,如果是 URL 将自动上传为永久素材。支持单个字符串或字符串数组(多篇文档对应多个封面图)', + renderTypeList: [ + FlowNodeInputTypeEnum.input, + FlowNodeInputTypeEnum.reference, + FlowNodeInputTypeEnum.JSONEditor + ], + valueType: WorkflowIOValueTypeEnum.string, + required: true, + toolDescription: 'cover image url or media_id or array of cover images' + }, + { + key: 'title', + label: '文章标题', + description: '图文消息的标题,支持单个字符串或字符串数组。如果不填写将尝试从 Markdown 中提取', + renderTypeList: [ + FlowNodeInputTypeEnum.input, + FlowNodeInputTypeEnum.reference, + FlowNodeInputTypeEnum.JSONEditor + ], + valueType: WorkflowIOValueTypeEnum.string, + required: true, + toolDescription: 'article title or array of article titles' + }, + { + key: 'author', + label: '作者', + description: '文章作者信息,支持单个字符串或字符串数组', + renderTypeList: [ + FlowNodeInputTypeEnum.input, + FlowNodeInputTypeEnum.reference, + FlowNodeInputTypeEnum.JSONEditor + ], + valueType: WorkflowIOValueTypeEnum.string, + required: false, + toolDescription: 'article author or array of authors' + }, + { + key: 'digest', + label: '文章摘要', + description: '文章摘要信息,如果不填写将自动从内容中提取。支持单个字符串或字符串数组', + renderTypeList: [ + FlowNodeInputTypeEnum.reference, + FlowNodeInputTypeEnum.textarea, + FlowNodeInputTypeEnum.JSONEditor + ], + valueType: WorkflowIOValueTypeEnum.string, + required: false, + toolDescription: 'article digest or array of digests, optional, less than 120 characters each' + }, + { + key: 'contentSourceUrl', + label: '原文链接', + description: '原文阅读链接地址,支持单个字符串或字符串数组', + renderTypeList: [ + FlowNodeInputTypeEnum.input, + FlowNodeInputTypeEnum.reference, + FlowNodeInputTypeEnum.JSONEditor + ], + valueType: WorkflowIOValueTypeEnum.string, + required: false, + toolDescription: 'original article link or array of links' + }, + { + key: 'needOpenComment', + label: '开启评论', + description: '是否开启评论功能,0 表示关闭,1 表示开启。支持单个数字或数字数组', + renderTypeList: [ + FlowNodeInputTypeEnum.input, + FlowNodeInputTypeEnum.reference, + FlowNodeInputTypeEnum.JSONEditor + ], + valueType: WorkflowIOValueTypeEnum.number, + required: false + }, + { + key: 'onlyFansCanComment', + label: '仅粉丝评论', + description: + '是否仅允许粉丝评论,0 表示所有人可评论,1 表示仅粉丝可评论。支持单个数字或数字数组', + renderTypeList: [ + FlowNodeInputTypeEnum.input, + FlowNodeInputTypeEnum.reference, + FlowNodeInputTypeEnum.JSONEditor + ], + valueType: WorkflowIOValueTypeEnum.number, + required: false + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.string, + key: 'media_id', + label: '素材ID', + description: '草稿箱中图文消息的媒体标识符' + }, + { + valueType: WorkflowIOValueTypeEnum.string, + key: 'error_message', + label: '错误信息', + description: '处理过程中的错误信息' + } + ] +}; + +const v1 = { + inputs: [ + { + key: 'accessToken', + label: '访问令牌', + description: '微信公众号 API 访问令牌(可选,与 appId/appSecret 二选一)', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + required: false + }, + { + key: 'markdownContent', + label: 'Markdown 内容', + description: '要转换的 Markdown 格式文章内容', + renderTypeList: [ + FlowNodeInputTypeEnum.input, + FlowNodeInputTypeEnum.reference, + FlowNodeInputTypeEnum.textarea + ], + valueType: WorkflowIOValueTypeEnum.string, + toolDescription: 'markdown format content', + required: true + }, + { + key: 'coverImage', + label: '封面图', + description: '封面图片 URL 或 media_id,如果是 URL 将自动上传为永久素材', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + required: true, + toolDescription: 'cover image url or media_id' + }, + { + key: 'title', + label: '文章标题', + description: '图文消息的标题,如果不填写将尝试从 Markdown 中提取', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + required: true, + toolDescription: 'article title' + }, + { + key: 'author', + label: '作者', + description: '文章作者信息', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + required: false, + toolDescription: 'article author' + }, + { + key: 'digest', + label: '文章摘要', + description: '文章摘要信息,如果不填写将自动从内容中提取', + renderTypeList: [ + FlowNodeInputTypeEnum.input, + FlowNodeInputTypeEnum.reference, + FlowNodeInputTypeEnum.textarea + ], + valueType: WorkflowIOValueTypeEnum.string, + required: false, + toolDescription: 'article digest, optional, less than 120 characters' + }, + { + key: 'contentSourceUrl', + label: '原文链接', + description: '原文阅读链接地址', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.string, + required: false, + toolDescription: 'original article link' + }, + { + key: 'needOpenComment', + label: '开启评论', + description: '是否开启评论功能,0 表示关闭,1 表示开启', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.number, + required: false + }, + { + key: 'onlyFansCanComment', + label: '仅粉丝评论', + description: '是否仅允许粉丝评论,0 表示所有人可评论,1 表示仅粉丝可评论', + renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], + valueType: WorkflowIOValueTypeEnum.number, + required: false + } + ], + outputs: [ + { + valueType: WorkflowIOValueTypeEnum.string, + key: 'media_id', + label: '素材ID', + description: '草稿箱中图文消息的媒体标识符' + }, + { + valueType: WorkflowIOValueTypeEnum.string, + key: 'error_message', + label: '错误信息', + description: '处理过程中的错误信息' + } + ] +}; + export default defineTool({ name: { 'zh-CN': '上传 Markdown 到草稿箱', @@ -13,245 +249,20 @@ export default defineTool({ toolDescription: '将 Markdown 内容转换为微信公众号图文消息格式,自动处理图片上传和封面图,然后保存到草稿箱。支持标题、作者、摘要等信息的自定义配置。', versionList: [ + { + value: '0.2.1', + description: '修复批量上传参数', + ...v2 + }, { value: '0.2.0', description: '批量上传版本(支持多篇文档)', - inputs: [ - { - key: 'accessToken', - label: '访问令牌', - description: '微信公众号 API 访问令牌(可选,与 appId/appSecret 二选一)', - renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], - valueType: WorkflowIOValueTypeEnum.string, - required: false - }, - { - key: 'markdownContent', - label: 'Markdown 内容', - description: '要转换的 Markdown 格式文章内容,支持单个字符串或字符串数组(多篇文档)', - renderTypeList: [ - FlowNodeInputTypeEnum.reference, - FlowNodeInputTypeEnum.textarea, - FlowNodeInputTypeEnum.JSONEditor - ], - valueType: WorkflowIOValueTypeEnum.string, - toolDescription: 'markdown format content or array of markdown contents', - required: true - }, - { - key: 'coverImage', - label: '封面图', - description: - '封面图片 URL 或 media_id,如果是 URL 将自动上传为永久素材。支持单个字符串或字符串数组(多篇文档对应多个封面图)', - renderTypeList: [ - FlowNodeInputTypeEnum.input, - FlowNodeInputTypeEnum.reference, - FlowNodeInputTypeEnum.JSONEditor - ], - valueType: WorkflowIOValueTypeEnum.string, - required: true, - toolDescription: 'cover image url or media_id or array of cover images' - }, - { - key: 'title', - label: '文章标题', - description: - '图文消息的标题,支持单个字符串或字符串数组。如果不填写将尝试从 Markdown 中提取', - renderTypeList: [ - FlowNodeInputTypeEnum.input, - FlowNodeInputTypeEnum.reference, - FlowNodeInputTypeEnum.JSONEditor - ], - valueType: WorkflowIOValueTypeEnum.string, - required: true, - toolDescription: 'article title or array of article titles' - }, - { - key: 'author', - label: '作者', - description: '文章作者信息,支持单个字符串或字符串数组', - renderTypeList: [ - FlowNodeInputTypeEnum.input, - FlowNodeInputTypeEnum.reference, - FlowNodeInputTypeEnum.JSONEditor - ], - valueType: WorkflowIOValueTypeEnum.string, - required: false, - toolDescription: 'article author or array of authors' - }, - { - key: 'digest', - label: '文章摘要', - description: '文章摘要信息,如果不填写将自动从内容中提取。支持单个字符串或字符串数组', - renderTypeList: [ - FlowNodeInputTypeEnum.reference, - FlowNodeInputTypeEnum.textarea, - FlowNodeInputTypeEnum.JSONEditor - ], - valueType: WorkflowIOValueTypeEnum.string, - required: false, - toolDescription: - 'article digest or array of digests, optional, less than 120 characters each' - }, - { - key: 'contentSourceUrl', - label: '原文链接', - description: '原文阅读链接地址,支持单个字符串或字符串数组', - renderTypeList: [ - FlowNodeInputTypeEnum.input, - FlowNodeInputTypeEnum.reference, - FlowNodeInputTypeEnum.JSONEditor - ], - valueType: WorkflowIOValueTypeEnum.string, - required: false, - toolDescription: 'original article link or array of links' - }, - { - key: 'needOpenComment', - label: '开启评论', - description: '是否开启评论功能,0 表示关闭,1 表示开启。支持单个数字或数字数组', - renderTypeList: [ - FlowNodeInputTypeEnum.input, - FlowNodeInputTypeEnum.reference, - FlowNodeInputTypeEnum.JSONEditor - ], - valueType: WorkflowIOValueTypeEnum.number, - required: false - }, - { - key: 'onlyFansCanComment', - label: '仅粉丝评论', - description: - '是否仅允许粉丝评论,0 表示所有人可评论,1 表示仅粉丝可评论。支持单个数字或数字数组', - renderTypeList: [ - FlowNodeInputTypeEnum.input, - FlowNodeInputTypeEnum.reference, - FlowNodeInputTypeEnum.JSONEditor - ], - valueType: WorkflowIOValueTypeEnum.number, - required: false - } - ], - outputs: [ - { - valueType: WorkflowIOValueTypeEnum.string, - key: 'media_id', - label: '素材ID', - description: '草稿箱中图文消息的媒体标识符' - }, - { - valueType: WorkflowIOValueTypeEnum.string, - key: 'error_message', - label: '错误信息', - description: '处理过程中的错误信息' - } - ] + ...v2 }, { value: '0.1.0', description: '单篇文章上传版本', - inputs: [ - { - key: 'accessToken', - label: '访问令牌', - description: '微信公众号 API 访问令牌(可选,与 appId/appSecret 二选一)', - renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], - valueType: WorkflowIOValueTypeEnum.string, - required: false - }, - { - key: 'markdownContent', - label: 'Markdown 内容', - description: '要转换的 Markdown 格式文章内容', - renderTypeList: [ - FlowNodeInputTypeEnum.input, - FlowNodeInputTypeEnum.reference, - FlowNodeInputTypeEnum.textarea - ], - valueType: WorkflowIOValueTypeEnum.string, - toolDescription: 'markdown format content', - required: true - }, - { - key: 'coverImage', - label: '封面图', - description: '封面图片 URL 或 media_id,如果是 URL 将自动上传为永久素材', - renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], - valueType: WorkflowIOValueTypeEnum.string, - required: true, - toolDescription: 'cover image url or media_id' - }, - { - key: 'title', - label: '文章标题', - description: '图文消息的标题,如果不填写将尝试从 Markdown 中提取', - renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], - valueType: WorkflowIOValueTypeEnum.string, - required: true, - toolDescription: 'article title' - }, - { - key: 'author', - label: '作者', - description: '文章作者信息', - renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], - valueType: WorkflowIOValueTypeEnum.string, - required: false, - toolDescription: 'article author' - }, - { - key: 'digest', - label: '文章摘要', - description: '文章摘要信息,如果不填写将自动从内容中提取', - renderTypeList: [ - FlowNodeInputTypeEnum.input, - FlowNodeInputTypeEnum.reference, - FlowNodeInputTypeEnum.textarea - ], - valueType: WorkflowIOValueTypeEnum.string, - required: false, - toolDescription: 'article digest, optional, less than 120 characters' - }, - { - key: 'contentSourceUrl', - label: '原文链接', - description: '原文阅读链接地址', - renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], - valueType: WorkflowIOValueTypeEnum.string, - required: false, - toolDescription: 'original article link' - }, - { - key: 'needOpenComment', - label: '开启评论', - description: '是否开启评论功能,0 表示关闭,1 表示开启', - renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], - valueType: WorkflowIOValueTypeEnum.number, - required: false - }, - { - key: 'onlyFansCanComment', - label: '仅粉丝评论', - description: '是否仅允许粉丝评论,0 表示所有人可评论,1 表示仅粉丝可评论', - renderTypeList: [FlowNodeInputTypeEnum.input, FlowNodeInputTypeEnum.reference], - valueType: WorkflowIOValueTypeEnum.number, - required: false - } - ], - outputs: [ - { - valueType: WorkflowIOValueTypeEnum.string, - key: 'media_id', - label: '素材ID', - description: '草稿箱中图文消息的媒体标识符' - }, - { - valueType: WorkflowIOValueTypeEnum.string, - key: 'error_message', - label: '错误信息', - description: '处理过程中的错误信息' - } - ] + ...v1 } ] }); diff --git a/modules/tool/packages/wechatOfficialAccount/children/uploadMarkdownToDraft/src/index.ts b/modules/tool/packages/wechatOfficialAccount/children/uploadMarkdownToDraft/src/index.ts index d2f76701..0949e4aa 100644 --- a/modules/tool/packages/wechatOfficialAccount/children/uploadMarkdownToDraft/src/index.ts +++ b/modules/tool/packages/wechatOfficialAccount/children/uploadMarkdownToDraft/src/index.ts @@ -8,7 +8,6 @@ import { downloadImageFromUrl } from '../../../lib/handler'; import { addInlineStyles } from './styles'; -import { addLog } from '@/utils/log'; // 辅助函数:解析字符串或字符串数组,支持 JSON 编码的数组 function parseStringOrArray(val: unknown): string[] { @@ -38,6 +37,16 @@ function parseStringOrArray(val: unknown): string[] { // 辅助类型:支持字符串或字符串数组(用于输入验证) const StringOrArray = z.union([z.string(), z.array(z.string())]); +// 辅助类型:支持数字、字符串(可解析为数字)或它们的数组 +const NumberOrStringArray = z + .union([z.number(), z.string(), z.array(z.union([z.number(), z.string()]))]) + .transform((val) => { + if (Array.isArray(val)) { + return val.map((item) => (typeof item === 'string' ? parseInt(item, 10) : item)); + } + return typeof val === 'string' ? parseInt(val, 10) : val; + }); + export const InputType = z .object({ // 认证参数(二选一) @@ -70,14 +79,8 @@ export const InputType = z author: StringOrArray.optional(), digest: StringOrArray.optional(), contentSourceUrl: StringOrArray.optional(), - needOpenComment: z - .union([z.number(), z.array(z.number())]) - .optional() - .default(0), - onlyFansCanComment: z - .union([z.number(), z.array(z.number())]) - .optional() - .default(0) + needOpenComment: NumberOrStringArray.optional().default(0), + onlyFansCanComment: NumberOrStringArray.optional().default(0) }) .refine( (data) => { @@ -108,7 +111,6 @@ export async function tool({ needOpenComment = 0, onlyFansCanComment = 0 }: z.infer): Promise> { - addLog.info(`${markdownContent}${typeof markdownContent}`); // 1. 获取 access_token let token = accessToken; if (!token) { @@ -248,7 +250,16 @@ async function processSingleArticle({ for (const imageUrl of imageUrls) { try { const wechatImageUrl = await uploadImageToWeChat(token, imageUrl); - processedHtml = processedHtml.replace(imageUrl, wechatImageUrl); + // HTML 中的 URL 是编码后的(& 变成 &),所以替换时也需要使用编码后的 URL + const encodedUrl = imageUrl + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/"/g, '"') + .replace(/'/g, '''); + // 使用正则表达式全局替换,确保同一图片 URL 的所有出现都被替换 + const escapedUrl = encodedUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); + processedHtml = processedHtml.replace(new RegExp(escapedUrl, 'g'), wechatImageUrl); } catch (error) { console.warn(`上传图片失败: ${imageUrl}`, error); // 保持原链接,继续处理其他图片 @@ -325,6 +336,7 @@ function sanitizeAndAddStyles(html: string): string { /** * 提取图片链接 + * 注意:HTML 中的 & 会被转义为 &,需要解码 */ function extractImageUrls(html: string): string[] { const imgRegex = /]+src="([^"]+)"/g; @@ -332,7 +344,14 @@ function extractImageUrls(html: string): string[] { let match; while ((match = imgRegex.exec(html)) !== null) { - urls.push(match[1]); + // 解码 HTML 实体,将 & 转回 & + const decodedUrl = match[1] + .replace(/&/g, '&') + .replace(/</g, '<') + .replace(/>/g, '>') + .replace(/"/g, '"') + .replace(/'/g, "'"); + urls.push(decodedUrl); } return urls; diff --git a/package.json b/package.json index b80ceec3..3ee60b85 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,10 @@ "dependencies": { "@ts-rest/core": "3.52.1", "@ts-rest/express": "3.52.1", - "@ts-rest/open-api": "3.52.1" + "@ts-rest/open-api": "3.52.1", + "@types/cheerio": "^0.22.35", + "@types/marked": "^5.0.2", + "cheerio": "^1.1.0", + "marked": "^17.0.1" } }