feat: add vision, TTS, and browser tools (17 tools total)

- VisionTool: image analysis via Z.AI GLM-4V multimodal API
- TTSTool: text-to-speech via node-edge-tts (free, auto-sends audio to chat)
- BrowserTool: web page content extraction via cheerio (strips HTML, extracts text)
- All 3 wired into tools/index.js + bot tool definitions + handlers
- TTS handler auto-sends generated audio as voice message to chat
This commit is contained in:
admin
2026-05-05 16:52:12 +00:00
Unverified
parent d7f1e3db90
commit e92e9f5b9d
7 changed files with 793 additions and 0 deletions

506
package-lock.json generated
View File

@@ -13,6 +13,7 @@
"@grammyjs/runner": "^2.0.3", "@grammyjs/runner": "^2.0.3",
"axios": "^1.14.0", "axios": "^1.14.0",
"chalk": "^5.4.0", "chalk": "^5.4.0",
"cheerio": "^1.2.0",
"commander": "^12.0.0", "commander": "^12.0.0",
"discord.js": "^14.26.4", "discord.js": "^14.26.4",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
@@ -21,6 +22,7 @@
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"glob": "^13.0.6", "glob": "^13.0.6",
"grammy": "^1.42.0", "grammy": "^1.42.0",
"node-edge-tts": "^1.2.10",
"openai": "^4.77.0", "openai": "^4.77.0",
"p-queue": "^8.0.1", "p-queue": "^8.0.1",
"winston": "^3.13.0", "winston": "^3.13.0",
@@ -382,6 +384,15 @@
"node": ">= 0.6" "node": ">= 0.6"
} }
}, },
"node_modules/agent-base": {
"version": "7.1.4",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
"license": "MIT",
"engines": {
"node": ">= 14"
}
},
"node_modules/agentkeepalive": { "node_modules/agentkeepalive": {
"version": "4.6.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz",
@@ -394,6 +405,30 @@
"node": ">= 8.0.0" "node": ">= 8.0.0"
} }
}, },
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/array-flatten": { "node_modules/array-flatten": {
"version": "1.1.1", "version": "1.1.1",
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
@@ -509,6 +544,12 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/boolbase": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
"integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
"license": "ISC"
},
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "5.0.5", "version": "5.0.5",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz",
@@ -565,6 +606,71 @@
"url": "https://github.com/chalk/chalk?sponsor=1" "url": "https://github.com/chalk/chalk?sponsor=1"
} }
}, },
"node_modules/cheerio": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-1.2.0.tgz",
"integrity": "sha512-WDrybc/gKFpTYQutKIK6UvfcuxijIZfMfXaYm8NMsPQxSYvf+13fXUJ4rztGGbJcBQ/GF55gvrZ0Bc0bj/mqvg==",
"license": "MIT",
"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.1.0",
"parse5": "^7.3.0",
"parse5-htmlparser2-tree-adapter": "^7.1.0",
"parse5-parser-stream": "^7.1.2",
"undici": "^7.19.0",
"whatwg-mimetype": "^4.0.0"
},
"engines": {
"node": ">=20.18.1"
},
"funding": {
"url": "https://github.com/cheeriojs/cheerio?sponsor=1"
}
},
"node_modules/cheerio-select": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/cheerio-select/-/cheerio-select-2.1.0.tgz",
"integrity": "sha512-9v9kG0LvzrlcungtnJtpGNxY+fzECQKhK4EGJX2vByejiMX84MFNQw4UxPJl3bFbTMw+Dfs37XaIkCwTZfLh4g==",
"license": "BSD-2-Clause",
"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"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/cheerio/node_modules/undici": {
"version": "7.25.0",
"resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz",
"integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==",
"license": "MIT",
"engines": {
"node": ">=20.18.1"
}
},
"node_modules/cliui": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
"integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
"license": "ISC",
"dependencies": {
"string-width": "^4.2.0",
"strip-ansi": "^6.0.1",
"wrap-ansi": "^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/color": { "node_modules/color": {
"version": "5.0.3", "version": "5.0.3",
"resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz", "resolved": "https://registry.npmjs.org/color/-/color-5.0.3.tgz",
@@ -578,6 +684,24 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"license": "MIT"
},
"node_modules/color-string": { "node_modules/color-string": {
"version": "2.1.4", "version": "2.1.4",
"resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz", "resolved": "https://registry.npmjs.org/color-string/-/color-string-2.1.4.tgz",
@@ -683,6 +807,34 @@
"node": ">= 8" "node": ">= 8"
} }
}, },
"node_modules/css-select": {
"version": "5.2.2",
"resolved": "https://registry.npmjs.org/css-select/-/css-select-5.2.2.tgz",
"integrity": "sha512-TizTzUddG/xYLA3NXodFM0fSbNizXjOKhqiQQwvhlspadZokn1KDy0NZFS0wuEubIYAV5/c1/lAr0TaaFXEXzw==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0",
"css-what": "^6.1.0",
"domhandler": "^5.0.2",
"domutils": "^3.0.1",
"nth-check": "^2.0.1"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/css-what": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz",
"integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">= 6"
},
"funding": {
"url": "https://github.com/sponsors/fb55"
}
},
"node_modules/debug": { "node_modules/debug": {
"version": "4.4.3", "version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
@@ -760,6 +912,61 @@
"url": "https://github.com/discordjs/discord.js?sponsor" "url": "https://github.com/discordjs/discord.js?sponsor"
} }
}, },
"node_modules/dom-serializer": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
"integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.2",
"entities": "^4.2.0"
},
"funding": {
"url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
}
},
"node_modules/domelementtype": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
"integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "BSD-2-Clause"
},
"node_modules/domhandler": {
"version": "5.0.3",
"resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
"integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
"license": "BSD-2-Clause",
"dependencies": {
"domelementtype": "^2.3.0"
},
"engines": {
"node": ">= 4"
},
"funding": {
"url": "https://github.com/fb55/domhandler?sponsor=1"
}
},
"node_modules/domutils": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz",
"integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==",
"license": "BSD-2-Clause",
"dependencies": {
"dom-serializer": "^2.0.0",
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3"
},
"funding": {
"url": "https://github.com/fb55/domutils?sponsor=1"
}
},
"node_modules/dotenv": { "node_modules/dotenv": {
"version": "16.6.1", "version": "16.6.1",
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz",
@@ -790,6 +997,12 @@
"integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==",
"license": "MIT" "license": "MIT"
}, },
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"license": "MIT"
},
"node_modules/enabled": { "node_modules/enabled": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz", "resolved": "https://registry.npmjs.org/enabled/-/enabled-2.0.0.tgz",
@@ -805,6 +1018,31 @@
"node": ">= 0.8" "node": ">= 0.8"
} }
}, },
"node_modules/encoding-sniffer": {
"version": "0.2.1",
"resolved": "https://registry.npmjs.org/encoding-sniffer/-/encoding-sniffer-0.2.1.tgz",
"integrity": "sha512-5gvq20T6vfpekVtqrYQsSCFZ1wEg5+wW0/QaZMWkFr6BqD3NfKs0rLCx4rrVlSWJeZb5NBJgVLswK/w2MWU+Gw==",
"license": "MIT",
"dependencies": {
"iconv-lite": "^0.6.3",
"whatwg-encoding": "^3.1.1"
},
"funding": {
"url": "https://github.com/fb55/encoding-sniffer?sponsor=1"
}
},
"node_modules/entities": {
"version": "4.5.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
"integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/es-define-property": { "node_modules/es-define-property": {
"version": "1.0.1", "version": "1.0.1",
"license": "MIT", "license": "MIT",
@@ -842,6 +1080,15 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/escalade": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
"integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/escape-html": { "node_modules/escape-html": {
"version": "1.0.3", "version": "1.0.3",
"resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz",
@@ -1113,6 +1360,15 @@
"url": "https://github.com/sponsors/ljharb" "url": "https://github.com/sponsors/ljharb"
} }
}, },
"node_modules/get-caller-file": {
"version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
"license": "ISC",
"engines": {
"node": "6.* || 8.* || >= 10.*"
}
},
"node_modules/get-intrinsic": { "node_modules/get-intrinsic": {
"version": "1.3.0", "version": "1.3.0",
"license": "MIT", "license": "MIT",
@@ -1241,6 +1497,37 @@
"node": ">= 0.4" "node": ">= 0.4"
} }
}, },
"node_modules/htmlparser2": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-10.1.0.tgz",
"integrity": "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ==",
"funding": [
"https://github.com/fb55/htmlparser2?sponsor=1",
{
"type": "github",
"url": "https://github.com/sponsors/fb55"
}
],
"license": "MIT",
"dependencies": {
"domelementtype": "^2.3.0",
"domhandler": "^5.0.3",
"domutils": "^3.2.2",
"entities": "^7.0.1"
}
},
"node_modules/htmlparser2/node_modules/entities": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
"integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/http-errors": { "node_modules/http-errors": {
"version": "2.0.1", "version": "2.0.1",
"license": "MIT", "license": "MIT",
@@ -1259,6 +1546,19 @@
"url": "https://opencollective.com/express" "url": "https://opencollective.com/express"
} }
}, },
"node_modules/https-proxy-agent": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.2",
"debug": "4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/human-signals": { "node_modules/human-signals": {
"version": "8.0.1", "version": "8.0.1",
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-8.0.1.tgz",
@@ -1277,6 +1577,18 @@
"ms": "^2.0.0" "ms": "^2.0.0"
} }
}, },
"node_modules/iconv-lite": {
"version": "0.6.3",
"resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
"integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
"license": "MIT",
"dependencies": {
"safer-buffer": ">= 2.1.2 < 3.0.0"
},
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/inherits": { "node_modules/inherits": {
"version": "2.0.4", "version": "2.0.4",
"license": "ISC" "license": "ISC"
@@ -1288,6 +1600,15 @@
"node": ">= 0.10" "node": ">= 0.10"
} }
}, },
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-plain-obj": { "node_modules/is-plain-obj": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-4.1.0.tgz",
@@ -1527,6 +1848,20 @@
"node": ">=10.5.0" "node": ">=10.5.0"
} }
}, },
"node_modules/node-edge-tts": {
"version": "1.2.10",
"resolved": "https://registry.npmjs.org/node-edge-tts/-/node-edge-tts-1.2.10.tgz",
"integrity": "sha512-bV2i4XU54D45+US0Zm1HcJRkifuB3W438dWyuJEHLQdKxnuqlI1kim2MOvR6Q3XUQZvfF9PoDyR1Rt7aeXhPdQ==",
"license": "MIT",
"dependencies": {
"https-proxy-agent": "^7.0.1",
"ws": "^8.13.0",
"yargs": "^17.7.2"
},
"bin": {
"node-edge-tts": "bin.js"
}
},
"node_modules/node-fetch": { "node_modules/node-fetch": {
"version": "2.7.0", "version": "2.7.0",
"license": "MIT", "license": "MIT",
@@ -1573,6 +1908,18 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/nth-check": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
"integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
"license": "BSD-2-Clause",
"dependencies": {
"boolbase": "^1.0.0"
},
"funding": {
"url": "https://github.com/fb55/nth-check?sponsor=1"
}
},
"node_modules/object-inspect": { "node_modules/object-inspect": {
"version": "1.13.4", "version": "1.13.4",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz",
@@ -1691,6 +2038,55 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/parse5": {
"version": "7.3.0",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
"integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
"license": "MIT",
"dependencies": {
"entities": "^6.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-htmlparser2-tree-adapter": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/parse5-htmlparser2-tree-adapter/-/parse5-htmlparser2-tree-adapter-7.1.0.tgz",
"integrity": "sha512-ruw5xyKs6lrpo9x9rCZqZZnIUntICjQAd0Wsmp396Ul9lN/h+ifgVV1x1gZHi8euej6wTfpqX8j+BFQxF0NS/g==",
"license": "MIT",
"dependencies": {
"domhandler": "^5.0.3",
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5-parser-stream": {
"version": "7.1.2",
"resolved": "https://registry.npmjs.org/parse5-parser-stream/-/parse5-parser-stream-7.1.2.tgz",
"integrity": "sha512-JyeQc9iwFLn5TbvvqACIF/VXG6abODeB3Fwmv/TGdLk2LfbWkaySGY72at4+Ty7EkPZj854u4CrICqNk2qIbow==",
"license": "MIT",
"dependencies": {
"parse5": "^7.0.0"
},
"funding": {
"url": "https://github.com/inikulin/parse5?sponsor=1"
}
},
"node_modules/parse5/node_modules/entities": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
"integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=0.12"
},
"funding": {
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/parseurl": { "node_modules/parseurl": {
"version": "1.3.3", "version": "1.3.3",
"resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz",
@@ -1802,6 +2198,15 @@
"node": ">= 6" "node": ">= 6"
} }
}, },
"node_modules/require-directory": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
"integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
"license": "MIT",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/safe-buffer": { "node_modules/safe-buffer": {
"version": "5.2.1", "version": "5.2.1",
"funding": [ "funding": [
@@ -2021,6 +2426,32 @@
"safe-buffer": "~5.2.0" "safe-buffer": "~5.2.0"
} }
}, },
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-final-newline": { "node_modules/strip-final-newline": {
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-4.0.0.tgz",
@@ -2164,6 +2595,28 @@
"version": "3.0.1", "version": "3.0.1",
"license": "BSD-2-Clause" "license": "BSD-2-Clause"
}, },
"node_modules/whatwg-encoding": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
"integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
"deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
"license": "MIT",
"dependencies": {
"iconv-lite": "0.6.3"
},
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-mimetype": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
"integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
"license": "MIT",
"engines": {
"node": ">=18"
}
},
"node_modules/whatwg-url": { "node_modules/whatwg-url": {
"version": "5.0.0", "version": "5.0.0",
"license": "MIT", "license": "MIT",
@@ -2235,6 +2688,23 @@
"url": "https://github.com/sponsors/sindresorhus" "url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/wrap-ansi": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/ws": { "node_modules/ws": {
"version": "8.20.0", "version": "8.20.0",
"license": "MIT", "license": "MIT",
@@ -2254,6 +2724,42 @@
} }
} }
}, },
"node_modules/y18n": {
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
"integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
"license": "ISC",
"engines": {
"node": ">=10"
}
},
"node_modules/yargs": {
"version": "17.7.2",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
"integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
"license": "MIT",
"dependencies": {
"cliui": "^8.0.1",
"escalade": "^3.1.1",
"get-caller-file": "^2.0.5",
"require-directory": "^2.1.1",
"string-width": "^4.2.3",
"y18n": "^5.0.5",
"yargs-parser": "^21.1.1"
},
"engines": {
"node": ">=12"
}
},
"node_modules/yargs-parser": {
"version": "21.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
"integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
"license": "ISC",
"engines": {
"node": ">=12"
}
},
"node_modules/yoctocolors": { "node_modules/yoctocolors": {
"version": "2.1.2", "version": "2.1.2",
"resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz", "resolved": "https://registry.npmjs.org/yoctocolors/-/yoctocolors-2.1.2.tgz",

View File

@@ -15,6 +15,7 @@
"@grammyjs/runner": "^2.0.3", "@grammyjs/runner": "^2.0.3",
"axios": "^1.14.0", "axios": "^1.14.0",
"chalk": "^5.4.0", "chalk": "^5.4.0",
"cheerio": "^1.2.0",
"commander": "^12.0.0", "commander": "^12.0.0",
"discord.js": "^14.26.4", "discord.js": "^14.26.4",
"dotenv": "^16.4.5", "dotenv": "^16.4.5",
@@ -23,6 +24,7 @@
"fs-extra": "^11.2.0", "fs-extra": "^11.2.0",
"glob": "^13.0.6", "glob": "^13.0.6",
"grammy": "^1.42.0", "grammy": "^1.42.0",
"node-edge-tts": "^1.2.10",
"openai": "^4.77.0", "openai": "^4.77.0",
"p-queue": "^8.0.1", "p-queue": "^8.0.1",
"winston": "^3.13.0", "winston": "^3.13.0",

View File

@@ -311,6 +311,28 @@ export async function initBot(config, api, tools, skills, agents) {
command: { type: 'string', description: 'Command to run' }, command: { type: 'string', description: 'Command to run' },
}, required: ['action'] }, }, required: ['action'] },
}, },
vision: {
description: 'Analyze an image from URL or file path. Returns detailed description and answers questions about the image.',
parameters: { type: 'object', properties: {
image_url: { type: 'string', description: 'Image URL (http/https) or local file path to analyze' },
question: { type: 'string', description: 'Specific question about the image (optional, defaults to full description)' },
}, required: ['image_url'] },
},
tts: {
description: 'Convert text to speech audio. Generates an MP3 file using Edge TTS (free, no API key needed).',
parameters: { type: 'object', properties: {
text: { type: 'string', description: 'Text to convert to speech (max 5000 chars)' },
voice: { type: 'string', description: 'Voice name (default: en-US-AvaNeural)' },
output_path: { type: 'string', description: 'Output file path (optional)' },
}, required: ['text'] },
},
browser: {
description: 'Fetch and extract readable content from a web page URL. Returns title, description, and main text content.',
parameters: { type: 'object', properties: {
url: { type: 'string', description: 'URL to fetch and extract content from' },
selector: { type: 'string', description: 'CSS selector for content extraction (optional, auto-detects article/main)' },
}, required: ['url'] },
},
delegate_agent: { delegate_agent: {
description: 'Delegate to a specialized agent role', description: 'Delegate to a specialized agent role',
parameters: { type: 'object', properties: { parameters: { type: 'object', properties: {
@@ -560,6 +582,39 @@ export async function initBot(config, api, tools, skills, agents) {
if (!tool) return '❌ Cron tool unavailable.'; if (!tool) return '❌ Cron tool unavailable.';
try { return await tool.execute(args); } catch (e) { return `${e.message}`; } try { return await tool.execute(args); } catch (e) { return `${e.message}`; }
}, },
vision: async (args) => {
const tool = svc.toolMap.get('vision');
if (!tool) return '❌ Vision tool unavailable.';
try { return await tool.execute(args); } catch (e) { return `${e.message}`; }
},
tts: async (args) => {
const tool = svc.toolMap.get('tts');
if (!tool) return '❌ TTS tool unavailable.';
try {
const result = await tool.execute(args);
// If audio was generated, send it as a voice message
if (result.startsWith('✅')) {
const filePath = result.match(/saved:\s*(.+)/)?.[1]?.trim();
if (filePath) {
try {
await svc.bot.api.sendAudio(svc.currentChatId, { source: filePath }, {
caption: '🔊 TTS',
performer: 'zCode',
});
return '✅ Audio sent as voice message.';
} catch (sendErr) {
return `${result}\n⚠ Could not auto-send audio: ${sendErr.message}`;
}
}
}
return result;
} catch (e) { return `${e.message}`; }
},
browser: async (args) => {
const tool = svc.toolMap.get('browser');
if (!tool) return '❌ Browser tool unavailable.';
try { return await tool.execute(args); } catch (e) { return `${e.message}`; }
},
delegate_agent: async (args) => { delegate_agent: async (args) => {
const agent = svc.agents.find(a => a.id === args.agent_id); const agent = svc.agents.find(a => a.id === args.agent_id);
if (!agent) return `❌ Agent not found: ${args.agent_id}`; if (!agent) return `❌ Agent not found: ${args.agent_id}`;
@@ -883,6 +938,7 @@ export async function initBot(config, api, tools, skills, agents) {
// ── Load conversation history for this chat ── // ── Load conversation history for this chat ──
const chatKey = conversation._key(ctx.chat.id, ctx.message?.message_thread_id); const chatKey = conversation._key(ctx.chat.id, ctx.message?.message_thread_id);
svc.currentChatId = ctx.chat.id; // Track for TTS auto-send
const history = await conversation.getContext(chatKey, text); const history = await conversation.getContext(chatKey, text);
// Create stream consumer for real-time edit-in-place // Create stream consumer for real-time edit-in-place

83
src/tools/BrowserTool.js Normal file
View File

@@ -0,0 +1,83 @@
import { logger } from '../utils/logger.js';
import axios from 'axios';
import * as cheerio from 'cheerio';
export class BrowserTool {
constructor(config = {}) {
this.name = 'browser';
this.description = 'Fetch and extract readable content from a web page URL. Returns title, meta description, and main text content stripped of HTML.';
this.timeout = config.timeout || 15000;
this.maxContentLength = config.maxContentLength || 50000; // chars
}
async execute({ url, selector }) {
if (!url) return '❌ url is required.';
try {
const response = await axios.get(url, {
timeout: this.timeout,
headers: {
'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en-US,en;q=0.5',
},
maxRedirects: 5,
validateStatus: (status) => status < 400,
});
const html = response.data;
const $ = cheerio.load(html);
// Remove scripts, styles, nav, footer, ads
$('script, style, nav, footer, header, aside, iframe, noscript, .ad, .ads, .advertisement, .sidebar, .cookie-banner').remove();
// Extract metadata
const title = $('title').text().trim() || $('meta[property="og:title"]').attr('content') || '';
const description = $('meta[name="description"]').attr('content') || $('meta[property="og:description"]').attr('content') || '';
const ogImage = $('meta[property="og:image"]').attr('content') || '';
// Extract main content
let content = '';
if (selector) {
content = $(selector).text().trim();
} else {
// Try common content containers
const contentSelectors = ['article', 'main', '.content', '.post', '.entry', '#content', '.article-body', 'section'];
for (const sel of contentSelectors) {
const el = $(sel);
if (el.length > 0) {
content = el.first().text().trim();
break;
}
}
// Fallback to body
if (!content) {
content = $('body').text().trim();
}
}
// Clean up whitespace
content = content.replace(/\s+/g, ' ').trim();
// Truncate if too long
if (content.length > this.maxContentLength) {
content = content.substring(0, this.maxContentLength) + '\n\n... [truncated]';
}
// Build result
let result = '';
if (title) result += `📄 **${title}**\n\n`;
if (description) result += `> ${description}\n\n`;
if (ogImage) result += `🖼 ${ogImage}\n\n`;
result += content;
if (!content) return `❌ Could not extract content from ${url}`;
return result;
} catch (error) {
logger.error(`Browser error: ${error.message}`);
if (error.code === 'ECONNABORTED') return `❌ Timeout fetching ${url} (${this.timeout}ms)`;
if (error.response) return `❌ HTTP ${error.response.status} for ${url}`;
return `❌ Browser error: ${error.message}`;
}
}
}

60
src/tools/TTSTool.js Normal file
View File

@@ -0,0 +1,60 @@
import { logger } from '../utils/logger.js';
import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const AUDIO_CACHE = path.join(__dirname, '..', '..', 'data', 'audio');
export class TTSTool {
constructor(config = {}) {
this.name = 'tts';
this.description = 'Convert text to speech audio. Returns the file path to the generated audio file.';
this.voice = config.voice || 'en-US-AvaNeural';
this.rate = config.rate || '+0%';
this.pitch = config.pitch || '+0Hz';
}
async execute({ text, output_path }) {
if (!text) return '❌ text is required.';
// Truncate very long text (Edge TTS has practical limits)
const maxChars = 5000;
if (text.length > maxChars) {
text = text.substring(0, maxChars);
logger.warn(`TTS: truncated text to ${maxChars} chars`);
}
try {
// Ensure audio cache dir exists
await fs.ensureDir(AUDIO_CACHE);
// Generate output path if not provided
const timestamp = Date.now();
const outputPath = output_path || path.join(AUDIO_CACHE, `tts_${timestamp}.mp3`);
// Use node-edge-tts
const { MsEdgeTTS } = await import('node-edge-tts');
const tts = new MsEdgeTTS();
await tts.setMetadata(this.voice, this.rate, this.pitch);
const readable = tts.toStream(text);
// Pipe to file
const writable = fs.createWriteStream(outputPath);
await new Promise((resolve, reject) => {
readable.pipe(writable);
writable.on('finish', resolve);
writable.on('error', reject);
readable.on('error', reject);
});
const stats = await fs.stat(outputPath);
logger.info(`TTS: generated ${outputPath} (${(stats.size / 1024).toFixed(1)}KB)`);
return `✅ Audio saved: ${outputPath} (${(stats.size / 1024).toFixed(1)}KB)`;
} catch (error) {
logger.error(`TTS error: ${error.message}`);
return `❌ TTS error: ${error.message}`;
}
}
}

79
src/tools/VisionTool.js Normal file
View File

@@ -0,0 +1,79 @@
import { logger } from '../utils/logger.js';
import axios from 'axios';
import fs from 'fs-extra';
import path from 'path';
import { fileURLToPath } from 'url';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export class VisionTool {
constructor(config = {}) {
this.name = 'vision';
this.description = 'Analyze an image from URL or file path. Returns a detailed description and answers specific questions about the image.';
this.apiClient = config.apiClient || null;
this.model = config.model || 'glm-4v-flash';
}
async execute({ image_url, question }) {
if (!image_url) return '❌ image_url is required.';
const userQuestion = question || 'Describe this image in detail.';
try {
// If it's a local file path, check if it exists
let imageUrl = image_url;
if (!image_url.startsWith('http')) {
const resolved = path.resolve(image_url);
if (!(await fs.pathExists(resolved))) {
return `❌ File not found: ${resolved}`;
}
// For local files, we'd need to base64 encode — for now require URLs
// Z.AI API supports URLs directly
imageUrl = resolved;
}
// Call Z.AI multimodal API (GLM-4V)
const { default: axios } = await import('axios');
const env = (await import('../config/env.js')).default;
const apiKey = env.ZAI_API_KEY;
const baseUrl = env.GLM_BASE_URL || 'https://api.z.ai/api/coding/paas/v4';
const response = await axios.post(`${baseUrl}/chat/completions`, {
model: this.model,
messages: [
{
role: 'user',
content: [
{
type: 'image_url',
image_url: { url: imageUrl },
},
{
type: 'text',
text: userQuestion,
},
],
},
],
max_tokens: 1024,
}, {
headers: {
'Authorization': `Bearer ${apiKey}`,
'Content-Type': 'application/json',
},
timeout: 30000,
});
const result = response.data?.choices?.[0]?.message?.content;
if (!result) return '❌ No response from vision model.';
return result;
} catch (error) {
logger.error(`Vision error: ${error.message}`);
if (error.response) {
return `❌ Vision API error ${error.response.status}: ${JSON.stringify(error.response.data?.error || error.response.data)?.substring(0, 200)}`;
}
return `❌ Vision error: ${error.message}`;
}
}
}

View File

@@ -13,6 +13,9 @@ import { TaskUpdateTool } from './TaskUpdateTool.js';
import { TaskListTool } from './TaskListTool.js'; import { TaskListTool } from './TaskListTool.js';
import { SendMessageTool } from './SendMessageTool.js'; import { SendMessageTool } from './SendMessageTool.js';
import { ScheduleCronTool } from './ScheduleCronTool.js'; import { ScheduleCronTool } from './ScheduleCronTool.js';
import { VisionTool } from './VisionTool.js';
import { TTSTool } from './TTSTool.js';
import { BrowserTool } from './BrowserTool.js';
// Tool definitions: env toggle flag, factory function // Tool definitions: env toggle flag, factory function
const TOOL_REGISTRY = [ const TOOL_REGISTRY = [
@@ -30,6 +33,9 @@ const TOOL_REGISTRY = [
{ env: null, Tool: TaskListTool, label: 'Task list' }, // bundled with TASKS { env: null, Tool: TaskListTool, label: 'Task list' }, // bundled with TASKS
{ env: 'ZCODE_ENABLE_SEND_MSG', Tool: SendMessageTool, label: 'Send message' }, { env: 'ZCODE_ENABLE_SEND_MSG', Tool: SendMessageTool, label: 'Send message' },
{ env: 'ZCODE_ENABLE_CRON', Tool: ScheduleCronTool, label: 'Schedule cron' }, { env: 'ZCODE_ENABLE_CRON', Tool: ScheduleCronTool, label: 'Schedule cron' },
{ env: 'ZCODE_ENABLE_VISION', Tool: VisionTool, label: 'Vision' },
{ env: 'ZCODE_ENABLE_TTS', Tool: TTSTool, label: 'TTS' },
{ env: 'ZCODE_ENABLE_BROWSER', Tool: BrowserTool, label: 'Browser' },
]; ];
export async function initTools() { export async function initTools() {
@@ -59,4 +65,5 @@ export {
FileReadTool, FileWriteTool, GlobTool, GrepTool, WebFetchTool, FileReadTool, FileWriteTool, GlobTool, GrepTool, WebFetchTool,
TaskCreateTool, TaskUpdateTool, TaskListTool, TaskCreateTool, TaskUpdateTool, TaskListTool,
SendMessageTool, ScheduleCronTool, SendMessageTool, ScheduleCronTool,
VisionTool, TTSTool, BrowserTool,
}; };